tcp协议传输方法&粘包问题


socket实现客户端和服务端

tcp协议可以用socket模块实现服务端可客户端的交互

# 服务端 import socket #生成一个socket对象 soc = socket.socket(socket.AF_INET, socket.SOCK_STREAM) #绑定地址跟端口号 soc.bind(('127.0.0.1', 8001)) #监听(半连接池的大小) soc.listen(5) #等着客户端来连接,conn相当于连接通道,addr是客户端的地址 conn,addr = soc.accept() #卡住,如果没有客户端连接,会一直卡在这,当有连接,才继续往下走 print(addr) while True: #等待接收,最大收取1024个字节 data=conn.recv(1024) #卡住,当客户端有数据过来,才会执行 print(data) #关闭通道 connlose() # 关闭套接字 soclose() # 客户端 import socket #生成一个socket对象 soc = socket.socket() # 连接服务器 soconnect(('127.0.0.1',8001)) while True: in_s = input('请输入要发送的数据:') #发送的数据必须是b格式,in_s.encode('utf8') 把字符串编码成b格式 #把b格式转成字符串 # ss = str(b'hello',encoding='utf8') # ss = b'hello'.decode('utf8') # #把字符串转成b格式 # by=bytes('hello',encoding='utf8') # by='hello'.encode('utf8') soc.send(in_s.encode('utf8'))

我们也可以完善一下服务端,让服务端也加上连接循环

import socket #生成一个socket对象 soc = socket.socket(socket.AF_INET, socket.SOCK_STREAM) #绑定地址跟端口号 soc.bind(('127.0.0.1', 8001)) #监听(半连接池的大小),不是连接数 soc.listen(3) #等着客户端来连接,conn相当于连接通道,addr是客户端的地址 while True: print('等待客户端连接') conn,addr = soc.accept() #卡主,如果没有客户端连接,会一直卡在这,当有连接,才继续往下走 print('有个客户端连接上了',addr) while True: try: #windows如果客户端断开,会报错,加了try #linux如果客户端,断开,不会报错,会收到空,所有当data为空时,也break #等待接收,最大收取1024个字节 data = conn.recv(1024) #会卡主,当客户端有数据过来,才会执行 if len(data) == 0: #处理linux客户端断开,如果在window下这段代码根本不会执行(即便是客服端发了空,这个地方也不会走到) break print(data) except Exception: break # 关闭通道 connlose() # 关闭套接字 soclose()

粘包问题

什么是粘包问题?通俗的说当客户端发送数据的时候,当一条数据还未接受的时候,下一条数据已经发送,这个时候俩条数据就会连在一起。如果这时候取的话,将俩条数据取出,就会出现问题。所谓粘包问题主要还是因为接收方不知道消息之间的界限,不知道一次性提取多少字节的数据所造成的

TCP(transport control protocol,传输控制协议)是面向连接的,面向流的,提供高可靠性服务。收发两端(客户端和服务器端)都要有一一成对的socket,因此,发送端为了将多个发往接收端的包,更有效的发到对方,使用了优化方法(Nagle算法),将多次间隔较小且数据量小的数据,合并成一个大的数据块,然后进行封包。这样,接收端,就难于分辨出来了,必须提供科学的拆包机制。 即面向流的通信是无消息保护边界的

解决粘包问题方法

那么我们将如何解决粘包问题呢

我们是否可以先计算出每次想要传出的数据长度,将其放在本次数据的开头,随后在服务端先读取长度,随后根据长度来分割已经粘包的数据。

这个方法看上去似乎是可行的,但是我们并不知道一次数据的长度会是几位数,可能只有9 字节,又有可能有998字节,这个时候应该怎么办呢?

在这里我们还要介绍一个模块:struct

他可以将一个数字打包成固定长度的四个字节

struct模块

import struct #把一个数字打包成固定长度的4字节 obj = struct.pack('i', 1098) print(obj) # b'J\x04\x00\x00' print(len(obj)) # 4 l = struct.unpack('i', obj)[0] print(l) 1098

是不是觉得豁然开朗

这样我们就可以写一个较为完善的服务端可客户端的通信了

解决粘包问题阉割版

# 客户端 import socket import struct soc=socket.socket() soconnect(('127.0.0.1',8001)) while True: in_s=input('请输入要执行的命令:') soc.send(in_s.encode('utf8')) head=soc.recv(4) l=struct.unpack('i',head)[0] # data=soc.recv(l) count=0 data_total=b'' while count<l: if l<1024: #如果接受的数据小于1024 ,直接接受数据大小 data=soc.recv(l) else:#如果接受的数据大于1024 if lcount>=1024: #总数据长度count(目前收到多少,count就是多少) 如果还大于1024 ,在收1024 data=soc.recv(1024) else: #总数据长度count(目前收到多少,count就是多少) 如果小于1024,只收剩下的部分就可 data=soc.recv(lcount) data_total+=data count+=len(data) print(str(data_total,encoding='gbk')) 服务端 import socket import subprocess import struct soc=socket.socket(socket.AF_INET,socket.SOCK_STREAM) soc.bind(('127.0.0.1',8001)) soc.listen(3) while True: print('等待客户端连接') conn,addr=soc.accept() print('有个客户端连接上了',addr) while True: try: data=conn.recv(1024) if len(data)==0: break print(data) obj = subprocess.Popen(str(data,encoding='utf8'), shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE) #执行正确的结果 b 格式,gbk编码(windows平台) msg=obj.stdout.read() #发送的时候需要先把长度计算出来 #头必须是固定长度 #10 #100 #先取出要发送数据长度l l=len(msg) #head 是固定四个字节 head=struct.pack('i',l) #发了头 conn.send(head) #发了内容 conn.send(msg) except Exception: break # 关闭通道 connlose() # 关闭套接字 soclose()

在这里使用1024字节,也就是1kb来进行循环,是怕所传输的文件过大,导致内存爆满。所以才进行循环

解决粘包问题终极版

但是碍于struct模块本身的限制,如果传输的文件实在太大,打到超过int限制时(当然,这样的文件几乎不存在),struct模块无法将其压缩成四个字节,那该怎么办?有人可能会说了,你怕不是个hape吧,赶快给爷爬,尽搞些有的没的

但是身为程序员,就要本着追求完美的原则,横眉冷对千夫指

下面就推出解决粘包问题的完美版本,其实就是带上一个字典,字典中有关于文件长度的数值,这时候只要将这个字典压成四个字节,另一端接收到这个字典后,根据字典中的值来判断数据的大小。这个时候,你还可以在字典中放入文件的其他信息,比如文件的名字或者

# 服务端 import socket import subprocess import struct soc=socket.socket(socket.AF_INET, socket.SOCK_STREAM) soc.bind(('127.0.0.1', 8001)) soc.listen(3) while True: print('等待客户端连接') conn,addr=soc.accept() print('有个客户端连接上了',addr) while True: try: data = conn.recv(1024) if len(data) == 0: break print(data) obj = subprocess.Popen(str(data, encoding='utf8'), shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE) #执行正确的结果 b 格式,gbk编码(windows平台) msg = obj.stdout.read() #发送的时候需要先把长度计算出来 #头必须是固定长度 #先发4位,头的长度 import json dic = dic_bytes=(json.dumps(dic)).encode('utf8') #head_count是4个字节的长度 head_count = struct.pack('i', len(dic_bytes)) print(dic) conn.send(head_count) #发送头部内容 conn.send(dic_bytes) #发了内容 conn.send(msg) except Exception: break # 关闭通道 connlose() # 关闭套接字 soclose() # 客户端 import socket import struct import json soc=socket.socket() soconnect(('127.0.0.1', 8001)) while True: in_s = input('请输入要执行的命令:') soc.send(in_s.encode('utf8')) #头部字典的长度 head_dic_len = soc.recv(4) #解出真正的长度 head_l = struct.unpack('i', head_dic_len)[0] #byte 字典的长度 #收真正的头部字典 dic_byte = soc.recv(head_l) head = json.loads(dic_byte) print(head) l = head['size'] count = 0 data_total = b'' print(l) while count < l: if l < 1024: #如果接受的数据小于1024 ,直接接受数据大小 data = soc.recv(l) else:#如果接受的数据大于1024 if lcount >= 1024: #总数据长度count(目前收到多少,count就是多少) 如果还大于1024 ,在收1024 data = soc.recv(1024) else: #总数据长度count(目前收到多少,count就是多少) 如果小于1024,只收剩下的部分就可 data = soc.recv(lcount) data_total += data count += len(data) print(str(data_total, encoding='gbk'))

tcp实例

写一个ftp软件,支持上传文件,下载文件
注册,登录之后才能下载,查看所有文件
删除文件

# 客户端 import socket import time import struct import json import os user_state = BASE_PATH = os.path.dirname(__file__) DB_PATH = os.path.join(BASE_PATH,'db') def send_cmon_msg(conn,msg): msg_len = len(msg) msg_stur = struct.pack('i',msg_len) conn.send(msg_stur) conn.send(msg.encode('utf8')) def recv_cmon_msg(conn): recv_stur = conn.recv(4) recv_len = struct.unpack('i',recv_stur)[0] recv_msg = conn.recv(recv_len).decode('utf8') return recv_msg def download_file(conn,file_name): name = user_state['name'] dic_stru = conn.recv(4) dic_len = struct.unpack('i',dic_stru)[0] dic = conn.recv(dic_len).decode('utf8') dic = json.loads(dic) size = dic['size'] file = conn.recv(size) file_path = os.path.join(DB_PATH,name) file_path = os.path.join(file_path,file_name) with open(file_path,'wb')as fw: fwrite(file) def upload_file(conn,file_path): with open(file_path,'rb')as fr: file = fr.read() send_upload_file(conn,file) def send_upload_file(conn,file): len_file = len(file) dic = print(len(dic)) dic = json.dumps(dic).encode('utf8') file_dic_stur = struct.pack('i',len(dic)) print(file_dic_stur) print(dic) print(file) conn.send(file_dic_stur) conn.send(dic) conn.send(file) soc=socket.socket() soconnect(('127.0.0.1',8009)) while True: msg1 = recv_cmon_msg(soc) while True: print(msg1) choice = input('请输入你的选择').strip() send_cmon_msg(soc,choice) if choice == '1': login_msg = recv_cmon_msg(soc) print(login_msg) name = input() send_cmon_msg(soc,name) pwd_msg = recv_cmon_msg(soc) print(pwd_msg) pwd = input() send_cmon_msg(soc,pwd) login_msg = recv_cmon_msg(soc) print(login_msg) if login_msg == 'login success': user_state['name'] = name msg2 = recv_cmon_msg(soc) print(msg2) choice = input('请输入你的选择').strip() send_cmon_msg(soc, choice) if choice == '1': file_lis = recv_cmon_msg(soc) file_lis = json.loads(file_lis) print(file_lis) continue elif choice == '2': file_name = input('请输入你要下载的文件名') send_cmon_msg(soc,file_name) download_file(soc,file_name) continue elif choice == '3': file_path = input('请输入你要上传的文件路径') send_cmon_msg(soc,file_path) upload_file(soc,file_path) continue else: break else: continue elif choice == '2': resgister_msg = recv_cmon_msg(soc) print(resgister_msg) name = input() send_cmon_msg(soc, name) pwd_msg = recv_cmon_msg(soc) print(pwd_msg) pwd = input() send_cmon_msg(soc, pwd) resgister_msg = recv_cmon_msg(soc) print(resgister_msg) continue break break soclose() #服务端 #导入模块 ''' 服务端 ''' import socket import struct import os, json user_state = BASE_PATH = os.path.dirname(__file__) DB_PATH = os.path.join(BASE_PATH,'db') if not os.path.exists(DB_PATH): os.mkdir(DB_PATH) def login(name,pwd): user_path = os.path.join(DB_PATH, f'') if not os.path.exists(user_path): return 'login fail' user_path = os.path.join(user_path, f'.json') if not os.path.isdir(user_path): return '该用户未注册' with open(user_path, 'r', encoding='utf8') as fr: user_dic = json.load(fr) fr.flush() if user_dic['pwd'] == pwd: user_state['name'] = name return '登录成功' else: return '登陆失败' def resgister(name, pwd): user_path = os.path.join(DB_PATH, f'') if os.path.exists(user_path): return '该用户已被注册' else: os.mkdir(user_path) user_dic = user_path = os.path.join(user_path, f'.json') with open(user_path, 'w', encoding='utf8') as fw: json.dump(user_dic, fw) fflush() return '用户注册成功' def quit(): return 'quit' def check_user_all_file(name): user_path = os.path.join(DB_PATH, f'') file_lis = os.listdir(user_path) return file_lis def client_download_file(file_path): name = user_state['name'] def client_del_file(file_path): name = user_state['name'] MSG_DIC = MSG = ''' 1: 登录 2: 注册 3: 退出 ''' MSG1 = ''' 1: 查看用户文件 2: 下载文件 3: 上传文件 4: 删除文件 5: 退出 ''' # MSG = '1:登陆\n2:注册\n3:退出' srur_msg = struct.pack('i', len(MSG)) srur_msg1 = struct.pack('i', len(MSG1)) soc = socket.socket() # print(len(MSG)) soc.bind(('127.0.0.1', 8085)) soc.listen(10) while True: conn, addr = soc.accept() while True: conn.send(srur_msg) conn.send(bytes(MSG.encode('gbk'))) while True: head_choice = conn.recv(4) head_choice_len = struct.unpack('i',head_choice)[0] head_choice = conn.recv(head_choice_len).decode('utf8') if head_choice == '3': msg = MSG_DIC[head_choice] break elif head_choice == '1': msg = '请输入你的用户名'.encode('utf8') msg_stur = struct.pack('i', len(msg)) conn.send(msg_stur) conn.send(bytes(msg)) msg = '请输入密码'.encode('utf8') msg_stur = struct.pack('i', len(msg)) conn.send(msg_stur) conn.send(bytes(msg)) head_name_pack = conn.recv(4) name_len = struct.unpack('i', head_name_pack)[0] name = conn.recv(name_len).decode('utf8') head_pwd_pack = conn.recv(4) pwd_len = struct.unpack('i', head_pwd_pack)[0] pwd = conn.recv(pwd_len).decode('utf8') msg = login(name, pwd).encode('utf8') # print('msg', msg) msg_stur = struct.pack('i', len(msg)) conn.send(msg_stur) conn.send(bytes(msg)) name = user_state['name'] if name: conn.send(srur_msg1) conn.send(bytes(MSG1.encode('gbk'))) head_choice = conn.recv(4) head_choice_len = struct.unpack('i', head_choice)[0] head_choice = conn.recv(head_choice_len).decode('utf8') print('head_choice', head_choice) if head_choice == '1': msg = json.dumps(check_user_all_file(name)) srur_msg = struct.pack('i', len(msg)) conn.send(srur_msg) print(msg.encode('utf8')) conn.send(bytes(msg.encode('utf8'))) elif head_choice == '2': head_download_file_pack = conn.recv(4) download_file_len = struct.unpack('i', head_download_file_pack)[0] download_file = conn.recv(download_file_len).decode('utf8') # if not download_file.startswith('"'): # download_file = f'"' # if not download_file.endswith('"'): # download_file = f'"' with open(download_file, 'rb')as fr: file = fr.read() file_len = len(file) file_len_dic = file_len_dic = json.dumps(file_len_dic) file_len_dic_stru = struct.pack('i', len(file_len_dic)) conn.send(file_len_dic_stru) conn.send(bytes(file_len_dic.encode('utf8'))) conn.send(file) elif head_choice == '3': upload_file_path_srtu = conn.recv(4) upload_file_path_head = struct.unpack('i', upload_file_path_srtu)[0] upload_file_path = conn.recv(upload_file_path_head).decode('utf8') print('upload_file_path',upload_file_path) file_size_head = conn.recv(4) file_head = struct.unpack('i', file_size_head)[0] file_size_dic = conn.recv(file_head).decode('utf8') print(file_size_dic) file_size_dic = eval(file_size_dic) file_size = file_size_dic['size'] file = conn.recv(file_size) file_path = os.path.join(DB_PATH, f'') download_file_name = upload_file_path.split('\\')[1] # print(download_file_name) # print(type(download_file_name)) file_path = os.path.join(file_path, f'') with open(file_path, 'wb')as fw: fwrite(file) elif head_choice == '4': del_file_path_srtu = conn.recv(4) del_file_path_head = struct.unpack('i', del_file_path_srtu)[0] del_file_path = conn.recv(del_file_path_head).decode('utf8').split('\\')[1] print('del_file_path', del_file_path) file_path = os.path.join(DB_PATH,f"") file_path = os.path.join(file_path, f"") os.remove(file_path) elif head_choice == '5': msg = quit() break else: msg = 'please input true choice'.encode('utf8') msg_stur = struct.pack('i', len(msg)) conn.send(msg_stur) conn.send(bytes(msg)) continue else: print(1546) elif head_choice == '2': msg = 'please input your name'.encode('utf8') print('len(msg)1', len(msg)) msg_stur = struct.pack('i', len(msg)) conn.send(msg_stur) conn.send(bytes(msg)) msg = 'please input your password'.encode('utf8') print('len(msg)2', len(msg)) msg_stur = struct.pack('i', len(msg)) conn.send(msg_stur) conn.send(bytes(msg)) head_name_pack = conn.recv(4) name_len = struct.unpack('i', head_name_pack)[0] name = conn.recv(name_len).decode('utf8') print('name', name) head_pwd_pack = conn.recv(4) pwd_len = struct.unpack('i', head_pwd_pack)[0] pwd = conn.recv(pwd_len).decode('utf8') print('pwd', pwd) msg = resgister(name,pwd).encode('utf8') print('msg',msg) msg_stur = struct.pack('i', len(msg)) conn.send(msg_stur) conn.send(bytes(msg)) print(1) else: msg = 'please input true choice'.encode('utf8') msg_stur = struct.pack('i',len(msg)) conn.send(msg_stur) conn.send(bytes(msg)) continue break break connlose() soclose()

上一篇:Qt CSS设置QTreeWidget拖动条宽度

下一篇:Postman 安装使用教程---图文讲解


Copyright © 2002-2019 k262电脑网 www.k262.cn 皖ICP备2020016292号
温馨提示:部分文章图片数据来源与网络,仅供参考!版权归原作者所有,如有侵权请联系删除!QQ:251442993 热门搜索 网站地图