粘包现象

Posted Sober--

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了粘包现象相关的知识,希望对你有一定的参考价值。

一、基于udp的套接字

udp是无链接的,先启动哪一端都不会报错

udp服务端:

ss = socket()   #创建一个服务器的套接字
ss.bind()       #绑定服务器套接字
while True :       #服务器无限循环
    cs = ss.recvfrom()/ss.sendto() # 对话(接收与发送)
ss.close()                         # 关闭服务器套接字

udp客户端:

cs = socket()   # 创建客户套接字
while True :      # 通讯循环
    cs.sendto()/cs.recvfrom()   # 对话(发送/接收)
cs.close()                      # 关闭客户套接字

1、udp套接字简单实例

服务端:

from socket import *

udp_ss=socket(AF_INET,SOCK_DGRAM)
udp_ss.bind((\'127.0.0.1\',8080))

while True:
    msg,addr=udp_ss.recvfrom(1024)
    print(msg,addr)
    udp_ss.sendto(msg.upper(),addr)

客户端:

from socket import *

udp_cs=socket(AF_INET,SOCK_DGRAM)

while True:
    msg=input(\'>>: \').strip()
    if not msg:continue
    udp_cs.sendto(msg.encode(\'utf-8\'),(\'127.0.0.1\',8080))
    msg,addr=udp_cs.recvfrom(1024)
    print(msg.decode(\'utf-8\'),addr)

2、模拟聊天(由于udp无连接,所以可以同时多个客户端去跟服务端通信)

服务端:

from socket import *

udp_ss=socket(AF_INET,SOCK_DGRAM)
udp_ss.bind((\'127.0.0.1\',8081))

while True:
    msg,addr=udp_ss.recvfrom(1024)
    print(\'来自[%s]的一条消息:%s\' %(addr,msg.decode(\'utf-8\')))
    msg_b=input(\'回复消息: \').strip()
    udp_ss.sendto(msg_b.encode(\'utf-8\'),addr)

客户端1:

from socket import *

udp_cs = socket(AF_INET,SOCK_DGRAM)

while True :
    msg = input(\'请输入消息,回车发送: \').strip()
    if msg == \'quit\' : break
    if not msg : continue
    udp_cs.sendto(msg.encode(\'utf-8\'),(\'127.0.0.1\',8081))

    back_msg,addr = udp_cs.recvfrom(1024)
    print(\'来自[%s]的一条消息:%s\' %(addr,back_msg.decode(\'utf-8\')))

udp_cs.close()

客户端2:

from socket import *

udp_cs = socket(AF_INET,SOCK_DGRAM)

while True :
    msg = input(\'请输入消息,回车发送: \').strip()
    if msg == \'quit\' : break
    if not msg : continue
    udp_cs.sendto(msg.encode(\'utf-8\'),(\'127.0.0.1\',8081))

    back_msg,addr = udp_cs.recvfrom(1024)
    print(\'来自[%s]的一条消息:%s\' %(addr,back_msg.decode(\'utf-8\')))

udp_cs.close()

二、粘包现象

先做粘包现象:

服务端:

from socket import *
phone=socket(AF_INET,SOCK_STREAM)
phone.setsockopt(SOL_SOCKET,SO_REUSEADDR,1)
phone.bind((\'127.0.0.1\',8080))
phone.listen(5)
conn,client_addr=phone.accept()

data1=conn.recv(1024)
print(\'data1: \',data1)
data2=conn.recv(1024)
print(\'data2:\',data2)

客户端:

from socket import *
phone=socket(AF_INET,SOCK_STREAM)
phone.connect((\'127.0.0.1\',8080))

phone.send(\'hello\'.encode(\'utf-8\'))
phone.send(\'world\'.encode(\'utf-8\'))

我们再将上个随笔里的ssh例子拿出来(先执行 ipconfig /all 再执行 dir 看结果)

客户端:

from socket import *
import subprocess
cs=socket(AF_INET,SOCK_STREAM)
cs.setsockopt(SOL_SOCKET,SO_REUSEADDR,1)
cs.bind((\'127.0.0.1\',8082))
cs.listen(5)

print(\'starting...\')
while True:
    conn,addr=cs.accept()
    print(\'-------->\',conn,addr)

    while True:
        try:
            cmd=conn.recv(1024)
            res = subprocess.Popen(cmd.decode(\'utf-8\'), shell=True,
                                   stdout=subprocess.PIPE,
                                   stderr=subprocess.PIPE)
            stdout=res.stdout.read()
            stderr=res.stderr.read()

            #发送命令的结果
            conn.send(stdout+stderr)
        except Exception:
            break
    conn.close() #挂电话
cs.close() #关机

服务端:

from socket import *
ss=socket(AF_INET,SOCK_STREAM) #买手机
ss.connect((\'127.0.0.1\',8082)) #绑定手机卡

#发,收消息
while True:
    cmd=input(\'>>: \').strip()
    if not cmd:continue
    ss.send(cmd.encode(\'utf-8\'))
    cmd_res=ss.recv(1024)
    print(cmd_res.decode(\'gbk\'))
ss.close()

注意:

subprocess模块的结果的编码是以当前所在的系统为准的,如果是windows,那么res.stdout.read()读出的就是GBK编码的,在接收端需要用GBK解码


 

三、粘包

注意:只有TCP有粘包现象,UDP永远不会粘包,首先需要掌握一个socket收发消息的原理

应用程序所看到的数据是一个整体,或说是一个流(stream),一条消息有多少字节对应用程序是不可见的,因此TCP协议是面向流的协议,这也是容易出现粘包问题的原因。

例如基于tcp的套接字客户端往服务端上传文件,发送时文件内容是按照一段一段的字节流发送的,在接收方看了,根本不知道该文件的字节流从何处开始,在何处结束

所谓粘包问题主要还是因为接收方不知道消息之间的界限,不知道一次性提取多少字节的数据所造成的。

此外,发送方引起的粘包是由TCP协议本身造成的,TCP为提高传输效率,发送方往往要收集到足够多的数据后才发送一个TCP段。若连续几次需要send的数据都很少,通常TCP会根据优化算法把这些数据合成一个TCP段后一次发送出去,这样接收方就收到了粘包数据。

两种情况会粘包:

1、发送端需要等缓冲区满才发送出去,造成粘包(发送数据时间间隔很短,数据了很小,会合到一起,产生粘包)

2、接收方不及时接收缓冲区的包,造成多个包接收(客户端发送了一段数据,服务端只收了一小部分,服务端下次再收的时候还是从缓冲区拿上次遗留的数据,产生粘包) 

拆包的发生情况:

当发送端缓冲区的长度大于网卡的MTU时,tcp会将这次发送的数据拆成几个数据包发送出去。

四、解决粘包方法

粘包现象中第一个现象解决:

解决一:(需要知道每次发过来的数据大小 不现实)

from socket import *
phone=socket(AF_INET,SOCK_STREAM)
phone.setsockopt(SOL_SOCKET,SO_REUSEADDR,1)
phone.bind((\'127.0.0.1\',8080))
phone.listen(5)
conn,client_addr=phone.accept()

data1=conn.recv(10)
print(\'data1: \',data1)
data2=conn.recv(4)
print(\'data2:\',data2)
服务端
from socket import *
phone=socket(AF_INET,SOCK_STREAM)
phone.connect((\'127.0.0.1\',8080))

phone.send(\'helloworld\'.encode(\'utf-8\'))
phone.send(\'egon\'.encode(\'utf-8\'))
客户端

解决二:

 服务端
 客户端

ssh例子问题解决:

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

from socket import *
phone=socket(AF_INET,SOCK_STREAM)
phone.setsockopt(SOL_SOCKET,SO_REUSEADDR,1)
phone.bind((\'127.0.0.1\',8080))
phone.listen(5)
conn,client_addr=phone.accept()

data1=conn.recv(1024)
print(\'data1: \',data1)
data2=conn.recv(1024)
print(\'data2:\',data2)
服务端
from socket import *
import time
phone=socket(AF_INET,SOCK_STREAM)
phone.connect((\'127.0.0.1\',8080))

phone.send(\'hello\'.encode(\'utf-8\'))
time.sleep(5)
phone.send(\'world\'.encode(\'utf-8\'))
客户端

五、struct模块(了解)

该模块可以把一个类型,如数字,转成固定长度的bytes

struct.pack(\'i\',11111111)
#struct.error: \'i\' format requires -2147483648 <= number <= 2147483647 #这个是范围

struct.pack用于将Python的值根据格式符,转换为字符串(因为Python中没有字节(Byte)类型)。它的函数原型为:struct.unpack(fmt, string)。

struct.unpack做的工作刚好与struct.pack相反,用于将字节流转换成python数据类型。它的函数原型为:struct.unpack(fmt, string),该函数返回一个元组

以上是关于粘包现象的主要内容,如果未能解决你的问题,请参考以下文章

socket网络编程:粘包现象以及解决方法(代码完善)

网络编程基础之粘包现象

详解啥是 TCP 粘包和拆包现象并演示 Netty 是如何解决的

透过现象看本质,我找到了Netty粘包与半包的这几种解决方案。

Netty进阶——粘包与半包(现象分析)

Netty进阶——粘包与半包(现象分析)