粘包问题
内容概要
- 基于tcp协议实现远程执行命令
- udp协议没有粘包问题
- 解决粘包问题
- 解决粘包问题(终极大招)
- socketserver模块的基本使用
内容详细
-
基于tcp协议实现远程执行命令
客户端
from socket import * client = socket(AF_INET,SOCK_STREAM) client.connect(('127.0.0.1',8080)) while True: cmd = input('请输入命令==》:').strip() # 一行命令超过1024个字节也基本不可能, if len(cmd) == 0:continue client.send(cmd.encode('utf-8')) # 这个客户端哪怕发千万条消息也要一条一条发 # 解决粘包问题思路 # 1、拿到数据的总大小total_size # 2、recv_size = 0,循环接收,没接收一次,recv_size += 接收的长度 # 3、直到recv_size = total_size,结束循环 cmd_res = client.recv(1024) # 本次接受,最大接受1024Bytes # 这里接受的是服务端发过来的命令的执行的结果 print(cmd_res.decode('utf-8')) # 粘包问题出现的原因 # 1、tcp是流式协议,数据像水流一样粘在一起,没有任何边界区分 # 2、收数据没收干净,有残留,就会与下一次结果混淆在一起 # 解决的核心法门就是:每次都收干净,不要任何残留服务端
# 服务端应该满足两个特点: # 1、一直对外提供服务 # 2、并发的服务多个客户端 import subprocess from socket import * server = socket(AF_INET, SOCK_STREAM) server.setsockopt(SOL_SOCKET,SO_REUSEADDR,1) server.bind(('127.0.0.1', 8080)) server.listen(5) # 服务端应该做两件事 # 第一件事:循环的从半链接池中取出链接请求与其建立双向链接,拿到链接对象 while True: conn, client_addr = server.accept() # 第二件事:拿到链接对象,与其通信循环 while True: try: cmd = conn.recv(1024) # 这个conn只对应一个客户端接受消息,哪怕有千万个客户端发消息也没事 # 如果你一条消息超过1024了,你把它改成8192,其是意义也不大,因为有可能超过了缓存的大小 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() # conn.send(stdout_res + stderr_res) conn.send(stdout_res) conn.send(stderr_res) except Exception: break conn.close() -
udp协议没有粘包问题
udp客户端
import socket client = socket.socket(socket.AF_INET,socket.SOCK_DGRAM) client.sendto(b'helloaaa',('127.0.0.1',8080)) client.sendto(b'worldaaa',('127.0.0.1',8080)) client.close()udp服务端
import socket server = socket.socket(socket.AF_INET,socket.SOCK_DGRAM) server.bind(('127.0.0.1',8080)) res1 = server.recvfrom(5) print(res1) res2 = server.recvfrom(5) print(res2) server.close() -
解决粘包问题
客户端
import struct from socket import * client = socket(AF_INET,SOCK_STREAM) client.connect(('127.0.0.1',8081)) while True: cmd = input('请输入命令==》:').strip() # 一行命令超过1024个字节也基本不可能, if len(cmd) == 0:continue client.send(cmd.encode('utf-8')) # 这个客户端哪怕发千万条消息也要一条一条发 # 解决粘包问题思路 # 一、先收固定长度的头:解析出数据的描述信息,包括数据的总大小total_size header = client.recv(4) total_size = struct.unpack('i',header)[0] # struct.unpack('i',header)是一个元组形式 # 二、根据解析出的描述信息,接收真实的数据 # 1、recv_size = 0,循环接收,没接收一次,recv_size += 接收的长度 # 2、直到recv_size = total_size,结束循环 recv_size = 0 cmd_res = b'' while recv_size < total_size: recv_data = client.recv(1024) recv_size += len((recv_data)) # cmd_res += recv_data print(recv_data.decode('utf-8'),end='') else: print() # 粘包问题出现的原因 # 1、tcp是流式协议,数据像水流一样粘在一起,没有任何边界区分 # 2、收数据没收干净,有残留,就会与下一次结果混淆在一起 # 解决的核心法门就是:每次都收干净,不要任何残留服务端
# 服务端应该满足两个特点: # 1、一直对外提供服务 # 2、并发的服务多个客户端 import subprocess import struct from socket import * server = socket(AF_INET, SOCK_STREAM) server.setsockopt(SOL_SOCKET,SO_REUSEADDR,1) server.bind(('127.0.0.1', 8081)) server.listen(5) # 服务端应该做两件事 # 第一件事:循环的从半链接池中取出链接请求与其建立双向链接,拿到链接对象 while True: conn, client_addr = server.accept() # 第二件事:拿到链接对象,与其通信循环 while True: try: cmd = conn.recv(1024) # 这个conn只对应一个客户端接受消息,哪怕有千万个客户端发消息也没事 # 如果你一条消息超过1024了,你把它改成8192,其是意义也不大,因为有可能超过了缓存的大小 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(stdout_res)+len(stderr_res) # 1、先发头信息(固定长度的bytes):对数据描述信息 # int==>固定长度的bytes header = struct.pack('i',total_size) conn.send(header) # 2、再发真实数据 conn.send(stdout_res) conn.send(stderr_res) except Exception: break conn.close() -
解决粘包问题(终极大招)
客户端
import struct import json from socket import * client = socket(AF_INET,SOCK_STREAM) client.connect(('127.0.0.1',8081)) while True: cmd = input('请输入命令==》:').strip() # 一行命令超过1024个字节也基本不可能, if len(cmd) == 0:continue client.send(cmd.encode('utf-8')) # 这个客户端哪怕发千万条消息也要一条一条发 # 接收端 # 1、先收4个字节,从中提取接下来要收的头的长度 header_size = client.recv(4) header_len = struct.unpack('i',header_size)[0] # 2、接收头,并解析 json_str_bytes = client.recv(header_len) json_str = json_str_bytes.decode('gbk') header_dic = json.loads(json_str) print(header_dic) total_size = header_dic['total_size'] # 3、接收真实的数据 recv_size = 0 cmd_res = b'' while recv_size < total_size: recv_data = client.recv(1024) recv_size += len((recv_data)) # cmd_res += recv_data print(recv_data.decode('gbk'),end='') else: print() # 粘包问题出现的原因 # 1、tcp是流式协议,数据像水流一样粘在一起,没有任何边界区分 # 2、收数据没收干净,有残留,就会与下一次结果混淆在一起 # 解决的核心法门就是:每次都收干净,不要任何残留服务端
# 服务端应该满足两个特点: # 1、一直对外提供服务 # 2、并发的服务多个客户端 import subprocess import struct import json from socket import * server = socket(AF_INET, SOCK_STREAM) server.setsockopt(SOL_SOCKET,SO_REUSEADDR,1) server.bind(('127.0.0.1', 8081)) server.listen(5) # 服务端应该做两件事 # 第一件事:循环的从半链接池中取出链接请求与其建立双向链接,拿到链接对象 while True: conn, client_addr = server.accept() # 第二件事:拿到链接对象,与其通信循环 while True: try: cmd = conn.recv(1024) # 这个conn只对应一个客户端接受消息,哪怕有千万个客户端发消息也没事 # 如果你一条消息超过1024了,你把它改成8192,其是意义也不大,因为有可能超过了缓存的大小 if len(cmd) == 0: break obj = subprocess.Popen(cmd.decode('gbk'), shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE ) stdout_res = obj.stdout.read() stderr_res = obj.stderr.read() total_size = len(stdout_res)+len(stderr_res) # 1、制作头 header_dic = { 'filename': 'a.txt', 'total_size':total_size, 'md5':'123123' } json_str = json.dumps(header_dic) json_str_bytes = json_str.encode('utf-8') # 2、先把头的长度发过去 header_size = struct.pack('i',len(json_str_bytes)) conn.send(header_size) # 3、发头信息 conn.send(json_str_bytes) # 4、再发真实数据 conn.send(stdout_res) conn.send(stderr_res) except Exception: break conn.close() -
socketserver模块的基本使用
-
基于tcp协议的使用
客户端
from socket import *client = socket(AF_INET,SOCK_STREAM)client.connect(('127.0.0.1',8888))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'))服务端
import socketserverclass MyRequestHandle(socketserver.BaseRequestHandler): def handle(self): # 如果是tcp协议,self.request==》conn 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', 8888), MyRequestHandle)s.serve_forever()# 服务端应该做两件事# 第一件事:循环的从半链接池中取出链接请求与其建立双向链接,拿到链接对象# while True:# conn, client_addr = server.accept()# 第二件事:拿到链接对象,与其通信循环 -
基于udp协议的使用
客户端
import socketclient = socket.socket(socket.AF_INET,socket.SOCK_DGRAM)while True: msg = input('==>:').strip() client.sendto('hello ycc 哈哈哈'.encode('utf-8'),('127.0.0.1',8080)) data,client_addr = client.recvfrom(1024) print('服务端发来消息:',data.decode('utf-8'))client.close()服务端
import socketserverimport socketclass MyRequestHandle(socketserver.BaseRequestHandler): def handle(self): # print(self.request) # 如果是tcp协议,self.request==》conn # print(self.client_address) 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', 8080), MyRequestHandle)s.serve_forever()# 相当于# while True:# data,client_addr = server.recvfrom(1024)# 启动一个线程处理后续的事情(data,client_addr)
-

浙公网安备 33010602011771号