1.3 長鏈接與短鏈接服務器
UDP通訊模型中,在通訊開始以前,不須要創建相關的連接,只須要發送數據便可,相似於生活中的「寫信」。併發
TCP通訊模型中,在通訊開始以前,必定要先創建相關的連接,才能發送數據,相似於生活中的"打電話"。socket
TCP在真正的讀寫操做以前,server與client之間必須創建一個鏈接,當讀寫操做完成後,雙方再也不須要這個鏈接時它們能夠釋放這個鏈接。tcp
鏈接的創建經過三次握手,釋放則須要四次握手,因此說每一個鏈接的創建都是須要資源消耗和時間消耗的。網站
2MSL即兩倍的MSL,TCP的TIME_WAIT狀態也稱爲2MSL等待狀態。當TCP的一端發起主動關閉,在發出最後一個ACK包後,即第3次握手完成後發送了第四次握手的ACK包後就進入了TIME_WAIT狀態,必須在此狀態上停留兩倍的MSL時間。等待2MSL時間主要目的是怕最後一個 ACK包對方沒收到,那麼對方在超時後將重發第三次握手的FIN包,主動關閉端接到重發的FIN包後能夠再發一個ACK應答包。spa
在TIME_WAIT狀態時兩端的端口不能使用,要等到2MSL時間結束纔可繼續使用。當鏈接處於2MSL等待階段時任何遲到的報文段都將被丟棄。設計
不過在實際應用中能夠經過設置 SO_REUSEADDR選項達到沒必要等待2MSL時間結束再使用此端口。3d
創建鏈接——數據傳輸——關閉鏈接 ... 創建鏈接——數據傳輸——關閉鏈接
創建鏈接——數據傳輸 ...(保持鏈接)... 數據傳輸——關閉鏈接
長鏈接能夠省去較多的TCP創建和關閉的操做,減小浪費,節約時間。對於頻繁請求資源的客戶來講,較適用長鏈接。
client與server之間的鏈接若是一直不關閉的話,會存在一個問題,隨着客戶端鏈接愈來愈多,server遲早有扛不住的時候,這時候server端須要採起一些策略,如關閉一些長時間沒有讀寫事件發生的鏈接,這樣能夠避免一些惡意鏈接致使server端服務受損;若是條件再容許就能夠以客戶端機器爲顆粒度,限制每一個客戶端的最大長鏈接數,這樣能夠徹底避免某個蛋疼的客戶端連累後端服務。
長鏈接多用於操做頻繁,點對點的通信,並且鏈接數不能太多狀況。每一個TCP鏈接都須要三次握手,這須要時間,若是每一個操做都是先鏈接,再操做的話那麼處理速度會下降不少,因此每一個操做完後都不斷開,再次處理時直接發送數據包就OK了,不用創建TCP鏈接。例如:數據庫的鏈接用長鏈接,若是用短鏈接頻繁的通訊會形成socket錯誤,並且頻繁的socket建立也是對資源的浪費。
而像WEB網站的http服務通常都用短連接,由於長鏈接對於服務端來講會耗費必定的資源,而像WEB網站這麼頻繁的成千上萬甚至上億客戶端的鏈接用短鏈接會更省一些資源,若是用長鏈接,並且同時有成千上萬的用戶,若是每一個用戶都佔用一個鏈接的話,那可想而知。因此短鏈接多用於併發量大,但每一個用戶無需頻繁操做的狀況。
在生活中,若是想讓別人能更夠打通我們的電話獲取相應服務的話,須要作如下幾件事情:
如同上面的電話機過程同樣,在程序中,若是想要完成一個tcp服務器的功能,須要的流程以下:
示例:
1 import socket 2 3 # 建立socket 4 tcpSocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 5 6 # 綁定本地信息 7 address = ("", 7788) 8 tcpSocket.bind(address) 9 10 # 使用socket建立的套接字默認的屬性是主動的,使用listen將其變爲被動的,這樣就能夠接收別人的鏈接的 11 tcpSocket.listen(5) 12 13 # 當有新的客戶端來鏈接服務器時,就會產生一個新的套接字來專門爲這個客戶端服務 14 # newSocket就是用來爲這個新來的客戶端服務的 15 # tcpSocket就能夠省下來專門等待其餘新客戶端的鏈接 16 newSocket, clientAddr = tcpSocket.accept() 17 18 # 接收對方發送過來的數據,最大接收1024個字節 19 recvData = newSocket.recv(1024) 20 print("接收到數據爲:", recvData.decode()) 21 22 # 發送一些數據給客戶端 23 newSocket.send("Thank you!".encode()) 24 25 # 關閉爲這個客戶端服務的套接字。只要關閉了,就意味着爲不能再爲這個客戶端服務了,若是還須要服務,只能再次從新鏈接 26 newSocket.close() 27 28 # 關閉監聽套接字。只要這個套接字關閉了,就意味着整個程序不能再接收任何新的客戶端的鏈接 29 tcpSocket.close()
運行效果:
TCP客戶端要比服務器端簡單不少。若是說服務器端是須要本身買手機、查手機卡、設置鈴聲、等待別人打電話流程的話,那麼客戶端就只須要找一個電話亭,拿起電話撥打便可,流程要少不少。
示例:
1 import socket 2 3 # 建立socket 4 tcpClientSocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 5 6 # 鏈接服務器端 7 serAddress = ("192.168.3.4", 7788) 8 tcpClientSocket.connect(serAddress) 9 10 # 提示用戶輸入數據 11 sendData = input("請輸入要發送給服務器的數據:") 12 13 tcpClientSocket.send(sendData.encode()) 14 15 # 接收對方發送過來的數據,最大接收1024個字節 16 recvData = tcpClientSocket.recv(1024) 17 print("接收到數據爲:", recvData.decode()) 18 19 # 關閉監聽套接字。只要這個套接字關閉了,就意味着整個程序不能再接收任何新的客戶端的鏈接 20 tcpClientSocket.close()
運行效果:
1 import socket 2 3 # 建立socket 4 tcpServerSocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 5 6 # 綁定本地信息 7 address = ("", 7788) 8 tcpServerSocket.bind(address) 9 10 # 使用socket建立的套接字默認的屬性是主動的,使用listen將其變爲被動的,這樣就能夠接收別人的鏈接了 11 tcpServerSocket.listen(5) 12 13 while True: 14 15 # 若是有新的客戶端來鏈接服務器,那麼就產生一個新的套接字專門爲這個客戶端服務器 16 # newSocket用來爲這個客戶端服務 17 # tcpSerSocket就能夠省下來專門等待其餘新客戶端的鏈接 18 newSocket, clientAddr = tcpServerSocket.accept() 19 20 while True: 21 22 # 接收對方發送過來的數據,最大接收大小爲2014字節 23 recvData = newSocket.recv(1024).decode() 24 25 # 若是對方發送「88」,則意味着客戶端關閉鏈接 26 if recvData == "88": 27 print("Receive:", recvData) 28 print("----關閉聊天----") 29 break 30 else: 31 print("Receive:", recvData) 32 sendData = input("Send:") 33 newSocket.send(sendData.encode()) 34 35 # 關閉這個客戶端服務的套接字,只要關閉了,就意味着爲不能再爲這個客戶端服務了 36 # 若是還須要服務,只能再次從新鏈接 37 newSocket.close() 38 39 # 關閉監聽套接字,只要這個套接字關閉了,就意味着整個程序不能再接收任何新的客戶端的鏈接 40 tcpSerSocket.close()
1 import socket 2 3 # 建立socket 4 tcpClientSocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 5 6 # 鏈接服務器端 7 serAddress = ("localhost", 7788) 8 tcpClientSocket.connect(serAddress) 9 10 while True: 11 12 # 提示用戶輸入數據 13 sendData = input("Send:") 14 15 tcpClientSocket.send(sendData.encode()) 16 if sendData == "88": 17 break 18 else: 19 # 接收對方發送過來的數據,最大接收1024個字節 20 recvData = tcpClientSocket.recv(1024) 21 print("Receive:", recvData.decode()) 22 23 # 關閉監聽套接字。只要這個套接字關閉了,就意味着整個程序不能再接收任何新的客戶端的鏈接 24 tcpClientSocket.close()