1、粘包现象
1.1、什么是粘包
只有TCP有粘包现象,UDP永远不会粘包
粘包不一定会发生
如果发生了:
1.可能是在客户端已经粘了
2.客户端没有粘,可能是在服务端粘了
socket收发消息的原理
应用程序所看到的数据是一个整体,或说是一个流(stream),一条消息有多少字节对应用程序是不可见的,因此TCP协议是面向流的协议,这也是容易出现粘包问题的原因。(因为TCP是流式协议,不知道啥时候开始,啥时候结束)。而UDP是面向消息的协议,每个UDP段都是一条消息,应用程序必须以消息为单位提取数据,不能一次提取任意字节的数据,这一点和TCP是很不同的。怎样定义消息呢?可以认为对方一次性write/send的数据为一个消息,需要明白的是当对方send一条信息的时候,无论底层怎样分段分片,TCP协议层会把构成整条消息的数据段排序完成后才呈现在内核缓冲区。
所谓粘包问题主要还是因为接收方不知道消息之间的界限,不知道一次性提取多少字节的数据所造成的
1.2、粘包的两种情况
1.2.1、方式一
发送端需要等缓冲区满才发送出去,造成粘包(发送数据时间间隔很短,数据量很小,会合到一起,产生粘包)
# 服务端
from socket import *
ip_port = ('127.0.0.1', 8080)
TCP_socket_server = socket(AF_INET, SOCK_STREAM)
TCP_socket_server.bind(ip_port)
TCP_socket_server.listen(5)
conn, addr = TCP_socket_server.accept()
data1 = conn.recv(10)
data2 = conn.recv(10)
print('----->', data1.decode('utf-8'))
print('----->', data2.decode('utf-8'))
conn.close()
# 客户端
import socket
BUFSIZE = 1024
ip_port = ('127.0.0.1', 8080)
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
res = s.connect_ex(ip_port)
s.send('hello'.encode('utf-8'))
s.send('allen'.encode('utf-8'))
1.2.2、方式二
接收方不及时接收缓冲区的包,造成多个包接收(客户端发送了一段数据,服务端只收了一小部分,服务端下次再收的时候还是从缓冲区拿上次遗留的数据,产生粘包)
# 服务端
from socket import *
ip_port = ('127.0.0.1', 8080)
TCP_socket_server = socket(AF_INET, SOCK_STREAM)
TCP_socket_server.bind(ip_port)
TCP_socket_server.listen(5)
conn, addr = TCP_socket_server.accept()
data1 = conn.recv(2) # 一次没有收完整
data2 = conn.recv(10) # 下次收的时候,会先取旧的数据,然后取新的
print('----->', data1.decode('utf-8'))
print('----->', data2.decode('utf-8'))
conn.close()
# 客户端
import socket
BUFSIZE = 1024
ip_port = ('127.0.0.1', 8080)
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
res = s.connect_ex(ip_port)
s.send('hello allen'.encode('utf-8'))
1.3、解决远程执行命令粘包问题
服务端
import socket
import subprocess
import struct
server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server.bind(('127.0.0.1', 8080))
server.listen(5)
while True:
conn, client_addr = server.accept()
while True:
try:
cmd = conn.recv(1024)
obj = subprocess.Popen(cmd.decode('utf-8'), shell=True,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE)
stdout = obj.stdout.read()
stderr = obj.stderr.read()
total_size = len(stdout) + len(stderr)
# 先发送数据的长度
conn.send(struct.pack('i', total_size))
# 发送真正的数据
conn.send(stdout)
conn.send(stderr)
except Exception as e:
break
conn.close()
server.close()
客户端
import socket
import struct
client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
client.connect(('127.0.0.1', 8080))
while True:
msg = input('请输入命令: ').strip()
client.send(msg.encode('utf-8'))
# 先收数据的长度
n = 0
header = b''
while n < 4:
data = client.recv(1)
header += data
n += len(data)
total_size = struct.unpack('i', header)[0]
# 收真正的数据
recv_size = 0
res = b''
while recv_size < total_size:
data = client.recv(1024)
res += data
recv_size += len(data)
print(res.decode('gbk'))
client.close()
1.4、定制复杂的报头(拷贝文件)
服务端
import os
import struct
import json
from socket import *
server = socket(AF_INET, SOCK_STREAM)
server.bind(('127.0.0.1', 8080))
server.listen(5)
while True:
conn, client_addr = server.accept()
while True:
try:
msg = conn.recv(1024).decode('utf-8')
cmd, file_path = msg.split()
# 判断文件是否存在
if not os.path.exists(file_path):
conn.send('路径不存在'.encode('utf-8'))
# 3、处理数据
if cmd == 'get':
# 一、制作报头
header_dict = {
'total_size': os.path.getsize(file_path),
'file_name': os.path.basename(file_path),
}
header_json_dict = json.dumps(header_dict)
header_json_bytes = header_json_dict.encode('utf-8')
# 二、发送数据
# 1、先发送报头的长度
conn.send(struct.pack('i', len(header_json_bytes)))
# 2、再发送报头
conn.send(header_json_bytes)
# 3、最后发送真实的数据
with open(r'{}'.format(file_path), mode='rb') as fp:
for line in fp:
conn.send(line)
except Exception as e:
break
conn.close()
server.close()
客户端
import socket
import struct
import json
client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
client.connect(('127.0.0.1', 8080))
while True:
msg = input('请输入具体操作(get 文件路径): ').strip()
if len(msg) == 0: continue
client.send(msg.encode('utf-8'))
# 1、先接收报头的长度
header = b''
n = 0
while n < 4:
data = client.recv(1)
header += data
n += len(data)
header_size = (struct.unpack('i', header)[0])
# 2、再接收报头
header_info = b''
n = 0
while n < header_size:
data1 = client.recv(1)
header_info += data1
n += len(data1)
header_info_dict = json.loads(header_info.decode('utf-8'))
# 3、最后接收真实的数据
recv_size = 0
with open(r'download/{}'.format(header_info_dict.get('file_name')), mode='wb') as fp:
while recv_size < header_info_dict.get('total_size'):
data = client.recv(1024)
fp.write(data)
recv_size += len(data)
client.close()