粘包现象

让我们基于tcp先制作一个远程执行命令的程序

res=subprocess.Popen(cmd.decode('utf-8'),

shell=True,

stderr=subprocess.PIPE,

stdout=subprocess.PIPE)

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

且只能从管道里读一次结果

只有TCP有粘包现象,udp永远你不会粘包,  tcp协议是面向流的协议, udp是面向消息的协议

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

tcp为提高传输效率,tcp优化算法会把一些数据合成一个tcp段后一次发送出去,这样接收方就收到了粘包数据

tcp在数据传输时,发送端先把数据发送到自己的缓存中,然后协议控制将缓存中的数据发往对端,对端返回一个ack=1,发送端则清理缓存中的数据,对端返回ack=0,则重新发送数据,所以tcp是可靠的

而udp发送数据,对端是不会返回确认信息的,因此不可靠

使用tcp协议远程执行命令

from socket import *

import subprocess

ip_port=('127.0.0.1',8080)
BUFSIZE=1024

tcp_socket_server=socket(AF_INET,SOCK_STREAM)

tcp_socket_server.bind(ip_port)

tcp_socket_server.listen(5)

while True:

    conn,addr=tcp_socket_server.accept()

    print('客户端‘,addr)

    

    while True:

        cmd=conn.recv(BUFSIZE)

        if len(cmd) == 0:break

   

        res = subprocess.Popen(cmd.decode('utf-8'),shell=True,

                                             stdout=subprocess.PIPE,

                                             stdin=subprocess.PIPE,

                                              stderr=subprocess.PIPE)       

      stderr=res.stderr.read()

      stdout=res.stdout.read()

       conn,send(stderr)

       conn,send(stdout)

客户端

import socket

BUFSIZE=1024

ip_port=('127.0.0.1',8080)

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

res=s.connect_ex(ip_port)

while True:

    msg=input('>>:').strip()

    if len(msg) == 0:break

    s.send(msg.encode('utf-8'))

    act_res=s.recv(BUFSIZE)

    print(act_res.decode('utf-8'),end=")

上述程序基于tcp的socket,在运行时会发生粘包

小面基于udp制作一个远程执行命令的程序

 from socket import *

import subprocess

ip_port=('127.0.0.1',8080)

bufsize=1024

udp_server=socket(AF_INET,SOCK=DGRAM)

udp_server.bind(ip_port)

while True:

    cmd,addr=udp_server.recvfrom(bufsize)

    print('用户命令’,cmd)

    

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

    stderr=res.stderr.read()

    stdout=res.stdout.read()

    udp_server.sendto(stderr,addr)

    udp_server.sendto(stdout,addr)

udp_server.close()

客户端

from socket import *

ip_port=('127.0.0.1',8080)

bufsize=1024

udp_client=socket(AF_INET,SOCK_DGRAM)

while True:

    msg=input('>>: ').strip()

    udp_client.sendto(msg.encode('utf-8'),ip_port)

    data,addr=udp_client.recvfrom(bufsize)

    print(data.decode('utf-8'),end=")

以上基于udp的socket,在运行时永远不会发生粘包

解决粘包的办法

问题的根源在于,接收端不知道发送端要传送的字节流的长度,所以解决粘包的办法就是围绕如何让发送端在发送数据前,把自己将要发送的字节流总大小让接收端知晓,然后接收端来一个死循环接受完所有数据

 low版解决方法

from socket import *

import subprocess

ip_port=('127.0.0.1',8080)

s=socket(AF_INET,SOCK.STREAM)

s.setsockopt(SOL_SOCKET,SO_REUSEADDR,1)

s.bind(ip_port)

s.listen(5)

while True:

    conn,addr=s.accept()

    print('客户端‘,addr)

    while True:

        msg=conn.recv(1024)

        if not msg:break

        res=subprocess.Popen(msg.decode('utf-8'),shell=True,

                                         stdin=subprocess.PIPE,

                                         stdout=subprocess.PIPE,

                                         stderr=subprocess.PIPE)

           err=res.stderr.read()

           if err:

                  ret=err

           else:

                ret=res.stdout.read()

           data_length=len(ret)

            conn.send(str(data_lenth).encode('utf-8'))

            data=conn.recv(1024).decode('utf-8')

            if data == 'recv_ready'

                    conn.sendall(ret)

        conn.close()

客户端

import socket,time

s=scoket.socket(socket.AF_INET,socket.SOCK.STRAM)

res=s.connect_ex(('127.0.0.1',8080))

while True:

    msg=input('>>:').strip()

    if len(msg) ==0:break

    if msg == 'quit':break

    s.send(msg.encode('utf-8'))

    length=int(s.recv(1024).decode('utf-8'))

    s.send('recv_ready'.encode('utf-8'))

    send_size=0

    recv_size=0

    data=b''

    while recv_size <length:

            data+=s.recv(1024)

            recv_size+=len(data)

print(data.deode('utf-8'))

程序运行速度远快于网络传输速度,所以在发送一段字节前,先用send去发送该字节流长度,这种方式会放大网络延迟带来的性能损耗

海峰老师解决粘包的方法

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

原文地址:https://www.cnblogs.com/mayicai/p/9220958.html