TCP协议是可靠协议,流式协议。
所以,一次接收不完全的数据会留在缓存里继续等待接收,而且,流式协议不知道何时数据传输完成。
这就导致了粘包问题。
利用 协议 ,规定 报头 ,从 报头 得到 数据的总大小,然后,循环取值,直到接收到 总大小的数据,结束 循环,进行下一次的发送。
这样就能够知道,一段完整的数据,的开始与结束,更不会粘包,而影响下一次的发送结果。
报头:数据的总大小
数据:发送的数据
协议:要有固定的长度
这里将数据长度,用
struct 模块,进行处理,把 int 转为 bytes ,可以转为固定长度
struct:
将数字转为固定长度的bytes类型
stuct.pack('i',111)
i 模式:将数字转为整型的bytes类型 ,即四个字节长度
stuct.unpack('i',x)
得到一个元组,第一个元素,为解压出来的值
接收端先接收,比如,4个字节。
用json,将头部序列化,别的应用也能反序列化得到头部信息(比如,字典 )
以远程执行命令socket为例。
服务端:
from socket import *
import subprocess
import struct
phone=socket(AF_INET,SOCK_STREAM) # 流式协议=》tcp协议
phone.setsockopt(SOL_SOCKET,SO_REUSEADDR,1) #就是它,在bind前加
phone.bind(('127.0.0.1',8083)) # 0-65535, 1024以前的都被系统保留使用
phone.listen(5) # 5指的是半连接池的大小
# 服务端应该做两件事
# 第一件事:循环地从板连接池中取出链接请求与其建立双向链接,拿到链接对象
while True:
conn,client_addr=phone.accept()
# 第二件事:拿到链接对象,与其进行通信循环
while True:
try:
data=conn.recv(1024) # 最大接收的数据量为1024Bytes,收到的是bytes类型
if len(data) == 0:
# 在unix系统洗,一旦data收到的是空
# 意味着是一种异常的行为:客户度非法断开了链接
break
result = subprocess.Popen(data.decode('utf-8'),shell=True,stdout=subprocess.PIPE,stderr=subprocess.PIPE)
out = result.stdout.read()
err = result.stderr.read()
conn.send( struct.pack('i',len(out) + len(err)) )
conn.send(out)
conn.send(err)
print("客户端发来的消息:",data.decode('utf-8'))
except Exception:
# 针对windows系统
break
# 6、关闭电话连接conn(必选的回收资源的操作)
conn.close()
# 7、关机(可选操作) 要实现不断地从半连接池中取出连接请求,服务器不能关闭
# phone.close()
客户端:
import socket
import struct
phone=socket.socket(socket.AF_INET,socket.SOCK_STREAM) # 流式协议=》tcp协议
phone.connect(('127.0.0.1',8083))
while True:
msg=input("命令>>>: ").strip() #msg=''
if len(msg) == 0:continue
phone.send(msg.encode('utf-8'))
top = phone.recv(4)
top_len = struct.unpack('i',top)
size = 0
while size < top[0]:
data=phone.recv(1024)
print(data.decode('gbk'),end='')
size += len(data)
#4、关闭连接(必选的回收资源的操作)
phone.close()
注意:
subprocess模块中,Popen这个执行命令的结果(stdout,stderr等)只能读一次,read()第一次有结果,第二次就没了
recv 的 nagle算法:将两个 时间间隔短 , 数据量小 的,结合为一个数据块。(流式协议)
第一次的send,一般是第一次recv的结果,第二次send,一般是第二次recv的结果。如果三次send,只有两次recv,那第三次的就会存放在缓存中,等下一次recv。
通常的,recv要不就是立马的send,要不就是上次send,剩余在缓存里的数据,它们根据nagle算法,来形成块,即每次recv得到的结果,数据块。
下面三张图,自己体会。。。