JohnLyu的blog

橙汁事务所艾欧泽亚分部

0%

tcp udp 通信实验

TPC/UDP 通信实验

实验环境准备

Python3

1
2
>>> python3 --version
Python 3.8.5

直接调用socketserver包

直接通过官方库调用socketserver.TCPServersocketserver.UDPServer即可实现实验要求.

TCP部分

tcp_server.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
import socketserver

class MyTCPHandler(socketserver.BaseRequestHandler):
"""
The request handler class for our server.

It is instantiated once per connection to the server, and must
override the handle() method to implement communication to the
client.
"""

def handle(self):
# self.request is the TCP socket connected to the client
self.data = self.request.recv(1024).strip()
print("{} wrote:".format(self.client_address[0]))
print(self.data)
# just send back the same data, but upper-cased
self.request.sendall(self.data.upper())

if __name__ == "__main__":
HOST, PORT = "localhost", 9999

# Create the server, binding to localhost on port 9999
with socketserver.TCPServer((HOST, PORT), MyTCPHandler) as server:
# Activate the server; this will keep running until you
# interrupt the program with Ctrl-C
server.serve_forever()

tcp_client.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import socket
import sys

HOST, PORT = "localhost", 9999
data = " ".join(sys.argv[1:])

# Create a socket (SOCK_STREAM means a TCP socket)
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:
# Connect to server and send data
sock.connect((HOST, PORT))
sock.sendall(bytes(data + "\n", "utf-8"))

# Receive data from the server and shut down
received = str(sock.recv(1024), "utf-8")

print("Sent: {}".format(data))
print("Received: {}".format(received))

启动server:

1
>>> python3 tcp_server.py 

启动client:

1
2
3
>>> python3 tcp_client.py hello_world
Sent: hello_world
Received: HELLO_WORLD

同时, server的terminal会打印:

1
2
127.0.0.1 wrote:
b'hello_world'

UDP部分

udp_server.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import socketserver

class MyUDPHandler(socketserver.BaseRequestHandler):
"""
This class works similar to the TCP handler class, except that
self.request consists of a pair of data and client socket, and since
there is no connection the client address must be given explicitly
when sending data back via sendto().
"""

def handle(self):
data = self.request[0].strip()
socket = self.request[1]
print("{} wrote:".format(self.client_address[0]))
print(data)
socket.sendto(data.upper(), self.client_address)

if __name__ == "__main__":
HOST, PORT = "localhost", 9999
with socketserver.UDPServer((HOST, PORT), MyUDPHandler) as server:
server.serve_forever()

udp_client.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import socket
import sys

HOST, PORT = "localhost", 9999
data = " ".join(sys.argv[1:])

# SOCK_DGRAM is the socket type to use for UDP sockets
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)

# As you can see, there is no connect() call; UDP has no connections.
# Instead, data is directly sent to the recipient via sendto().
sock.sendto(bytes(data + "\n", "utf-8"), (HOST, PORT))
received = str(sock.recv(1024), "utf-8")

print("Sent: {}".format(data))
print("Received: {}".format(received))

启动server:

1
>>> python3 udp_server.py 

启动client:

1
2
3
>>> python3 udp_client.py hello_udp
Sent: hello_udp
Received: HELLO_UDP

同时, server的terminal会打印:

1
2
127.0.0.1 wrote:
b'hello_udp'

利用socket模块手写server/client

参考

The Open Group Base Specifications Issue 7, 2018 edition
IEEE Std 1003.1-2017 (Revision of IEEE Std 1003.1-2008)

SOCK_STREAM

Provides sequenced, reliable, bidirectional, connection-mode byte streams, and may provide a transmission mechanism for out-of-band data.

重写socketserver

mysocketserver.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
"""
a minimal socket server

refer
https://docs.python.org/3/library/socket.html
"""

# Copyright (c) 2020. John Lyu


import socket
import logging


_logger = logging.getLogger()

class MyBaseServer():
"""
refer to https://pubs.opengroup.org/onlinepubs/9699919799/functions/socket.html
> The Open Group Base Specifications Issue 7, 2018 edition
> IEEE Std 1003.1-2017 (Revision of IEEE Std 1003.1-2008)

> SOCK_STREAM
> Provides sequenced, reliable, bidirectional, connection-mode byte streams, and may provide a transmission mechanism for out-of-band data.

> SOCK_DGRAM
> Provides datagrams, which are connectionless-mode, unreliable messages of fixed maximum length.

A TCP connection is in fact a socket which use SOCK_STREAM type.
"""

SOCKET_TYPE = None

def __init__(self, host_and_port):
"""
Constructor for MyTCPServe
"""
self.server_address = host_and_port
self.socket_type = self.SOCKET_TYPE
self.socket = socket.socket(socket.AF_INET,
self.socket_type)

try:
self.bind()
self.active_server()
_logger.info(f"strat server at {self.server_address}")
except Exception as ex:
_logger.error(ex)

def active_server(self):
pass

def bind(self):
self.socket.bind(self.server_address)
self.server_address = self.socket.getsockname()

def handle(self, request, client_address):
pass

def serve_forever(self):
"""first, just allow one active connection, backlog is set to 0"""
pass

def server_close(self):
self.socket.close()

def __enter__(self):
return self

def __exit__(self, *args):
self.server_close()

包含了最基础的要素和流程, 在init中构造socket并bind端口, 预留active_server接口来封装TCP模式中的socket.listen.

接下来用handle函数处理数据, 函数将会在其子类中实现.

tcp_server.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
#  Copyright (c) 2020. John Lyu

import socket

import mysocketserver


class MyTCPServer(mysocketserver.MyBaseServer):
"""Documents for MyTCPServer"""
SOCKET_TYPE = socket.SOCK_STREAM

def __init__(self, address_port):
"""Constructor for MyTCPServer"""
super(MyTCPServer, self).__init__(address_port)

def active_server(self):
self.socket.listen(0)

def serve_forever(self):
while True:
try:
conn, addr = self.socket.accept()
except KeyboardInterrupt as ex:
break
except Exception as ex:
break
if conn:
with conn:
self.handle(conn, addr)


def handle(self, request, client_address):
# self.request is the TCP socket connected to the client
data = request.recv(1024) # max length is 1024

print("{} wrote:".format(client_address[0]))
print(data)
# just send back the same data, but upper-cased
request.sendall(data.upper()[::-1])
self.shutdown_request(request)


def shutdown_request(self, request):
"""Called to shutdown and close an individual request."""
try:
#explicitly shutdown. socket.close() merely releases
#the socket and waits for GC to perform the actual close.
request.shutdown(socket.SHUT_WR)
except OSError:
pass #some platforms may raise ENOTCONN here

if __name__ == "__main__":
HOST, PORT = "localhost", 9999

# Create the server, binding to localhost on port 9999
with MyTCPServer((HOST, PORT)) as server:
# Activate the server; this will keep running until you
# interrupt the program with Ctrl-C
server.serve_forever()

继承MyBaseServer, 完善serve_forever函数, 完善active_server函数, 注意listenaccept都是TCP特有的方法.

重写handle函数, 循环读取request中的数据(此处限制最大只能读取1024字节!), 大写然后反向排列之后返回.

测试结果:

启动server:

1
>>> python3 tcp_server.py 

启动client:

1
2
3
4
5
6
>>> python3 tcp_client.py dog
Sent: dog
Received:
GOD

//注意到换行符\n的位置在最前

同时, server的terminal会打印:

1
2
127.0.0.1 wrote:
b'dog\n'

udp_server.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
#  Copyright (c) 2020. John Lyu

import socket

import mysocketserver


class MyUDPServer(mysocketserver.MyBaseServer):
"""Documents for MyUDPServer"""
SOCKET_TYPE = socket.SOCK_DGRAM

def serve_forever(self):
while True:
data, addr = self.socket.recvfrom(1024)

self.handle(data, addr)


def handle(self, request, client_address):
# self.request is the UDP socket connected to the client
data = request # max length is 1024

print("{} wrote:".format(client_address[0]))
print(data)
# just send back the same data, but upper-cased
self.socket.sendto(data.upper()[::-1], client_address)


if __name__ == "__main__":
HOST, PORT = "localhost", 9999

# Create the server, binding to localhost on port 9999
with MyUDPServer((HOST, PORT)) as server:
# Activate the server; this will keep running until you
# interrupt the program with Ctrl-C
server.serve_forever()

UDP简单很多, 无需listen和accept, 也不会产生新的socket. 注意因为没有连接, 所以返回结果的时候是做了和客户端相同的操作, 直接针对ip和port又sendto了一遍. 测试结果同上.

多客户端测试

如果在以上客户端的源代码中增加一个sleep, 并且多次调用会怎么样呢?

multi_tcp_client.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
#  Copyright (c) 2020. John Lyu

import socket
import time
from multiprocessing import Pool


HOST, PORT = "localhost", 9999


def tcp_send(data):
# Create a socket (SOCK_STREAM means a TCP socket)
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:
# Connect to server and send data
sock.connect((HOST, PORT))
time.sleep(5)
data = str(data)
sock.sendall(bytes(data, "utf-8"))
# Receive data from the server and shut down
received = str(sock.recv(1024), "utf-8")

print("Sent: {}".format(data))
print("Received: {}".format(received))

if __name__ == '__main__':
with Pool(8) as p:
p.map(tcp_send, list(range(16)))

我对服务端的输出略作修改, 打印accept函数结束和handle函数结束的时间:

1
2
3
4
5
6
7
8
9
10
11
12
13
def serve_forever(self):
while True:
try:
conn, addr = self.socket.accept()
print(f"[{datetime.datetime.today()}] start connect {addr}")
except KeyboardInterrupt as ex:
break
except Exception as ex:
break
if conn:
with conn:
self.handle(conn, addr)
print(f"[{datetime.datetime.today()}] finish handle {addr}")

服务端的输出结果是:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
[2020-09-28 19:47:21.988152] start connect ('127.0.0.1', 61652)
127.0.0.1 wrote:
b'0'
[2020-09-28 19:47:26.994171] finish handle ('127.0.0.1', 61652)
[2020-09-28 19:47:26.994515] start connect ('127.0.0.1', 61653)
127.0.0.1 wrote:
b'1'
[2020-09-28 19:47:27.009974] finish handle ('127.0.0.1', 61653)
[2020-09-28 19:47:27.010101] start connect ('127.0.0.1', 61654)
127.0.0.1 wrote:
b'2'
[2020-09-28 19:47:27.010227] finish handle ('127.0.0.1', 61654)
[2020-09-28 19:47:27.010302] start connect ('127.0.0.1', 61655)
127.0.0.1 wrote:
b'3'
[2020-09-28 19:47:27.020250] finish handle ('127.0.0.1', 61655)
[2020-09-28 19:47:27.020644] start connect ('127.0.0.1', 61656)
127.0.0.1 wrote:
b'4'
[2020-09-28 19:47:27.024446] finish handle ('127.0.0.1', 61656)
[2020-09-28 19:47:27.025111] start connect ('127.0.0.1', 61657)
127.0.0.1 wrote:
b'6'
[2020-09-28 19:47:27.025381] finish handle ('127.0.0.1', 61657)
[2020-09-28 19:47:27.026690] start connect ('127.0.0.1', 61658)
127.0.0.1 wrote:
b'5'
[2020-09-28 19:47:27.027257] finish handle ('127.0.0.1', 61658)
[2020-09-28 19:47:27.027485] start connect ('127.0.0.1', 61659)
127.0.0.1 wrote:
b'7'
[2020-09-28 19:47:27.027582] finish handle ('127.0.0.1', 61659)
[2020-09-28 19:47:27.027634] start connect ('127.0.0.1', 61660)
127.0.0.1 wrote:
b'8'
[2020-09-28 19:47:32.000856] finish handle ('127.0.0.1', 61660)
[2020-09-28 19:47:32.001133] start connect ('127.0.0.1', 61662)
127.0.0.1 wrote:
b'10'
[2020-09-28 19:47:32.016493] finish handle ('127.0.0.1', 61662)
[2020-09-28 19:47:32.016773] start connect ('127.0.0.1', 61661)
127.0.0.1 wrote:
b'9'
[2020-09-28 19:47:32.017032] finish handle ('127.0.0.1', 61661)
[2020-09-28 19:47:32.017282] start connect ('127.0.0.1', 61663)
127.0.0.1 wrote:
b'11'
[2020-09-28 19:47:32.024383] finish handle ('127.0.0.1', 61663)
[2020-09-28 19:47:32.024795] start connect ('127.0.0.1', 61664)
127.0.0.1 wrote:
b'13'
[2020-09-28 19:47:32.029014] finish handle ('127.0.0.1', 61664)
[2020-09-28 19:47:32.029147] start connect ('127.0.0.1', 61665)
127.0.0.1 wrote:
b'12'
[2020-09-28 19:47:32.029308] finish handle ('127.0.0.1', 61665)
[2020-09-28 19:47:32.029422] start connect ('127.0.0.1', 61666)
127.0.0.1 wrote:
b'14'
[2020-09-28 19:47:32.029518] finish handle ('127.0.0.1', 61666)
[2020-09-28 19:47:32.029600] start connect ('127.0.0.1', 61667)
127.0.0.1 wrote:
b'15'
[2020-09-28 19:47:32.029682] finish handle ('127.0.0.1', 61667)

令人惊讶的是, 仅有在第一次accept到第一次finish handle中, 间隔了5秒的时间, 然后立刻处理了15个当前已经发生的请求.

可见accept函数仅仅是从建立连接的队列中取出一个连接, 而并不会阻塞连接的产生.

我们修改listen(0)为listen(1), 再次观察输出:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
[2020-09-28 19:50:59.085068] start connect ('127.0.0.1', 61694)
127.0.0.1 wrote:
b'0'
[2020-09-28 19:51:04.085859] finish handle ('127.0.0.1', 61694)
[2020-09-28 19:51:04.086169] start connect ('127.0.0.1', 61695)
127.0.0.1 wrote:
b'1'
[2020-09-28 19:51:04.137850] finish handle ('127.0.0.1', 61695)
[2020-09-28 19:51:04.138167] start connect ('127.0.0.1', 61702)
127.0.0.1 wrote:
b'8'
[2020-09-28 19:51:09.087899] finish handle ('127.0.0.1', 61702)
[2020-09-28 19:51:09.088197] start connect ('127.0.0.1', 61703)
127.0.0.1 wrote:
b'9'
[2020-09-28 19:51:09.143399] finish handle ('127.0.0.1', 61703)
[2020-09-28 19:51:09.143657] start connect ('127.0.0.1', 61704)
127.0.0.1 wrote:
b'10'
[2020-09-28 19:51:14.094122] finish handle ('127.0.0.1', 61704)
[2020-09-28 19:51:14.094636] start connect ('127.0.0.1', 61705)
127.0.0.1 wrote:
b'11'
[2020-09-28 19:51:14.150364] finish handle ('127.0.0.1', 61705)
[2020-09-28 19:51:14.150668] start connect ('127.0.0.1', 61706)
127.0.0.1 wrote:
b'12'
[2020-09-28 19:51:19.097659] finish handle ('127.0.0.1', 61706)
[2020-09-28 19:51:19.097987] start connect ('127.0.0.1', 61707)
127.0.0.1 wrote:
b'13'
[2020-09-28 19:51:19.153941] finish handle ('127.0.0.1', 61707)
[2020-09-28 19:51:19.154163] start connect ('127.0.0.1', 61708)
127.0.0.1 wrote:
b'14'
[2020-09-28 19:51:24.104571] finish handle ('127.0.0.1', 61708)
[2020-09-28 19:51:24.104871] start connect ('127.0.0.1', 61709)
127.0.0.1 wrote:
b'15'
[2020-09-28 19:51:24.159250] finish handle ('127.0.0.1', 61709)

当第一个链接被处理时, 第二个连接已经进入了backlog队列, 因此, 第一个连接处理完毕时, 可以立刻处理第二个连接. 此后, 每两个连接的debug信息在几乎同一时间发生.

让我们修改multi_tcp_client中, sleep函数的位置.

1
2
3
4
5
6
7
8
9
10
# Connect to server and send data
sock.connect((HOST, PORT))
data = str(data)
sock.sendall(bytes(data, "utf-8"))
# Receive data from the server and shut down
received = str(sock.recv(1024), "utf-8")

# send for sleep
time.sleep(3)
sock.sendall(bytes(data, "utf-8")) # 在tcp_server中也需要添加对应的receive

可以看到, 我们将sleep函数移动到了通信阶段, 用来模拟耗时比较长的连接.

测试结果为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
[2020-09-28 20:05:47.595561] start connect ('127.0.0.1', 61967)
127.0.0.1 wrote:
b'0'
[2020-09-28 20:05:50.597057] finish handle ('127.0.0.1', 61967)
[2020-09-28 20:05:50.597355] start connect ('127.0.0.1', 61968)
127.0.0.1 wrote:
b'1'
[2020-09-28 20:05:53.599988] finish handle ('127.0.0.1', 61968)
[2020-09-28 20:05:53.600498] start connect ('127.0.0.1', 61977)
127.0.0.1 wrote:
b'8'
[2020-09-28 20:05:56.603293] finish handle ('127.0.0.1', 61977)
[2020-09-28 20:05:56.603585] start connect ('127.0.0.1', 61978)
127.0.0.1 wrote:
b'9'
[2020-09-28 20:05:59.604627] finish handle ('127.0.0.1', 61978)
[2020-09-28 20:05:59.604995] start connect ('127.0.0.1', 61979)
127.0.0.1 wrote:
b'10'
[2020-09-28 20:06:02.606392] finish handle ('127.0.0.1', 61979)
[2020-09-28 20:06:02.607370] start connect ('127.0.0.1', 61980)
127.0.0.1 wrote:
b'11'
[2020-09-28 20:06:05.614071] finish handle ('127.0.0.1', 61980)
[2020-09-28 20:06:05.614558] start connect ('127.0.0.1', 61981)
127.0.0.1 wrote:
b'12'
[2020-09-28 20:06:08.616184] finish handle ('127.0.0.1', 61981)
[2020-09-28 20:06:08.616566] start connect ('127.0.0.1', 61982)
127.0.0.1 wrote:
b'13'
[2020-09-28 20:06:11.619736] finish handle ('127.0.0.1', 61982)
[2020-09-28 20:06:11.620305] start connect ('127.0.0.1', 61983)
127.0.0.1 wrote:
b'14'
[2020-09-28 20:06:14.625111] finish handle ('127.0.0.1', 61983)
[2020-09-28 20:06:14.625440] start connect ('127.0.0.1', 61984)
127.0.0.1 wrote:
b'15'
[2020-09-28 20:06:17.630012] finish handle ('127.0.0.1', 61984)

可以发现产生了阻塞, 并且有丢包, 2-7的连接被丢掉了. 让我们修改listen参数回0, 再次测试:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
[2020-09-28 20:08:35.079838] start connect ('127.0.0.1', 62008)
127.0.0.1 wrote:
b'0'
[2020-09-28 20:08:38.084865] finish handle ('127.0.0.1', 62008)
[2020-09-28 20:08:38.085164] start connect ('127.0.0.1', 62009)
127.0.0.1 wrote:
b'1'
[2020-09-28 20:08:41.086948] finish handle ('127.0.0.1', 62009)
[2020-09-28 20:08:41.087304] start connect ('127.0.0.1', 62010)
127.0.0.1 wrote:
b'2'
[2020-09-28 20:08:44.088548] finish handle ('127.0.0.1', 62010)
[2020-09-28 20:08:44.088838] start connect ('127.0.0.1', 62011)
127.0.0.1 wrote:
b'3'
[2020-09-28 20:08:47.092957] finish handle ('127.0.0.1', 62011)
[2020-09-28 20:08:47.093275] start connect ('127.0.0.1', 62012)
127.0.0.1 wrote:
b'4'
[2020-09-28 20:08:50.095218] finish handle ('127.0.0.1', 62012)
[2020-09-28 20:08:50.095506] start connect ('127.0.0.1', 62013)
127.0.0.1 wrote:
b'6'
[2020-09-28 20:08:53.098302] finish handle ('127.0.0.1', 62013)
[2020-09-28 20:08:53.098758] start connect ('127.0.0.1', 62014)
127.0.0.1 wrote:
b'5'
[2020-09-28 20:08:56.104663] finish handle ('127.0.0.1', 62014)
[2020-09-28 20:08:56.104967] start connect ('127.0.0.1', 62015)
127.0.0.1 wrote:
b'7'
[2020-09-28 20:08:59.107536] finish handle ('127.0.0.1', 62015)
[2020-09-28 20:08:59.107826] start connect ('127.0.0.1', 62016)
127.0.0.1 wrote:
b'8'
[2020-09-28 20:09:02.113798] finish handle ('127.0.0.1', 62016)
[2020-09-28 20:09:02.114221] start connect ('127.0.0.1', 62017)
127.0.0.1 wrote:
b'9'
[2020-09-28 20:09:05.116121] finish handle ('127.0.0.1', 62017)
[2020-09-28 20:09:05.116354] start connect ('127.0.0.1', 62018)
127.0.0.1 wrote:
b'10'
[2020-09-28 20:09:08.121383] finish handle ('127.0.0.1', 62018)
[2020-09-28 20:09:08.121712] start connect ('127.0.0.1', 62021)
127.0.0.1 wrote:
b'11'
[2020-09-28 20:09:11.123565] finish handle ('127.0.0.1', 62021)
[2020-09-28 20:09:11.123847] start connect ('127.0.0.1', 62022)
127.0.0.1 wrote:
b'12'
[2020-09-28 20:09:14.125535] finish handle ('127.0.0.1', 62022)
[2020-09-28 20:09:14.125742] start connect ('127.0.0.1', 62025)
127.0.0.1 wrote:
b'13'
[2020-09-28 20:09:17.129745] finish handle ('127.0.0.1', 62025)
[2020-09-28 20:09:17.130031] start connect ('127.0.0.1', 62026)
127.0.0.1 wrote:
b'14'
[2020-09-28 20:09:20.132656] finish handle ('127.0.0.1', 62026)
[2020-09-28 20:09:20.132866] start connect ('127.0.0.1', 62027)
127.0.0.1 wrote:
b'15'
[2020-09-28 20:09:23.135194] finish handle ('127.0.0.1', 62027)

延迟依旧, 但是TimeoutError导致的连接中断消失了.

多连接解决方案

针对多连接的解决方案有很多, 在此次试验中优先测试multiprocessing, select, libuv.

multiprocessing server

针对原tcp_server中, server_forever函数进行修改:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
def serve_forever(self):
while True:
try:
conn, addr = self.socket.accept()
print(f"[{datetime.datetime.today()}] start connect {addr}")
except KeyboardInterrupt as ex:
break
except Exception as ex:
break
if conn:
process = multiprocessing.Process(target=handle, args=(conn, addr))
# self.handle(conn, addr)
process.daemon = True
process.start()

再次测试 得到结果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
[2020-09-28 20:32:22.081443] start connect ('127.0.0.1', 62655)
[2020-09-28 20:32:22.106736] start connect ('127.0.0.1', 62656)
[2020-09-28 20:32:22.117108] start connect ('127.0.0.1', 62657)
[2020-09-28 20:32:22.184763] start connect ('127.0.0.1', 62658)
[2020-09-28 20:32:22.191656] start connect ('127.0.0.1', 62659)
[2020-09-28 20:32:22.195872] start connect ('127.0.0.1', 62660)
[2020-09-28 20:32:22.288573] start connect ('127.0.0.1', 62661)
[2020-09-28 20:32:22.340250] start connect ('127.0.0.1', 62662)
('127.0.0.1', 62655) wrote:
b'0'
('127.0.0.1', 62659) wrote:
b'4'
('127.0.0.1', 62656) wrote:
b'1'
('127.0.0.1', 62657) wrote:
b'2'
('127.0.0.1', 62658) wrote:
b'3'
('127.0.0.1', 62660) wrote:
b'5'
('127.0.0.1', 62662) wrote:
b'7'
('127.0.0.1', 62661) wrote:
b'6'
[2020-09-28 20:32:26.034654] finish handle ('127.0.0.1', 62655)
[2020-09-28 20:32:26.037662] start connect ('127.0.0.1', 62663)
[2020-09-28 20:32:26.041779] finish handle ('127.0.0.1', 62659)
[2020-09-28 20:32:26.044135] start connect ('127.0.0.1', 62664)
[2020-09-28 20:32:26.047419] finish handle ('127.0.0.1', 62656)
[2020-09-28 20:32:26.048139] finish handle ('127.0.0.1', 62657)
[2020-09-28 20:32:26.061695] finish handle ('127.0.0.1', 62658)
[2020-09-28 20:32:26.063596] start connect ('127.0.0.1', 62665)
[2020-09-28 20:32:26.061698] finish handle ('127.0.0.1', 62660)
[2020-09-28 20:32:26.071198] start connect ('127.0.0.1', 62666)
[2020-09-28 20:32:26.073034] finish handle ('127.0.0.1', 62662)
[2020-09-28 20:32:26.074966] finish handle ('127.0.0.1', 62661)
[2020-09-28 20:32:26.080513] start connect ('127.0.0.1', 62667)
[2020-09-28 20:32:26.089662] start connect ('127.0.0.1', 62668)
[2020-09-28 20:32:26.107653] start connect ('127.0.0.1', 62669)
[2020-09-28 20:32:26.120258] start connect ('127.0.0.1', 62670)
('127.0.0.1', 62663) wrote:
b'8'
('127.0.0.1', 62669) wrote:
b'15'
('127.0.0.1', 62670) wrote:
b'14'
('127.0.0.1', 62666) wrote:
b'11'
('127.0.0.1', 62668) wrote:
b'13'
('127.0.0.1', 62665) wrote:
b'10'
('127.0.0.1', 62667) wrote:
b'12'
('127.0.0.1', 62664) wrote:
b'9'
[2020-09-28 20:32:29.896884] finish handle ('127.0.0.1', 62663)
[2020-09-28 20:32:29.900672] finish handle ('127.0.0.1', 62669)
[2020-09-28 20:32:29.909882] finish handle ('127.0.0.1', 62670)
[2020-09-28 20:32:29.917748] finish handle ('127.0.0.1', 62666)
[2020-09-28 20:32:29.925119] finish handle ('127.0.0.1', 62665)
[2020-09-28 20:32:29.928934] finish handle ('127.0.0.1', 62668)
[2020-09-28 20:32:29.934114] finish handle ('127.0.0.1', 62667)
[2020-09-28 20:32:29.935469] finish handle ('127.0.0.1', 62664)

可见已经可以实现无延迟的处理request.

select模式

完全重写了serve_forever函数, 因为select的编程模式中, 对同一socket的读写操作不再是同步, 阻塞的, 因此, 原handle函数不再使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
def serve_forever(self):
while True:
read_sockets, write_sockets, error_sockets = select.select(self.connection_list, self.write_list, self.connection_list)

for sock in read_sockets:
if sock == self.socket:
sockfd, addr = self.socket.accept()
sockfd.setblocking(0)
self.connection_list.append(sockfd)
self.message_queues[sockfd] = Queue()
print(f"[{datetime.datetime.now()}] Client {addr} connected")

# Some incoming message from a client
else:
# Data recieved from client, process it
try:
# In Windows, sometimes when a TCP program closes abruptly,
# a "Connection reset by peer" exception will be thrown
data = sock.recv(1024)
if data == '' and sock in self.write_list:
self.write_list.remove(sock)
if sock not in self.write_list:
print(f"[{datetime.datetime.now()}] get data: {data}")
self.message_queues[sock].put(data)
self.write_list.append(sock)

# client disconnected, so remove from socket list
except Exception as ex:
# print(ex)
print(f"[{datetime.datetime.now()}] Client {addr} is offline")
sock.close()
self.connection_list.remove(sock)
del self.message_queues[sock]

for sock in write_sockets:
sname = id(sock)
data = self.message_queues[sock].get_nowait()
sock.sendall(data)
self.write_list.remove(sock)

测试结果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
[2020-09-28 21:37:23.874508] Client ('127.0.0.1', 63481) connected
[2020-09-28 21:37:23.874573] get data: b'0'
[2020-09-28 21:37:23.874960] Client ('127.0.0.1', 63482) connected
[2020-09-28 21:37:23.875002] get data: b'1'
[2020-09-28 21:37:23.883249] Client ('127.0.0.1', 63483) connected
[2020-09-28 21:37:23.883356] get data: b'2'
[2020-09-28 21:37:23.895548] Client ('127.0.0.1', 63484) connected
[2020-09-28 21:37:23.895659] get data: b'3'
[2020-09-28 21:37:23.896493] Client ('127.0.0.1', 63485) connected
[2020-09-28 21:37:23.896615] Client ('127.0.0.1', 63486) connected
[2020-09-28 21:37:23.896641] get data: b'4'
[2020-09-28 21:37:23.896706] get data: b'5'
[2020-09-28 21:37:23.899129] Client ('127.0.0.1', 63487) connected
[2020-09-28 21:37:23.899175] get data: b'6'
[2020-09-28 21:37:23.901726] Client ('127.0.0.1', 63488) connected
[2020-09-28 21:37:23.901775] get data: b'7'
[2020-09-28 21:37:26.879538] get data: b'fin'
[2020-09-28 21:37:26.879723] get data: b'fin'
[2020-09-28 21:37:26.880561] Client ('127.0.0.1', 63488) is offline
[2020-09-28 21:37:26.880714] Client ('127.0.0.1', 63488) is offline
[2020-09-28 21:37:26.882456] Client ('127.0.0.1', 63490) connected
[2020-09-28 21:37:26.882997] Client ('127.0.0.1', 63489) connected
[2020-09-28 21:37:26.883083] get data: b'8'
[2020-09-28 21:37:26.883237] get data: b'9'
[2020-09-28 21:37:26.888187] get data: b'fin'
[2020-09-28 21:37:26.888785] Client ('127.0.0.1', 63489) is offline
[2020-09-28 21:37:26.889816] Client ('127.0.0.1', 63491) connected
[2020-09-28 21:37:26.889922] get data: b'10'
[2020-09-28 21:37:26.898865] get data: b'fin'
[2020-09-28 21:37:26.899299] get data: b'fin'
[2020-09-28 21:37:26.899793] get data: b'fin'
[2020-09-28 21:37:26.901391] Client ('127.0.0.1', 63491) is offline
[2020-09-28 21:37:26.901686] Client ('127.0.0.1', 63491) is offline
[2020-09-28 21:37:26.902033] Client ('127.0.0.1', 63491) is offline
[2020-09-28 21:37:26.904855] get data: b'fin'
[2020-09-28 21:37:26.905892] get data: b'fin'
[2020-09-28 21:37:26.906721] Client ('127.0.0.1', 63492) connected
[2020-09-28 21:37:26.907406] Client ('127.0.0.1', 63493) connected
[2020-09-28 21:37:26.907519] Client ('127.0.0.1', 63493) is offline
[2020-09-28 21:37:26.907654] Client ('127.0.0.1', 63493) is offline
[2020-09-28 21:37:26.907854] get data: b'13'
[2020-09-28 21:37:26.908168] Client ('127.0.0.1', 63494) connected
[2020-09-28 21:37:26.908221] get data: b'12'
[2020-09-28 21:37:26.908484] Client ('127.0.0.1', 63496) connected
[2020-09-28 21:37:26.908548] get data: b'11'
[2020-09-28 21:37:26.908755] Client ('127.0.0.1', 63495) connected
[2020-09-28 21:37:26.908794] get data: b'15'
[2020-09-28 21:37:26.909165] get data: b'14'
[2020-09-28 21:37:29.887799] get data: b'fin'
[2020-09-28 21:37:29.888304] get data: b'fin'
[2020-09-28 21:37:29.890121] Client ('127.0.0.1', 63495) is offline
[2020-09-28 21:37:29.890436] Client ('127.0.0.1', 63495) is offline
[2020-09-28 21:37:29.893419] get data: b'fin'
[2020-09-28 21:37:29.894439] Client ('127.0.0.1', 63495) is offline
[2020-09-28 21:37:29.912341] get data: b'fin'
[2020-09-28 21:37:29.912730] get data: b'fin'
[2020-09-28 21:37:29.913233] get data: b'fin'
[2020-09-28 21:37:29.913477] get data: b'fin'
[2020-09-28 21:37:29.914070] get data: b'fin'
[2020-09-28 21:37:29.915454] Client ('127.0.0.1', 63495) is offline
[2020-09-28 21:37:29.915544] Client ('127.0.0.1', 63495) is offline
[2020-09-28 21:37:29.915603] Client ('127.0.0.1', 63495) is offline
[2020-09-28 21:37:29.915654] Client ('127.0.0.1', 63495) is offline
[2020-09-28 21:37:29.915702] Client ('127.0.0.1', 63495) is offline

同样达到了非阻塞的效果.