首页 > 代码库 > socket tcp 粘包解决
socket tcp 粘包解决
何为粘包:
先看代码
session=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
在定义socket对象的时候 有两个参数 一个是 socket地址家族,另一个是处理类型socket.SOCK_STREAM,注意是 ‘stream’:流
那既然是流处理类型,理解上就是 水流式 处理数据。 这个时候数据是没有边界(也就是没有从头开始,到哪里)的概念就像下图
现在执行命令很正常:
执行了一个cat /etc/passwd ,
也能显示,但是后面发生了什么鬼
在主机上执行命令: ,可以看到 ,远程执行cat 的时候只是 拿到了rtkit ,而rtkit后面的 数据没有cat 到,这就是粘包,数据没有 头和尾,导致程序也不知道头和尾在哪,直到recv 虽然取了1024数据,但是数据只取了一半,还有一半在缓存里,没有取出来,导致在执行下一次执行命令的时候又取了1024字节的数据但是数据仍然不止1024字节,又没有取完,就导致了后面一直乱了。
服务端在接收到 cat /etc/passwd 的时候,然后将字节转换成命令, 并读取结果 将结果send回客户端 ,而客户端这时也是recv 1024 ,所以如果数据过多,客户端这里从自己的bruff cache 中读取到的1-1024 字节不够收,就造成了数据不对应 粘包的现象。
解决方法: 服务器在recv 字节处理后的stdout.read() 和stderr.read() 的结果都要加一个报头。
报头: 有固定的长度。
并且还有对数据信息的描述
需要一个新模块 import struct ,
struct 使用: server 端
import struct
cmd=conn.recv(1024) res=subprocess.Popen(cmd.decode(‘utf-8‘),shell=True,stdout=subprocess.PIPE,stderr=subprocess.PIPE) out_res=res.stdout.read() err_res=res.stderr.read() data_size=len(out_res)+len(err_res) #获取执行结果的长度 #发送报头 conn.send(struct.pack(‘i‘,data_size)) # struct.pack 的 i 是表示 4 个字节 4*8=32位bytes的存储 ,这 i 里面就已经包括了整个数据的长度,这样客户端在recv 结果的时候也知道要收多长的字节 #发送数据部分 conn.send(out_res) 再发送 stdout.read() conn.send(err_res) 再发送stderr.read()
完整代码:
#!/usr/bin/env python import socket,subprocess,struct talk=socket.socket(socket.AF_INET,socket.SOCK_STREAM) ip_port=(‘192.168.100.149‘,9000) talk.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1) talk.bind(ip_port) talk.listen(2) while True: conn,addr=talk.accept() print(‘=============host============‘,addr) while True: try: data=conn.recv(1024) if not data:break print(data) res=subprocess.Popen(data,shell=True,stderr=subprocess.PIPE,stdout=subprocess.PIPE) cmd_out=res.stdout.read() cmd_err=res.stderr.read() info_size=len(cmd_out)+len(cmd_err) conn.send(struct.pack(‘i‘,info_size)) conn.send(cmd_out) conn.send(cmd_err) except Exception: break conn.close() talk.close()
struct 使用:client 端
分析:server端已经做好了报头,那 client 在接收的时候也应该先接收报头长度,再接收报头,,,,,,,,,,这样就知道数据的长度,再recv 自己 的 buffer cachhe 数据。
import socket,struct talk=socket.socket(socket.AF_INET,socket.SOCK_STREAM) talk.connect((‘192.168.100.149‘,9000)) while True: msg=input(‘>>>>>‘).strip() if not msg:continue talk.send(bytes(msg,encoding=‘utf-8‘)) msg_head=talk.recv(4) head_unpack=struct.unpack(‘i‘,msg_head)[0] print(msg_head) recv_size=0 recv_data=b‘‘ while recv_size<head_unpack: data=talk.recv(1024) recv_size+=len(data) recv_data+=data print(recv_data.decode(‘utf-8‘)) socket.close()
执行效果:
现在已经解决粘包的问题,但是 报头 不仅仅描述文件的长度(大小), 还应该包含一些其它的信息如 文件大小 文件名:
那现在就可以用到字典的格式存储这些值了。
server 端代码
#!/usr/bin/env python #!-*- coding:utf-8 -*- import socket,json,subprocess,struct 需要用到 json 序列化,因为字典在传的时候只能是字节 形式 session=socket.socket(socket.AF_INET,socket.SOCK_STREAM) session.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1) 设置地址重用 ip_port=(‘192.168.100.149‘,9000) session.bind(ip_port) session.listen(4) while True: conn,addr=session.accept() #通讯无限循环 while True: try: client_cmd=conn.recv() res=subprocess.Popen(client_cmd.encode(‘utf-8‘),shell=True,stdout=subprocess.PIPE,stderr=subprocess.PIPE) cmd_result=res.stdout.read() cmd_err=res.stderr.read() cmd_result_size=len(cmd_result)+len(cmd_err) #所有执行结果的 长度 head_dic={‘data_size‘:cmd_result_size} #将所有执行结果的长度,放在 dict 里面 head_json=json.dumps(head_dic) #将字典做成 json 序列化 head_bytes=head_json.encode(‘utf-8‘) #再转成utf-8的字节形式 head_len=len(head_bytes) #再获取 报头 字典 的长度 conn.send(struct.pack(‘i‘,head_len)) #发送字典 报头的长度 conn.send(head_bytes) # 再发送所有数据的长度 conn.send(cmd_result) # 发送执行结果 的数据 conn.send(cmd_err) #发送执行结果的数据 except Exception: #捕捉任何异常,终止本次 break conn.close() #所有程序走完才close 这一个客户端 的连接 session.close() #关闭整个socket
client端代码
#!/usr/bin/env python #!-*- coding:utf-8 -*- import socket,json,subprocess,struct session=socket.socket(socket.AF_INET,socket.SOCK_STREAM) conn_ip_port=(‘192.168.100.149‘,9000) session.connect(conn_ip_port) #对应服务端的session.accept() while True: cmd=input(‘>>>>: ‘).strip() if not cmd:continue session.send(bytes(cmd,encoding=‘utf-8‘)) #对应服务端conn.recv(1024) head_struct=session.recv(4) #对应服务端conn.send(struct.pack(‘i‘,head_len)) print(‘四个字节报头‘,head_struct) head_recv_data_len=struct.unpack(‘i‘,head_struct)[0] #对应服务端conn.send(struct.pack(‘i‘,head_len)) head_recv=session.recv(head_recv_data_len) #对应服务端 conn.send(head_bytes) head_json=head_recv.decode(‘utf-8‘) #对应服务端 head_bytes=head_json.encode(‘utf-8‘) head_dic=json.loads(head_json) #对应服务端 head_json=json.dumps(head_dic) print(head_dic) data_size=head_dic[‘data_size‘] recv_size=0 recv_data=b‘‘ while recv_size<data_size: data=session.recv(1024) #对应服务端 conn.send(head_bytes) conn.send(out_res)conn.send(err_res) recv_size+=len(data) recv_data+=data print(recv_data.decode(‘utf-8‘)) session.close()
FTP 上传下载文件功能: …………………………………………………………………………..
socket tcp 粘包解决