粘包
先基于tcp协议写一个远程执行命令的程序: 客户端:
from socket import *
client = socket(AF_INET, SOCK_STREAM)
client.connect(('127.0.0.1', 8080))
while True:
msg = input('请输入命令:>>>').strip()
if len(msg) == 0:
continue
if msg == 'quit':
break
client.send(msg.encode('utf-8'))
cmd_res = client.recv(1024)
print(cmd_res.decode('gbk'))
client.close()
服务端:
from socket import *
import subprocess
server = socket(AF_INET, SOCK_STREAM)
server.bind(('127.0.0.1', 8080))
server.listen(5)
while True:
conn, client_address = server.accept()
while True:
try:
cmd = conn.recv(1024)
if len(cmd) == 0:
break
obj = subprocess.Popen(cmd.decode('utf-8'),
shell=True,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE
)
stdout_res = obj.stout.read()
stderr_res = obj.stderr.read()
conn.send(stdout_res+stderr_res)
except Exception:
break
conn.close()
在上述代码运行过程有可能会出现粘包的现象。
何为粘包现象
TCP协议是面向流的协议,在数据传输过程,数据就像流水一样,从服务端发送至客户端的缓存中,服务端可能会一次性发送大量的数据,而客户端一次只能接收一定数量的数据,就会导致客户端的缓存中还留有服务端一次性发送的数据,还未被接收的数据,然后服务端再发送数据,就导致上次未接收完的数据与新发送的数据首尾相连(TCP协议是面向流的协议,数据都是连在一起的),就导致了粘包现象。
两种情况下会发生粘包。
1.发送端需要等缓冲区满才发送出去,造成粘包(发送数据时间间隔很短,数据了很小,会合到一起,产生粘包 2.接收方不及时接收缓冲区的包,造成多个包接收(客户端发送了一段数据,服务端只收了一小部分,服务端下次再收的时候还是从缓冲区拿上次遗留的数据,产生粘包)
所以最开始的代码,就会因为客户端发送命令,服务端接收命令,并将命令的执行结果发送给客户端,而有些命令的结果会过长,导致客户端接收不完全,导致数据遗留在了缓存区,形成了粘包现象
解决粘包问题
解决粘包问题思路: 1、先收固定长度的头,解析出数据的描述信息,包括;数据的总大小total_size 2、recv_size=0,循环接收,每接收一次,recv_size+=接收的长度 3、直到recv_size=total_size 客户端:
from socket import *
import struct
client = socket(AF_INET, SOCK_STREAM)
client.connect(('127.0.0.1', 8080))
while True:
msg = input('请输入命令:>>>').strip()
if len(msg) == 0:
continue
if msg == 'quit':
break
header = client.recv(4)
total_size = struct.unpack('i', header)[0]
recv_size = 0
while recv_size < total_size:
recv_data = client.recv(1024)
recv_size += len(recv_data)
print(recv_data.decode('utf-8'))
client.send(msg.encode('utf-8'))
cmd_res = client.recv(1024)
print(cmd_res.decode('utf-8'))
client.close()
服务端:
from socket import *
import subprocess
import struct
server = socket(AF_INET, SOCK_STREAM)
server.bind(('127.0.0.1', 8080))
server.listen(5)
while True:
conn, client_address = server.accept()
while True:
try:
cmd = conn.recv(1024)
if len(cmd) == 0:
break
obj = subprocess.Popen(cmd.decode('utf-8'),
shell=True,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE
)
stdout_res = obj.stout.read()
stderr_res = obj.stderr.read()
total_size = len(stderr_res) + len(stdout_res)
header = struct('i', total_size)
conn.send(header)
conn.send(stdout_res)
conn.send(stderr_res)
except Exception:
break
conn.close()
通常情况下,头部的信息不仅仅是包含数据的大小,而应该会有更多的信息,例如,文件名字等等 解决方法之一: 将头部信息写入字典 优化之后:
客户端:
import json
from socket import *
import struct
client = socket(AF_INET, SOCK_STREAM)
client.connect(('127.0.0.1', 8080))
while True:
msg = input('请输入命令:>>>').strip()
if len(msg) == 0:
continue
if msg == 'quit':
break
header_size = client.recv(4)
header_len = struct.unpack('i', header_size)[0]
json_str_bytes = client.recv(header_len)
json_str = json_str_bytes.decode('utf-8')
header_dic = json.loads(json_str)
total_size = header_dic['total_size']
recv_size = 0
while recv_size < total_size:
recv_data = client.recv(1024)
recv_size += len(recv_data)
print(recv_data.decode('utf-8'))
client.send(msg.encode('utf-8'))
cmd_res = client.recv(1024)
print(cmd_res.decode('utf-8'))
client.close()
服务端:
from socket import *
import subprocess
import struct
import json
server = socket(AF_INET, SOCK_STREAM)
server.bind(('127.0.0.1', 8080))
server.listen(5)
while True:
conn, client_address = server.accept()
while True:
try:
cmd = conn.recv(1024)
if len(cmd) == 0:
break
obj = subprocess.Popen(cmd.decode('utf-8'),
shell=True,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE
)
stdout_res = obj.stdout.read()
stderr_res = obj.stderr.read()
total_size = len(stderr_res) + len(stdout_res)
header_dic = {
'filename': 'a.txt',
'total_size': 123545687,
'md5': 'asd789a8s7cv46x57a8w'
}
json_str = json.dumps(header_dic)
json_str_bytes = json_str.encode('utf-8')
header_size = struct.pack('i', len(json_str_bytes))
conn.send(header_size)
conn.send(json_str_bytes)
conn.send(stdout_res)
conn.send(stderr_res)
except Exception:
break
conn.close()
socketserver模块
基于tcp协议
解决多并发问题: 服务端:
import socketserver
class MyRequestHandle(socketserver.BaseRequestHandler):
def handle(self):
print(self.request)
print(self.client_address)
while True:
try:
msg = self.request.recv(1024)
if len(msg) == 0:
break
self.request.send(msg.upper())
except Exception:
break
self.request.close()
s = socketserver.ThreadingTCPServer(('127.0.0.1', 8099), MyRequestHandle)
s.serve_forever()
client = socket(AF_INET, SOCK_STREAM)
client.connect(('127.0.0.1', 8099))
while True:
msg = input('请输入你的信息:').strip()
if len(msg) == 0:
continue
client.send(msg.encode('utf-8'))
res = client.recv(1024)
print(res.decode('utf-8'))
基于udp协议
服务端:
class MyRequestHanlde(socketserver.BaseRequestHandler):
def handle(self):
client_data = self.request[0]
server = self.request[1]
client_address = self.client_address
print('客户端发来的数据:%s' % client_data)
server.sendto(client_data.upper(), client_address)
s = socketserver.ThreadingUDPServer(('127.0.0.1', 8090), MyRequestHanlde)
s.serve_forever()
客户端:
client = socket(AF_INET, SOCK_DGRAM)
client.connect(('127.0.0.1', 8090))
while True:
msg = input('请输入你的信息:').strip()
if len(msg) == 0:
continue
client.send(msg.encode('utf-8'))
res = client.recv(1024)
print(res.decode('utf-8'))
|