socket tcp 粘包解决

Posted Cloud-Tony

tags:

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

何为粘包:

先看代码

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

在定义socket对象的时候 有两个参数 一个是   socket地址家族,另一个是处理类型socket.SOCK_STREAM,注意是  ‘stream’:流

那既然是流处理类型,理解上就是 水流式  处理数据。 这个时候数据是没有边界(也就是没有从头开始,到哪里)的概念就像下图

现在执行命令很正常:

image

 

执行了一个cat /etc/passwd   ,

image

也能显示,但是后面发生了什么鬼

image

在主机上执行命令: ,可以看到 ,远程执行cat 的时候只是 拿到了rtkit ,而rtkit后面的 数据没有cat 到,这就是粘包,数据没有  头和尾,导致程序也不知道头和尾在哪,直到recv  虽然取了1024数据,但是数据只取了一半,还有一半在缓存里,没有取出来,导致在执行下一次执行命令的时候又取了1024字节的数据但是数据仍然不止1024字节,又没有取完,就导致了后面一直乱了。 
image

 

 

服务端在接收到 cat  /etc/passwd   的时候,然后将字节转换成命令,       并读取结果 将结果send回客户端 ,而客户端这时也是recv 1024 ,所以如果数据过多,客户端这里从自己的bruff cache 中读取到的1-1024 字节不够收,就造成了数据不对应 粘包的现象。

解决方法:  服务器在recv 字节处理后的stdout.read()  和stderr.read() 的结果都要加一个报头

报头: 有固定的长度。

            并且还有对数据信息的描述

需要一个新模块  import struct  ,

struct  使用: server 端

import struct
cmd=conn.recv(1024)
 res=subprocess.Popen(cmd.decode(\'utf-8\'),shell=True,stdout=subprocess.PIPE,stderr=subprocess.PIPE)
out_res=res.stdout.read()
err_res=res.stderr.read()
data_size=len(out_res)+len(err_res)  #获取执行结果的长度  
#发送报头
conn.send(struct.pack(\'i\',data_size))   #  struct.pack 的 i 是表示 4 个字节  4*8=32位bytes的存储 ,这   i   里面就已经包括了整个数据的长度,这样客户端在recv 结果的时候也知道要收多长的字节
#发送数据部分
conn.send(out_res)    再发送 stdout.read()
conn.send(err_res)     再发送stderr.read()

完整代码:

#!/usr/bin/env python
import socket,subprocess,struct
talk=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
ip_port=(\'192.168.100.149\',9000)
talk.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)
talk.bind(ip_port)
talk.listen(2)

while True:
    conn,addr=talk.accept()
    print(\'=============host============\',addr)
    while True:
        try:
             data=conn.recv(1024)
             if not data:break
             print(data)
             res=subprocess.Popen(data,shell=True,stderr=subprocess.PIPE,stdout=subprocess.PIPE)
             cmd_out=res.stdout.read()
             cmd_err=res.stderr.read()
             info_size=len(cmd_out)+len(cmd_err)
             

             conn.send(struct.pack(\'i\',info_size))
             conn.send(cmd_out)
             conn.send(cmd_err)
        except Exception:
            break
    conn.close()
talk.close()

 

struct 使用:client 端

分析:server端已经做好了报头,那 client 在接收的时候也应该先接收报头长度,再接收报头,,,,,,,,,,这样就知道数据的长度,再recv   自己 的 buffer cachhe 数据。

import socket,struct
talk=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
talk.connect((\'192.168.100.149\',9000))
while True:
    msg=input(\'>>>>>\').strip()
    if not msg:continue
    talk.send(bytes(msg,encoding=\'utf-8\'))
    msg_head=talk.recv(4)
    head_unpack=struct.unpack(\'i\',msg_head)[0]
    print(msg_head)
    recv_size=0
    recv_data=b\'\'
    while recv_size<head_unpack:
        data=talk.recv(1024)
        recv_size+=len(data)
        recv_data+=data

    print(recv_data.decode(\'utf-8\'))
socket.close()

执行效果:

image

 

现在已经解决粘包的问题,但是  报头 不仅仅描述文件的长度(大小),  还应该包含一些其它的信息如   文件大小   文件名:

那现在就可以用到字典的格式存储这些值了。

server 端代码

#!/usr/bin/env python
#!-*- coding:utf-8 -*-
import socket,json,subprocess,struct         需要用到   json  序列化,因为字典在传的时候只能是字节 形式
session=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
session.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)    设置地址重用
ip_port=(\'192.168.100.149\',9000)       
session.bind(ip_port)       
session.listen(4)      
while True:
    conn,addr=session.accept()     #通讯无限循环
    while True:
        try:
            client_cmd=conn.recv()    
            res=subprocess.Popen(client_cmd.encode(\'utf-8\'),shell=True,stdout=subprocess.PIPE,stderr=subprocess.PIPE)     
            cmd_result=res.stdout.read()  
            cmd_err=res.stderr.read()   
            cmd_result_size=len(cmd_result)+len(cmd_err)     #所有执行结果的 长度
            head_dic={\'data_size\':cmd_result_size}    #将所有执行结果的长度,放在 dict 里面
            head_json=json.dumps(head_dic)             #将字典做成 json 序列化
            head_bytes=head_json.encode(\'utf-8\')      #再转成utf-8的字节形式

            head_len=len(head_bytes)    #再获取 报头  字典  的长度

            conn.send(struct.pack(\'i\',head_len))    #发送字典 报头的长度
            conn.send(head_bytes)       #  再发送所有数据的长度
            conn.send(cmd_result)        #   发送执行结果 的数据
            conn.send(cmd_err)            #发送执行结果的数据
        except Exception:      #捕捉任何异常,终止本次
            break  
    conn.close()    #所有程序走完才close  这一个客户端 的连接
session.close()   #关闭整个socket

 

client端代码

#!/usr/bin/env python
#!-*- coding:utf-8 -*-
import socket,json,subprocess,struct
session=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
conn_ip_port=(\'192.168.100.149\',9000)
session.connect(conn_ip_port)   #对应服务端的session.accept()

while True:
    cmd=input(\'>>>>: \').strip()
    if not cmd:continue
    session.send(bytes(cmd,encoding=\'utf-8\'))   #对应服务端conn.recv(1024)
    head_struct=session.recv(4)      #对应服务端conn.send(struct.pack(\'i\',head_len))
    print(\'四个字节报头\',head_struct)
    head_recv_data_len=struct.unpack(\'i\',head_struct)[0]  #对应服务端conn.send(struct.pack(\'i\',head_len))

    head_recv=session.recv(head_recv_data_len)      #对应服务端 conn.send(head_bytes)
    head_json=head_recv.decode(\'utf-8\')    #对应服务端 head_bytes=head_json.encode(\'utf-8\')

    head_dic=json.loads(head_json)   #对应服务端 head_json=json.dumps(head_dic)
    print(head_dic)
    data_size=head_dic[\'data_size\']

    recv_size=0
    recv_data=b\'\'
    while recv_size<data_size:
        data=session.recv(1024)   #对应服务端 conn.send(head_bytes)   conn.send(out_res)conn.send(err_res)

        recv_size+=len(data)
        recv_data+=data
    print(recv_data.decode(\'utf-8\'))
session.close()

 

FTP 上传下载文件功能:    …………………………………………………………………………..

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

粘包产生的原因 socket 基于tcp实现远程执行命令(解决粘包)low

TCP粘包问题分析和解决(全)

Netty——解决TCP粘包、拆包

TCP通信粘包问题分析和解决

转载TCP粘包问题分析和解决(全)

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