day43-1 粘包问题

粘包问题

213131

TCP与UDP协议

  1. TCP(transport control protocol,传输控制协议)是面向连接的,面向流的,提供高可靠性服务。收发两端(客户端和服务器端)都要有一一成对的socket,因此,发送端为了将多个发往接收端的包,更有效的发到对方,使用了优化方法(Nagle算法),将多次间隔较小且数据量小的数据,合并成一个大的数据块,然后进行封包。这样,接收端,就难于分辨出来了,必须提供科学的拆包机制。 即面向流的通信是无消息保护边界的
  2. UDP(user datagram protocol,用户数据报协议)是无连接的,面向消息的,提供高效率服务。不会使用块的合并优化算法,, 由于UDP支持的是一对多的模式,所以接收端的skbuff(套接字缓冲区)采用了链式结构来记录每一个到达的UDP包,在每个UDP包中就有了消息头(消息来源地址,端口等信息),这样,对于接收端来说,就容易进行区分处理了。 即面向消息的通信是有消息保护边界的
  3. tcp是基于数据流的,于是收发的消息不能为空,这就需要在客户端和服务端都添加空消息的处理机制,防止程序卡住,而udp是基于数据报的,即便是你输入的是空内容(直接回车),那也不是空消息,udp协议会帮你封装上消息头,实验略

粘包现象

socket收发消息的原理

服务端可以1kb,1kb地发向客户端送数据,客户端的应用程序可以在缓存当中2kb,2kb地取走数据,当然也可以更多,或都更少。也就是说,应用程序看到的数据是来个整体。或者说是一个流。一条消息有多少字节对应用程序是不可见的,TCP协议是面向流的协议,这就是它容易粘包的问题原因。

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

tcp协议才会有粘包问题,udp协议没有

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

粘包情况一:发送端需要等缓冲区满才发送出去,造成粘包(发送数据时间间隔很短,数据量很小,TCP优化算法会当做一个包发出去,产生粘包)

1、服务端

from socket import *

server=socket(AF_INET,SOCK_STREAM)
server.bind(('127.0.0.1',8080))
server.listen(5)

conn,client_addr=server.accept()

res1=conn.recv(1024)
print('第一次:',res1)
res2=conn.recv(1024)
print('第二次:',res2)

conn.close()
server.close()

2、客户端


from socket import *

client=socket(AF_INET,SOCK_STREAM)
client.connect(('127.0.0.1',8080))

# 这两次发送数据由于数据量小且时间短,所以会被TCP优化到一起
client.send(b'hello')
client.send(b'world')
client.close()

先启动服务端,后再启动客户端,服务端得到的结果为:

扫描二维码关注公众号,回复: 6650534 查看本文章
第一次: b'helloworld'
第二次: b''

粘包情况二:客户端发关了一段数据,服务端只收了一小部分,服务端下次再收的时候不是从缓冲区拿上次遗留的数据,产生粘包。

情况一的,客户端不变,服务端略作修改,如下:

from socket import *

server=socket(AF_INET,SOCK_STREAM)
server.bind(('127.0.0.1',8080))
server.listen(5)

conn,client_addr=server.accept()

res1=conn.recv(2)   # 只接受两个字节
print('第一次:',res1)
res2=conn.recv(3)
print('第二次:',res2)

conn.close()
server.close()
第一次: b'he'
第二次: b'llo'

解决方案

问题的根源在于,接收端不知道发送端将要传送的字节流的长度,所以解决粘包问题的方法就是在发送端发送前,发一个头文件包,告诉发送的字节流总大小

第一次情况

服务端

from socket import *
import struct

server=socket(AF_INET,SOCK_STREAM)
server.bind(('127.0.0.1',8080))
server.listen(5)

conn,client_addr=server.accept()
# 因为struct中的i就是接受4个字节
res_bytes = conn.recv(4)
count_len = struct.unpack('i', res_bytes)[0]
res1 = conn.recv(count_len)
print('第一次:',res1)

res_bytes = conn.recv(4)
count_len = struct.unpack('i', res_bytes)[0]
res2 = conn.recv(count_len)
print('第二次:',res2)

conn.close()
server.close()

客户端

from socket import *
import struct

client=socket(AF_INET,SOCK_STREAM)
client.connect(('127.0.0.1',8080))

count_len = len('hello')

res_bytes = struct.pack('i', count_len)
# 先发送字节流总大小,再发送数据
client.send(res_bytes)
client.send(b'hello')

count_len = len('world')
res_bytes = struct.pack('i', count_len)
client.send(res_bytes)
client.send(b'world')
client.close()

第二种情况,就以ssh的tasklist命令为例,本来该命令由于太长,无法获取

服务端

import socket
import subprocess
import struct

phone = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

phone.bind(("127.0.0.1", 8087))
phone.listen(5)
print('wait...')
while True:
    conn, client_addr = phone.accept()
    print(client_addr)

    while True:
        try:

            cmd = conn.recv(1024)
            print(cmd)

            pipeline = subprocess.Popen(cmd.decode('utf-8'),
                                        shell=True,
                                        stderr=subprocess.PIPE,
                                        stdout=subprocess.PIPE)

            stdout = pipeline.stdout.read()
            stderr = pipeline.stderr.read()

            print(stderr)
            print(stdout)
            count_len = len(stderr) + len(stdout)
            res_len = struct.pack('i', count_len)
            # 同理,先发送字节流大小,再发送数据
            conn.send(res_len)
            conn.send(stdout + stderr)
        except ConnectionResetError:
            break

    conn.close()

客户端

import socket
import struct

phone = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
phone.connect(('127.0.0.1', 8087))

while True:
    msg = input('please your enter msg')
    phone.send(msg.encode('utf-8'))

    count_len = phone.recv(4)
    res_len = struct.unpack('i', count_len)[0]

    data = phone.recv(res_len)
    print(data.decode('gbk'))

猜你喜欢

转载自www.cnblogs.com/lucky75/p/11098937.html