TCP协议,传输数据:报头 + 数据,循环接收,解决粘包问题

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得到的结果,数据块。
下面三张图,自己体会。。。

原文地址:https://www.cnblogs.com/pythonwl/p/12746099.html