8-26 1,967PVs
服务器的特点是等待请求,返回应答。对于客户端来说,一般只需要两步。建立Socket对象调用connect()来建立一个和服务器的连接。对于服务器,这个过程需要4步。1.建立Socket对象。2.建立Socket选项。3.绑定到一个端口(可以使一个指定的网卡)。4.侦听连接。举一个简单的TCP服务器的代码片段:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
host = '' prot = 51423 #Step 1(Create the socket object) s = socket.socket(socket.AF_INET,socket.SOCK_STREAM) #Step 2 (Set the socket options) s.socketopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1) #Step 3 (Bind to a port adn interface) s.bind((host,port)) #Step 4 (Listen for connections) s.listen(5) |
服务器的程序结构都是一样的,全都小心的使用了无限循环的方法,这里是一个基本的服务器例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
#!/usr/bin/env python # Base Server - Chapter 3 - baseicserver.py import socket host = '' # Bind to all interfaces port = 51423 s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) s.bind((host, port)) print "Waiting for connections..." s.listen(1) while 1: clientsock, clientaddr = s.accept() print "Got connection from", clientsock.getpeername() clientsock.close() print "helloworld" |
通常来说,使用while 1的无线循环会耗尽CPU资源,但是在本程序中并不会出现这种情况,程序调用了accept()函数,这个函数会等待返回值赋给Clientsock和clientaddr,所以并不会占用过多CPU资源,这样的函数称为阻塞函数。
为了增强程序的健壮性,我们往往会再加以写差错处理的代码。Python中使用的是try和except。 使用try来处理差错时,基本在通信部分。第一个用的try的部分是accept(),这个函数只在TCP中使用。第二个try处理连接的代码。第三个包含了对Close()的调用。
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 |
#!/usr/bin/env python # Server With Error Handling - Chapter 3 - errorserver.py import socket, traceback host = '' # Bind to all interfaces port = 51423 s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) s.bind((host, port)) s.listen(1) while 1: try: clientsock, clientaddr = s.accept() print "happy world" except KeyboardInterrupt: raise except: traceback.print_exc() continue # Process the connection try: print "Got connection from", clientsock.getpeername() # Process the request here except (KeyboardInterrupt, SystemExit): raise except: traceback.print_exc() # Close the connection try: clientsock.close() print "hello world" except KeyboardInterrupt: raise except: traceback.print_exc() |
使用三个try的方式之外还可以使用try…finally语句来确保Socket被关闭,在调用close()函数前,使用一个finally语句来关闭Socket。这种情况下,任何捕获不到的异常如果在try和finally之间都会使Socket关闭。
对于TCP连接,服务器的负担会比较大。但对于UDP,却是客户端处理难度比较大。原因是UDP通信,服务器没法察觉和解决丢失信息包的问题,所以由客户端负担起了这个责任。UDP协议的服务器只需要实现简单的应答就可以。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
#!/usr/bin/env python # UDP Echo Server - Chapter 3 - udpechoserver.py import socket, traceback host = '' # Bind to all interfaces port = 51423 s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) s.bind((host, port)) while 1: try: message, address = s.recvfrom(8192) print "Got data from", address print message # Echo it back s.sendto(message, address) except (KeyboardInterrupt, SystemExit): raise except: traceback.print_exc() |
现在有大部分服务器都都是用inetd,意思是守护进程,这一部分没有细看。上一个程序是UDP的服务器。下面举一个TCP的服务器例子:
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 |
#!/usr/bin/env python # Echo Server - Chapter 3 - echoserver.py import socket, traceback host = '' # Bind to all interfaces port = 51423 s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) s.bind((host, port)) s.listen(1) while 1: try: clientsock, clientaddr = s.accept() except KeyboardInterrupt: raise except: traceback.print_exc() continue # Process the connection try: print "Got connection from", clientsock.getpeername() while 1: data = clientsock.recv(4096) if not len(data): break clientsock.sendall(data) except (KeyboardInterrupt, SystemExit): raise except: traceback.print_exc() # Close the connection try: clientsock.close() except KeyboardInterrupt: raise except: traceback.print_exc() |
这个程序会返回所有Client发送给Controller的消息。下面举一个tcpClient的例子:
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 |
#!/usr/bin/env python # Echo client with deadlock - Chapter 3 - echoclient.py import socket, sys port = 51423 host = 'localhost' data = "x" * 10485760 # 10MB of data s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.connect((host, port)) byteswritten = 0 while byteswritten < len(data): startpos = byteswritten endpos = min(byteswritten + 1024, len(data)) byteswritten += s.send(data[startpos:endpos]) sys.stdout.write("Wrote %d bytesr" % byteswritten) sys.stdout.flush() s.shutdown(1) print "All data sent." while 1: buf = s.recv(1024) if not len(buf): break sys.stdout.write(buf) |
总结:
服务器主要是等待客户端请求并发送响应。服务器的Socket有很多选项,最常用的是SO_REUSEADDR,它允许端口在Socket关闭后马上被重新调用。TCP和UDP的服务器有一些区别。TCP服务器会用accept()来为每一个连接的客户端建立一个新的Socket,UDP服务器一般只是使用一个单一的Socket,并完全依靠从recvfrom()返回的值来判断往哪里发送。inetd和xinetd提供了方便的方法来侦听连接并把他们转给服务器处理。通过inetd和xinetd工作的服务器会默认把他们的标准输入输出设置为Socket。服务器程序不是交互进行的,需要一个方法来实现与操作员交换信息。unix有syslog接口,python也有一个相关模块。当服务器和客户端都停下里等候一个行为时,有可能会出现死锁(deadlock)这时需要适当的使用超时,把死锁出现的频率降到最低。