SocketServer.py这个文件700来行,除去注释大概300来行左右,据网友称该模块实乃学习类继承之典范。
要理解这个模块真的非常的简单,也让人体会到同步编程的简单性。另外我现在比较关注web编程,所以会比较关注tcp部分忽略掉udp(源码版本Python2.7.11)

类继承关系

不说别的,单单看到这张图就能唬住好多人,感觉很高大上有没有

1
2
3
4
5
6
7
8
9
10
11
12
13
+------------+
| BaseServer |
+------------+
|
v
+-----------+ +------------------+
| TCPServer |------->| UnixStreamServer |
+-----------+ +------------------+
|
v
+-----------+ +--------------------+
| UDPServer |------->| UnixDatagramServer |
+-----------+ +--------------------+

同步处理类比

服务端嘛,请允许我污一下,就像古装剧里面的怡红院,都有个门口接客的老鸨,有客户来就引进去然后交给失足少女。老鸨就是服务端的监听socket,失足少女就是具体处理的业务逻辑。可以写成如下伪代码

1
2
3
4
5
6
7
import socket
s = socket.socket()
s.bind(('localhost',9999))
s.listen(2048)
while True:
client,addr = s.accept()
handle(client,addr,s)

可以说socketserver就是由上面最基本的步骤,为了扩展性而写的代码。上面的伪代码handle(client,addr,s)是一部分,上面的分为另外一部分。

socketserver因为是同步的,所以理解起来比asyncore要简单许多。而且注释写的非常详尽,我就不详述BaseServer/BaseRequestHandler的类方法了,说一下几个有点意思的地方。

  1. BaseServer部分基本就代表了监听套接字,然而还是提供了2个对连接套接字的方法,就是处理完成之后关闭啦~~shutdown_request/close_request
  2. handle_request方法和server_forever是非常相似的,区别就是handler_request只相应一个请求。大概是用来调试吧。该函数还调起了一个特别垃圾的handletimeout函数。看名字是不是以为是对连接套接字的超时处理函数--,实际上是等多久还没来一个新连接会触发,可是这个需求基本没有。所以我非常认为这个handle_request仅仅用来调试一次而已
  3. 上面的伪代码并没有用到select,为什么socketserver就用到了,其实如果只是为了处理tcp那么此处是没有必要用select的,因为tcp需要accept而udp是直接recvfrom就好了。用select只是为了通知有数据来了。都是为了适应多种情况才用的select。另外需要注意的是它不是用的文件描述符,是直接用的self,这里只要self实现了file_no方法就可以了(参照官方文档)。还有select另外套了一个_eintr_retry函数。这里是因为某些情况下select会被操作系统中断而引发异常(比如使用single)
  4. 可以很明显的看到实现的ThreadingTCPServer是通过继承的方式实现的,实质就是处理连接套接字的时候使用多线程或者多进程,可以想到要实现相同的效果用装饰器同样是可以的
  5. StreamRequestHandler中使用了socket.makefile将连接套接字分成了读和写2个类文件对象(只能是阻塞socket)。可以感受到的优势就是read(num)返回的长度是准确的,recv就没有这个优势
  6. 用到了threading.Event,这个地方我是没多搞明白。调用shutdown方法会中断循环从而关闭服务。一般会在处理线程调用这个。如果不是在处理线程调用那么会发生死锁。。。我能想到的只有这里

socks5 DEMO(引用自http://xiaoxia.org/2011/03/29/written-by-python-socks5-server/)

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
import socket, select, SocketServer, struct
class Socks5Handle(SocketServer.StreamRequestHandler):
def tcprelay(self, sock, remote):
fdset = [sock, remote]
while True:
r, w, e = select.select(fdset, [], [])
if sock in r:
if remote.send(sock.recv(4096)) <= 0: break
if remote in r:
if sock.send(remote.recv(4096)) <= 0: break
def handle(self):
try:
print('socks connection from ', self.client_address)
sock = self.connection
# 1. Version
sock.recv(262)
sock.send(b"\x05\x00")
# 2. Request
data = self.rfile.read(4)
mode = ord(data[1])
addrtype = ord(data[3])
if addrtype == 1: # IPv4
addr = socket.inet_ntoa(self.rfile.read(4))
elif addrtype == 3: # Domain name
addr = self.rfile.read(ord(sock.recv(1)[0]))
port = struct.unpack('>H', self.rfile.read(2))
reply = b"\x05\x00\x00\x01"
try:
if mode == 1: # 1. Tcp connect
remote = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
remote.connect((addr, port[0]))
print('Tcp connect to', addr, port[0])
else:
reply = b"\x05\x07\x00\x01" # Command not supported
local = remote.getsockname()
reply += socket.inet_aton(local[0]) + struct.pack(">H", local[1])
except socket.error:
# Connection refused
reply = '\x05\x05\x00\x01\x00\x00\x00\x00\x00\x00'
sock.send(reply)
# 3. Transfering
if reply[1] == '\x00': # Success
self.tcprelay(sock, remote)
except socket.error:
print('socket error')
def main():
server = SocketServer.ThreadingTCPServer(('', 1081), Socks5Handle)
server.serve_forever()
if __name__ == '__main__':
main()

和asyncore对比

asyncore和socketserver同样实现了并发。对比一下
1.从名字都可以看出来server。socketserver只能用来实现server,而asyncore还可以实现客户端
2.socketserver实现了多线程和多进程,asyncore框架是单线程事件循环

相关资料

SocketServer – Creating network servers.