(一)單進程服務器數組
1 from socket import * 2 3 serSocket = socket(AF_INET, SOCK_STREAM) 4 5 # 重複使用綁定的信息,當咱們服務器先掛掉時,不會影響客戶端的操做 6 serSocket.setsockopt(SOL_SOCKET, SO_REUSEADDR , 1) 7 8 localAddr = ('', 7788) 9 10 serSocket.bind(localAddr) 11 12 serSocket.listen(5) 13 14 while True: 15 16 print('-----主進程,,等待新客戶端的到來------') 17 18 newSocket,destAddr = serSocket.accept() 19 20 print('-----主進程,,接下來負責數據處理[%s]-----'%str(destAddr)) 21 22 try: 23 while True: 24 recvData = newSocket.recv(1024) 25 if len(recvData)>0: 26 print('recv[%s]:%s'%(str(destAddr), recvData)) 27 else: 28 print('[%s]客戶端已經關閉'%str(destAddr)) 29 break 30 finally: 31 newSocket.close() 32 33 serSocket.close()
當服務器爲一個客戶端服務時,而另外的客戶端發起了connect,只要服務器listen的隊列有空閒的位置,就會爲這個新客戶端進行鏈接,而且客戶端能夠發送數據,但當服務器爲這個新客戶端服務時,可能一次性把全部數據接收完畢
(二)多進程服務器服務器
1 from socket import * 2 from multiprocessing import * 3 from time import sleep 4 5 # 處理客戶端的請求併爲其服務 6 def dealWithClient(newSocket,destAddr): 7 while True: 8 recvData = newSocket.recv(1024) 9 if len(recvData)>0: 10 print('recv[%s]:%s'%(str(destAddr), recvData)) 11 else: 12 print('[%s]客戶端已經關閉'%str(destAddr)) 13 break 14 15 newSocket.close() 16 17 18 def main(): 19 20 serSocket = socket(AF_INET, SOCK_STREAM) 21 serSocket.setsockopt(SOL_SOCKET, SO_REUSEADDR , 1) 22 localAddr = ('', 7788) 23 serSocket.bind(localAddr) 24 serSocket.listen(5) 25 26 try: 27 while True: 28 print('-----主進程,,等待新客戶端的到來------') 29 newSocket,destAddr = serSocket.accept() 30 31 print('-----主進程,,接下來建立一個新的進程負責數據處理[%s]-----'%str(destAddr)) 32 client = Process(target=dealWithClient, args=(newSocket,destAddr)) 33 client.start() 34 35 #由於已經向子進程中copy了一份(引用),而且父進程中這個套接字也沒有用處了 36 #因此關閉 37 newSocket.close() 38 finally: 39 #當爲全部的客戶端服務完以後再進行關閉,表示再也不接收新的客戶端的連接 40 serSocket.close() 41 42 if __name__ == '__main__': 43 main()
(三)多線程服務器網絡
1 #coding=utf-8 2 from socket import * 3 from threading import Thread 4 from time import sleep 5 6 # 處理客戶端的請求並執行事情 7 def dealWithClient(newSocket,destAddr): 8 while True: 9 recvData = newSocket.recv(1024) 10 if len(recvData)>0: 11 print('recv[%s]:%s'%(str(destAddr), recvData)) 12 else: 13 print('[%s]客戶端已經關閉'%str(destAddr)) 14 break 15 16 newSocket.close() 17 18 19 def main(): 20 21 serSocket = socket(AF_INET, SOCK_STREAM) 22 serSocket.setsockopt(SOL_SOCKET, SO_REUSEADDR , 1) 23 localAddr = ('', 7788) 24 serSocket.bind(localAddr) 25 serSocket.listen(5) 26 27 try: 28 while True: 29 print('-----主進程,,等待新客戶端的到來------') 30 newSocket,destAddr = serSocket.accept() 31 32 print('-----主進程,,接下來建立一個新的進程負責數據處理[%s]-----'%str(destAddr)) 33 client = Thread(target=dealWithClient, args=(newSocket,destAddr)) 34 client.start() 35 36 #由於線程中共享這個套接字,若是關閉了會致使這個套接字不可用, 37 #可是此時在線程中這個套接字可能還在收數據,所以不能關閉 38 #newSocket.close() 39 finally: 40 serSocket.close() 41 42 if __name__ == '__main__': 43 main()
(四)單進程服務器---非堵塞模式多線程
1 from socket import * 2 import time 3 4 # 用來存儲全部的新連接的socket 5 g_socketList = [] 6 7 def main(): 8 serSocket = socket(AF_INET, SOCK_STREAM) 9 serSocket.setsockopt(SOL_SOCKET, SO_REUSEADDR , 1) 10 localAddr = ('', 7788) 11 serSocket.bind(localAddr) 12 #能夠適當修改listen中的值來看看不一樣的現象 13 serSocket.listen(1000) 14 #將套接字設置爲非堵塞 15 #設置爲非堵塞後,若是accept時,恰巧沒有客戶端connect,那麼accept會 16 #產生一個異常,因此須要try來進行處理 17 serSocket.setblocking(False) 18 19 while True: 20 21 #用來測試 22 #time.sleep(0.5) 23 24 try: 25 newClientInfo = serSocket.accept() 26 except Exception as result: 27 pass 28 else: 29 print("一個新的客戶端到來:%s"%str(newClientInfo)) 30 newClientInfo[0].setblocking(False) 31 g_socketList.append(newClientInfo) 32 33 # 用來存儲須要刪除的客戶端信息 34 needDelClientInfoList = [] 35 36 for clientSocket,clientAddr in g_socketList: 37 try: 38 recvData = clientSocket.recv(1024) 39 if len(recvData)>0: 40 print('recv[%s]:%s'%(str(clientAddr), recvData)) 41 else: 42 print('[%s]客戶端已經關閉'%str(clientAddr)) 43 clientSocket.close() 44 g_needDelClientInfoList.append((clientSocket,clientAddr)) 45 except Exception as result: 46 pass 47 48 for needDelClientInfo in needDelClientInfoList: 49 g_socketList.remove(needDelClientInfo) 50 51 if __name__ == '__main__': 52 main()
(五)單進程服務器---select版併發
在多路複用的模型中,比較經常使用的有select模型和epoll模型。這兩個都是系統接口,由操做系統提供。固然,Python的select模塊進行了更高級的封裝。app
網絡通訊被Unix系統抽象爲文件的讀寫,一般是一個設備,由設備驅動程序提供,驅動能夠知道自身的數據是否可用。支持阻塞操做的設備驅動一般會實現一組自身的等待隊列,如讀/寫等待隊列用於支持上層(用戶層)所需的block或non-block操做。設備的文件的資源若是可用(可讀或者可寫)則會通知進程,反之則會讓進程睡眠,等到數據到來可用的時候,再喚醒進程。socket
這些設備的文件描述符被放在一個數組中,而後select調用的時候遍歷這個數組,若是對應的文件描述符可讀則會返回改文件描述符。當遍歷結束以後,若是仍然沒有一個可用設備文件描述符,select讓用戶進程則會睡眠,直到等待資源可用的時候在喚醒,遍歷以前那個監視的數組。每次遍歷都是依次進行判斷的。ide
1 import select 2 import socket 3 import sys 4 5 6 server = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 7 server.bind(('', 7788)) 8 server.listen(5) 9 10 inputs = [server, sys.stdin] 11 12 running = True 13 14 while True: 15 16 # 調用 select 函數,阻塞等待 17 readable, writeable, exceptional = select.select(inputs, [], []) 18 19 # 數據抵達,循環 20 for sock in readable: 21 22 # 監聽到有新的鏈接 23 if sock == server: 24 conn, addr = server.accept() 25 # select 監聽的socket 26 inputs.append(conn) 27 28 # 監聽到鍵盤有輸入 29 elif sock == sys.stdin: 30 cmd = sys.stdin.readline() 31 running = False 32 break 33 34 # 有數據到達 35 else: 36 # 讀取客戶端鏈接發送的數據 37 data = sock.recv(1024) 38 if data: 39 sock.send(data) 40 else: 41 # 移除select監聽的socket 42 inputs.remove(sock) 43 sock.close() 44 45 # 若是檢測到用戶輸入敲擊鍵盤,那麼就退出 46 if not running: 47 break 48 49 server.close()
select目前幾乎在全部的平臺上支持,其良好跨平臺支持也是它的一個優勢。函數
select的一個缺點在於單個進程可以監視的文件描述符的數量存在最大限制,在Linux上通常爲1024,能夠經過修改宏定義甚至從新編譯內核的方式提高這一限制,可是這樣也會形成效率的下降。測試
通常來講這個數目和系統內存關係很大,具體數目能夠cat /proc/sys/fs/file-max察看。32位機默認是1024個。64位機默認是2048.
對socket進行掃描時是依次掃描的,即採用輪詢的方法,效率較低。
當套接字比較多的時候,每次select()都要經過遍歷FD_SETSIZE個Socket來完成調度,無論哪一個Socket是活躍的,都遍歷一遍。這會浪費不少CPU時間。
(六)單進程服務器---epoll版
epoll對文件描述符的操做有兩種模式:LT(level trigger)和ET(edge trigger)。LT模式是默認模式,LT模式與ET模式的區別以下:
LT模式:當epoll檢測到描述符事件發生並將此事件通知應用程序,應用程序能夠不當即處理該事件。下次調用epoll時,會再次響應應用程序並通知此事件。 ET模式:當epoll檢測到描述符事件發生並將此事件通知應用程序,應用程序必須當即處理該事件。若是不處理,下次調用epoll時,不會再次響應應用程序並通知此事件。
1 import socket 2 import select 3 4 # 建立套接字 5 s = socket.socket(socket.AF_INET,socket.SOCK_STREAM) 6 7 # 設置能夠重複使用綁定的信息 8 s.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1) 9 10 # 綁定本機信息 11 s.bind(("",7788)) 12 13 # 變爲被動 14 s.listen(10) 15 16 # 建立一個epoll對象 17 epoll=select.epoll() 18 19 # 測試,用來打印套接字對應的文件描述符 20 # print s.fileno() 21 # print select.EPOLLIN|select.EPOLLET 22 23 # 註冊事件到epoll中 24 # epoll.register(fd[, eventmask]) 25 # 注意,若是fd已經註冊過,則會發生異常 26 # 將建立的套接字添加到epoll的事件監聽中 27 epoll.register(s.fileno(),select.EPOLLIN|select.EPOLLET) 28 29 30 connections = {} 31 addresses = {} 32 33 # 循環等待客戶端的到來或者對方發送數據 34 while True: 35 36 # epoll 進行 fd 掃描的地方 -- 未指定超時時間則爲阻塞等待 37 epoll_list=epoll.poll() 38 39 # 對事件進行判斷 40 for fd,events in epoll_list: 41 42 # print fd 43 # print events 44 45 # 若是是socket建立的套接字被激活 46 if fd == s.fileno(): 47 conn,addr=s.accept() 48 49 print('有新的客戶端到來%s'%str(addr)) 50 51 # 將 conn 和 addr 信息分別保存起來 52 connections[conn.fileno()] = conn 53 addresses[conn.fileno()] = addr 54 55 # 向 epoll 中註冊 鏈接 socket 的 可讀 事件 56 epoll.register(conn.fileno(), select.EPOLLIN | select.EPOLLET) 57 58 59 elif events == select.EPOLLIN: 60 # 從激活 fd 上接收 61 recvData = connections[fd].recv(1024) 62 63 if len(recvData)>0: 64 print('recv:%s'%recvData) 65 else: 66 # 從 epoll 中移除該 鏈接 fd 67 epoll.unregister(fd) 68 69 # server 側主動關閉該 鏈接 fd 70 connections[fd].close() 71 72 print("%s---offline---"%str(addresses[fd]))