粘包出现的原因:
UDP没有粘包是因为UDP是面向消息的,TCP出现是因为TCP的工作原理出现的粘包现象。TCP是面向流的,没有起点和结尾,不知道一条消息有多少字节,UDP发送消息会发送消息的长度,这也就是为什么UDP发送消息为空时不会阻塞,只有缓冲区为空时才会阻塞recv()函数。面向流的消息(TCP)是无边界的,面向报文的消息(UDP)是有边界的。只有TCP有粘包现象(nagle算法的存在)。TCP套接字没有一收一发的规矩,而UDP有这个规则。为什么说tcp可靠是因为每次发消息都会从客户端返回一个ack,才会删除自己缓存的信息。
粘包出现的两种方式:
1、服务端出现粘包:当发送的消息数据的间隔短,会将几次的分开的消息一次性读出来。产生粘包。
服务端:一次性收到‘hai’。
import socket
ip_port = ('127.0.0.1', 8080)
back_log = 5
buffer_size = 1024
ser = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
ser.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
ser.bind(ip_port)
ser.listen(back_log)
while 1:
con, address = ser.accept()
res = con.recv(1024)
print(res)
>>> b'hai'
客户端:分三次发送'h','a','i'.
import socket
p = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
p.connect(('127.0.0.1', 8080))
while 1:
p.send('h'.encode('utf-8'))
p.send('a'.encode('utf-8'))
p.send('i'.encode('utf-8'))
2、客户端出现粘包:数据无法一次性读完
客户端:两次接收都是上次的数据。
import socket
p = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
p.connect(('127.0.0.1', 8080))
print(p.recv(2))
print(p.recv(2))
>>> b'12'
>>> b'34'
服务端:一次性发送大量的数据
import socket
ip_port = ('127.0.0.1', 8080)
back_log = 5
buffer_size = 1024
ser = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
ser.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
ser.bind(ip_port)
ser.listen(back_log)
con, address = ser.accept()
con.send('123456987845641321545645641654564165132'.encode('utf-8'))
解决粘包的方案:
服务端:
import socket
import subprocess
# 配置信息
ip_port = ('127.0.0.1', 8080)
back_log = 5
buffer_size = 1024
# 声明套接字类型
ser = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
ser.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
ser.bind(ip_port)
ser.listen(back_log)
# 连接循环
while 1:
con, address = ser.accept()
# 通信循环
while 1:
try:
msg = con.recv(buffer_size)
print('服务器收到消息', msg.decode('utf-8'))
if msg.decode('utf-8') == '1':
con.close()
# 子进程Popen的方式去运行一个命令,输出的结果放到管道中
res = subprocess.Popen(msg.decode('utf-8'), shell=True, stdout=subprocess.PIPE)
# 从管道中读取信息
fin = res.stdout.read().decode('gbk')
# 发送数据字节长度,防止粘包
bit = fin.encode('utf-8').__sizeof__()
con.send(str(bit).encode('utf-8')) # 这两次粘一起,第一个send用固定的字节接受,就不会出现粘包
con.send(fin.encode('utf-8'))
except Exception as e:
break
客户端:
import socket
p = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
p.connect(('127.0.0.1', 8080))
while 1:
msg = input('please input')
# 防止发送空消息
if not msg:
continue
# 输入1退出
if msg == '1':
break
# 发送消息时编码
p.send(msg.encode('utf-8'))
# 接收需要正式接受多少字节长度的数据长度,防止粘包
bit = p.recv(24)
# 接收数据
data = p.recv(int(bit.decode('utf-8')))
print(data.decode('utf-8'))
p.close()
TIPS:
✳:result = subprocess.Popen('字符串的命令',shell=True,stdout=subprocess.Pipe,stdin=subprocess.Pipe,stderr=subprocess.Pipe)
shell=True 是否运行cmd,stdout、stdin、stderr=subprocess.Pipe 标准输出、标准输入、标准错误都输出到管道内,默认丢给屏幕。
stdout.out.read() 读出数据。
✳:发送长度时也可以用struct模块:pack('i',数据) 压包 , unpack('i',数据) 解包
‘ i ’表示int型,‘ch’表示字符等等。。。。
✳:s.connect_ex()函数是connect拓展出错时不会报异常,返回异常.
s.getsockname() 获取当前套接字的地址.
s.getpeername()连接到当前套接字的远程地址.
✳:两个send中间加一个rece就可以解决粘包
✳:sendall方法就是反复调用send方法直到文件传输完毕,如果中间发生错误就不会返回穿了多少数据.
✳:一次send最大8k,当发送一个较大的文件时,不可能一次性发过去因为内存不够用,所以每次发送8k的数据8192个字节。
偏函数:
只能绑定第一个参数的成为固定值
from functools import partial
def add(x,y):
return x+y
func = partial(add,1) # 绑定一个参数默认值为一
print(func(1))