详解python中TCP协议中的粘包问题

  

TCP协议中的粘包问题

  

  

基于TCP实现一个简易远程cmd功能

        #服务端   进口套接字   导入子流程   切断=socket.socket ()   sever.bind ((127.0.0.1, 33521))   sever.listen ()   而真正的:   客户端地址=sever.accept ()   而真正的:   试一试:   cmd=client.recv (1024) .decode (utf - 8)   p1=子流程。Popen (cmd, shell=True, stdout=子流程。管,stderr=subprocess.PIPE)   data=https://www.yisu.com/zixun/p1.stdout.read ()   err_data=p1.stderr.read ()   client.send(数据)   client.send (err_data)   除了ConnectionResetError:   print(连接坏了)   client.close ()   打破   sever.close ()   & # 8203;   & # 8203;   & # 8203;   #客户端   进口套接字   客户=socket.socket ()   client.connect ((127.0.0.1, 33521))   而真正的:   cmd=输入(“请输入指令(问\问退出)在祝辞:').strip () .lower ()   如果cmd==拔省?   打破   client.send (cmd.encode (utf - 8))   data=https://www.yisu.com/zixun/client.recv (1024)   print (data.decode (gbk))   client.close ()      

上述是基于TCP协议的远程cmd简单功能,在运行时会发生粘包。

  

  

只有TCP会发生粘包现象,UDP协议永远不会发生粘包;

  

TCP(传输控制协议,传输控制协议)流式协议。在插座中TCP协议是按照字节数进行数据的收,发数据的发送方发出的数据往往接收方不知道数据到底长度是多长,而TCP协议由于本身为了提高传输的效率,发送方往往需要收集到足够的数据才会进行发送。使用了优化方法纳格尔(算法),将多次间隔较小且数据量小的数据,合并成一个大的数据块,然后进行封包。这样,接收端,就难于分辨出来了,必须提供科学的拆包机制,即面向流的通信是无消息保护边界的。

  

UDP(用户数据报协议,用户数据报协议)数据报协议。在插座中UDP协议收发数据是以数据报为单位,服务端和客户端收发数据是以一个单位,所以不会使用块的合并优化算法,,由于UDP支持的是一对多的模式,所以接收端的skbuff(套接字缓冲区)采用了链式结构来记录每一个到达的UDP包,在每个UDP包中就有了消息头(消息来源地址,端口等信息),这样,对于接收端来说,就容易进行区分处理了。即面向消息的通信是有消息保护边界的。

  

TCP协议不会丢失数据,UDP协议会丢失数据。

  

udp的recvfrom是阻塞的,一个recvfrom (x)必须对唯一一个sendinto (y),收完了x个字节的数据就算完成,若是y> x数据就丢失,这意味着udp根本不会粘包,但是会丢数据,不可靠。

  

tcp的协议数据不会丢,没有收完包,下次接收,会继续上次继续接收,己端总是在收到ack时才会清除缓冲区内容。数据是可靠的,但是会粘包。

  

  

1。由于TCP协议的优化算法,当单个数据包较小的时候,会等到缓冲区满才会发生数据包前后数据叠加在一起的情况,然后取的时候就分不清了到底是哪段数据,这是第一种粘包。

  

2。当发送的单个数据包较大超过缓冲区时,收数据方一次就只能取一部分的数据,下次再收数据方再收数据将会延续上次为接收数据。这是第二种粘包。

  

粘包的本质问题就是接收方不知道发送数据方一次到底发送了多少数据,解决问题的方向也是从控制数据长度着手,也就是如何设置缓冲区的问题

  

  

解决问题思路:上述已经明确粘包的产生是因为接收数据时不知道数据的具体长度,所以我们应该先发送一段数据表明我们发送的数据长度,那么就不会产生数据没有发送或者没有收取完全的情况。

  

<强> 1。结构模块(结构体)

  

结构模块的功能可以将python中的数据类型转换成C语言中的结构体(字节类型)

        进口结构   s=123456789   res=结构。包(我,年代)   打印(res)   & # 8203;   它=结构。解压缩(“我”,res)   打印(它)   打印(它[0])      

<强> 2。粘包的解决方案基本版

  

既然我们拿到了一个可以固定长度的办法,那么应用结构体模块,可以固定长度了。

  

为字节流加上自定义固定长度报头,报头中包含字节流长度,然后一次发送到对端,对端在接收时,先从缓存中取出定长的报头,然后再取真实数据

        #服务器端   进口套接字   导入子流程   进口结构   切断=socket.socket ()   sever.bind ((127.0.0.1, 33520))   sever.listen ()   而真正的:   客户端地址=sever.accept ()   而真正的:   试一试:   cmd=client.recv (1024) .decode (utf - 8)   #利用子进程模块启动程序   p=子流程。Popen (cmd, shell=True, stdout=子流程。管,stderr=subprocess.PIPE)   #管道输出的信息有正确和错误的   data=https://www.yisu.com/zixun/p.stdout.read ()   err_data=p.stderr.read ()   #先将数据的长度发送给客户端   长度=len(数据)+ len (err_data)   #利用结构体模块将数据的长度信息转化成固定的字节   len_data=结构。包(“我”,长度)   #以下将信息传输给客户端   # 1。数据的长度   client.send (len_data)   # 2。正确的数据   client.send(数据)   # 2。错误管道的数据   client.send (err_data)   除了例外e:   client.close ()   打印(“连接中断....”)   打破      & # 8203;   #客户端   进口套接字   进口结构   & # 8203;   客户=socket.socket ()   client.connect ((127.0.0.1, 33520))   而真正的:   cmd=输入(“请输入指令祝辞祝辞:').strip () .encode (“utf - 8”)   client.send (cmd)   # 1。先接收传过来数据的长度是多少,我们通过结构模块固定了字节长度为4   长度=client.recv (4)   #将结构的字节再转回去整型数字   len_data=https://www.yisu.com/zixun/struct.unpack(“我”,长度)   打印(len_data)   len_data=https://www.yisu.com/zixun/len_data [0]   打印(“数据长度为% s:“% len_data)   & # 8203;   all_data=https://www.yisu.com/zixun/b”   recv_size=0   # 2。接收真实的数据   #循环接收直到接收到数据的长度等于数据的真实长度(总长度)   而recv_size & lt;len_data:   data=https://www.yisu.com/zixun/client.recv (1024)   recv_size +=len(数据)   all_data +=数据   ?   打印(“接收长度% s的% recv_size)   print (all_data.decode (gbk))

详解python中TCP协议中的粘包问题