UDP协议通信的套接字、socketserver模块实现并发
基于 UDP 协议的套接字
UDP 协议又叫数据报协议,也就是说基于 UDP 协议发数据,每发一段数据,都是一个带着报头的数据;而 TCP 是数据流协议,每发一段数据,都是一个数据流,这导致了粘包,解决粘包问题的做法是在逻辑层面为每一个数据流加一个头,将它变成数据报;而 UDP 本身就是数据报,所以 UDP 没有粘包问题
# 服务端.py
import socket
server = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
server.bind(('127.0.0.1', 8080))
# UDP没有建立链接的过程,没有listen和accept
while True:
data = server.recvfrom(1024)
print(data)
server.close()
# 客户端.py
import socket
client = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
# UDP没有connect
while True:
msg = input('>>>: ').strip()
client.sendto(msg.encode(), ('127.0.0.1', 8080))
# 客户端输入 hello,服务端接收到数据
>>>: hello
(b'hello', ('127.0.0.1', 56890))
b'hello' 是客户端发送的数据,('127.0.0.1', 56890) 是客户端的 IP 和端口。对于 TCP 协议来说,已经建立好链接,用的是 conn.receive 、conn.send , 客户端的 IP 和端口没有用到。对于 UDP 协议,服务端拿到 IP 和端口,才能回复数据
# 服务端.py
import socket
server = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
server.bind(('127.0.0.1', 8080))
while True:
data, client_addr = server.recvfrom(1024)
print(data)
server.sendto(data.upper(), client_addr)
server.close()
# 客户端.py
import socket
client = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
while True:
msg = input('>>>: ').strip()
client.sendto(msg.encode(), ('127.0.0.1', 8080))
data, server_addr = client.recvfrom(1024)
print(data)
>>>: dsadsad
b'DSADSAD'
>>>: dbjsa
b'DBJSA'
>>>:
b'dsadsad'
b'dbjsa'
这里客户端直接回车,也能发送接收
>>>:
b''
b''
对于 TCP 协议,发完空然后等待接收,空给了操作系统,操作系统啥都没收到,就不会送给对方任何数据,对方一直等待接收,客户端也等待接收,就会一直卡住,所以 TCP 协议不能发空数据,但是 UDP 可以,UDP 是数据报协议,自带报头,空数据发出去也会带
关闭服务端,客户端正常运行,只是收不到服务端的数据,客户端发送还是正常运行,发完操作系统就直接把缓存中的数据清除,对方爱收不收
数据报协议的特点
TCP 协议是流式协议,并不是一发一收,可以是多个发送,一个接收。但是对于数据报协议,是一发一收,收的是完整数据报,没有粘包问题,但是缓冲区大小要足够装数据包大小,建议不要超过 512 字节
# 服务端.py
from socket import *
server = socket(AF_INET, SOCK_DGRAM)
server.bind(('127.0.0.1', 8080))
print(server.recvfrom(1024))
print(server.recvfrom(1024))
print(server.recvfrom(1024))
# 客户端.py
from socket import *
client = socket(AF_INET, SOCK_DGRAM)
client.sendto(b'hello', ('127.0.0.1', 8080))
client.sendto(b'world', ('127.0.0.1', 8080))
client.sendto(b'nihao', ('127.0.0.1', 8080))
# 服务端打印
(b'hello', ('127.0.0.1', 53668))
(b'world', ('127.0.0.1', 53668))
(b'nihao', ('127.0.0.1', 53668))
TCP 协议传输可靠,但是不如 UDP 协议传输速率快,UDP 发完就结束,没有等待对方的确认信息,数据容易丢失,不可靠
socketserver 模块
# 服务端.py
import socket
server = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
server.bind(('127.0.0.1', 8080))
while True:
data, client_addr = server.recvfrom(1024)
print(data)
server.sendto(data.upper(), client_addr)
server.close()
# 客户端1.py
import socket
client = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
while True:
msg = input('>>>: ').strip()
client.sendto(msg.encode(), ('127.0.0.1', 8080))
data, server_addr = client.recvfrom(1024)
print(data)
# 客户端2.py
import socket
client = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
while True:
msg = input('>>>: ').strip()
client.sendto(msg.encode(), ('127.0.0.1', 8080))
data, server_addr = client.recvfrom(1024)
print(data)
有一个服务端,多个客户端,如果是 TCP 协议,客户端 1 发数据,客户端 2 也发数据,客户端 2 不能收到数据,是因为服务端在与客户端 1 通信,无法再建立与客户端 2 的链接,不可能与客户端 2 通信。
而这里的 UDP 协议,客户端 1、2 都进行发送数据,都能够接收数据,客户端接收数据的时间间隔很短,看起来像是实现了并发,但是这个并发有局限性。如果有千万级别的客户端,那第一个和最后一个必定不是同时接收到数据。或者发送的数据量特别大,在客户端 1 发送接收的过程中,客户端 2 是处于一直等待的状态。
基于 TCP 协议
# 服务端.py
from socket import *
server = socket(AF_INET, SOCK_STREAM)
server.bind(('127.0.0.1', 8080))
server.listen(5)
# 链接循环
while True:
conn, client_addr = server.accept()
print(client_addr)
# 通信循环
while True:
try:
data = conn.recv(1024)
if len(data) == 0:
break
print("-->收到客户端的消息: ", data)
except ConnectionResetError:
break
conn.close()
server.close()
基于 TCP 协议,服务端不能实现并发,是因为服务端做了两件事,一个是建链接,一个是做通信,这两件事相当于是一个人做的,在建链接的同时不能做通信,所以实现不了并发。因此需要将这两件事分开
# 服务端.py
import socketserver
# 自定义类用来处理通信循环, 必须继承 BaseRequestHandler, 必须有一个 handle 方法
class MyTcpHandler(socketserver.BaseRequestHandler):
def handle(self):
print(self.client_address)
if __name__ == '__main__':
# socketserver 是 IO 密集型操作的程序,因此使用多线程
server = socketserver.ThreadingTCPServer(('127.0.0.1', 8080), MyTcpHandler)
# 链接循环
server.serve_forever()
# 客户端.py
from socket import *
client = socket(AF_INET, SOCK_STREAM)
client.connect(('127.0.0.1', 8080))
while True:
msg = input('>>>: ').strip()
if len(msg) == 0:
continue
client.send(msg.encode('utf-8'))
data = client.recv(1024)
print(data)
client.close()
# 客户端.py
from socket import *
client = socket(AF_INET, SOCK_STREAM)
client.connect(('127.0.0.1', 8080))
while True:
msg = input('>>>: ').strip()
if len(msg) == 0:
continue
client.send(msg.encode('utf-8'))
data = client.recv(1024)
print(data)
client.close()

这样,服务端就实现了可以不停的建立链接,接下来只要实现通信循环的问题,只需要将上面的拷贝到 handle 方法中即可
import socketserver
# 自定义类用来处理通信循环, 必须继承 BaseRequestHandler, 必须有一个 handle 方法
class MyTcpHandler(socketserver.BaseRequestHandler):
def handle(self):
# 通信循环
while True:
try:
# BaseRequestHandler中有request,相当于conn
data = self.request.recv(1024)
if len(data) == 0:
break
print("-->收到客户端的消息: ", data)
self.request.send(data.upper())
except ConnectionResetError:
break
if __name__ == '__main__':
# socketserver 是 IO 密集型操作的程序,因此使用多线程
server = socketserver.ThreadingTCPServer(('127.0.0.1', 8080), MyTcpHandler)
# 链接循环
server.serve_forever()
这样基于 TCP 协议就实现了并发
基于 UDP 协议
# 服务端.py
import socketserver
class MyUDPHandler(socketserver.BaseRequestHandler):
def handle(self):
print(self.request)
if __name__ == '__main__':
server = socketserver.ThreadingUDPServer(('127.0.0.1', 8081), MyUDPHandler)
server.serve_forever()
# 客户端.py
from socket import *
client = socket(AF_INET, SOCK_DGRAM)
while True:
client.sendto(b'Hello', ('127.0.0.1', 8081))
data, server_addr = client.recvfrom(1024)
print(data)
服务端打印出
self.request值为(b'Hello', <socket.socket fd=296, family=2, type=2, proto=0, laddr=('127.0.0.1', 8081)>)
# 服务端.py
import socketserver
class MyUDPHandler(socketserver.BaseRequestHandler):
def handle(self):
# print(self.request)
#self.request[1].sendto(self.request[0].upper(), 客户端的地址)
data, sock = self.request
sock.sendto(data.upper(), self.client_address)
if __name__ == '__main__':
server = socketserver.ThreadingUDPServer(('127.0.0.1', 8081), MyUDPHandler)
server.serve_forever()
这样基于 UDP 协议就实现了并发

浙公网安备 33010602011771号