用PYTHON快速开发一个实用的SOCKET服务器【转】
原文:http://www.cnblogs.com/WuErPIng/archive/2005/10/29/264671.html
首先,要明白不是所有的socket服务都需要高性能。如果要求高性能,使用IOCP或EPoll模式用C/C++来完成,直接用API写,用ACE的proactor封装来完成是比较恰当的行为。但当性能不是主要问题时,用Python来写socket服务,并享受高的开发效率将是一件快乐的事。下面,是用python完成的一个每thread/connect的一个echo服务。
经常的,在写一段Python代码时,我会先打开《Python Cookbook》(O\’Reilly)一书,看看有没有所需要的(这也是保证效率的一种方式),下面的代码就是摘自此书。
2 class MyHandler(SocketServer.BaseRequestHandler):
3 def handle(self):
4 while 1:
5 dataReceived = self.request.recv(1024)
6 if not dataReceived: break
7 self.request.send(dataReceived)
8 myServer = SocketServer.ThreadingTCPServer((\’\’,8881), MyHandler)
9 myServer.serve_forever( )
只用数行代码就完成工作,是不是非常轻松愉快。注意,这还不是一个实用程序,只是一个简单的示例。但这个示例指示了方向,下面我会把完成一个真正的服务端的一些小技巧一一列出。不过,在这之前,用几行代码完成一个测试用的客户端是一个不错的主意:
2 remote_host = \’127.0.0.1\’
3 remote_port = 9919
4 send_buf = open(\’binary.txt\’, \’rb\’).read()
5 #send_buf = send_buf.replace(\’\x0D\x0A\’, \’\’)
6 sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
7 sock.connect((remote_host, remote_port))
8 sock.send(send_buf)
9 response_data = sock.recv(1024)
10 print response_data
11 sock.close( )
看着上面写的这些代码,是不是感觉开发效率不一般的高 ^_^,下面进入正题
现在,我们来解决遇到第一个问题,MyHander是继承自SocketServer.BaseRequestHandler,但文档对这个模块介绍不怎么详细。不详细的原因?我想是因为这个类实在很简单。打开Lib目录下的SocketServer.py文件,我们直接看代码:
2 def __init__(self, request, client_address, server):
3 self.request = request
4 self.client_address = client_address
5 self.server = server
6 try:
7 self.setup()
8 self.handle()
9 self.finish()
10 finally:
11 sys.exc_traceback = None # Help garbage collection
12
13 def setup(self):
14 pass
15 def handle(self):
16 pass
17 def finish(self):
18 pass
19
一眼可知,类实现的是一个简单的template模式,定义了setup, handle, finish让继承者重载,模式方法__init__则定义了三个方法的调用顺序同时保证三个方法的运行。 很显然,如果我们要在退出时关闭连接,重定义finish是一个很自然的行为。
2 self.request.close()
第二个问题,如何记日志。Python有日志模块logging。
2 logging.basicConfig(level=logging.DEBUG,
3 format=\’%(asctime)s %(levelname)s %(message)s\’,
4 filename=\’log.txt\’,
5 filemode=\’a+\’)
不过实际使用中需要做一点点的补充。因为在多线程程序中,要记录日志需要线程相关的唯一ID来识别一些东西。我没有找到直接的线程ID(哪位兄弟找到了请告知),但Python中有一个名为id的内建函数,用来返回一个对象的identity (注1)。将要记录的信息预定义一个模板,我们就能得到一个漂亮的输出了。
2 return \’[id.\’ + str(id(self.request)) + \’]: \’ + str(s)
3 def Log(self, s):
4 ss = self.LogTemplate(s)
5 print ss
6 logging.info(ss)
7 def LogErr(self, s):
8 ss = self.LogTemplate(s)
9 print ss
10 logging.error(ss)
下面我们可以这样写了
2 self.Log(\’进入处理线程\’)
3 def finish(self):
4 self.request.close()
5 self.Log(“退出处理线程“)
另外模块binascii对日志也很有用,我就会用到binascii.b2a_hex来帮助把一串二进制转成可见的ASCII,象接收到的数据就最好用b2a_hex转换后再记日志。
第三个问题,超时处理。不多说了,直接贴出代码。
2 self.request.settimeout(60)
3 def handle(self):
4 while 1:
5 try:
6 #接收和发送操作,略
7 except socket.timeout:
8 print “caught socket.timeout exception“
每完成一小步,可以试试用测试程序发送你想发送的内容进行测试。你会非常高兴的看到,完成一个socket服务是如此的简单。
注1:
id( | object) |
Return the “identity\’\’ of an object. This is an integer (or long integer) which is guaranteed to be unique and constant for this object during its lifetime. Two objects with non-overlapping lifetimes may have the same id() value. (Implementation note: this is the address of the object.)