不少人應該都使用過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()
在使用上面的程序進行通訊時,是沒有達到併發的。也就意味着,當第一個客戶端佔用服務端的資源的時候,第二個客戶端再向服務端發起請求,是沒法進行通訊的。經過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服務器。無限循環的等待並服務於客戶端請求。