利用Python搭建简单的多线程Web服务器-代码示例(基于TCP服务端)

多任务版web服务器程序的实现:

web服务器基于TCP服务端开发,其基本构成都是相同的,但有最大一点的差异是:
客户端请求及服务端响应的内容,必须符合html协议,否则将无法获取数据。

  • 客户端请求报文格式示例:

# 请求行(还有POST请求方式)
GET / HTTP/1.1\r\n
# 请求体
Host: www.itcast.cn\r\n
Connection: keep-alive\r\n
Upgrade-Insecure-Requests: 1\r\n
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Safari/537.36\r\n
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,/;q=0.8\r\n
Accept-Encoding: gzip, deflate\r\n
Accept-Language: zh-CN,zh;q=0.9\r\n
Cookie: pgv_pvi=1246921728; \r\n
# 空行(不能省略)
\r\n

  • 服务端响应报文格式示例:

# 响应行
HTTP/1.1 200 OK\r\n
# 响应体
Server: Tengine\r\n
Content-Type: text/html; charset=UTF-8\r\n
Transfer-Encoding: chunked\r\n
Connection: keep-alive\r\n
Date: Fri, 23 Nov 2018 02:01:05 GMT\r\n
# 空行(不能省略)
\r\n
# 响应体(正文)
<!DOCTYPE html><html lang=“en”> …</html>

特别说明:
每项数据之后都要使用:\r\n(见格式示例)

以上内容为web传输与普通TCP传输代码的差异。
本文以多线程web客户端作为代码示例。
当客户端和服务端建立连接成功,创建子线程,使用子线程专门处理客户端的请求,防止主线程阻塞。
把创建的子线程设置成为守护主线程,防止主线程无法退出。

  • 代码示例
    注:代码中使用的html源文件是提前放在代码文件同级目录中static目录下的,你可以在网上找到用于练习的html文件。


    使用的html文件
import threading
import socket

def handle_recv(new_socket, ip_port):
    print("客户端已连接:", ip_port)
    # 获取浏览器发送的http请求报文数据并解码
    recieve_content = new_socket.recv(4096).decode("utf-8")
    print(recieve_content)
    # 获取用户请求资源的路径(根据浏览器发送的请求行提取)
    path = recieve_content.split(" ",maxsplit=3)[1]
    # 指定如果访问根目录时,返回index.html内容
    if path == "/":
        path = "/index.html"
    # 响应行
    response_line = "HTTP/1.1 200 OK\r\n"
    # 响应头
    response_headers = "Content-Type: text/html;charset=utf-8\r\n"
    # 加入判断,如请求的路径存在,则正常返回,当客服端请求的路径不存在时,返回错误页面
    try:
        with open("./static%s" % path, "rb") as f:
            response_body = f.read()
    except FileNotFoundError:
        response_line = "HTTP/1.1 404 NOT FOUND\r\n"
        path = "/error.html"
        with open("./static%s" % path, "rb") as f:
            response_body = f.read()
    # 服务端发送符合html要求的数据给客服端
    new_socket.send((response_line + response_headers + "\r\n").encode("utf-8") + response_body)
    new_socket.close()

if __name__ == '__main__':
    # 创建服务端tcp socket对象
    server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    # 程序退出端口号立即释放
    server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
    # 绑定端口号
    server_socket.bind(("", 8090))
    # 设置监听
    server_socket.listen(128)
    # 循环等待接受客户端的连接请求
    while True:
        # 主线程负责创建新的套接字接口
        new_socket, ip_port = server_socket.accept()
        # 当客户端和服务端建立连接成功,创建子线程,使用子线程专门处理客户端的请求,防止主线程阻塞。
        sub_thread = threading.Thread(target=handle_recv,args=(new_socket, ip_port))
        # 把创建的子线程设置成为守护主线程,防止主线程无法退出。
        sub_thread.setDaemon(True)
        # 启动线程执行对应的任务
        sub_thread.start()

运行呜呜段程序,在浏览器中访问http://127.0.0.1:8090/index2.html

服务端窗口返回接收到的浏览器请求数据如下,提取的请求文件路径即是通过第一行的请求行提取:

客户端已连接: ('127.0.0.1', 53729)
客户端已连接: ('127.0.0.1', 53730)
GET /index2.html HTTP/1.1 #请求行
Host: 127.0.0.1:8090
Connection: keep-alive
Cache-Control: max-age=0
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.103 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,/;q=0.8,application/signed-exchange;v=b3
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.9

GET /web.jpg HTTP/1.1
Host: 127.0.0.1:8090
Connection: keep-alive
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.103 Safari/537.36
Accept: image/webp,image/apng,image/,/*;q=0.8
Referer: http://127.0.0.1:8090/index2.html
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.9

客服端浏览器返回的页面如下:


image.png

当输入不存在的页面时:
将返回设定好的404错误页面,可以看到,页面的响应体和状态信息和我们代码中设置的一致。


image.png

将代码改写为面向对象编程结构

构造面向对象的代码块,将可以在复用时非常方便的创建新服务器对象。

import threading
import socket

class HTTPWebServer(object):
    def __init__(self,port):
        server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
        server_socket.bind(("", port))
        server_socket.listen(128)
        self.server_socket = server_socket
    @staticmethod
    def handle_recv(new_socket, ip_port):
        print("客户端已连接:", ip_port)
        recieve_content = new_socket.recv(1024).decode("utf-8")
        print(recieve_content)
        path = recieve_content.split(" ",maxsplit=2)[1]
        if path == "/":
            path = "/index.html"
        response_line = "HTTP/1.1 200 OK\r\n"
        response_headers = "Content-Type: text/html;charset=utf-8\r\n"
        try:
            with open("./static%s" % path, "rb") as f:
                response_body = f.read()
        except FileNotFoundError:
            response_line = "HTTP/1.1 404 NOT FOUND\r\n"
            path = "/error.html"
            with open("./static%s" % path, "rb") as f:
                response_body = f.read()
        new_socket.send((response_line + response_headers + "\r\n").encode("utf-8") + response_body)
        new_socket.close()
    def start(self):
        while True:
            new_socket, ip_port = self.server_socket.accept()
            sub_thread = threading.Thread(target=self.handle_recv,args=(new_socket, ip_port))
            sub_thread.setDaemon(True)
            sub_thread.start()


 

if __name__ == '__main__':
    port = 8080
    server = HTTPWebServer(port)
    server.start()

小结

这样就完成了一个简单的web服务器,可用于浏览器访问。

  1. 当客户端和服务端建立连接成功,创建子线程,使用子线程专门处理客户端的请求,防止主线程阻塞。
  2. 使用.setDaemon(True)设置主线程守护,防止主线程无法退出。
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 222,183评论 6 516
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 94,850评论 3 399
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 168,766评论 0 361
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 59,854评论 1 299
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 68,871评论 6 398
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 52,457评论 1 311
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,999评论 3 422
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,914评论 0 277
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 46,465评论 1 319
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 38,543评论 3 342
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,675评论 1 353
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 36,354评论 5 351
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 42,029评论 3 335
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,514评论 0 25
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,616评论 1 274
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 49,091评论 3 378
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,685评论 2 360

推荐阅读更多精彩内容

  • 一. 操作系统概念 操作系统位于底层硬件与应用软件之间的一层.工作方式: 向下管理硬件,向上提供接口.操作系统进行...
    月亮是我踢弯得阅读 5,974评论 3 28
  • Swift1> Swift和OC的区别1.1> Swift没有地址/指针的概念1.2> 泛型1.3> 类型严谨 对...
    cosWriter阅读 11,113评论 1 32
  • 从哪说起呢? 单纯讲多线程编程真的不知道从哪下嘴。。 不如我直接引用一个最简单的问题,以这个作为切入点好了 在ma...
    Mr_Baymax阅读 2,766评论 1 17
  • 多线程 1.悲观锁和乐观锁 http://www.importnew.com/21037.html 悲观锁悲观锁,...
    bigfish1129阅读 372评论 0 0
  • 必备的理论基础 1.操作系统作用: 隐藏丑陋复杂的硬件接口,提供良好的抽象接口。 管理调度进程,并将多个进程对硬件...
    drfung阅读 3,548评论 0 5