粘包就是recv(buffer_size)时每次只收取1024字节,如果信息过大,就会积累到下次收取产生粘包
只有基于TCP的套接字有粘包现象
基于UDP的套接字没有粘包现象
所谓粘包问题主要还是因为接收方不知道消息之间的界限,不知道一次性提取多少字节的数据造成的.
TCP(传输控制协议),是可靠传输
是面向连接的,面向流的,提供高可靠性服务.收发段都要有一对一承兑的socket,因此,发送端为了将发往接收端的包,更有效的发送到对方,使用了优化方法(Nagle算法),将多次间隔较小且数据量小的数据,合并成一个大的数据块,然后进行封包.这样,接收端就难分辨出来了,必须提供科学的拆包机制.即面向流的通信是无消息保护边界的.
UDP(用户数据报协议),是不可靠传输
是无连接的,面向消息的,提供高效率服务.不会使用块的合并优化算法,由于UDP是支持一对多的模式,所以接收端的skbuff(套接字缓冲区)采用了链式结构来记录每一个到达的UDP包,在每个UDP包中就有了消息头(消息来源地址,端口等信息),这样,对于接收端来说,就容易进行区分处理 了.即面向消息的通信是由消息保护边界的.
tcp是基于数据流的,余数收发的消息不能为空,这就需要在客户端和服务端都添加空消息的处理机制,防止程序卡住,而udp是基于数据报的,即便是输入空内容,也不是空消息,udp协议会帮你封装上消息头.
send()和recv()不是一一对应关系
当多次发送的信息数据过小时间间隔过段,或发送端信息一次信息过大接收段字节限制过小,都会产生粘包现象
sendto()和recvfrom()是一一对应关系
解决socket的粘包问题
服务端:
#!/usr/bin/env python
from socket import *
import subprocess
import json
import struct
ip_port = ('127.0.0.1',8080)
buffer_size = 1024
back_log = 5
filename = 'malin'
tcp_server = socket(AF_INET,SOCK_STREAM)
tcp_server.setsockopt(SOL_SOCKET,SO_REUSEADDR,1) #防止出现IP地址被占用的错误
tcp_server.bind(ip_port)
tcp_server.listen(back_log)
while True:
print('服务器运行了')
conn,addr = tcp_server.accept()
while True:
try:
cmd = conn.recv(buffer_size).decode('utf8')
if not cmd:
break
res = subprocess.Popen(cmd,shell=True,stderr=subprocess.PIPE,stdout=subprocess.PIPE) #解析命令
res_err = res.stderr.read() #读取错误管道的结果
res_out = res.stdout.read() #读取标准输出管道的结果
res_cd = '执行完成'.encode('gbk')
if not res_err and not res_out: #管道内都为空时
length = len(res_cd)
else:
length = len(res_err)+len(res_out)
header_dic = {'filename':filename,'filesize':length} #报头
header_json = json.dumps(header_dic).encode('gbk') #报头序列化,然后编码
header_len = len(header_json) #报头长度
conn.send(struct.pack('i', header_len)) #报头长度打包,发送固定4字节
conn.send(header_json) #发送报头
if not res_err and not res_out:
conn.send(res_cd) #当管道内都为空时发送
else:
conn.send(res_err) #发送标准错误管道内容
conn.send(res_out) #发送标准输出管道内容
except Exception as e:
print(e)
break
conn.close()
客户端:
#!/usr/bin/env python
from socket import *
import struct
import json
from functools import partial
ip_port = ('127.0.0.1',8080)
buffer_size = 1024
tcp_client = socket(AF_INET,SOCK_STREAM)
tcp_client.connect(ip_port) #连接服务端
while True: #通讯循环
cmd = input('>>:').strip() #输入CMD命令
if not cmd: #命令为空时重新输入
continue
if cmd == 'quit': #正常退出
break
tcp_client.send(cmd.encode('utf8')) #发送命令内容
head_len_struck = tcp_client.recv(4) #接收打包后的报头长度
head_len = struct.unpack('i',head_len_struck)[0] #解包获取报头长度
head_json = tcp_client.recv(head_len) #根据报头长度接收报头
header = json.loads(head_json.decode('gbk')) #报头反序列化
print('报头:',header)
file_size = header['filesize'] #获取报头里面的文件长度信息
data = b''
i = 0
while i < file_size: #根据文件长度,循环收取文件
data += tcp_client.recv(buffer_size)
i += buffer_size
print(data.decode('gbk'))
tcp_client.close()