这个读书笔记是学习Let’s Build A Web Server系列。原文地址:
https://ruslanspivak.com/lsbaws-part1/ 包含3个部分
python有很多web框架,django,flask,tornodo,web.py。我们可以基于这些框架来开发我们的网站。这些框架其实是给我们封装了很多底层的实现。比如WSGI,模板映射等功能。为了更好的理解和开发web服务器。我们必须了解这些实现的细节和原理,这样才能更好的理解和优化
作者还专门通过写了一个小故事来说明这个道理:
有一天,一位女士散步时经过一个工地,看见有三个工人在干活。她问第一个人,“你在做什么?”第一个人有点不高兴,吼道“难道你看不出来我在砌砖吗?”女士对这个答案并不满意,接着问第二个人他在做什么。第二个人回答道,“我正在建造一堵砖墙。”然后,他转向第一个人,说道:“嘿,你砌的砖已经超过墙高了。你得把最后一块砖拿下来。”女士对这个答案还是不满意,她接着问第三个人他在做什么。第三个人抬头看着天空,对她说:“我在建造这个世界上有史以来最大的教堂”。就在他望着天空出神的时候,另外两个人已经开始争吵多出的那块砖。他慢慢转向前两个人,说道:“兄弟们,别管那块砖了。这是一堵内墙,之后还会被刷上石灰的,没人会注意到这块砖。接着砌下层吧。”
这个故事的寓意在于,当你掌握了整个系统的设计,明白不同的组件是以何种方式组合在一起的(砖块,墙,教堂)时候,你就能够更快地发现并解决问题(多出的砖块)。
但是,这个故事与从头开发一个网络服务器有什么关系呢?
在我看来,要成为一名更优秀的程序员,你必须更好地理解自己日常使用的软件系统,而这就包括了编程语言、编译器、解释器、数据库与操作系统、网络服务器和网络开发框架。而要想更好、更深刻地理解这些系统,你必须从头重新开发这些系统,一步一个脚印地重来一遍。
我们每天都在用浏览器上网。当我们打开浏览器输入网址的时候,浏览器上会显示网页的内容,在这个过程中,上网是如何发生的呢。作者用了下面这个图展示了一个简单的数据流程图。
简单点说,在web server上搭建了第一个网络服务器,永久的等待客户发起的请求,当服务器收到请求后,它会产生响应并反馈给客户端。客户端和服务器之间的通信,是以HTTP协议进行的,浏览器就是收到这些HTTP响应数据后,解析完毕然后展示出来
当然整个交互过程比这张图要复杂得多。更加复杂的过程可以参考下面这个图。这其中包含了TCP三次握手。HTTP消息交互流程等。比上面的图更加细化了一些
那么我们继续往下刨根问底的问下,这些数据是如何组装和解析的呢。这就需要了解TCP/IP协议结构,HTTP协议就是基于TCP/IP协议模型来传输消息的。协议架构如下。HTTP协议就位于应用层。
有了上面的层次结构,再来看下数据的组装以及协议,在每个协议层都解析出各自的头以及信息。并把剩下的消息递交给上层继续解析。这就好比我们现在的包裹快递,在每个投递站打上各自的投递信息。最终达到的时候我们就可以查到一个完整的包裹传递路线。
当然如果还要更具体的话,还有ARP查询过程,DNS查询过程等等。这些就不在这里一一介绍了。前面介绍了整个HTTP协议报文的传输以及结构,下面就来看下如何实现具体的实例。
import socket
HOST,PORT=\'\',8888
listen_socket=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
listen_socket.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)
listen_socket.bind((HOST,PORT))
listen_socket.listen(1)
print \'Serving HTTP on port %s....\' % PORT
while True:
client_connection,client_address=listen_socket.accept()
print \'client_connection is %s,the connection from %s\' % (client_connection,client_address)
request=client_connection.recv(1024)
print request
http_response="""
HTTP/1.1 200 OK
Hello world!"""
client_connection.sendall(http_response)
client_connection.close()
运行该文件并在浏览器中输入http://localhost:8888/,可以看到浏览器中反馈的信息如下
我们来分析下背后运行的原理:
我们来看你所输入的网络地址。它的名字叫URL(Uniform Resource Locator,统一资源定位符),其基本结构如下:
通过URL,你告诉了浏览器它所需要发现并连接的网络服务器地址,以及获取服务器上的页面路径。不过在浏览器发送HTTP请求之前,它首先要与目标网络服务器建立TCP连接。然后,浏览器再通过TCP连接发送HTTP请求至服务器,并等待服务器返回HTTP响应。当浏览器收到响应的时候,就会在页面上显示响应的内容,而在上面的例子中,浏览器显示的就是“Hello, World!”这句话。
那么,在客户端发送请求、服务器返回响应之前,二者究竟是如何建立起TCP连接的呢?要建立起TCP连接,服务器和客户端都使用了所谓的套接字(socket)我们来看下socket的使用过程。整个过程可以参考下图:
(1)首先是初始话了HOST地址和端口,这里如果不指明地址的话。那么就是默认的本地地址127.0.0.1
HOST,PORT=\'\',8888
listen_socket=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
socket.socket(socket.AF_INET,socket.SOCK_STREAM)生成一个socket实例,里面使用的参数分别是使用的地址族,套接字类型,协议编号。详细的参数定义参考下表
(2)设置socket选项setsockopt(level,optname,value),level一般都是socket.SOL_SOCKET。optname的参数定义参考下表
listen_socket.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)
(3) 绑定IP与端口到套接字并开始监听
listen_socket.bind((HOST,PORT))
listen_socket.listen(1)
(4) 收到客户端发来的数据
client_connection,client_address=listen_socket.accept()
accept接受连接并返回(conn,address),其中conn是新的套接字对象,可以用来接收和发送数据。address是连接客户端的地址。接收TCP 客户的连接(阻塞式)等待连接的到来
request=client_connection.recv(1024)
接受套接字的数据。数据以字符串形式返回,bufsize指定最多可以接收的数量。flag提供有关消息的其他信息,通常可以忽略
我们通过打印可以看到接受的内容。request就是整个客户端发送的数据,包含请求方式:
GET /hello HTTP/1.1, 以及对应的头消息。
/usr/bin/python2.7 /home/zhf/py_prj/web_server/webserver1.py
Serving HTTP on port 8888....
client_connection is <socket._socketobject object at 0x7fc30eae36e0>,the connection from (\'127.0.0.1\', 38412)
#request的打印内容:
GET /hello HTTP/1.1
Host: localhost:8888
User-Agent: Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:58.0) Gecko/20100101 Firefox/58.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Accept-Encoding: gzip, deflate
Cookie: Pycharm-35cf7131=702c9c37-5112-486b-a03b-3cebfe91963b
Connection: keep-alive
Upgrade-Insecure-Requests: 1
下面这幅图展示的是HTTP请求的基本结构:
(5) 发送反馈
http_response="""
HTTP/1.1 200 OK
Hello world!"""
client_connection.sendall(http_response)
sendall将string中的数据发送到连接的套接字,但在返回之前会尝试发送所有数据。成功返回None,失败则抛出异常。内部通过递归调用send,将所有内容发送出去。下面这张图显示的是服务器返回至客户端的HTTP响应详情:
响应中包含了状态行HTTP/1.1 200 OK,之后是必须的空行,然后是HTTP响应的正文。
响应的状态行HTTP/1.1 200 OK中,包含了HTTP版本、HTTP状态码以及与状态码相对应的原因短语(Reason Phrase)。浏览器收到响应之后,会显示响应的正文,这就是为什么你会在浏览器中看到“Hello, World!”这句话。
这就是网络服务器基本的工作原理了。简单回顾一下:网络服务器首先创建一个侦听套接字(listening socket),并开启一个永续循环接收新连接;客户端启动一个与服务器的TCP连接,成功建立连接之后,向服务器发送HTTP请求,之后服务器返回HTTP响应。要建立TCP连接,客户端和服务器都使用了套接字。