网络通信与并发编程(三)粘包现象解决方案、socketserver实现并发

粘包现象解决方案、socketserver实现并发

一、粘包现象解决方案

1.发送数据大小

有了上一篇文章的分析,我们知道tcp协议之所以会出现粘包现象,是因为无法得知每次传输的字节数。如果我们人为的给传输的数据添加一个报头用来表示接收内容的字节数就可以解决这个问题了。

#服务端
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_addr=server.accept()

    while True:
        try:
            cmd=conn.recv(1024)
            if len(cmd) == 0:break
            obj=subprocess.Popen(cmd.decode('utf-8'),
                             shell=True,
                             stderr=subprocess.PIPE,
                             stdout=subprocess.PIPE
                             )

            stdout=obj.stdout.read()
            stderr=obj.stderr.read()
            #1、制作报头(固定长度)
            total_size=len(stdout) + len(stderr)
            #struct模块可以将一个数据类型转为固定长度的bytes
            header=struct.pack('i',total_size)
            #2、发送固定长度的报头
            conn.send(header)
            #3、发送真实的数据
            conn.send(stdout)
            conn.send(stderr)
        except ConnectionResetError:
            break

    conn.close()
#客户端
from socket import *
import struct

client=socket(AF_INET,SOCK_STREAM)
client.connect(('127.0.0.1',8080))

while True:
    cmd=input('>>: ').strip()
    if len(cmd) == 0:continue
    client.send(cmd.encode('utf-8'))
    #1、接收固定长度的报头(bytes)
    header=client.recv(4)
    #从报头中解析接收内容的字节数
    total_size=struct.unpack('i',header)[0]
    #2、接收真实的数据
    #recv_size表示已接收字节数,total_size表示总的字节数
    recv_size=0
    res=b''
    while recv_size < total_size :
        data=client.recv(1024)
        res+=data
        recv_size+=len(data)

    print(res.decode('gbk'))

接收结果:
映像名称 PID 会话名 会话# 内存使用
========================= ======== ================ =========== ============
System Idle Process 0 Services 0 8 K
System 4 Services 0 152 K
(接收内容过长,中间部分略去)
tasklist.exe 16076 Console 1 9,520 K
WmiPrvSE.exe 15416 Services 0 10,212 K

2.发送数据信息

在一般的文件传输中,我们除了可以看到文件的大小,还可以看见文件名称,创建日期当信息,为了将这些信息加入到报头中,我们可以将上面的代码进一步优化为如下形式:

#服务端
from socket import *
import subprocess
import struct
import json,time

server=socket(AF_INET,SOCK_STREAM)
server.bind(('127.0.0.1',8080))
server.listen(5)

while True:
    conn,client_addr=server.accept()

    while True:
        try:
            cmd=conn.recv(1024)
            if len(cmd) == 0:break
            # 运行系统命令
            obj=subprocess.Popen(cmd.decode('utf-8'),
                             shell=True,
                             stderr=subprocess.PIPE,
                             stdout=subprocess.PIPE
                             )

            stdout=obj.stdout.read()
            stderr=obj.stderr.read()
            #将报头信息制作成字典
            header_dic={
                'filename':'tasklist',
                'total_size':len(stdout) + len(stderr),
                'create_time':time.strftime('%Y-%m-%d %H')
            }
            #通过json格式将报头字典转为bytes
            header_json=json.dumps(header_dic)
            header_bytes=header_json.encode('utf-8')
			
			#由于报头字典大小超出struct的限制,所以此处先发送4个字节标识报头字典的大小
            #1、发送报头字典大小
            conn.send(struct.pack('i',len(header_bytes)))
            #2、发送报头字典
            conn.send(header_bytes)
            #3、发送真实的数据
            conn.send(stdout)
            conn.send(stderr)
        except ConnectionResetError:
            break

    conn.close()
#客户端
from socket import *
import struct
import json

client=socket(AF_INET,SOCK_STREAM)
client.connect(('127.0.0.1',8080))

while True:
    cmd=input('>>: ').strip()
    if len(cmd) == 0:continue
    client.send(cmd.encode('utf-8'))
    #1、收4个字节,这4个字节表示报头长度
    header_len=struct.unpack('i',client.recv(4))[0]
    #2、再接收报头
    header_bytes=client.recv(header_len)
    #通过json解析出报头字典
    header_json=header_bytes.decode('utf-8')
    header_dic=json.loads(header_json)
    print(header_dic)
    total_size=header_dic['total_size']
    #3、接收真实的数据
    recv_size=0
    res=b''
    while recv_size < total_size :
        data=client.recv(1024)
        res+=data
        recv_size+=len(data)

    print(res.decode('gbk'))

可以看到结果中接收到了报头信息:
{‘filename’: ‘tasklist’, ‘total_size’: 17942, ‘create_time’: ‘2024-10-18 18’}
映像名称 PID 会话名 会话# 内存使用
========================= ======== ================ =========== ============
System Idle Process 0 Services 0 8 K
System 4 Services 0 152 K
(接收内容过长,中间部分略去)
tasklist.exe 16076 Console 1 9,520 K
WmiPrvSE.exe 15416 Services 0 10,212 K

二、socketserver实现并发

1.tcp版的socketserver并发

#客户端
import socket
#表示当前开启的客户端序号
#并发通信时,可以开启斗个客户端与服务端通信
num=33
phone=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
phone.connect(('127.0.0.1',8080)) 

while True:
    msg = 'client%s'%num
    if len(msg) == 0:continue
    phone.send(msg.encode('utf-8'))
    data=phone.recv(1024)
    print(data)

phone.close()
#服务端
import socketserver
#MyHandler为自定义的通信函数
class MyHandler(socketserver.BaseRequestHandler):
	#MyHandler继承了abc类,必须复写handle函数
    def handle(self):
        #通信循环
        while True:
            try:
            	#self.request中存放这通信连接
            	#self.client_address中存放着客户端的ip和端口号
				#self.server中存放着套接字对象
                data=self.request.recv(1024)
                if len(data) == 0:break
                self.request.send(data.upper())
            except ConnectionResetError:
                break

if __name__ == '__main__':
	#创建socketserver对象
    s=socketserver.ThreadingTCPServer(('127.0.0.1',8080),MyHandler,bind_and_activate=True)
    #serve_forever函数在有客户端发送请求时创建一个线程
    #这个线程会根据建立的通信链接与客户端通信(创建MyHandler对象并调用handle方法)
    s.serve_forever()

2.udp版的socketserver并发

#客户端
import socket
num=33
client=socket.socket(socket.AF_INET,socket.SOCK_DGRAM)
while True:
    msg='client%s'%num
    client.sendto(msg.encode('utf-8'),('127.0.0.1',8080))
    data,server_addr=client.recvfrom(1024)
    print(data)

client.close()
#服务端
import socketserver
class MyHandler(socketserver.BaseRequestHandler):
    def handle(self):
        # 通信循环
        #self.request[1]存放着套接字对象
        #self.client_address存放着客户端的信息
        #self.request[0]存放着客户端发送的信息
        data = self.request[0]
        self.request[1].sendto(data.upper(), self.client_address)


if __name__ == '__main__':
    s = socketserver.ThreadingUDPServer(('127.0.0.1', 8080), MyHandler)
    #当客户端发送信息以后,serve_forever函数会创建一个线程与客户端进行通信
    s.serve_forever()
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值