IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 网络协议 -> 从零搭建简易的Web服务器 -> 正文阅读

[网络协议]从零搭建简易的Web服务器

本文将使用python套接字编程从零搭建一个简易的web服务器,对应于教材《计算机网络:自顶向下方法》第二章后面套接字编程作业,我们先来看一看客户机(浏览器)和服务器交互的过程中在服务器端发生了哪些事情:

  1. 当一个客户(浏览器)联系服务器时创建一个连接套接字;
  2. 服务器从这个连接接受HTTP请求;
  3. 解释该请求以确定所请求的特定文件;
  4. 从服务器的文件系统获得请求的文件;
  5. 创建一个由请求的文件组成的HTTP响应报文,报文前有首部行;
  6. 经TCP连接向请求的浏览器发送响应;如果文件不存在,则返回404 Not Found差错报文。

1.Web服务器

假设我们通过浏览器向服务器请求的文件是HelloWorld.html,文件内容自定(我这里写的内容就是一句话:太棒了,服务器正常工作!),我们需要将该文件放在与服务器同级的目录下,然后通过浏览器向服务器发起请求,服务器按照上述步骤进行响应。服务器端的全部代码如下:

from socket import *

serverSocket = socket(AF_INET, SOCK_STREAM)
serverSocket.bind(('', 6789))
serverSocket.listen(1)
while True:
    print('服务器已就位')
    connectionSocket, addr = serverSocket.accept()
    try:
        message = connectionSocket.recv(1024).decode()
        filename = message.split()[1]
        f = open(filename[1:], encoding='utf-8')
        outputdata = f.read()
        header = 'HTTP/1.1 200 OK\nConnection: close\nContent-Type: text/html\nContent-Length: %d\n\n' % (len(outputdata)+24)
        connectionSocket.send(header.encode())
        for i in range(0, len(outputdata)):
            connectionSocket.send(outputdata[i].encode())
        connectionSocket.send("\r\n".encode())
        connectionSocket.close()
    except IOError:
        header = 'HTTP/1.1 404 Not Found'
        connectionSocket.send(header.encode())
        connectionSocket.close()

bind(('', 6789))指定了套接字与端口号6789绑定,如果代码是运行在本地的,那么只需在浏览器中输入http://localhost:6789/HelloWorld.html即可访问页面;如果代码是部署在云服务器上的,就需要将IP改为服务器的公网IP,通过这种方式,我们可以很容易地在服务器上部署类似个人简介这样的静态网页。

listen(1)指定了服务器在同一时刻只接受一个请求,后续我们将通过多线程编码来同时处理多个请求。

在构造的头部信息中,我们通过Content-Length指定了实体(封装的TCP报文)的长度,即等于数据长度 + TCP头部信息长度,通常TCP的头部信息长度是20字节,但是通过实际观察网页源代码我发现少了四个字节,不难猜测这是因为TCP的选项字段占用了四个字节,因此这里头部信息长度就是24字节。我们甚至不需要自己指定报文长度,只需要返回最基本的HTTP/1.1 200 OK即可。

这里插入一个题外话,关于Content-Length的使用,通过实践我发现存在以下四种情况:

  1. 不显式指定Content-Length,前端页面显示完好,数据完整;
  2. 显示指定Content-Length且小于实体的长度,前端页面显示不完好,数据缺失;
  3. 显示指定Content-Length且等于实体的长度,前端页面显示完好,数据完整;
  4. 显示指定Content-Length且大于实体的长度,前端页面不显示,浏览器控制台报错ERR_CONTENT_LENGTH_MISMATCH

也就是说,最糟糕的情况是指定的长度大于实体的长度,由于长度不匹配,浏览器会报错且前端不显示任何东西。如果指定的长度小于实体长度,浏览器只取消息实体的前面一部分,则前端页面显示不完好。在效果上,不显示指定和显式指定为实体长度都是一样的,如果怕麻烦可以不指定。

2.多线程Web服务器

参照上面的代码,一个最基本的简易Web服务器就搭建好了,但是它在同一时刻只能处理一个请求,现在我们给它升下级,我们使用多线程的方式让它能够同时处理多个请求。具体代码如下:

from socket import *
import threading


def tcp_process(connectionSocket):
    print(threading.current_thread())
    try:
        message = connectionSocket.recv(1024).decode()
        print(repr(message))
        print(message)
        filename = message.split()[1]
        f = open(filename[1:], encoding='utf-8')
        outputdata = f.read()
        header = 'HTTP/1.1 200 OK\nConnection: close\nContent-Type: text/html\nContent-Length: %d\n\n' % (len(outputdata)+24)
        connectionSocket.send(header.encode())
        for i in range(0, len(outputdata)):
            connectionSocket.send(outputdata[i].encode())
        connectionSocket.send("\r\n".encode())
        connectionSocket.close()
    except IOError:
        header = 'HTTP/1.1 404 Not Found'
        connectionSocket.send(header.encode())
        connectionSocket.close()


if __name__ == "__main__":
    serverSocket = socket(AF_INET, SOCK_STREAM)
    serverSocket.bind(('', 6789))
    serverSocket.listen(10)
    while True:
        print('服务器已就位')
        connectionSocket, addr = serverSocket.accept()
        thread = threading.Thread(target=tcp_process, args=(connectionSocket, ))
        thread.start()

从上面可以看出,我们只是将connectionSocket交给一个具体的线程来执行,该线程负责为具体的客户服务,而主进程不必等待它服务完这个用户就可以接受下一个用户的请求,这样就大大提高了服务器的工作效率。

3.客户端

最后我们来看看客户端的代码,通过客户端可以不经过浏览器直接向服务器发起请求。

from socket import *

clientSocket = socket(AF_INET, SOCK_STREAM)
clientSocket.connect(('localhost', 6789))
while True:
    header = 'GET /HelloWorld.html HTTP/1.1\nHost: localhost:6789\nConnection: keep-alive\nUser-Agent: Mozilla/5.0\n\n'
    clientSocket.send(header.encode())
    message = clientSocket.recv(1024)
    print(message.decode())

当我通过运行客户端代码向服务器发起请求时,虽然数据成功获取了,但是在客户端也收到了以下错误:

ConnectionResetError: [WinError 10054] 远程主机强迫关闭了一个现有的连接。

这个错误一般出现在爬虫过程中,因为抓取信息太过频繁,而被服务器认定为恶意攻击。但是这里显然不是这个原因,在我将服务器代码中的connectionSocket.close()注释掉之后,这个错误就没有了,但是产生错误具体的原因至今未明,如果有知道的同学欢迎在评论区告诉我。

  网络协议 最新文章
使用Easyswoole 搭建简单的Websoket服务
常见的数据通信方式有哪些?
Openssl 1024bit RSA算法---公私钥获取和处
HTTPS协议的密钥交换流程
《小白WEB安全入门》03. 漏洞篇
HttpRunner4.x 安装与使用
2021-07-04
手写RPC学习笔记
K8S高可用版本部署
mySQL计算IP地址范围
上一篇文章      下一篇文章      查看所有文章
加:2021-08-23 17:02:36  更:2021-08-23 17:03:12 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/25 21:31:48-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码