粘包:
1.粘包问题出现的原因: (udp不会出现粘包问题)
1.1.tcp是流式协议,数据像水流一样黏在一起,没有任何边界区分
1.2.收数据没收干净,有残留,就会下一次结果混淆在一起去(客户端接受限制,发送端数据量太大)
2.解决粘包问题思路:
1.当时短连接的情况下,不用考虑粘包的情况
2.如果发送数据无结构,如文件传输,这样发送方只管发送,接收方只管接收存储就ok,也不用考虑粘包
3.如果双方建立长连接,需要在连接后一段时间内发送不同结构数据
3.1发送端给每个数据包添加包首部,首部中应该至少包含数据包的长度,这样接收端在接收到数据后,通过读取包首部的长度字段,便知道每一个数据包的实际长度了。
3.2发送端将每个数据包封装为固定长度(不够的可以通过补0填充),这样接收端每次从接收缓冲区中读取固定长度的数据就自然而然的把每个数据包拆分开来。
3.3可以在数据包之间设置边界,如添加特殊符号,这样,接收端通过这个边界就可以将不同的数据包拆分开。
服务端:
import socket
import subprocess #Popen(子进程的创建和管理都靠他处理)
import struct
phone = socket.socket() #默认TCP协议
phone.bind(('127.0.0.1',8080)) #绑定ip加端口
phone.listen(5) #半连接池大小
while True:
conn,client_addr = phone.accept() #conn为三次握手的双向通路,client_addr为ip+端口
while True:
try:
cmd = conn.recv(1024)
if len(cmd) == 0:break
obj = subprocess.Popen(cmd.decode('gbk'), #shell命令,可以是字符串或者序列类型(list,元组)
shell=True, #如果该参数为 True,将通过操作系统的 shell 执行指定的命令。
stdout=subprocess.PIPE, #输出
stderr=subprocess.PIPE #错误句柄
)
stdout_res = obj.stdout.read()
stderr_res = obj.stderr.read()
total_size = len(stdout_res) + len(stderr_res)
#先发头部信息(固定长度的bytes):对数据描述的信息
#int-->固定长度的bytes
header = struct.pack('i',total_size) #按照指定格式将字节流转换为Python指定的数据类型(pick为字符串类型),短的用i,长的用Q
conn.send(header)
#再发真实数据
conn.send(stdout_res)
conn.send(stderr_res)
except Exception:
break
conn.close()
客户端:
import socket
import struct
client = socket.socket(socket.AF_INET,socket.SOCK_STREAM) #流式协议--> tcp协议
client.connect(('127.0.0.1',8080)) #进行3次握手
while True:
msg = input('输入要发送的消息:').strip()
if len(msg) == 0:continue #解决输入空格的bug
client.send(msg.encode('utf8')) #编码以二进制的形式发送
#解决粘包问题的思路
# 2.1:先收固定长度的头:解析出数据的描述信息,包括数据的总大小total_size
header = client.recv(4) #如果是Q的话就是8
# total_size = xxxx
total_size = struct.unpack('i',header)[0] #解包
# recv_size = 0,循环接受,每接受一次,recv_size+=接受的长度
recv_size = 0
# 直到recv_size = total_size,结束循环
while recv_size < total_size:
recv_data = client.recv(1024)
recv_size+=len(recv_data)
print(recv_data.decode('gbk'),end='')
print('')
client.close()
解决粘包的终极大招:
################################模板
服务端:
#解决粘包问题终极版
import socket
import subprocess #Popen(子进程的创建和管理都靠他处理)
import struct
import json
phone = socket.socket() #默认TCP协议
phone.bind(('127.0.0.1',8081)) #绑定ip加端口
phone.listen(5) #半连接池大小
while True:
conn,client_addr = phone.accept() #conn为三次握手的双向通路,client_addr为ip+端口
while True:
try:
cmd = conn.recv(1024)
if len(cmd) == 0:break
obj = subprocess.Popen(cmd.decode('gbk'), #shell命令,可以是字符串或者序列类型(list,元组)
shell=True, #如果该参数为 True,将通过操作系统的 shell 执行指定的命令。
stdout=subprocess.PIPE, #输出
stderr=subprocess.PIPE #错误句柄
)
stdout_res = obj.stdout.read()
stderr_res = obj.stderr.read()
total_size = len(stdout_res) + len(stderr_res)
#1.制作头部
header_dic = {
'filename':'a.txt', #文件名称
'total_size':total_size, #文件长度
'md5':'21561adas61d6'
}
json_str = json.dumps(header_dic) #数据类型转成字符串
json_str_bytes = json_str.encode('gbk') #字符串转成二进制
#2.先发头部信息(固定长度的bytes):对数据描述的信息
#int-->固定长度的bytes
header = struct.pack('i',len(json_str_bytes)) #按照指定格式将字节流转换为Python指定的数据类型(pick为字符串类型),短的用i,长的用Q
conn.send(header) #发送头的长度 4个字节
#3.发头信息
conn.send(json_str_bytes)
#再发真实数据
conn.send(stdout_res)
conn.send(stderr_res)
except Exception:
break
conn.close()
客户端:
#解决粘包问题终极版
import socket
import struct
import json
client = socket.socket(socket.AF_INET,socket.SOCK_STREAM) #流式协议--> tcp协议
client.connect(('127.0.0.1',8081)) #进行3次握手
while True:
msg = input('输入要发送的消息:').strip()
if len(msg) == 0:continue #解决输入空格的bug
client.send(msg.encode('gbk')) #编码以二进制的形式发送
# 接收端
# 1.先收到4个字节,从中提取接下来头的长度
header = client.recv(4)
#2.接收头,并解析
header_len = struct.unpack('i',header)[0] #header_len为二进制的长度
json_str_bytes = client.recv(header_len) #接收
json_str = json_str_bytes.decode('gbk') #二进制转成字符串
header_dic = json.loads(json_str) #字符串转成数据类型
print(header_dic)
total_size = header_dic['total_size'] #得到头的长度
#接收真实的数据
recv_size = 0
# 直到recv_size = total_size,结束循环
while recv_size < total_size:
recv_data = client.recv(1024)
recv_size+=len(recv_data)
print(recv_data.decode('gbk'),end='')
print('')
client.close()
运行得到的结果为:
{'filename': 'a.txt', 'total_size': 65, 'md5': '21561adas61d6'}
'weqeqw' 不是内部或外部命令,也不是可运行的程序
或批处理文件。