今日内容:
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)