高手过招--论TCP之粘包的解决方法

粘包,就是查询的内容都粘到一起了,比如客户端发送ipconfig /all命令到服务端,客户端的只收取一次服务端的返回结果,且设置为一次只能取出1024个字节的数据。
假设ipconfig /all这条命令的返回结果大小是2048个字节,这就意味着还有1024没有取出来,仍然会保存在客户端的缓存中。此时客户端再发送一个dir命令到服务端,
客户端收到的返回信息就是剩下的ipconfig /all的结果,因为TCP数据传输是队列形式,原理就是先进先出。这时候并没有出现dir的真正结果,这种情况就叫做粘包。
有人可能会说,那为什么不加大客户端的可接收范围呢,比如把1024改成2048,大哥,你客户端知道自己发送的命令执行结果的大小吗?显然不知道,不知道设置个屁呀!
那么问题来了,到底该怎么解决粘包问题呢?、
很简单,思路就是我们只需要拿到ipconfig /all命令的大小就可以知道该收多少字节了,拿到了命令执行结果的字节,我们就知道该收取多少了,这时候就可以改客户端的1024了对不对,改成2048??
如果你这么玩,我只想对你说三个字:臭傻逼。

应该写个循环,多收几次,直到收完结束循环不就解决问题了吗??
来吧,上代码:


服务端:
import socketserver
import subprocess
ip_port=('127.0.0.1',8080)

class Myserver(socketserver.BaseRequestHandler):
    def handle(self):
        while True:
            try:
                cmd=self.request.recv(1024).decode('utf-8')
                if not cmd:break
                res=subprocess.Popen(cmd,shell=True,
                                    stderr=subprocess.PIPE,
                                    stdout=subprocess.PIPE,
                                    stdin=subprocess.PIPE)
                res_err=res.stderr.read()
                if res_err:
                    res_cmd=res_err
                res_cmd=res.stdout.read()
                if not res_cmd:
                    res_cmd='空命令执行了'.encode('gbk')
                cmd_length=len(res_cmd)
                self.request.sendall(str(cmd_length).encode('gbk'))
                data=self.request.recv(1024)
                self.request.sendall(res_cmd)
            except Exception as e:
                break

s=socketserver.ThreadingTCPServer(ip_port,Myserver)
print('server starting...')
s.serve_forever()

客户端:
import socket
ip_port=('127.0.0.1',8080)

class Myclient:
    def __init__(self):
        self.sock=socket.socket()
        self.sock.connect(ip_port)
        self.exec()
    def exec(self):
        while True:
            cmd=input('输入命令:').strip()
            if not cmd:continue
            if cmd == 'quit':break
            self.sock.send(cmd.encode('utf-8'))

            res_cmd=0
            data=b''
            cmd_length=self.sock.recv(1024).decode('gbk')
            self.sock.send('ok'.encode('utf-8'))
            while res_cmd < int(cmd_length):
                data+=self.sock.recv(1024)
                res_cmd += len(data)
            #print(res_cmd)
            print(data.decode('gbk'))


m=Myclient()

上面是用类的方式写的,解决了多线程问题,可以同时开多个客户端且互不影响。
下面是原始的写法:

服务端:
from socket import *
import subprocess
ip_port=('127.0.0.1',8003)
buffer_size=1024
backlog=5

tcp_server=socket(AF_INET,SOCK_STREAM)
tcp_server.bind(ip_port)
tcp_server.listen(backlog)

while True:
    print('服务端开始运行...')
    conn,addr=tcp_server.accept()
    print('客户端已连接上来...')
    while True:
        try:
            cmd_msg=conn.recv(1024).decode('utf-8')
            if not cmd_msg:break
            res=subprocess.Popen(cmd_msg,shell=True,
                                 stderr=subprocess.PIPE,
                                 stdout=subprocess.PIPE,
                                 stdin=subprocess.PIPE)
            res_err=res.stderr.read()
            if res_err:
                res_cmd=res_err
            res_cmd=res.stdout.read()
            if not res_cmd:
                res_cmd='空命令已执行,只是没结果.'.encode('gbk')
            cmd_length=len(res_cmd)
            conn.send(str(cmd_length).encode('utf'))
            data=conn.recv(1024)
            conn.send(res_cmd)
        except Exception:
            break
    conn.close()
tcp_server.close()

客户端:
from socket import *
ip_port=('127.0.0.1',8003)
buffer_size=1024


tcp_client=socket(AF_INET,SOCK_STREAM)
tcp_client.connect(ip_port)

while True:
    cmd_msg=input('输入命令:')
    if not cmd_msg:continue
    if cmd_msg == 'quit':break
    tcp_client.send(cmd_msg.encode('utf-8'))
    #收到的命令执行结果大小占多少字节
    cmd_length=tcp_client.recv(1024).decode('utf')

    '''
       下面这句话其实是为了防止粘包,因为粘包的情况有两种:
       (1) 待读取结果太大超出了1024的范围
       (2) 前后两条send命令且他们中间没有recv
    '''
    tcp_client.send('ok'.encode('utf-8'))
    client_cmd_length=0
    res_cmd=b''
    while client_cmd_length < int(cmd_length):
        res_cmd += tcp_client.recv(1024)
        client_cmd_length = len(res_cmd)
    print(res_cmd.decode('gbk'))
tcp_client.close()

注意:subprocess执行后的命令必须用gbk字符集

怎么样?你感觉这种解决方法牛逼不??
悄悄告诉你,low逼的很。
下面我就传授你们一个高逼格的玩法,让你们见识下什么叫做盖世神功。

高端玩法:
这里要用到struct库
思路(以ipconfig命令举例):
1.服务端先拿到客户端发来ipconfig执行结果的大小并赋给变量cmd_length(记得转成int类型)
2.服务端利用struct的pack方法把cmd_length(必须要是int类型)包装成一个4字节的内容并发送给客户端
3.服务端再发送真实的命令执行结果到客户端
4.客户端先收取4个字节,这样就拿到了ipconfig执行结果的大小(第2步已经把此结果封装到一个4字节的空间中了)
5.客户端拿到ipconfig执行结果的大小后(假设为2000字节),此时就可以按照上面low版的方法写个循环继续操作了。

服务端:
from socket import *
import subprocess
import struct
ip_port=('127.0.0.1',8003)
buffer_size=1024
backlog=5

tcp_server=socket(AF_INET,SOCK_STREAM)
tcp_server.bind(ip_port)
tcp_server.listen(backlog)

while True:
    print('服务端开始运行...')
    conn,addr=tcp_server.accept()
    print('客户端已连接上来...')
    while True:
        try:
            cmd_msg=conn.recv(1024).decode('utf-8')
            if not cmd_msg:break
            res=subprocess.Popen(cmd_msg,shell=True,
                                 stderr=subprocess.PIPE,
                                 stdout=subprocess.PIPE,
                                 stdin=subprocess.PIPE)
            res_err=res.stderr.read()
            if res_err:
                res_cmd=res_err
            res_cmd=res.stdout.read()
            if not res_cmd:
                res_cmd='空命令已执行,只是没结果.'.encode('gbk')
            cmd_length=len(res_cmd)
            #int类型默认就是pack成4字节,得到的cmd_length是字节类型
            cmd_length=struct.pack('i',cmd_length)
            conn.send(cmd_length)
            conn.send(res_cmd)
        except Exception:
            break
    conn.close()
tcp_server.close()


客户端:
from socket import *
import struct
ip_port=('127.0.0.1',8003)
buffer_size=1024


tcp_client=socket(AF_INET,SOCK_STREAM)
tcp_client.connect(ip_port)

while True:
    cmd_msg=input('输入命令:')
    if not cmd_msg:continue
    if cmd_msg == 'quit':break
    tcp_client.send(cmd_msg.encode('utf-8'))
    #收到的命令执行结果大小占多少字节
    cmd_length=tcp_client.recv(4)
    cmd_length=struct.unpack('i',cmd_length)
    client_cmd_length=0
    res_cmd=b''
    while client_cmd_length < cmd_length[0]:
        res_cmd += tcp_client.recv(1024)
        client_cmd_length = len(res_cmd)
    print(res_cmd.decode('gbk'))
tcp_client.close()

怎么样?惊不惊喜?意不意外?高不高端?
其实还有更高端的玩法,你想知道吗?对不起,确实有,但是我不会,哈哈哈哈哈哈哈。。。

posted @ 2024-08-23 09:10  疯狂Python  阅读(55)  评论(0)    收藏  举报