这几天在看一起写一个 Web 服务器,终于把Web Server 基本要做的事情搞懂了,无非就是建立Server端的socket和连接Client的socket,获取Client的Header信息,然后将必要的信息(请求url地址等)发给flask、django等web 框架,获取框架的结果然后再将信息发给Client,最后关闭链接。当然这其中还要包括HTTP报文的处理,不过这在一般的计算机网络课都有提及(这时才发现课堂上学的还是有用的……),遵循比较固定的格式(具体代码见原文,这里就不搬运了)。
文中实现的确切来说不是像Apache和Nginx那样的Server,而是WSGI Server,即Python Web服务器网关接口,它是一个中间层,用于将Nginx Server等和Flask等应用联系起来,Server负责流量分发资源分配等工作,应用则主要关注逻辑部分。当然可以把Server和应用写在一起,实际上Flask的实例代码就能做到。不过这样不利于应用的可扩展性,并且应用要关注过多的资源申请和释放的工作,对于普通开发者这是没有太大必要的。
WSGI是一种规范,遵循这种规范的框架有flask、django、bottle等,优点就在于你可以换用这几种框架而不必改动太多服务器方的代码。
一个符合规范的应用是这样的(即flask等框架应该照如下格式实现):
def app(environ,start_response):
start_response('200 OK',[('Content-Type','text/plain')])
yield "Hello world!\n"
WSGI会调用这个方法来获取结果,其中environ是环境参数,start_response是WSGI提供的方法,参数分别是HTTP状态码和响应头部,最后返回内容(再交由WSGI处理)。
那么要怎么处理多个用户请求呢?Apache用的是多进程,Nginx用的是多线程,一般来说多线程的效率会更高一点,实际上Nginx的性能确实也比Apache更好。
为了简单起见文章用了多进程的fork方法,每有一个用户连接就fork一个进程,处理完后要及时关闭这就要用到signal了,用os.fork后返回0的是子进程,处理完后调用os._exit(0),但这还不够,还必须调用在父进程中调用os.wait。什么时候调用wait呢,就是到子进程退出发出SIGCHLD信号时,我们可以在父进程中绑定信号,以后当收到信号后它会异步处理(也有一些特殊情况,具体见python2.7 signal一节的官方文档)。
CPython 并没有实现所有的Linux signal,所以并不是所有系统中断都可以接受得到。另外,主线程才可以接受到信号,其它线程可以发出信号,因此线程间同步应该用锁之类的东西而不应该用signal。有一个变通的方式是设置一个全局变量,然后在主线程中接受到信号时修改变量,各个线程运行时时不时的检查这个变量,不过这并不是一种好的编程方式。
一个信号处理的范例是这样的:
signal.signal(signal.SIGALRM, handler)
注册了一个SIGALRM信号,当这个信号产生时调用handler函数
handler是这样的:
def handler(signum, frame):
print 'Signal handler called with signal', signum
raiseIOError("Couldn't open device!")
signum表示信号量(用一个数值表示)
怎么触发这个信号呢,可以调用:
signal.alarm(5) # 5 seconds
也可以取消它
signal.alarm(0)
官方文档见signal-python2.7,signal-python3.5