1. 程式人生 > >對 Python Socket 程式設計的初探

對 Python Socket 程式設計的初探

對於python網路程式設計來說,免不了要用到socket模組。下面主要分享一下個人對python socket的一些理解。

socket程式設計步驟

  1. 服務端建立一個socket,繫結地址和埠,然後監聽埠上傳入的連線,一旦有連線進來,就通過accept函式接收傳入的連線。
  2. 客戶端也是建立一個socket。繫結遠端地址和埠,然後建立連線,傳送資料。

服務端socket

下面通過一段例項程式碼來詳細說明

服務端 socker_server.py

Python
1234567891011121314151617181920212223242526272829303132 importsocketimportsysHOST="127.0.0.1"PORT=10000s=Noneforres insocket.getaddrinfo(HOST,PORT,socket.AF_UNSPEC,socket.SOCK_STREAM,0,socket.AI_PASSIVE):af,socktype,proto,canonname,sa=restry:s=socket.socket(af,socktype
,proto)exceptsocket.error asmsg:s=Nonecontinuetry:s.bind(sa)s.listen(5)exceptsocket.error asmsg:s.close()s=NonecontinuebreakifsisNone:print'could not open socket'sys.exit(1)conn,addr=s.accept()print'Connected by',addrwhile1:data=conn.recv(1024)ifnotdata:breakconn.send(data)conn.close()

首先我們通過socket.getaddrinnfo函式將host/port轉換成一個包含5元組的序列。這個5元組包含我們建立一個socket連線所需要的所有必要引數。返回的5元組分別是 (family, sockettype, proto, canonname, sockaddr)

family 地址簇,用與socket()函式的第一個引數。主要有以下幾個

  1. socket.AF_UNIX 用與單一機器下的程序通訊
  2. socket.AF_INET 用與伺服器之間相互通訊,通常都用這個。
  3. socket.AF_INET6 支援IPv6

sockettype socket型別,用與socket()函式的第二個引數,常用的有

  1. socket.SOCK_STREAM 預設,用於TCP協議
  2. socket.SOCK_DGRAM 用於UDP協議

proto 協議,用於socket()函式的第三個引數。 getaddrinnfo函式會根據地址格式和socket型別,返回合適的協議

canonname 一個規範化的host name。

sockaddr 描述了一個socket address .是一個二元組,主要用於bind()和connect()函式

接下來建立一個socket物件,傳入getaddrinnfo函式返回的af,sockettype,proto。

Python
1 s=socket.socket(af,socktype,proto)

然後繫結socket address

Python
1 s.bind(sa)

開啟監聽模式

Python
1 s.listen(5)

listen函式會監聽連線到socket上的連線,引數表示在拒絕連線之前系統可以掛起的最大連線佇列數量為5。這些連線還沒有被accept處理。數量不能無限大,通常指定5。

一旦我們監聽到了連線,就會呼叫accept函式接收連線

Python
1 conn,addr=s.accept()

accept函式返回一個二元組,conn是一個新的socket物件,用來接收和傳送資料。addr表示另一端的socket地址。

接下來我們就可以用conn物件傳送和接收資料了

Python
12 data=conn.recv(1024)# 接收資料, 這裡指定一次最多接收的字元數量為1024conn.send(data)# 傳送資料

這裡我們接收到一個連線socket就會停止執行,所以如果要迴圈連線的話,將accept函式放入到一個死迴圈裡。

客戶端socket

客戶端socket程式設計相對比較簡單,通過connect和服務端建立連線之後,就可以相互通訊了。socket_client.py如下

Python
123456789101112131415161718192021 forres insocket.getaddrinfo(HOST,PORT,socket.AF_UNSPEC,socket.SOCK_STREAM):af,socktype,proto,canonname,sa=restry:s=socket.socket(af,socktype,proto)exceptsocket.error asmsg:s=Nonecontinuetry:s.connect(sa)exceptsocket.error asmsg:s.close()s=NonecontinuebreakifsisNone:print'could not open socket'sys.exit(1)s.sendall('Hello, world')data=s.recv(1024)s.close()print'Received',repr(data)

以上主要是針對TCP流資料的socket程式設計。對於UDP協議的資料,處理略有不同。譬如傳送接收UDP資料包處理函式為:

Python
12 socket.sendto(string,flags,address)socket.recvfrom(bufsize[,flags])#返回(string, address),string是返回的資料,address是傳送方的socket地址

SocketServer模組

python中網路程式設計除了socket模組還提供了SocketServer模組,這一模組主要是對socket模組進行了封裝,將socket的物件的建立,繫結,連線,接收,傳送,關閉都封裝在裡面,大大簡化了網路服務的程式設計。

此模組提供了以下2個主要的網路服務類,用於建立相應的套接字流

  1. TCPServer 建立TCP協議的套接字流
  2. UDPServer 建立UDP協議的套接字流

我們有了套接字流物件,還需要一個請求處理類。SocketServer模組提供了請求處理類有BaseRequestHandler,以及它的派生類StreamRequestHandler和DatagramRequestHandler。所以只要繼承這3個類中的一個,然後重寫handle函式,此函式將用來處理接收到的請求。下面看一個服務端的程式碼示例

Python
12345678910111213141516171819202122 importSocketServerclassMyTCPHandler(SocketServer.StreamRequestHandler):"""建立請求處理類,重寫handle方法。此外也可以重寫setup()和finish()來做一些請求處理前和處理後的一些工作"""defhandle(self):# self.request is the TCP socket connected to the clientself.data=self.request.recv(1024).strip()print"{} wrote:".format(self.client_address[0])printself.data# just send back the same data, but upper-casedself.request.sendall(self.data.upper())if__name__=="__main__":HOST,PORT="localhost",10000server=SocketServer.TCPServer((HOST,PORT),MyTCPHandler)# Activate the server; this will keep running until you# interrupt the program with Ctrl-C# server.shutdown()server.serve_forever()# 一直迴圈接收請求# server.handle_request() # 只處理一次請求就退出

看著是不是程式碼簡單了很多,而且SocketServer模組內部使用了多路複用IO技術,可以實現更好的連線效能。看serve_forever函式的原始碼用到了select模組。通過傳入socket物件呼叫select.select()來監聽socket物件的檔案描述符,一旦發現socket物件就緒,就通知應用程式進行相應的讀寫操作。原始碼如下:

123456789101112131415161718192021 def serve_forever(self,poll_interval=0.5):"""Handle one request at a time until shutdown.        Polls for shutdown every poll_interval seconds. Ignores        self.timeout. If you need to do periodic tasks, do them in        another thread.        """self.__is_shut_down.clear()try:whilenotself.__shutdown_request:# XXX: Consider using another file descriptor or# connecting to the socket to wake this up instead of# polling. Polling reduces our responsiveness to a# shutdown request and wastes cpu at all other times.r,w,e=_eintr_retry(select.select,[self],[],[],poll_interval)ifselfinr:self._handle_request_noblock()finally:self.__shutdown_request=Falseself.__is_shut_down.set()

即使使用了select技術,TCPServer,UDPServer處理請求仍然是同步的,意味著一個請求處理完,才能處理下一個請求。但SocketServer模組提供了另外2個類用來支援非同步的模式。

  1. ForkingMixIn 利用多程序實現非同步
  2. ThreadingMixIn 利用多執行緒實現非同步

看名字就知道用mixin模式來實現非同步。而mixin模式可以通過多繼承來實現,所以通過對網路服務類進行多繼承的方式就可以實現非同步模式

12 classThreadedTCPServer(SocketServer.ThreadingMixIn,SocketServer.TCPServer):pass

針對ThreadindMixIn,實現非同步的原理也就是在內部對每個請求建立一個執行緒來處理。看原始碼

Python
123456 defprocess_request(self,request,client_address):"""Start a new thread to process the request."""t=threading.Thread(target=self.process_request_thread,args=(request,client_address))t.daemon=self.daemon_threadst.start()

下面提供一個非同步模式的示例