今日内容:
1.半连接数
2.粘包问题
3.自定义报头
1. 半连接数
三次握手没有完成 称之为半连接
原因1 恶意客户端没有返回第三次握手信息
原因2 服务器没空及时处理你的请求
TCP流式协议, 数据之间没有分界, 就像水 一杯水和一杯牛奶倒在一起了!
UDP 用户数据报协议
粘包 仅发生在TCP协议中
发送端 发送的数据量小 并且间隔短 会粘
接收端 一次性读取了两次数据的内容 会粘
接收端 没有接收完整 剩余的内容 和下次发送的粘在一起
无论是那种情况,其根本原因在于 接收端不知道数据到底有多少
粘包问题解决方案:
先发长度给对方 再发真实数据
#发送端
1.使用struct 将真实数据的长度转为固定的字节数据
2.发送长度数据
3.发送真实数据
接收端
1.先收长度数据 字节数固定
2.再收真实数据 真实可能很长 需要循环接收
服务器:
import socket import struct import subprocess # 创建服务器套接字对象 server = socket.socket() # 绑定IP和端口 server.bind((‘192.168.13.29‘, 8080)) server.listen(1) while True: # 完成与客户端的三次握手 conn, address = server.accept() while True: try: # 获取客户端的报头 cmd = conn.recv(4) if not cmd: break # 拿到的是解码后的字符串 num = struct.unpack(‘i‘, cmd)[0] # 设定缓冲区的大小 buffer_size = 1024 # 设置已经接收的大小 recv_size = 0 # 设置初始信息量 info = b‘‘ while True: # 查看管道中剩余的数量,如果大于1024,那就继续接收1024 if num - recv_size >= buffer_size: temp = conn.recv(buffer_size) print(‘123‘) else: # 否则,则接收总数减去已接受所剩余的数量,解决粘包问题 temp = conn.recv(num - recv_size) print(‘123‘) info += temp recv_size += len(temp) if num == recv_size: break p = subprocess.Popen(info.decode(‘utf-8‘), shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE) out_info = p.stdout.read() err_info = p.stderr.read() all_info = out_info + err_info len_size = len(all_info) byte_len = struct.pack(‘i‘, len_size) conn.send(byte_len) conn.send(all_info) except Exception as f: print(‘异常原因:‘, f) break conn.close()
客户端:
import socket import struct client = socket.socket() # 创建连接 client.connect((‘192.168.13.29‘, 8080)) while True: try: cmd = input(‘请输入指令:‘).strip() if not cmd: break # 拿到指令转换为字节的长度 byte_cmd = struct.pack(‘i‘, len(cmd.encode(‘utf-8‘))) # 先发长度 client.send(byte_cmd) # 再发指令 client.send(cmd.encode(‘utf-8‘)) # 然后收取服务端的报头 info_size = client.recv(4) # 将报头解码为原数字类型,获得数据的总长度 len_info = struct.unpack(‘i‘, info_size)[0] # 设置接收数据的大小 buffer_size = 2048 # 设置已经接收的大小 recv_size = 0 # 设置初始信息量 info = b‘‘ while True: if len_info - recv_size > buffer_size: temp = client.recv(buffer_size) else: temp = client.recv(len_info - recv_size) recv_size += len(temp) info += temp if recv_size == len_info: break print(info.decode(‘gbk‘)) except Exception as f: print(‘异常原因是:%s‘ % f) client.close()
自定义报头:
当需要在传输数据时 传呼一些额外参数时就需要自定义报头
报头本质是一个json 数据
具体过程如下:
发送端
1 发送报头长度
2 发送报头数据 其中包含了文件长度 和其他任意的额外信息
3 发送文件内容
接收端
1.接收报头长度
2.接收报头信息
案例:服务器与客户端实现文件传输
服务器:
import socket import os import json import struct # 获得套接字对象 server = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # 绑定IP server.bind((‘192.168.13.29‘, 8080)) server.listen(2) while True: conn, address = server.accept() f = None try: path = r‘D:\python练习\python十万行代码\学day32\机密数据文件.txt‘ # 获取文件大小 file_size = os.path.getsize(path) # 制作报头 file_info = {‘file_name‘: ‘机密数据文件.txt‘, ‘file_size‘: file_size} # 使用json序列化报头成为字符串并编码,编程二进制 file_str = json.dumps(file_info).encode(‘utf-8‘) # 发送报头长度 conn.send(struct.pack(‘q‘, len(file_str))) # 发送报头 conn.send(file_str) # 发送文件 f = open(path, ‘rb‘) while True: temp = f.read(1024) if not temp: break conn.send(temp) print(‘文件发送完毕‘) except Exception as f: print(‘异常原因%s‘ % f) finally: if f:f.close() conn.close()
客户端:
import socket import struct import json # 获取客户端套接字对象 client = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # 获得服务器IP和端口 try: client.connect((‘192.168.13.29‘, 8080)) print(‘连接成功‘) # 接收报头的长度 head_size就是一个pack好的字典,8个字节长度 head_size = struct.unpack(‘q‘, client.recv(8))[0] # 接收报头数据 head_str = client.recv(head_size).decode(‘utf-8‘) # 得到报头字符串形式的字典 file_info = json.loads(head_str) # 使用json反序列化得到字典 print(‘报头数据%s‘ % file_info) file_size = file_info.get(‘file_size‘) # 获取文件大小 file_name = file_info.get(‘file_name‘) # 获取文件名称 # 再接收文件内容 revc_size = 0 # 已接收的长度 buffer_size = 2048 # 每次接收数据的大小 f = open(file_name, ‘wb‘) while True: if file_size - revc_size >= buffer_size: temp = client.recv(buffer_size) else: temp = client.recv(file_size - revc_size) f.write(temp) revc_size += len(temp) print(‘已经下载%1f%%‘ % (revc_size / file_size * 100)) if file_size == revc_size: break f.close() except Exception as f: print(‘异常原因:%s‘ % f)
原文:https://www.cnblogs.com/liguodeboke/p/10946846.html