Python socket 粘包问题 报头
2.基本语法
客户端 import socket # 1.创建socket对象 client = socket.socket() # 2.链接服务器 client.connect((ip,port)) # 3.收发数据 通常需要循环 client.send(二进制数据) # 只能发二进制数据 client.recv(字节数) # 收多少字节数 阻塞直到接收到数据 # 4.断开链接 client.close() 服务端 # 1.创建socket对象 server = socket.socket() # 2.绑定一个固定的ip和端口 server.bind(ip,port)) # 3.开始监听客户端的到来 server.listen() # 可不填 # 4.接收客户端的链接请求 conn,addr = server.accept() # 阻塞直到客户链接到来 没有新连接则不可能执行该函数 # 5.收发数据 通常需要循环 conn.send(二进制数据) # 只能发二进制数据 conn.recv(字节数) # 收多少字节数 阻塞直到接收到数据 # 6.关闭双行通道和服务器 conn.close() server.close() 复制代码
服务端套接字函数
s.bind() 绑定(主机,端口号)到套接字
s.listen() 开始TCP监听
s.accept() 被动接受TCP客户的连接,(阻塞式)等待连接的到来
客户端套接字函数
s.connect() 主动初始化TCP服务器连接
s.connect_ex() connect()函数的扩展版本,出错时返回出错码,而不是抛出异常
公共用途的套接字函数
s.recv() 接收TCP数据
s.send() 发送TCP数据(send在待发送数据量大于己端缓存区剩余空间时,数据丢失,不会发完)
s.sendall() 发送完整的TCP数据(本质就是循环调用send,sendall在待发送数据量大于己端缓存区剩余空间时,数据不丢失,循环调用send直到发完)
s.recvfrom() 接收UDP数据
s.sendto() 发送UDP数据
s.getpeername() 连接到当前套接字的远端的地址
s.getsockname() 当前套接字的地址
s.getsockopt() 返回指定套接字的参数
s.setsockopt() 设置指定套接字的参数
s.close() 关闭套接字
面向锁的套接字方法
s.setblocking() 设置套接字的阻塞与非阻塞模式
s.settimeout() 设置阻塞套接字操作的超时时间
s.gettimeout() 得到阻塞套接字操作的超时时间
3.循环连接和循环通信
客户端 import socket client = socket.socket() client.connect(('127.0.0.1',8080)) while True: msg = input('>>>:').encode('utf-8') if len(msg) == 0:continue client.send(msg) data = client.recv(1024) print(data) 服务端 import socket """ 服务端 固定的ip和port 24小时不间断提供服务 """ server = socket.socket() # 生成一个对象 server.bind(('127.0.0.1',8080)) # 绑定ip和port server.listen(5) # 半连接池 while True: conn, addr = server.accept() # 等到别人来 conn就类似于是双向通道 print(addr) # ('127.0.0.1', 51323) 客户端的地址 while True: try: data = conn.recv(1024) print(data) # b'' 针对mac与linux 客户端异常退出之后 服务端不会报错 只会一直收b'' if len(data) == 0:break conn.send(data.upper()) except ConnectionResetError as e: print(e) break conn.close()
# ==================================================客户端 import socket from 二_CMD程序 import smallTool import struct client = socket.socket() try: client.connect(("127.0.0.1",1688)) print("链接成功!") while True: msg = input("请输入要执行指令:").strip() if msg == "q": break if not msg: continue # 发送指令 # 先发长度 len_bytes = struct.pack("q",len(msg.encode("utf-8"))) client.send(len_bytes) # 在发指令 client.send(msg.encode("utf-8")) data = smallTool.recv_data(client) print(data.decode("GBK")) client.close() except ConnectionRefusedError as e: print("链接服务器失败了!",e) except ConnectionResetError as e: print("服务器挂了!", e) client.close() import socket import subprocess import struct from 二_CMD程序 import smallTool server = socket.socket(socket.AF_INET,socket.SOCK_STREAM) server.bind(("127.0.0.1",1688)) server.listen() # back while True: # socket,addr一个元组 客户端的ip和port client,addr = server.accept() print("客户端链接成功!") # 循环收发数据 while True: try: cmd = smallTool.recv_data(client) if not cmd: break print(cmd) p = subprocess.Popen(cmd.decode("utf-8"),shell=True,stdout=subprocess.PIPE,stderr=subprocess.PIPE) # 不要先读err错误信息 它会卡主 原因不详 linux不会有问题 tasklist netstat - ano啥的 data = p.stdout.read() err_data = p.stderr.read() len_size = len(data) + len(err_data) print("服务器返回了: %s " % len_size) len_bytes = struct.pack("q",len_size) # 在发送真实数据前先发送 长度 client.send(len_bytes) # 返回的结果刚好就是二进制 # 发送真实数据 client.send(data + err_data) except ConnectionResetError as e: print("客户端了挂了!",e) break client.close() #server.close()
1.可能是由于你已经启动了服务器程序,却又再次启动了服务器程序,同一个端口不能被多个进程使用导致!
2.三次握手或四次挥手时,发生了异常导致对方程序已经结束而服务器任然处于time_wait状态导致!
3.在高并发的场景下,由于链接的客户端太多,也会产生大量处于time_wait状态的链接
解决的方案:
第1种原因,很简单关闭之前运行的服务器即可
第2,3中原因导致的问题,有两种解决方案:
from socket import SOL_SOCKET,SO_REUSEADDR sk.setsockopt(SOL_SOCKET,SO_REUSEADDR,1) #就是它,在bind前加
-
发送端 发送的数据量小 并且间隔短 会粘
-
接收端 一次性读取了两次数据的内容 会粘
-
接收端 没有接收完整 剩余的内容 和下次发送的粘在一起
无论是那种情况,其根本原因在于 接收端不知道数据到底有多少
解决方案就是 提前告知接收方 数据的长度
3.解决方法
先发长度给对方 再发真实数据
#发送端
1.使用struct 将真实数据的长度转为固定的字节数据
2.发送长度数据
3.发送真实数据
接收端
1.先收长度数据 字节数固定
2.再收真实数据 真实可能很长 需要循环接收
发送端和接收端必须都处理粘包 才算真正的解决了
客户端 import socket client = socket.socket() client.connect(("127.0.0.1",1688)) client.send("hello".encode("utf-8")) client.send("python".encode("utf-8")) client.close() 服务端 import socket server = socket.socket() server.bind(("127.0.0.1",1688)) server.listen() client,addr = server.accept() data1 = client.recv(5) print(data1) data2 = client.recv(6) print(data2) client.close()
4.struct模块
但是由于negle优化机制的存在,长度信息和数据还是有可能会粘包,而接受方并不知道长度信息具体几个字节,所以现在的问题是如何能够长度信息做成一个固定长度的bytes数据</p> 我们可以将字符串拼接为一个固定长度的字符 但是这样太麻烦,struct模块为我们提供了一个功能,可以将整数类型转换为固定长度的bytes,此时就派上用场了 # struct模块使用 import struct # 整型转bytes res = struct.pack("i",100) print(res) print(len(res)) # bytes转整型 res2 = struct.unpack("i",res) # 返回一个元组 print(res2) print(res2[0])
客户端 import socket import struct c = socket.socket() c.connect(("127.0.0.1",8888)) while True: cmd = input(">>>:").strip() c.send(cmd.encode("utf-8")) data = c.recv(4) length = struct.unpack("i",data)[0] print(length) size = 0 res = b"" while size < length: temp = c.recv(1024) size += len(temp) res += temp print(res.decode("gbk")) 服务端 import socket import subprocess import struct server = socket.socket() server.bind(("127.0.0.1",8888)) server.listen() while True: client, addr = server.accept() while True: cmd = client.recv(1024).decode("utf-8") p = subprocess.Popen(cmd,shell=True,stdout=-1,stderr=-1) data = p.stdout.read()+p.stderr.read() length = len(data) len_data = struct.pack("i",length) client.send(len_data) print(length) client.send(data)
当需要在传输数据时 传呼一些额外参数时就需要自定义报头例如我们要实现文件上传下载,不光要传输文件数据,还需要传输文件名字,md5值等等,报头本质是一个json 数据。
2.
客户端 import socket import struct import json client = socket.socket() client.connect(('127.0.0.1',8080)) while True: msg = input('>>>:').encode('utf-8') if len(msg) == 0:continue client.send(msg) # 1.先接受字典报头 header_dict = client.recv(4) # 2.解析报头 获取字典的长度 dict_size = struct.unpack('i',header_dict)[0] # 解包的时候一定要加上索引0 # 3.接收字典数据 dict_bytes = client.recv(dict_size) dict_json = json.loads(dict_bytes.decode('utf-8')) # 4.从字典中获取信息 print(dict_json) recv_size = 0 real_data = b'' while recv_size < dict_json.get('file_size'): # real_size = 102400 data = client.recv(1024) real_data += data recv_size += len(data) print(real_data.decode('gbk')) # 服务端 import socket import subprocess import struct import json server = socket.socket() server.bind(('127.0.0.1',8080)) server.listen(5) while True: conn, addr = server.accept() while True: try: cmd = conn.recv(1024) if len(cmd) == 0:break cmd = cmd.decode('utf-8') obj = subprocess.Popen(cmd,shell=True,stdout=subprocess.PIPE,stderr=subprocess.PIPE) res = obj.stdout.read() + obj.stderr.read() d = {'name':'jason','file_size':len(res),'info':'asdhjkshasdad'} json_d = json.dumps(d) # 1.先制作一个字典的报头 header = struct.pack('i',len(json_d)) # 2.发送字典报头 conn.send(header) # 3.发送字典 conn.send(json_d.encode('utf-8')) # 4.再发真实数据 conn.send(res) # conn.send(obj.stdout.read()) # conn.send(obj.stderr.read()) except ConnectionResetError: break conn.close()