Python網絡編程(2)-粘包現象及socketserver模塊實現TCP併發

一、 基於Tcp的遠程調用命令實現

  不少人應該都使用過Xshell工具,這是一個遠程鏈接工具,經過上面的知識,就能夠模擬出Xshell遠程鏈接服務器並調用命令的功能。shell

  Tcp服務端代碼以下:服務器

 1 import socket,subprocess
 2 ip_port = ("127.0.0.1",8000)
 3 tcp_server = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
 4 tcp_server.bind(ip_port)
 5 tcp_server.listen(5)
 6 print("the server has started")
 7 while True:
 8     try:
 9         conn,addr = tcp_server.accept()
10         cmd = conn.recv(1024)
11         if not cmd:break
12         res = subprocess.Popen(cmd.decode("utf-8"),shell=True,
13                                stdout=subprocess.PIPE,
14                                stdin=subprocess.PIPE,
15                                stderr=subprocess.PIPE)
16         error = res.stderr.read()
17         if error:
18             cmd_res = error
19         else:
20             cmd_res = res.stdout.read()
21         if not cmd_res:
22             cmd_res = "the reply is".encode("gbk")
23         conn.send(cmd_res)
24     except Exception:
25         break

  Tcp客戶端代碼以下:網絡

 1 import socket,subprocess
 2 ip_port = ("127.0.0.1",8000)
 3 tcp_client = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
 4 tcp_client.connect(ip_port)
 5 while True:
 6     cmd = input(">>>>").strip()
 7     if not cmd:continue
 8     if cmd ==quit:break
 9     tcp_client.send(cmd.encode("utf-8"))
10     cmd_res = tcp_client.recv(1024)
11     print("the reply is ",cmd_res.decode("gbk"))
12 tcp_client.close()

   但在用上面兩個代碼進行模擬Xshell遠程調用的過程當中,可能會出現一個問題:粘包併發

二、 粘包

  在數據信息傳輸過程當中,發動數據的速度多是3kb/s,可是接收數據的速度多是10kb/s,甚至更快,應用程序看到的數據都是一個總體或能夠說是一個流(stream),一條數據信息有多少字節,應用程序是不可見的。Tcp是面向鏈接,面向流的協議,這也就成爲Tcp容易出現粘包現象的緣由。基於Tcp的套接字客戶端像服務端上傳文件的時候,文件的內容是按照一個個小的字節段傳輸的,接收方根本不知道何時開始,何時結束。框架

  而Udp將數據信息分紅一個個小的數據段,在提取數據的時候,不能以字節爲單位任意提取數據,只能以數據段爲單位進行提取。因此只有Tcp纔會出現粘包現象,而Udp就不會。socket

  粘包問題主要形成緣由就是接收方不知道數據消息之間的界限,(即開始點和終止點),不知道一次所需提取的數據是多少,因此形成的問題。tcp

  Tcp是基於數據流、面向鏈接的協議,由於收發的消息不能爲空,在服務端和客戶端都必須添加空消息的處理機制,防止程序運行過程當中卡住。Udp是基於數據報、無鏈接的協議,即便發送的是空消息,但實質上它會將發送的空消息封裝上消息頭,不會收到影響。工具

  在以下兩種狀況下很容易發生粘包現象:ui

  (1)   發送數據時間的間隔過短,數據量很小,就會粘合在一種,產生粘包現象。spa

  (2)   客戶端發送數據後,服務端只接受一部分,再次發送其它數據的時候,服務端會先從緩衝區提取上一次的遺留數據,產生粘包。

  解決粘包問題的根本就在於:要讓接收方知道將要接收數據消息的長度。基於上面的Tcp遠程調用命令的程序,該如何解決粘包問題?

  服務端:

 1 import socket,subprocess,struct
 2 ip_port = ("127.0.0.1",8000)
 3 tcp_server = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
 4 tcp_server.bind(ip_port)
 5 tcp_server.listen(5)
 6 print("the server has started")
 7 while True:
 8     while True:
 9         conn,addr = tcp_server.accept()
10         try:
11             cmd = conn.recv(1024)
12             if not cmd:break
13             res = subprocess.Popen(cmd.decode("utf-8"),shell=True,
14                                    stdout=subprocess.PIPE,
15                                    stdin=subprocess.PIPE,
16                                    stderr=subprocess.PIPE)
17             error = res.stderr.read()
18             if error:
19                 cmd_res = error
20             else:
21                 cmd_res = res.stdout.read()
22             if not cmd_res:
23                 cmd_res = "the reply is".encode("gbk")
24             length = len(cmd_res)
25             send_length = struct.pack("i",length)
26             conn.send(send_length,cmd_res)
27         except Exception:
28             break

  客戶端:

 1 import socket,subprocess,struct
 2 from functools import partial
 3 ip_port = ("127.0.0.1",8000)
 4 tcp_client = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
 5 tcp_client.connect(ip_port)
 6 while True:
 7     cmd = input(">>>>").strip()
 8     if not cmd:continue
 9     if cmd ==quit:break
10     tcp_client.send(cmd.encode("utf-8"))
11  
12 
13     recv_length = tcp_client.recv(4)
14     length = struct.unpack("i",recv_length)[0]
15     recv_msg = "".join(iter(partial(tcp_client.recv,1024),b""))
16     print("the reply is",recv_msg.decode("gbk"))
17 tcp_client.close()

三、 socketserver模塊

  在使用上面的程序進行通訊時,是沒有達到併發的。也就意味着,當第一個客戶端佔用服務端的資源的時候,第二個客戶端再向服務端發起請求,是沒法進行通訊的。經過sockserver能夠實現該功能。

  sockserver是標準庫中的一個高級模塊,經過socketserver,將可使用類來編寫網絡應用程序,以面向對象的方式的處理方式將更具備組織性和邏輯性。

  socketserver包含了4個基本的類:基於TCP流式套接字的TCPServer;基於UDP數據報套接字的UDPServer,以及基於文件的基礎同步UnixStreamServer和UnixDatagramServer。使用最多的TCPServer,後面三個不多用到。

  在socketserver服務器框架中,基本全部的處理邏輯代碼會放在一個請求處理程序中,(即一個類),服務端每當收到一個請求,就會實例化一個處理程序,而且調用相應的方法來響應客戶端發來的請求,BaseRequestHandler類把全部的操做都放在handle方法中,這個方法會訪問屬性self.request中的客戶端套接字。

  服務端作以下改寫:

 1 import socketserver,struct
 2 class Server2(socketserver.BaseRequestHandler):
 3     def handle(self):
 4        while True:
 5            try:
 6                data = self.request.recv(1024)
 7                if not data:break
 8                self.request.send(data.title())
 9            except Exception:
10                break
11 if __name__ == "__main":
12     s = socketserver.ThreadingTCPServer(("127.0.0.1",8080),Server2)
13     s.serve_forever()

   在這個程序中,self.request就等同於前面建立簡單TCP服務端的conn,經過重寫handle方法,該方法在默認狀況下爲空。當接收到一個客戶端請求的時候,他就會調用handle()方法,而後經過socketserver.ThreadingTCPServer方法,傳入給定的主機ip地址、端口號和所定義的類,最後使用serve_forever()方法來啓動TCP服務器。無限循環的等待並服務於客戶端請求。

相關文章
相關標籤/搜索