服務端應該遵循:python
1.綁定一個固定的ip和portlinux
2.一直對外提供服務,穩定運行算法
3.可以支持併發shell
基礎版套接字:json
from socket import * server = socket(AF_INET, SOCK_STREAM) server.bind(('127.0.0.1', 8080)) server.listen(5) conn, client_addr = server.accept() # 通訊循環 while True: data = conn.recv(1024) conn.send(data.upper()) conn.close() server.close()
from socket import * client = socket(AF_INET, SOCK_STREAM) client.connect(('127.0.0.1', 8080)) # 通訊循環 while True: msg=input('>>: ').strip() client.send(msg.encode('utf-8')) data=client.recv(1024) print(data) client.close()
以上的程序存在兩個bug數組
1.當客戶端單方面終止程序時,服務端拋出異常(linux能夠用判斷是否爲空來處理)服務器
2.recv收到空時,一直在等待。併發
解決方法:ssh
1.異常捕獲socket
2.再輸入時進行判斷
改進版:
from socket import * server = socket(AF_INET, SOCK_STREAM) server.bind(('127.0.0.1', 8082)) server.listen(5) conn, client_addr = server.accept() print(client_addr) # 通訊循環 while True: try: data = conn.recv(1024) if len(data) == 0:break # 針對linux系統 print('-->收到客戶端的消息: ',data) conn.send(data.upper()) except ConnectionResetError: break conn.close() server.close()
from socket import * client = socket(AF_INET, SOCK_STREAM) client.connect(('127.0.0.1', 8081)) # 通訊循環 while True: msg=input('>>: ').strip() #msg='' if len(msg) == 0:continue client.send(msg.encode('utf-8')) #client.send(b'') # print('has send') data=client.recv(1024) # print('has recv') print(data) client.close()
連接循環:服務器改進
from socket import * server = socket(AF_INET, SOCK_STREAM) server.bind(('127.0.0.1', 8082)) server.listen(5) conn, client_addr = server.accept() print(client_addr) # 通訊循環 while True: try: data = conn.recv(1024) if len(data) == 0:break # 針對linux系統 print('-->收到客戶端的消息: ',data) conn.send(data.upper()) except ConnectionResetError: break conn.close() server.close()
模擬ssh實現遠程執行命令
from socket import * import subprocess server = socket(AF_INET, SOCK_STREAM) server.bind(('127.0.0.1', 8081)) server.listen(5) # 連接循環 while True: conn, client_addr = server.accept() print(client_addr) # 通訊循環 while True: try: cmd = conn.recv(1024) #cmd=b'dir' if len(cmd) == 0: break # 針對linux系統 obj=subprocess.Popen(cmd.decode('utf-8'), shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE ) stdout=obj.stdout.read() stderr=obj.stderr.read() print(len(stdout) + len(stderr)) conn.send(stdout+stderr) except ConnectionResetError: break conn.close() server.close()
from socket import * client = socket(AF_INET, SOCK_STREAM) client.connect(('127.0.0.1', 8081)) # 通訊循環 while True: cmd=input('>>: ').strip() if len(cmd) == 0:continue client.send(cmd.encode('utf-8')) cmd_res=client.recv(1024000) print(cmd_res.decode('gbk')) client.close()
recv實際上是和本地計算機要數據,因此解碼的時候應該用gbk格式
注:只有tcp存在粘包現象,udp永遠不存在粘包。
tcp是面向流的協議,發送端能夠1k,1k的發送數據,而接收端能夠2k,2k的提取數據,發送方每每收集到足夠多的數據後才生成一個tcp段,若連續幾回須要發送的數據都不多,一般tcp會根據(Nagle)優化算法,把這些數據合成一個TCP段後發出去,這樣接收方就收到了粘包數據。
總的來講,粘包問題就是由於接收方不知道消息之間的界限,不知道一次性提取多少字節形成的。
解決方法:(服務端)爲字節流加上一個報頭,將這個報頭(字典形式)json序列化,編碼。而後用struct發送報頭的長度,再發送報頭,最後發送真實數據
(客戶端)先解出報頭的長度(struct),再接收報頭,將拿到的報頭解碼再反序列化,獲得報頭字典,最後接收真正的數據
# 服務端必須知足至少三點: # 1. 綁定一個固定的ip和port # 2. 一直對外提供服務,穩定運行 # 3. 可以支持併發 from socket import * import subprocess import struct import json server = socket(AF_INET, SOCK_STREAM) server.bind(('127.0.0.1', 8081)) server.listen(5) # 連接循環 while True: conn, client_addr = server.accept() print(client_addr) # 通訊循環 while True: try: cmd = conn.recv(1024) # cmd=b'dir' if len(cmd) == 0: break # 針對linux系統 obj = subprocess.Popen(cmd.decode('utf-8'), shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE ) stdout = obj.stdout.read() stderr = obj.stderr.read() # 1. 先製做報頭 header_dic = { 'filename': 'a.txt', 'md5': 'asdfasdf123123x1', 'total_size': len(stdout) + len(stderr) } header_json = json.dumps(header_dic) header_bytes = header_json.encode('utf-8') # 2. 先發送4個bytes(包含報頭的長度) conn.send(struct.pack('i', len(header_bytes))) # 3 再發送報頭 conn.send(header_bytes) # 4. 最後發送真實的數據 conn.send(stdout) conn.send(stderr) except ConnectionResetError: break conn.close() server.close()
from socket import * import struct import json client = socket(AF_INET, SOCK_STREAM) client.connect(('127.0.0.1', 8081)) # 通訊循環 while True: cmd=input('>>: ').strip() if len(cmd) == 0:continue client.send(cmd.encode('utf-8')) #1. 先收4bytes,解出報頭的長度 header_size=struct.unpack('i',client.recv(4))[0] #2. 再接收報頭,拿到header_dic header_bytes=client.recv(header_size) header_json=header_bytes.decode('utf-8') header_dic=json.loads(header_json) print(header_dic) total_size=header_dic['total_size'] #3. 接收真正的數據 cmd_res=b'' recv_size=0 while recv_size < total_size: data=client.recv(1024) recv_size+=len(data) cmd_res+=data print(cmd_res.decode('gbk')) client.close()
補充:struct模塊
該模塊能夠幫一個類型,如數字,轉成固定長度的bytes
一、 struct.pack
struct.pack用於將Python的值根據格式符,轉換爲字符串(由於Python中沒有字節(Byte)類型,能夠把這裏的字符串理解爲字節流,或字節數組)。其函數原型爲:struct.pack(fmt, v1, v2, ...),參數fmt是格式字符串,關於格式字符串的相關信息在下面有所介紹。v1, v2, ...表示要轉換的python值。
二、 struct.unpack
struct.unpack作的工做恰好與struct.pack相反,用於將字節流轉換成python數據類型。它的函數原型爲:struct.unpack(fmt, string),該函數返回一個元組。
import struct obj1=struct.pack('i',13321111) print(obj1,len(obj1))#b'\x97C\xcb\x00' 4 res1=struct.unpack('i',obj1) print(res1[0])#13321111