1.粘包现象
每个 socket 被创建后,都会分配两个缓冲区,输入缓冲区和输出缓冲区。write()/send() 并不会立即向网络中传输数据,而是先将数据写入缓冲区中,再由TCP协议将数据从缓冲区发送到目标机器。一旦将数据写入到缓冲区,函数就可以成功返回,不管它们有没有到达目标机器,也不管它们何时被发送到网络,这些都是TCP协议负责的事情,tcp的协议数据不会丢,没有收完包,下次接收,会继续上次继续接收,己端总是在收到ack时才会清除缓冲区内容。数据是可靠的,但是会粘包。两种情况下会发生粘包现象:
现象1:第一次客户端send数据,总长度>1024字节,发送到服务端recv(),服务端send()至客户端recv缓冲区,但因数据总长度>1024,多出的那部分数据停留在输入缓冲区第二次客户单send数据,一触发send,就执行recv,此时能拿到上次剩余的数据
现象2:客户端输入的数据过短,缓冲区会等到较满时才会发送至服务端,此时拿到的数据可能是多次send较短数据的拼接结果
代码查看缓冲区的大小
import socket
server.bind(('127.0.0.1',8010))
server.listen(3)
print(server.getsockopt(socket.SOL_SOCKET,socket.SO_SNDBUF)) # 输出缓冲区大小
print(server.getsockopt(socket.SOL_SOCKET,socket.SO_RCVBUF)) # 输入缓冲区大小
2.粘包现象的解决
struct模块,可以把一个类型,如数字,转成固定长度的bytes,对照表如下图所示:
Format | C Type | Python Type | Standard Size | Notes |
x | pad byte | no value | | |
c | char | string of length 1 | 1 | |
b | signed char | integer | 1 | (3) |
B | unsigned char | integer | 1 | (3) |
? | _Bool | bool | 1 | (1) |
h | short | integer | 2 | (3) |
H | unsigned short | integer | 2 | (3) |
i | int | integer | 4 | (3) |
I | unsigned int | integer | 4 | (3) |
l | long | integer | 4 | (3) |
L | unsigned long | integer | 4 | (3) |
q | long long | integer | 8 | (2),(3) |
Q | unsigned long long | integer | 8 | (2),(3) |
f | float | float | 4 | (4) |
d | double | float | 8 | (4) |
s | char[] | string | | |
P | char[] | string | | |
p | void * | integer | | (5),(3) |
具体转化方式:
import struct
# 将一个数字转化成等长度的bytes类型。
ret = struct.pack('i', 183346)
print(ret, type(ret), len(ret))
# 通过unpack反解回来
ret1 = struct.unpack('i',ret)[0]
print(ret1, type(ret1), len(ret1))
转换固定字节
01.low版
由于接收端不知道发送端将要传送的字节流的长度,所以解决粘包的方法就是围绕,如何让发送端在发送数据前,把自己将要发送的字节流总数按照固定字节发送给接收端后面跟上总数据,然后接收端先接收固定字节的总字节流,再循环接收完所有数据。
server端:
import socket
import subprocess
import struct
server = socket.socket()
server.bind(('127.0.0.1',8848))
server.listen(5)
while True:
con, addr = server.accept()
while 1:
try:
cmd = con.recv(1024).decode('utf-8')
obj = subprocess.Popen(cmd,
shell=True,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE
)
right_msg = obj.stdout.read()
error_msg = obj.stderr.read()
total_data_len = len(right_msg + error_msg)
# 把数据总长度转化为固定长度的bytes类型
total_datasize_bytes = struct.pack('i',total_data_len)
# 发送固定长度的字节
con.send(total_datasize_bytes)
con.send(right_msg + error_msg) # 发送所有数据
except Exception:
break
con.close()
server.close()
client端:
import socket
import struct
client = socket.socket()
client.connect(('127.0.0.1',8848))
while True:
cmd = input('>>>').strip()
client.send(cmd.encode('utf-8'))
# 接收固定长度的字节
head_bytes = client.recv(4) # b'x20\x00\x10\x00'
# 将固定长度的字节还原成原int类型
total_data_len = struct.unpack('i',head_bytes)[0]
print('总长度 ---> ',total_data_len)
# 循环接收数据
total_data = b''
while len(total_data) < total_data_len:
data = client.recv(1024)
total_data += data
print(total_data.decode('gbk'))
client.close()
02.自定制报头版
可以把报头做成字典,字典里包含将要发送的真实数据的描述信息,然后json序列化,然后用struck将序列化后的数据长度打包成4个字节。
我们在网络上传输的所有数据 都叫做数据包,数据包里的所有数据都叫做报文,报文里面不止有你的数据,还有ip地址、mac地址、端口号等等,其实所有的报文都有报头,这个报头是协议规定的
发送时:01.先发报头长度
02.编码报头内容然后发送
03.最后发真实内容.
import socket
import subprocess
import json
import os
import struct
server = socket.socket()
server.bind(('本地回环地址', 端口号))
server.listen(5)
while True:
con, addr = server.accept()
while 1:
try:
obj_dic = {
'user_name': '文件名',
'user_md5': md5值
'user_path': 文件路径,
'user_size': 文件大小(os.path.getsize(路径))
}
obj_dic_json = json.dumps(obj_dic)
obj_dic_json_bytes = obj_dic_json.encode('utf-8')
obj_dic_json_bytes_pack = struct.pack('i', len(obj_dic_json_bytes))
con.send(obj_dic_json_bytes_pack)
con.send(obj_dic_json_bytes)
# 发送具体数据
con.send()
except Exception:
break
con.close()
server.close()
接收时:01.先收报头长度,用struct解包取出来
02.根据取出的长度收取报头内容,然后解码,反序列化
03.从反序列化的结果中取出待取数据的描述信息,然后去取真实的数据内容
import socket
import struct
import json
client = socket.socket()
client.connect(('服务端ip',端口号))
while True:
data = input('>>>')
client.send(data.encode('utf-8'))
obj_dic_json_bytes_pack = client.recv(4)
obj_dic_json_bytes_size = struct.unpack('i',obj_dic_json_bytes_pack)
obj_dic_json_bytes = client.recv(obj_dic_json_bytes_size)
obj_dic_json = obj_dic_json_bytes.decode('utf-8')
obj_dic = json.loads(obj_dic_json)
obj_data = b''
while len(obj_data) < dic['user_size']:
obj_data += client.recv(1024)
print(obj_data.decode('gbk'))
client.close()
3.udp协议下的socket通信
udp协议: 效率很高,但数据不安全
udp-server端
import socket
server = socket.socket(type=socket.SOCK_DGRAM)
server.bind(('本地回环地址', 端口号))
try:
while True:
client_data, client_addr = server.accept()
print('来自%s的消息:%s'%(client_addr,client_data.decode('utf-8')))
to_client_data = input('回复:').strip().encode('utf-8')
server.send(to_client_data,client_addr)
finally:
server.close()
udp-client端:
import socket
client = socket.socket(type=socket.SOCK_DGRAM)
while True:
to_server_data = input('给服务端发送:').strip().encode('utf-8')
client.send(to_server_data,('服务端ip',端口号))
server_data, server_addr = server.recvfrom(1024)
print('来自%s的信息:%s'%(server_addr,server_data.decode('utf-8')))