Python中實現socket通信,socket通信的服務端比較復雜,而客戶端非常簡單,所以客戶端基本上都是用sockct模塊實現,而服務
端用有很多模塊可以使用。下面就說一下服務端可使用的模塊。
| 模塊名 | 簡介 | 使用情況 |
|---|---|---|
| socket | 最原始,最低端的模塊,如果你想親自體驗socket的整個實現過程,那就用它吧 | 不用 |
| SocketServer | 它把socket的實現進行了很好的封裝,比如server端要為每個TCP連接創建一個新的線程/進程等等,這些你不用關心,它會幫你搞定,用戶的主要工作是寫已連接TCP/UDP的處理方法handle() | 比較常用 |
| select | 在一個線程/進程中同時監控處理多個已連接好的socket | 比較常用 |
| Twisted | 很牛逼的一個模塊,功能很強大,已經算是一個框架了 | 比較常用 |
| 其它異步框架 | 如gevent等 |
比較常用 |
下面用上面幾個常用的模塊實現TCP類型socket通信:Client端發送字符串,Server端收到后在數據前加處理線程/進程的id返回,Client端收到后打
印出來。Client端如果輸入的是空字符,那就關閉Client的socket,接着結束該Client進程(它會觸發Server端對應的connected_socket.recv()返回空
字符串,關閉connect_sock。以下都是在Windows上運行通過。如果想結束server或client端,那就直接kill就行了,它會自動釋放占用的所有資源。
1、使用socket模塊
Server(多線程實現)
在Winows下,子進程的入口參數不是能有socket類對象,詳見http://bugs.python.org/issue11119,所以要想實現多進程比較麻煩;而Linux上沒
有這個問題,在下面代碼的基礎上很小的修改就行實現。Server端監聽TCP連接,對於建立好的每個連接,監聽進程為其創建一個線程/進程,並檢測線程/
進程的狀態,如果已結束,那就進行收尾工作。監聽進程使用的是非阻塞Socket(不是立即返回,有超時),這是因為監聽進程除了處理新連接之外還要檢
查子進程的狀態。如果設定成阻塞,那它將所有已建立的連接POP出來由交由子線程后,就會一直阻塞在accept(),如果一直沒有新的已建立好的連接,那它
就會一直阻塞下去,就沒有辦法檢測子線程的狀態了。所以為了既能檢查已建立的連接隊列又能檢查子進程的狀態,需把該socket設置成非阻塞。handle()是
為每個建立好的連接創建的子線程的入口。
#-*- coding:utf-8 -*- import socket import threading BUFSIZE = 1024
def handle(connected_sock): while True: data = connected_sock.recv(BUFSIZE) if len(data) >0: print 'receive:',data
cur_thread = threading.current_thread() send_data = '{}:{}'.format(cur_thread.ident,data) connected_sock.sendall(send_data) #用sendall,不要用send,send並不一定發送所有send_data,可能發送了部分就返回了
print 'send:',send_data else: print 'close the connected socket and terminate sub thread' connected_sock.close() break HOST = '' PORT = 12356 ADDR = (HOST,PORT) sub_threads = [] listen_sock = socket.socket() listen_sock.settimeout(5.0) #設定超時時間后,socket其實內部變成了非阻塞,但有一個超時時間 listen_sock.bind(ADDR) listen_sock.listen(2) print 'build connect when new TCP comes' while True: try: connected_sock,client_addr = listen_sock.accept() except socket.timeout: length = len(sub_threads) while length: sub = sub_threads.pop(0) sub_id = sub.ident #進程ID sub.join(0.1) #等待線程結束,0.1秒 if sub.isAlive(): sub_threads.append(sub) else: print 'killed sub thread ',sub_id length -=1 else: t = threading.Thread(target=handle,name='sub thread',args=(connected_sock,)) #它繼承了listen_socket的阻塞/非阻塞特性,因為listen_socket是非阻塞的,所以它也是非阻塞的 #要讓他變為阻塞,所以要調用setblocking connected_sock.setblocking(1) t.start() sub_threads.append(t)
Client端
#-*- coding:utf-8 -*- import socket HOST = 'localhost' PORT = 12356 ADDR =(HOST,PORT) BUFSIZE = 1024 sock = socket.socket() try: a = sock.connect(ADDR) except Exception,e: print 'error',e sock.close() sys.exit() print 'have connected with server' while True: data = raw_input('> ') if len(data)>0:
print 'send:',data sock.sendall(data) #不要用send() recv_data = sock.recv(BUFSIZE) print 'receive::',recv_data else: sock.close() break
2、SocketServer模塊
Server(多線程實現,Linux下建議用多進程)
在Linux上可以用多進程實現,基本上把下面代碼中的ThreadingTCPServer改為ForkingTCPServer就可以了。在Windows下無法用多進程,因為
SocketServer為每個已連接創建進程時,用的是os.fork(),windows上沒有fork() API,哎,干嘛不搞個兼容Windows的API啊。
可以看到,下面的代碼非常簡潔,用戶不用去子線程的結束后,父進程對它的收尾,也不用關心socket的關閉。這些都由SocketServer完成。
Handler中的handle()方法與上面用socket模塊寫的handle方法基本相同。
#-*- coding:utf-8 -*- from SocketServer import BaseRequestHandler,ThreadingTCPServer import threading BUF_SIZE=1024 class Handler(BaseRequestHandler): def handle(self): while True: data = self.request.recv(BUF_SIZE) if len(data)>0: print 'receive=',data cur_thread = threading.current_thread() response = '{}:{}'.format(cur_thread.ident,data) self.request.sendall(response) print 'send:',response else: print 'close' break if __name__ == '__main__': HOST = '' PORT = 12356 ADDR = (HOST,PORT) server = ThreadingTCPServers(ADDR,Handler) #參數為監聽地址和已建立連接的處理類 print 'listening' server.serve_forever() #監聽,建立好TCP連接后,為該連接創建新的socket和線程,並由處理類中的handle方法處理
