第二章主要在上一章的基础上介绍了以下内容:
1. ForkingMixIn
2. ThreadingMixIn
3. select.select
4. select.epoll
5. Diesel库
ForkingMixIn 和 ThreadingMixIn属于socketserver(python2是SocketServer)模块,该模块能够简化编写web服务器的工作。其包含四种基本的服务器class:
TCPServer 使用TCP协议,在服务器和客户端之间建立持续的连续,安全;
UDPServer 使用UDP协议,采用数据包的方法在服务器和客户端之间传递数据,有丢失包的可能,但是传输速度很快;
UnixStreamServer和UnixDatagramServer 比较少使用, 分别与TCPServer、UDPServer相似,但是基于Unix上定义的套接字,在其他平台上不能使用。
图1. Servers的继承关系
以上四种类都是同步的,即下一个请求开始前,上一个请求必须完成。显然,他们并不适用于当请求需要较长处理时间的情况。为每个请求创建单独的进程或线程的方式可以解决这个问题,即实现服务器和客户端的异步通信,为此socketserver模块添加了ForkingMixIn和ThreadingMixIn两个类。
为了创建一个服务,首先通过继承BaseRequestHandler类并重写其handler()方法得到一个句柄类,它将用来处理到达服务器的请求;
class ForkingServerRequestHandler(SocketServer.BaseRequestHandler):
然后以服务器地址和上一步得到的句柄实例化前面继承自ForkingMixIn/ThreadingMixIn和TCPServer/UDPServer/UnixStreamServer/UnixDatagramServer的server,
class ForkingServer(SocketServer.ForkingMixIn, SocketServer.TCPServer,):
pass
server = ForkingServer((SERVER_HOST, SERVER_PORT), ForkingServerRequestHandler)
注意:ForkingMixIn必须写在TCPServer前面,因为它重载了TCPServer类的方法。
最后,调用handler_request()或者server_forever()方法来开始处理请求。
server_thread = threading.Thread(target=server.serve_forever)
值得注意的是,如果server类继承自ThreadingMixIn,则需要明确指定遇到异常停止时的处理方法,ThreadingMixIn类定义了daemon_threads方法,其指定了服务器是否需要等待直到所有线程终止,默认为False。
server_thread.setDaemon(True)
下面是一个简单的例子:
import os
import socket
import threading
import SocketServer
SERVER_HOST = 'localhost'
SERVER_PORT = 0 # tells the kernel to pick up a port dynamically
BUF_SIZE = 1024
ECHO_MSG = 'Hello echo server!'
class ForkedClient():
""" A client to test forking server"""
def __init__(self, ip, port):
# Create a socket
self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# Connect to the server
self.sock.connect((ip, port))
def run(self):
""" Client playing with the server"""
# Send the data to server
current_process_id = os.getpid()
print 'PID %s Sending echo message to the server : "%s"' %
(current_process_id, ECHO_MSG)
sent_data_length = self.sock.send(ECHO_MSG)
print "Sent: %d characters, so far..." %sent_data_length
# Display server response
response = self.sock.recv(BUF_SIZE)
print "PID %s received: %s" % (current_process_id,
response[5:])
def shutdown(self):
""" Cleanup the client socket """
self.sock.close()
class ForkingServerRequestHandler(SocketServer.BaseRequestHandler):
def handle(self):
# Send the echo back to the client
data = self.request.recv(BUF_SIZE)
current_process_id = os.getpid()
response = '%s: %s' % (current_process_id, data)
print "Server sending response [current_process_id: data] =
[%s]" %response
self.request.send(response)
return
class ForkingServer(SocketServer.ForkingMixIn,
SocketServer.TCPServer,
):
"""Nothing to add here, inherited everything necessary from
parents"""
pass
def main():
# Launch the server
server = ForkingServer((SERVER_HOST, SERVER_PORT),
ForkingServerRequestHandler)
ip, port = server.server_address # Retrieve the port number
server_thread = threading.Thread(target=server.serve_forever)
server_thread.setDaemon(True) # don't hang on exit
server_thread.start()
print 'Server loop running PID: %s' %os.getpid()
# Launch the client(s)
client1 = ForkedClient(ip, port)
client1.run()
client2 = ForkedClient(ip, port)
client2.run()
# Clean them up
server.shutdown()
client1.shutdown()
client2.shutdown()
server.socket.close()
if __name__ == '__main__':
main()