先看代码
session=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
在定义socket对象的时候 有两个参数 一个是 socket地址家族,另一个是处理类型socket.SOCK_STREAM,注意是 ‘stream’:流
那既然是流处理类型,理解上就是 水流式 处理数据。 这个时候数据是没有边界(也就是没有从头开始,到哪里)的概念就像下图
现在执行命令很正常:
执行了一个cat /etc/passwd ,
也能显示,但是后面发生了什么鬼
在主机上执行命令: ,可以看到 ,远程执行cat 的时候只是 拿到了rtkit ,而rtkit后面的 数据没有cat 到,这就是粘包,数据没有 头和尾,导致程序也不知道头和尾在哪,直到recv 虽然取了1024数据,但是数据只取了一半,还有一半在缓存里,没有取出来,导致在执行下一次执行命令的时候又取了1024字节的数据但是数据仍然不止1024字节,又没有取完,就导致了后面一直乱了。
服务端在接收到 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()
执行效果:
现在已经解决粘包的问题,但是 报头 不仅仅描述文件的长度(大小), 还应该包含一些其它的信息如 文件大小 文件名:
那现在就可以用到字典的格式存储这些值了。
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 上传下载文件功能: …………………………………………………………………………..