【转】 Python网络编程的一些代码片断与分析
2011-06-02 16:46:41| 分类: 计算机-Python|字号 订阅
转载自
Arbow
最终编辑 Arbow
最终编辑 Arbow
平时在工作中都是用Java搞网络编程,而C的网络编程几乎没有写过。但是我们可以在Python中实现类似C那样的网络编程,毕竟在Python中,很多库都是对c库,unix库的简单封装。 p.s 本文随时会补充,使用rss的童子们要注意了 另外,本文的代码基本来自于 http://blog.chinaunix.net/u/19742/article_66836.html ,特此声明。 1. Socket Server 没什么好说的,跟C一个样,特别是Socket的参数。。。 #!/usr/bin/env python #coding=utf-8 imp s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) s.setsockopt(socket.SOL_SOCKET, socket.TCP_NODELAY, 1) s.setsockopt(socket.SOL_SOCKET, socket.SO_LINGER, struct.pack('ii', False, 0)) s.bind(("localhost", 8080)) s.listen(10) print "Hostname is", socket.gethostname() while(1): client_sock, client_addr = s.accept() print "Client", client_sock.getpeername(), " connected, address is", client_addr client_sock.close() 要注意的是 socket.SO_LINGER 这个参数,需要用 struct.pack 将两个值转化为整数。 这个Server接收1个客户端连接,输出信息后关闭。若在accept后用线程处理,加入读写处理逻辑,这样就是一个最简单的网络服务器了。 不过,考虑到Python中的GIL,那个假的Thread,我可是宁愿 os.fork() 一个子进程来处理读写,也比用啥Thread好。 不过子进程阿,线程之类的还是落伍了,有一些好的IO复用方式可以实现非阻塞的网络编程。 2. Select Select是比较‘落伍'的模型了,不过对于1024个连接以下的普通服务器,用这个也没有什么问题。对于LuaSocket这种只支持Ansi C的玩意,就只能用select了。 #!/usr/bin/env python #coding=utf-8 """ 非阻塞socket的使用(此程序在ubuntu linux和windows xp上测试,Windows可以支持select.select) 监控socket的三个list:in/out/err 程序以5000ms的时间长度为间隔,如果有客户端的请求,接收连接并进行显示;如果没有的话, 每隔5000ms显示一次"no da """ imp host = "" port = 5000 s = socket.socket(socket.AF_INET,socket.SOCK_STREAM) s.bind((host,port)) s.listen(5) while 1: infds,outfds,errfds = select.select([s,],[],[],5) # 如果infds状态改变,进行处理,否则不予理会 if len(infds) != 0: clientsock,clientaddr = s.accept() buf = clientsock.recv(8196) if len(buf) != 0: print "Socket from ", clientaddr, " send ", buf clientsock.close() print "no da 这段代码省略了一些socket参数设置,主要关注点在于while 1后面的逻辑上。将句柄放到select函数的列表参数中,就可以一个线程内阻塞获取所有句柄的事件了。我觉得这样挺好的,比起啥线程处理靠谱。 3. Poll Select默认只能支持1024个句柄,要更多的并发连接,就要靠poll了。poll相比select复杂了些,有个监听事件的注册。 #!/usr/bin/env python #coding=utf-8 """ 非阻塞socket的使用(此程序在ubuntu linux上测试,Windows没有select.poll) 监控socket的三个状态:IN/ERR/HUP 程序以5000ms的时间长度为间隔,如果有客户端的请求,接收连接并进行显示;如果没有的话, 每隔5000ms显示一次"no da """ imp host = "" port = 5000 s = socket.socket(socket.AF_INET,socket.SOCK_STREAM) s.bind((host,port)) s.listen(5) # 创建poll对象,仅用于unix-like系统 p = select.poll() p.register(s.fileno(),select.POLLIN|select.POLLERR|select.POLLHUP) while 1: results = p.poll(5000) # 如果有结果返回,进行处理,否则不予理会 if len(results) != 0: if results[0][1] == select.POLLIN: clientsock,clientaddr = s.accept() buf = clientsock.recv(8196) if len(buf) != 0: print "Socket from ", clientaddr, " send ", buf clientsock.close() print "no da 不过在poll了一堆结果之后,处理跟select还是差不多的,属于啥类型的事件就执行啥逻辑。这里特别要注意select类型的选择,Eurasia3里面就做得很好,参考 http://eurasia.googlecode.com/svn/trunk/src/eurasia/web.py : 读事件:POLLIN|POLLPRI 写事件:POLLOUT 错误事件:POLLERR|POLLHUP|POLLNVAL 4. EPoll Poll在C10K这样的场景下很吃力,这时候就需要epoll了。在Java下面,只能选择SUN JDK5 update10以上的版本,要是还在用JDK1.4,只能干瞪眼。幸好,Python中,我们只需要很少的步骤就可以让服务器使用牛X的epoll模型: 先到 http://sourceforge.net/projects/pyepoll/ 下载pyepoll库,安装 在上面的例子代码中加入 imp for attr in ['poll', 'error', 'POLLIN', 'POLLPRI', 'POLLOUT', 'POLLERR', 'POLLHUP', 'POLLNVAL']: setattr(select, attr, getattr(epoll, attr)) 我靠,就是这样简单! 此外,还可以使用 libevent 或者 libev 的python库,它们也使用epoll! 5. 半关闭 不懂半关闭的自己搜索去。常见场景是使用 echo -ne "Some text" | nc host port 的时候,echo的文本发送给服务器后,会关闭客户端socket的写通道,只保留读通道。这个时候,如果是貌似牛X的JavaNIO,就会报一个网络连 接已关闭的SB错误。Python和Erlang都不存在这种问题: 这是服务器端的例子: """ 半开连接服务器:创建的每个socket只允许进行一次读取操作 """ imp host = "" port = 5000 s = socket.socket(socket.AF_INET,socket.SOCK_STREAM) s.bind((host,port)) s.listen(5) while 1: clientsock,clientaddr = s.accept() buf = clientsock.recv(8196) # 关闭读(接收)操作 clientsock.shutdown(socket.SHUT_RD) if len(buf) != 0: print str(clientaddr),buf clientsock.send("OK\r\n"); clientsock.close() 6. 管道 Python里面,不仅仅是网络连接句柄,子进程句柄,文件句柄都可以用 select,poll的。 #!/usr/bin/env python #coding=utf-8 imp vmstat_pipe = subprocess.Popen('vmstat 1', shell=True, bufsize=1024, stdout=subprocess.PIPE).stdout vmstat_pipe = subprocess.Popen('iostat 2', shell=True, bufsize=1024, stdout=subprocess.PIPE).stdout while 1: infds,outfds,errfds = select.select([vmstat_pipe,vmstat_pipe],[],[],1) if len(infds) != 0: for p in infds: print "Get ", p.readline(), "from pipe", p 因此在Python中,主线程内完成客户端Socket的逻辑和后段子进程管道的读取,是很easy的事情。 7. 广播 #!/usr/bin/env python #coding=utf-8 """ 广播服务器,接收来自广播的消息,并进行应答 设置socket选项:SO_BROADCAST """ imp host = "" port = 50000 s = socket.socket(socket.AF_INET,socket.SOCK_DGRAM) s.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1) s.setsockopt(socket.SOL_SOCKET,socket.SO_BROADCAST,1) s.bind((host,port)) while 1: message,address = s.recvfrom(8192) print "Got da s.sendto("I am here",address) s.close() 这种UDP包的广播没啥特别的,就是SO_BROADCAST参数设置而已。 简单介绍了下常见的一些网络编程代码,更多可以到文中提到的作者blog上面看。这些代码只能算是Demo级别,有空我会写一下靠谱的网络服务器代码分析,比如 eurasia3 的 web.py。 |