先看代码

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



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

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

现在执行命令很正常:

java socket 粘包解决 socket tcp 粘包_json

 

执行了一个cat /etc/passwd   ,

java socket 粘包解决 socket tcp 粘包_java socket 粘包解决_02

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

java socket 粘包解决 socket tcp 粘包_java socket 粘包解决_03

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

java socket 粘包解决 socket tcp 粘包_shell_04

 

 

服务端在接收到 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()



执行效果:

java socket 粘包解决 socket tcp 粘包_数据_05

 

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

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

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 上传下载文件功能:    …………………………………………………………………………..