标题很宏伟,实现很遥远。


首先要说明这其实是一个不可能完成的任务,至少不是一片博文就能完成的任务。避免浪费看到标题而满心期待同道的时间。



主要是想以Apache为蓝本,通过Python脚本的方式一步一步实现Apache的核心功能。进而了解Python网络编程以及有关于Web服务性能相关的方方面面。



闲言少叙,直接上代码!





import socket 
 
 
 
 
  
 
 

   server=socket.socket(socket.AF_INET,socket.SOCK_STREAM) 
 
 
 
 
  
 
 

   server.bind(('',1234)) 
 
 
 

   server.listen(5) 
 
 
 

   while True: 
 
 
 

           client,addr=server.accept() 
 
 
 

           data=client.recv(1024) 
 
 
 

           client.send("My WebServer") 
 
 
 

           client.close()



使用ab命令对这个服务器进行测试:



ab -n500 -c500 http://127.0.0.1:1234/



测试结果如下:




可以看到,这个简单几行代码的服务器的吞吐量是每秒2000次。这个服务器除了可以返回字符串“My WebServer”以外,什么都不能干。所以第一步,至少让这个WebServer可以根据用户的请求读取相应的文件返回给客户端。


通过打印data可以得到客户端代理提交到WebServer的信息。



GET / HTTP/1.1 
  
   

    User-Agent: curl/7.12.1 (i686-redhat-linux-gnu) libcurl/7.12.1 OpenSSL/0.9.7a zlib/1.2.1.2 libidn/0.5.6 
  
   

    Host: 127.0.0.1:1234 
  
   

    Pragma: no-cache 
  
   

    Accept: */*


我们在这里只关心GET请求的内容,通过解析用户的GET请求后获取指定目录中的文件内容后再返回给客户端。



import socket 
  
   

    import re 
  
   

    DocumentRoot="/root/" 
  
   

    server=socket.socket(socket.AF_INET,socket.SOCK_STREAM) 
  
   

    server.bind(('',1235)) 
  
   

    server.listen(5) 
  
   

    while True: 
  
   

            client,addr=server.accept() 
  
   

            data=client.recv(1024) 
  
   

            m=re.match('GET /(.*) ',data)#由于ab使用的是http1.0 因此与上例有所区别 
  
   
 #为应对在进行ab测试时候可能的异常   
   

                    index=DocumentRoot+m.group(1) 
  
   

                    html=open(index,"r") 
  
   

                    client.send(html.read()) 
  
   

                    client.close() 
  
   

            except: 
  
   

                    client.close()



通过ab命令进行测试的结果如下:


可以看到由于增加了匹配用户请求与读写文件的开销,我们的WebServer减少了四分之一的工作性能。可即便如此,我们的Web服务器也要比提供相同服务的Apache服务的吞吐量略胜一筹。以下是Apache服务的吞吐量。



事情并没有如此简单,大家都注意到了我们的网页连1k都不到。在真实的生产环境中,网页的大小一般都是50k以上。修改index.html文件,复制5000行My WebServer。



如图所示,修改后的文件大小是51k



Apache服务器的测试结果。

而我们的web服务器由于错误太多,干脆不能进行ab测试。也许你会说因为我们的web服务器是单进程模式,只有当完整完成一次用户请求后才能为下一次请求提供服务。但问题的关键是没有返回标准的http信息头。修改后的代码如下:


import socket 
  
   

    import re 
  
   

    DocumentRoot="/root/" 
  
   

    server=socket.socket(socket.AF_INET,socket.SOCK_STREAM) 
  
   

    server.bind(('',1235)) 
  
   

    server.listen(5) 
  
   

    while True: 
  
   

            client,addr=server.accept() 
  
   

            data=client.recv(1024) 
  
   

            m=re.match('GET /(.*) ',data) 
  
   

            try: 
  
   

                    index=DocumentRoot+m.group(1) 
  
   

                    html=open(index,"r") 
  
   

                    index="""HTTP/1.1 200 OK 
  
   

    Content-Type: text/html; charset=UTF-8 
  
   
    
   

     """+html.read() 
  
   

                    client.send(index) 
  
   

                    client.close() 
  
   

            except: 
  
   

                    client.close()



测试结果还是可以让我们满意的。

我们的测试都是在本地服务器上进行的,因此网络带宽是被忽略的。因为我们的服务器是单进程的。因此,如果有一个用户处理的比较慢,结果是不堪设想的。



增加多进程后的代码如下:


import socket 
  
   

    import re 
  
   

    import os 
  
   

    import sys 
  
   
    
   

    DocumentRoot="/root/" 
  
   

    server=socket.socket(socket.AF_INET,socket.SOCK_STREAM) 
  
   

    server.bind(('',1234)) 
  
   

    server.listen(5) 
  
   

    while True: 
  
   

            try: 
  
   

                    client,addr=server.accept() 
  
   

            except: 
  
   

                    sys.exit(0) 
  
   

            if os.fork(): 
  
   

                    client.close() 
  
   

            else: 
  
   
 server.close()   
   

                    data=client.recv(1024) 
  
   

                    m=re.match('GET /(.*) ',data) 
  
   

                    try: 
  
   

                            index=DocumentRoot+m.group(1) 
  
   

                            html=open(index,"r") 
  
   

                            index="""HTTP/1.1 200 OK 
  
   

    Content-Type: text/html; charset=UTF-8 
  
   
    
   

     """+html.read() 
  
   

                            client.send(index) 
  
   

                            client.close() 
  
   

                    except: 
  
   

                            client.close()



可以通过ab命令的测试了。但测试结果:


一直不相上下的吞吐率降到了106.27。

让Python落败的原因可以确认就是Python的os.fork()的处理方式,与系统、网络I/O没有直接的关系。