---引入
Socket的英文原義是“孔”或“插座”,在Unix的進程通信機制中又稱為‘套接字’。套接字實際上並不復雜,它是由一個ip地址以及一個端口號組成。Socket正如其英文原意那樣,像一個多孔插座。一台主機猶如布滿各種插座(ip地址)的房間,每個插座有很多插口(端口),通過這些插口接入電線(進程)我們可以燒水,看電視,玩電腦……
應用程序通常通過"套接字"向網絡發出請求或者應答網絡請求。
套接字的作用之一就是用來區分不同應用進程,當某個進程綁定了本機ip的某個端口,那么所有傳送至這個ip地址上的這個端口的所有數據都會被內核送至該進程進行處理。
---python中的socket
import socket s1=socket.socket(family,type) #family參數代表地址家族,可為AF_INET或AF_UNIX。AF_INET家族包括Internet地址,AF_UNIX家族用於同一台機器上的進程間通信。 #type參數代表套接字類型,可為SOCK_STREAM(流套接字,就是TCP套接字)和SOCK_DGRAM(數據報套接字,就是UDP套接字)。 #默認為family=AF_INET type=SOCK_STREM
#返回一個整數描述符,用這個描述符來標識這個套接字
2 綁定套接字
s1.bind( address ) #由AF_INET所創建的套接字,address地址必須是一個雙元素元組,格式是(host,port)。host代表主機,port代表端口號。
#如果端口號正在使用、主機名不正確或端口已被保留,bind方法將引發socket.error異常。 #例: ('192.168.1.1',9999)
3 監聽套接字
s1.listen( backlog ) #backlog指定最多允許多少個客戶連接到服務器。它的值至少為1。收到連接請求后,這些請求需要排隊,如果隊列滿,就拒絕請求。
4 等待接受連接
connection, address = s1.accept() #調用accept方法時,socket會時入“waiting”狀態,也就是處於阻塞狀態。客戶請求連接時,方法建立連接並返回服務器。 #accept方法返回一個含有兩個元素的元組(connection,address)。 #第一個元素connection是所連接的客戶端的socket對象(實際上是該對象的內存地址),服務器必須通過它與客戶端通信; #第二個元素 address是客戶的Internet地址。
5 處理階段
connection.recv(bufsize[,flag])
#注意此處為connection #接受套接字的數據。數據以字符串形式返回,bufsize指定最多可以接收的數量。flag提供有關消息的其他信息,通常可以忽略 connection.send(string[,flag]) #將string中的數據發送到連接的套接字。返回值是要發送的字節數量,該數量可能小於string的字節大小。即:可能未將指定內容全部發送。
6 傳輸結束,關閉連接
s1.close() #關閉套接字
python編寫客戶端
1 創建socket對象
import socket s2=socket.socket()
2 連接至服務器端
s2.connect(address) #連接到address處的套接字。一般,address的格式為元組(hostname,port),如果連接出錯,返回socket.error錯誤。
3 處理階段
s2.recv(bufsize[,flag]) #接受套接字的數據。數據以字符串形式返回,bufsize指定最多可以接收的數量。flag提供有關消息的其他信息,通常可以忽略 s2.send(string[,flag]) #將string中的數據發送到連接的套接字。返回值是要發送的字節數量,該數量可能小於string的字節大小。即:可能未將指定內容全部發送。
4 連接結束,關閉套接字
s2.close()
socket.getaddrinfo(host, port, family=0, type=0, proto=0, flags=0) #獲取要連接的對端主機地址 sk.bind(address) 將套接字綁定到地址。address地址的格式取決於地址族。在AF_INET下,以元組(host,port)的形式表示地址。
sk.listen(backlog) 開始監聽傳入連接。backlog指定在拒絕連接之前,可以掛起的最大連接數量。 backlog等於5,表示內核已經接到了連接請求,但服務器還沒有調用accept進行處理的連接個數最大為5,這個值不能無限大,因為要在內核中維護連接隊列 sk.setblocking(bool) 是否阻塞(默認True),如果設置False,那么accept和recv時一旦無數據,則報錯。
sk.accept() 接受連接並返回(conn,address),其中conn是新的套接字對象,可以用來接收和發送數據。address是連接客戶端的地址。接收TCP 客戶的連接(阻塞式)等待連接的到來 sk.connect(address) 連接到address處的套接字。一般,address的格式為元組(hostname,port),如果連接出錯,返回socket.error錯誤。 sk.connect_ex(address) 同上,只不過會有返回值,連接成功時返回 0 ,連接失敗時候返回編碼,例如:10061 sk.close() 關閉套接字 sk.recv(bufsize[,flag]) 接受套接字的數據。數據以字符串形式返回,bufsize指定最多可以接收的數量。flag提供有關消息的其他信息,通常可以忽略。 sk.recvfrom(bufsize[.flag]) 與recv()類似,但返回值是(data,address)。其中data是包含接收數據的字符串,address是發送數據的套接字地址。 sk.send(string[,flag]) 將string中的數據發送到連接的套接字。返回值是要發送的字節數量,該數量可能小於string的字節大小。即:可能未將指定內容全部發送。 sk.sendall(string[,flag]) 將string中的數據發送到連接的套接字,但在返回之前會嘗試發送所有數據。成功返回None,失敗則拋出異常。內部通過遞歸調用send,將所有內容發送出去。 sk.sendto(string[,flag],address) 將數據發送到套接字,address是形式為(ipaddr,port)的元組,指定遠程地址。返回值是發送的字節數。該函數主要用於UDP協議。 sk.settimeout(timeout) 設置套接字操作的超時期,timeout是一個浮點數,單位是秒。值為None表示沒有超時期。一般,超時期應該在剛創建套接字時設置,
因為它們可能用於連接的操作(如 client 連接最多等待5s ) sk.getpeername() 返回連接套接字的遠程地址。返回值通常是元組(ipaddr,port)。 sk.getsockname() 返回套接字自己的地址。通常是一個元組(ipaddr,port) sk.fileno() 套接字的文件描述符 socket.sendfile(file, offset=0, count=None) 發送文件
class BaseServer:
#我們創建服務類時,需要指定(地址,端口),服務處理類。 def __init__(self, server_address, RequestHandlerClass): """Constructor. May be extended, do not override.""" self.server_address = server_address self.RequestHandlerClass = RequestHandlerClass self.__is_shut_down = threading.Event() self.__shutdown_request = False #…………此處省略n多代碼,當我們執行server_forever方法時,里面就會調用很多服務類中的其他方法,但最終會調用finish_request方法。 def finish_request(self, request, client_address): """Finish one request by instantiating RequestHandlerClass.""" self.RequestHandlerClass(request, client_address, self) #finish_request方法中執行了self.RequestHandlerClass(request, client_address, self)。self.RequestHandlerClass是什么呢?
#self.RequestHandlerClass = RequestHandlerClass(就在__init__方法中)。所以finish_request方法本質上就是創建了一個服務處理實例。
class BaseRequestHandler: def __init__(self, request, client_address, server): self.request = request self.client_address = client_address self.server = server self.setup() try: self.handle() finally: self.finish()
#當我們創建服務處理類實例時,就會運行handle()方法,而handle()方法則一般是我們處理事務邏輯的代碼塊。 #…………此處省略n多代碼
TCPServer針對TCP套接字流
UDPServer針對UDP數據報套接字
UnixStreamServer和UnixDatagramServer針對UNIX域套接字,不常用。
他們之間的繼承關系:
服務類的方法:

class SocketServer.BaseServer:這是模塊中的所有服務器對象的超類。它定義了接口,如下所述,但是大多數的方法不實現,在子類中進行細化。 BaseServer.fileno():返回服務器監聽套接字的整數文件描述符。通常用來傳遞給select.select(), 以允許一個進程監視多個服務器。 BaseServer.handle_request():處理單個請求。處理順序:get_request(), verify_request(), process_request()。如果用戶提供handle()方法拋出異常,將調用服務器的handle_error()方法。如果self.timeout內沒有請求收到, 將調用handle_timeout()並返回handle_request()。 BaseServer.serve_forever(poll_interval=0.5): 處理請求,直到一個明確的shutdown()請求。每poll_interval秒輪詢一次shutdown。忽略self.timeout。如果你需要做周期性的任務,建議放置在其他線程。 BaseServer.shutdown():告訴serve_forever()循環停止並等待其停止。python2.6版本。 BaseServer.address_family: 地址家族,比如socket.AF_INET和socket.AF_UNIX。 BaseServer.RequestHandlerClass:用戶提供的請求處理類,這個類為每個請求創建實例。 BaseServer.server_address:服務器偵聽的地址。格式根據協議家族地址的各不相同,請參閱socket模塊的文檔。 BaseServer.socketSocket:服務器上偵聽傳入的請求socket對象的服務器。 服務器類支持下面的類變量: BaseServer.allow_reuse_address:服務器是否允許地址的重用。默認為false ,並且可在子類中更改。 BaseServer.request_queue_size 請求隊列的大小。如果單個請求需要很長的時間來處理,服務器忙時請求被放置到隊列中,最多可以放request_queue_size個。一旦隊列已滿,來自客戶端的請求將得到 “Connection denied”錯誤。默認值通常為5 ,但可以被子類覆蓋。 BaseServer.socket_type:服務器使用的套接字類型; socket.SOCK_STREAM和socket.SOCK_DGRAM等。 BaseServer.timeout:超時時間,以秒為單位,或 None表示沒有超時。如果handle_request()在timeout內沒有收到請求,將調用handle_timeout()。 下面方法可以被子類重載,它們對服務器對象的外部用戶沒有影響。 BaseServer.finish_request():實際處理RequestHandlerClass發起的請求並調用其handle()方法。 常用。 BaseServer.get_request():接受socket請求,並返回二元組包含要用於與客戶端通信的新socket對象,以及客戶端的地址。 BaseServer.handle_error(request, client_address):如果RequestHandlerClass的handle()方法拋出異常時調用。默認操作是打印traceback到標准輸出,並繼續處理其他請求。 BaseServer.handle_timeout():超時處理。默認對於forking服務器是收集退出的子進程狀態,threading服務器則什么都不做。 BaseServer.process_request(request, client_address) :調用finish_request()創建RequestHandlerClass的實例。如果需要,此功能可以創建新的進程或線程來處理請求,ForkingMixIn和ThreadingMixIn類做到這點。常用。 BaseServer.server_activate():通過服務器的構造函數來激活服務器。默認的行為只是監聽服務器套接字。可重載。 BaseServer.server_bind():通過服務器的構造函數中調用綁定socket到所需的地址。可重載。 BaseServer.verify_request(request, client_address):返回一個布爾值,如果該值為True ,則該請求將被處理,反之請求將被拒絕。此功能可以重寫來實現對服務器的訪問控制。默認的實現始終返回True。client_address可以限定客戶端,比如只處理指定ip區間的請求。 常用。
這個幾個服務類都是同步處理請求的:一個請求沒處理完不能處理下一個請求。要想支持異步模型,可以利用多繼承讓server類繼承ForkingMixIn 或 ThreadingMixIn mix-in classes。
ForkingMixIn利用多進程(分叉)實現異步。
ThreadingMixIn利用多線程實現異步。
要實現一項服務,還必須派生一個handler class請求處理類,並重寫父類的handle()方法。handle方法就是用來專門是處理請求的。該模塊是通過服務類和請求處理類組合來處理請求的。
SocketServer模塊提供的請求處理類有BaseRequestHandler,以及它的派生類StreamRequestHandler和DatagramRequestHandler。從名字看出可以一個處理流式套接字,一個處理數據報套接字。
請求處理類有三種方法:
-
setup
() -
Called before the
handle()
method to perform any initialization actions required. The default implementation does nothing.也就是在handle()之前被調用,主要的作用就是執行處理請求之前的初始化相關的各種工作。默認不會做任何事。(如果想要讓其做一些事的話,就要程序員在自己的請求處理器中覆蓋這個方法(因為一般自定義的請求處理器都要繼承python中提供的BaseRequestHandler,ps:下文會提到的),然后往里面添加東西即可)
-
handle
() -
This function must do all the work required to service a request. The default implementation does nothing. Several instance attributes are available to it; the request is available as
self.request
; the client address asself.client_address
; and the server instance asself.server
, in case it needs access to per-server information.The type of
self.request
is different for datagram or stream services. For stream services,self.request
is a socket object; for datagram services,self.request
is a pair of string and socket.handle()的工作就是做那些所有與處理請求相關的工作。默認也不會做任何事。他有數個實例參數:self.request self.client_address self.server
-
finish
() -
Called after the
handle()
method to perform any clean-up actions required. The default implementation does nothing. Ifsetup()
raises an exception, this function will not be called.在handle()方法之后會被調用,他的作用就是執行當處理完請求后的清理工作,默認不會做任何事

class BaseRequestHandler: """Base class for request handler classes. This class is instantiated for each request to be handled. The constructor sets the instance variables request, client_address and server, and then calls the handle() method. To implement a specific service, all you need to do is to derive a class which defines a handle() method. The handle() method can find the request as self.request, the client address as self.client_address, and the server (in case it needs access to per-server information) as self.server. Since a separate instance is created for each request, the handle() method can define arbitrary other instance variariables. """ def __init__(self, request, client_address, server): self.request = request self.client_address = client_address self.server = server self.setup() try: self.handle() finally: self.finish() def setup(self): pass def handle(self): pass def finish(self): pass # The following two classes make it possible to use the same service # class for stream or datagram servers. # Each class sets up these instance variables: # - rfile: a file object from which receives the request is read # - wfile: a file object to which the reply is written # When the handle() method returns, wfile is flushed properly class StreamRequestHandler(BaseRequestHandler): """Define self.rfile and self.wfile for stream sockets.""" # Default buffer sizes for rfile, wfile. # We default rfile to buffered because otherwise it could be # really slow for large data (a getc() call per byte); we make # wfile unbuffered because (a) often after a write() we want to # read and we need to flush the line; (b) big writes to unbuffered # files are typically optimized by stdio even when big reads # aren't. rbufsize = -1 wbufsize = 0 # A timeout to apply to the request socket, if not None. timeout = None # Disable nagle algorithm for this socket, if True. # Use only when wbufsize != 0, to avoid small packets. disable_nagle_algorithm = False def setup(self): self.connection = self.request if self.timeout is not None: self.connection.settimeout(self.timeout) if self.disable_nagle_algorithm: self.connection.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, True) self.rfile = self.connection.makefile('rb', self.rbufsize) self.wfile = self.connection.makefile('wb', self.wbufsize) def finish(self): if not self.wfile.closed: try: self.wfile.flush() except socket.error: # An final socket error may have occurred here, such as # the local error ECONNABORTED. pass self.wfile.close() self.rfile.close() class DatagramRequestHandler(BaseRequestHandler): # XXX Regrettably, I cannot get this working on Linux; # s.recvfrom() doesn't return a meaningful client address. """Define self.rfile and self.wfile for datagram sockets.""" def setup(self): from io import BytesIO self.packet, self.socket = self.request self.rfile = BytesIO(self.packet) self.wfile = BytesIO() def finish(self): self.socket.sendto(self.wfile.getvalue(), self.client_address)
從源碼中可以看出,BaseRequestHandler中的setup()/handle()/finish()什么內容都沒有定義,而他的兩個派生類StreamRequestHandler和DatagramRequestHandler則都重寫了setup()/finish()。
因此當我們需要自己編寫socketserver程序時,只需要合理選擇StreamRequestHandler和DatagramRequestHandler之中的一個作為父類,然后自定義一個請求處理類,並在其中重寫handle()方法即可。
用socketserver創建一個服務的步驟:
1 創建一個request handler class(請求處理類),合理選擇StreamRequestHandler和DatagramRequestHandler之中的一個作為父類(當然,使用BaseRequestHandler作為父類也可),並重寫它的handle()方法。
2 實例化一個server class對象,並將服務的地址和之前創建的request handler class傳遞給它。
3 調用server class對象的handle_request() 或 serve_forever()方法來開始處理請求。

import socketserver class MyTCPHandler(socketserver.BaseRequestHandler): """ The request handler class for our server. It is instantiated once per connection to the server, and must override the handle() method to implement communication to the client. """ def handle(self): # self.request is the TCP socket connected to the client self.data = self.request.recv(1024).strip() print("{} wrote:".format(self.client_address[0])) print(self.data) # just send back the same data, but upper-cased self.request.sendall(self.data.upper()) if __name__ == "__main__": HOST, PORT = "localhost", 9999 # Create the server, binding to localhost on port 9999 server = socketserver.TCPServer((HOST, PORT), MyTCPHandler) # Activate the server; this will keep running until you # interrupt the program with Ctrl-C server.serve_forever()

import socket import sys HOST, PORT = "localhost", 9999 data = " ".join(sys.argv[1:]) # Create a socket (SOCK_STREAM means a TCP socket) sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) try: # Connect to server and send data sock.connect((HOST, PORT)) sock.sendall(bytes(data + "\n", "utf-8")) # Receive data from the server and shut down received = str(sock.recv(1024), "utf-8") finally: sock.close() print("Sent: {}".format(data)) print("Received: {}".format(received))