Loading

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.receiveconn.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 协议就实现了并发

posted @ 2018-11-06 19:05  湫兮  阅读(380)  评论(0)    收藏  举报