zoukankan      html  css  js  c++  java
  • 自己动手开发网络服务器(一)

    这个读书笔记是学习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/,可以看到浏览器中反馈的信息如下

    我们来分析下背后运行的原理:

    我们来看你所输入的网络地址。它的名字叫URLUniform 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连接,客户端和服务器都使用了套接字。

  • 相关阅读:
    SpringCloud--Ribbon--源码解析--Ribbon入口实现
    SpringCloud--Ribbon--使用demo
    装饰着模式(Decorator Pattern)
    SpringCloud--Eureka--配置
    SpringCloud--Eureka--原理及源码解析
    SpringCloud--Eureka--搭建
    观察者模式(Observer Pattern)
    策略模式(Strategy Pattern)
    xeus-clickhouse: Jupyter 的 ClickHouse 内核
    Spring的学习与实战(续)
  • 原文地址:https://www.cnblogs.com/zhanghongfeng/p/8451968.html
Copyright © 2011-2022 走看看