粘包,就是查询的内容都粘到一起了,比如客户端发送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()
怎么样?惊不惊喜?意不意外?高不高端?
其实还有更高端的玩法,你想知道吗?对不起,确实有,但是我不会,哈哈哈哈哈哈哈。。。