Day 24 tcp,udp 粘包问题,socketserver并发
osi 七层协议的传输层:TCP/UDP协议


tcp与udp的对比
tcp协议:

传输控制协议,提供的是面向连接、可靠的字节流服务。当客户和服务器彼此交换数据前,必须先在双方之间建立一个TCP连接,之后才能传输数据。TCP提供超时重发,丢弃重复数据,检验数据,
流量控制等功能,保证数据能从一端传到另一端。
当应用程序希望通过 TCP 与另一个应用程序通信时,它会发送一个通信请求。这个请求必须被送到一个确切的地址。在双方“握手”之后,TCP 将在两个应用程序之间建立一个全双工
(full-duplex) 的通信。
这个全双工的通信将占用两个计算机之间的通信线路,直到它被一方或双方关闭为止。
# 面向连接的 可靠的 全双工的 流式传输 # 面向连接 :同一时刻只能和一个客户端通信 # 三次握手、四次挥手 # 可靠的 :数据不丢失、慢 # 全双工 :能够双向通信 # 流式传输 :粘包 无边界
udp协议:
# 用户数据报协议,是一个简单的面向数据报的运输层协议。UDP不提供可靠性,它只是把应用程序传给IP层的数据报发送出去,但是并不能保证它们能到达目的地。由于UDP在传输数据报前不用
在客户和服务器之间建立一个连接,且没有超时重发等机制,故而传输速度很快。
# 无连接的 面向数据包 不可靠的 快速的
#无连接的 :不需要accept/connect 也没有握手
#面向数据包的 :不会粘包
#不可靠的 :没有自动回复的机制
#快速的 :没有那些复杂的计算、保证数据传输的机制

tcp的粘包现象(4种可能导致粘包):
黏包现象只发生在tcp协议中:
1.从表面上看,黏包问题主要是因为发送方和接收方的缓存机制、tcp协议面向流通信的特点。
2.实际上,主要还是因为接收方不知道消息之间的界限,不知道一次性提取多少字节的数据所造成的
1.发送端粘包 合包机制 + 缓存区

2.接收到粘包 延迟接受 + 缓存区

3.流式传输
电流 高低电压
所以我们说 tcp协议是无边界的流式传输
4,拆包


解决粘包的方法:
借助struct模块,我们知道长度数字可以被转换成一个标准大小的4字节数字。因此可以利用这个特点来预先发送数据长度。
|
发送时 |
接收时 |
|
先发送struct转换好的数据长度4字节 |
先接受4个字节使用struct转换成数字来获取要接收的数据长度 |
|
再发送数据 |
再按照长度接收数据 |
# 1.两个连续的send就会发生粘包 # 2.用struct自定义协议可以解决粘包问题 # 3.什么情况下我们不需要解决粘包 : 文件的传输 # 4.自定义协议的进阶版本 # 先发送字符串的长度,再发送字符串 # 先发送json的长度,再发送json,json的字典中包含着下一条信息的长度,然后按照长度接受
import struct ret = struct.pack('i',560000) print(ret,len(ret)) # b'\x80\x8b\x08\x00' 4 res = struct.unpack('i',ret) print(res[0]) # 560000
服务器端:
file_path = r'F:\爬虫\2017年全新Python3.6网络爬虫实战案例5章(基础+实战+框架+分布式)教程34课 压缩(www.axxzy.com)(2)\[www.17zixueba.com]章节5: 分布式篇\[www.17zixueba.com]课时32:Scrapy分布式原理及Scrapy-Redis源码解析.mp4' file_name = os.path.basename(file_path) file_size = os.path.getsize(file_path) file_info = {'file_size':file_size, 'file_name':file_name, 'operate':'upload'} file_info_json = json.dumps(file_info) file_info_bytes = file_info_json.encode() # print(file_info_bytes) #b'{"file_size": 66300817, "file_name": "[www.17zixueba.com]\\u8bfe\\u65f632\\uff1aScrapy\\u5206\\u5e03\\u5f0f\\u539f\\u7406\\u53caScrapy-Redis\\u6e90\\u7801\\u89e3\\u6790.mp4", "operate": "upload"}' bytes_len = len(file_info_bytes) # 先发送文件信息的长度,再发送文件信息 sk.send(struct.pack('i', bytes_len)) # 计算出字典bytes的长度,压缩成 b'\xb6\x00\x00\x00' ,对方只要接受4个字节就可以获取到字典的具体长度了 sk.send(file_info_bytes) # 此时,对方已经知道要接收多长的字节 # 文件上传 with open(file_path, 'rb') as f1: while file_size > 0: content = f1.read(2048) file_size -= len(content) sk.send(content)
客户端:
bytes_len = conn.recv(4) # 固定接收4个字节内容,便可以获取到 字典的长度 info_len = struct.unpack('i', bytes_len)[0] # 解压获得字典长度 js_info = conn.recv(info_len).decode() # 知道长度便可以收取到字典内容 info_dic = json.loads(js_info) # 转成字典 print(info_dic) with open(info_dic['file_name'], 'wb') as f: while info_dic['file_size'] > 0: content = conn.recv(2048) f.write(content) info_dic['file_size'] -= len(content)
基于tcp协议的socket
服务器端:
import socket sk = socket.socket() # 创建套接字对象 sk.bind(('127.0.0.1', 9999)) # 绑定套接字 sk.listen(5) # 监听 conn, addr = sk.accept() # 接收客户端链接 ret = conn.recv(1024) # 接收客户端信息 print(ret) # 打印客户端信息 conn.send(b'hello') # 给客户端发送信息 conn.close() # 关闭客户端套接字 sk.close() # 关闭服务器套接字
客户端:
import socket sk = socket.socket() # 创建套接字对象 sk.connect(('127.0.0.1', 9999)) # 尝试链接服务器 sk.send(b'i am fine') # 发送内容 ret = sk.recv(1024) # 对话(发送,接收) print(ret) sk.close() # 关闭客户端套接字
基于udp协议的socket
服务器端:
import socket udp_sk = socket.socket(type=socket.SOCK_DGRAM) #创建一个服务器的套接字 udp_sk.bind(('127.0.0.1',9000)) #绑定服务器套接字 msg,addr = udp_sk.recvfrom(1024) print(msg) udp_sk.sendto(b'hi',addr) # 对话(接收与发送) udp_sk.close() # 关闭服务器套接字
客户端:
import socket ip_port=('127.0.0.1',9000) udp_sk=socket.socket(type=socket.SOCK_DGRAM) udp_sk.sendto(b'hello',ip_port) back_msg,addr=udp_sk.recvfrom(1024) print(back_msg.decode('utf-8'),addr)
socketserver:
import socketserver class Myserver(socketserver.BaseRequestHandler): def handle(self): conn = self.request while True: conn.send(b'hello') server = socketserver.ThreadingTCPServer(('127.0.0.1',9000),Myserver) server.serve_forever()

浙公网安备 33010602011771号