自建mini-web服务器01

本文详细介绍了如何使用Python和socket构建一个迷你Web服务器,涵盖了TCP与UDP的区别、HTTP协议、多进程、多线程、协程及epoll实现。通过逐步讲解服务器的创建、HTTP响应、处理用户请求等内容,展示了从基础到高级的Web服务器实现技巧。
摘要由CSDN通过智能技术生成


以下代码和网站模板已经上传github: 代码传送门

一、socket

    之前的文章有写过socket的一些东西,这次用来做服务器的底层的也是socket来构建。我们重新回到起点,来谈谈我们应该使用TCP还是UDP来进行通信?

1.1 TCP与UDP的区别

    我们知道,UDP是面向无连接的,即发送的信息是在不知道对方是否能接受到消息的情况下。而TCP是面向连接的,即发送信息是在已知对方一定能收到信息的情况下发送的。

    并且文件是需要确保每个字节都需要发送到,因此如果选用UDP作为传输协议,那么很可能出现丢包的情况,导致传输过来的文件出现缺失。但是TCP就不会出现这样的情况,它的三次握手会确保客户端与服务器连接,并且确保每个字节都能传到。

下面来整理一下TCP与UDP的区别

  • 面向连接(确认有创建三方交握,连接已创建才作传输。)
  • 有序数据传输
  • 重发丢失的数据包
  • 舍弃重复的数据包
  • 无差错的数据传输
  • 阻塞/流量控制

1.2 setsockopt

    在服务器与客户端断开连接后,通常存在一个时间段,是用来确认被关闭一方能确实收到关闭的信息的,所以这个时候端口是被上一个程序所占用的。因此为了方便,我们需要使用setsockopt来将这个消除。

# 设置当服务器先close 即服务器端4次挥手之后资源能够立即释放,这样就保证了,下次运行程序时 可以立即绑定7890端口
mini_web_server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)

1.3 TCP的创建流程

  • 创建套接字
  • 绑定ip
  • 改主动为被动
  • 设置监听
  • 接收/发送数据
  • 关闭套接字

    那接下来,我们就可以根据这个来做一个自己的mini服务器了。当然在写服务器之前,我们需要了解一下HTTP协议。

二、HTTP协议

    HTTP协议是网页(客户端)与服务器(服务端)之间的一种连接协议,现在最常用的版本是1.1。具体的在上篇文章有讲,这里就只说一下我们需要用到的两部分,即报文头部和报文主体。报文头部是用来告诉对方自己本次连接自己的一些信息,而报文主体是用来存放传输的数据。

2.1 请求GET格式

    GET /path HTTP/1.1
    Header1: Value1
    Header2: Value2
    Header3: Value3

    每个Header一行一个,换行符是\r\n。

2.2 请求POST格式

    POST /path HTTP/1.1
    Header1: Value1
    Header2: Value2
    Header3: Value3

    body data goes here...

    当遇到连续两个\r\n时,Header部分结束,后面的数据全部是Body。

2.3 响应格式

    200 OK
    Header1: Value1
    Header2: Value2
    Header3: Value3

    body data goes here...

    HTTP响应如果包含body,也是通过\r\n\r\n来分隔的。

    请再次注意,Body的数据类型由Content-Type头来确定,如果是网页,Body就是文本,如果是图片,Body就是图片的二进制数据。

    当存在Content-Encoding时,Body数据是被压缩的,最常见的压缩方式是gzip,所以,看到Content-Encoding: gzip时,需要将Body数据先解压缩,才能得到真正的数据。压缩的目的在于减少Body的大小,加快网络传输。

三、实现返回固定内容的服务器

    首先,我们可以确定需要使用TCP作为文件传输协议,当我们在浏览器上输入127.0.0.1在加上我们绑定的端口,此时就实现了客户端向服务器发送请求报文,当我们的程序收到报文之后,就获得了一个新的socket对象,我们在根据这个对象返回我们想让浏览器显示的内容即可。

3.1代码实现

# -*- coding: utf-8 -*-
import socket


def recv(web_client):
    recv_data = web_client.recv(1024).decode("utf-8")
    request_header_lines = recv_data.splitlines()
    for line in request_header_lines:
        print(line)
    # 组织相应 头信息(header)
    response_headers = "HTTP/1.1 200 OK\r\n"  # 200表示找到这个资源
    response_headers += "\r\n"  # 用一个空的行与body进行隔开
    # 组织 内容(body)
    response_body = "Worker:summer"

    response = response_headers + response_body
    web_client.send(response.encode("utf-8"))
    web_client.close()


def main():
    # 创建套接字
    mini_web_server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    # 设置当服务器先close 即服务器端4次挥手之后资源能够立即释放,这样就保证了,下次运行程序时 可以立即绑定7890端口
    mini_web_server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
    # 绑定端口
    mini_web_server.bind(("", 7890))
    # 主动改被动
    mini_web_server.listen(128)
    while True:
        # 接受相应
        web_client, clint_addr = mini_web_server.accept()
        # 返回参数
        recv(web_client)


if __name__ == '__main__':
    main()

3.2实现页面

在这里插入图片描述

3.3收到的请求报文

GET / HTTP/1.1
Host: 127.0.0.1:7890
Connection: keep-alive
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.66 Safari/537.36

四、实现返回用户需要的页面

    这里主要与上面不同的地方出现在recv的时候,我们知道我们想要的内容是在请求报文的第一段即GET 方法的后面,那我们可以用正则来匹配用户输入的内容即可,然后根据这个内容我们在打开相应的文件然后返回内容即可。如果这个文件存在直接返回即可但是文件不存在,我们可以发送404的响应报文,然后打开404的文件将内容返回即可。

4.1 代码实现

# -*- coding: utf-8 -*-
import re
import socket

# 这里配置服务器
DOCUMENTS_ROOT = "./html"


def recv(clint_socket):
    # 获取客户端发送的信息
    global f, response_headers
    recv_data = clint_socket.recv(1024).decode('utf-8')
    # print(recv_data)    # 获取该信息的第一行GET / HTTP/1.1
    request_header_line_0 = recv_data.splitlines()[0]

    # 从客户端发送的信息中获取客户端想要的资源
    get_file_name = re.match(r"[^/]+(/[^ ]*)", request_header_line_0).group(1)
    # print("clint want file_name is %s" % get_file_name)

    if get_file_name == "/":
        file_name = DOCUMENTS_ROOT + "/index.html"
    else:
        file_name = DOCUMENTS_ROOT + get_file_name

    try:
        # 如果存在资源,将资源发送给对方
        f = open(file_name, "rb")
    except IOError:
        # 如果不存在资源,则发送404响应
        response_headers = "HTTP/1.1 404 NOT FOUND\r\n"
        page_404 = DOCUMENTS_ROOT + "/404.html"
        f = open(page_404, "rb")
    else:
        response_headers = "HTTP/1.1 200 OK\r\n"

    finally:
        response_headers += "\r\n"
        resopnse_body = f.read()
        f.close()
        clint_socket.send(response_headers.encode('utf-8'))
        # 再发送body
        clint_socket.send(resopnse_body)
        # 关闭套接字
        clint_socket.close()


def main():
    # 创建套接字
    http_socket_server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    # 设置当服务器先close 即服务器端4次挥手之后资源能够立即释放,这样就保证了,下次运行程序时 可以立即绑定7890端口
    http_socket_server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
    # 绑定端口
    http_socket_server.bind(("", 7890))
    # 改主动为被动
    http_socket_server.listen
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值