python后端学习(七)HTTP协议、实现WEB服务器

2023-11-01

HTTP协议简介

浏览器---->服务器发送的请求格式如下:
	GET / HTTP/1.1    请求方式 路径 协议及版本
	Host: 127.0.0.1:8080   请求的地址
	Connection: keep-alive  长连接
	Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8   可接受数据格式
	Upgrade-Insecure-Requests: 1
	User-Agent: Mozilla/5.0 (Windows NT 5.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/49.0.2623.75 Safari/537.36  浏览器相关
	Accept-Encoding: gzip, deflate, sdch  可接受的数据压缩格式
	Accept-Language: zh-CN,zh;q=0.8  可接受语言

服务器--->浏览器回送的数据格式如下:
	HTTP/1.1 200 OK     
	Bdpagetype: 1
	Bdqid: 0xe87cb3f700023783
	Bduserid: 0
	Cache-Control: private   私有缓存
	Connection: Keep-Alive
	Content-Encoding: gzip   压缩格式
	Content-Type: text/html; charset=utf-8    数据格式
	Cxy_all: baidu+55617f8533383cbe48d5d2b7dc84b7f0
	Date: Fri, 20 Oct 2017 00:59:55 GMT    服务器时间
	Expires: Fri, 20 Oct 2017 00:59:11 GMT
	Server: BWS/1.1   服务器版本  一般是Apache或Nginx  大公司用自己开发的服务器              
	Set-Cookie: BDSVRTM=0; path=/
	Set-Cookie: BD_HOME=0; path=/
	Set-Cookie: H_PS_PSSID=1463_21080_17001_20929; path=/; domain=.baidu.com
	Strict-Transport-Security: max-age=172800
	Vary: Accept-Encoding
	X-Powered-By: HPHP
	X-Ua-Compatible: IE=Edge,chrome=1
	Transfer-Encoding: chunked

	<h1>haha</h1>  第一个回车以后全是body
使用谷歌/火狐浏览器分析

在Web应用中,服务器把网页传给浏览器,实际上就是把网页的HTML代码发送给浏览器,让浏览器显示出来。而浏览器和服务器之间的传输协议是HTTP,所以:

  • HTML是一种用来定义网页的文本,会HTML,就可以编写网页;

  • HTTP是在网络上传输HTML的协议,用于浏览器和服务器的通信。

Chrome浏览器提供了一套完整地调试工具,非常适合Web开发。

安装好Chrome浏览器后,打开Chrome,在菜单中选择“视图”,“开发者”,“开发者工具”,就可以显示开发者工具:
在这里插入图片描述
说明

  • Elements显示网页的结构
  • Network显示浏览器和服务器的通信
    我们点Network,确保第一个小红灯亮着,Chrome就会记录所有浏览器和服务器之间的通信:
http协议的分析

当我们在地址栏输入www.sina.com时,浏览器将显示新浪的首页。在这个过程中,浏览器都干了哪些事情呢?
通过Network的记录,我们就可以知道。在Network中,找到www.sina.com那条记录,点击,右侧将显示Request Headers,点击右侧的view source,我们就可以看到浏览器发给新浪服务器的请求:
在这里插入图片描述
在这里插入图片描述

说明

最主要的头两行分析如下,第一行:

    GET / HTTP/1.1

GET表示一个读取请求,将从服务器获得网页数据,/表示URL的路径,URL总是以/开头,/就表示首页,最后的HTTP/1.1指示采用的HTTP协议版本是1.1。目前HTTP协议的版本就是1.1,但是大部分服务器也支持1.0版本,主要区别在于1.1版本允许多个HTTP请求复用一个TCP连接,以加快传输速度。

从第二行开始,每一行都类似于Xxx: abcdefg:

Host: www.sina.com

表示请求的域名是www.sina.com。如果一台服务器有多个网站,服务器就需要通过Host来区分浏览器请求的是哪个网站。

服务器响应

继续往下找到Response Headers,点击view source,显示服务器返回的原始响应数据:
在这里插入图片描述
HTTP响应分为Header和Body两部分(Body是可选项),我们在Network中看到的Header最重要的几行如下:

    HTTP/1.1 200 OK

200表示一个成功的响应,后面的OK是说明。

如果返回的不是200,那么往往有其他的功能,例如

  • 失败的响应有404 Not Found:网页不存在
  • 500 Internal Server Error:服务器内部出错
  • …等等…
   Content-Type: text/html

Content-Type指示响应的内容,这里是text/html表示HTML网页。

请注意,浏览器就是依靠Content-Type来判断响应的内容是网页还是图片,是视频还是音乐。浏览器并不靠URL来判断响应的内容,所以,即使URL是http://www.baidu.com/meimei.jpg,它也不一定就是图片。

HTTP响应的Body就是HTML源码,我们在菜单栏选择“视图”,“开发者”,“查看网页源码”就可以在浏览器中直接查看HTML源码:
在这里插入图片描述

浏览器解析过程

当浏览器读取到新浪首页的HTML源码后,它会解析HTML,显示页面,然后,根据HTML里面的各种链接,再发送HTTP请求给新浪服务器,拿到相应的图片、视频、Flash、JavaScript脚本、CSS等各种资源,最终显示出一个完整的页面。所以我们在Network下面能看到很多额外的HTTP请求。

总结

跟踪了新浪的首页,我们来总结一下HTTP请求的流程:

步骤1:浏览器首先向服务器发送HTTP请求,请求包括:
方法:GET还是POST,GET仅请求资源,POST会附带用户数据;

路径:/full/url/path;

域名:由Host头指定:Host: www.sina.com

以及其他相关的Header;

如果是POST,那么请求还包括一个Body,包含用户数据

步骤2:服务器向浏览器返回HTTP响应,响应包括:
响应代码:200表示成功,3xx表示重定向,4xx表示客户端发送的请求有错误,5xx表示服务器端处理时发生了错误;

响应类型:由Content-Type指定;

以及其他相关的Header;

通常服务器的HTTP响应会携带内容,也就是有一个Body,包含响应的内容,网页的HTML源码就在Body中。

步骤3:如果浏览器还需要继续向服务器请求其他资源,比如图片,就再次发出HTTP请求,重复步骤1、2。
Web采用的HTTP协议采用了非常简单的请求-响应模式,从而大大简化了开发。当我们编写一个页面时,我们只需要在HTTP请求中把HTML发送出去,不需要考虑如何附带图片、视频等,浏览器如果需要请求图片和视频,它会发送另一个HTTP请求,因此,一个HTTP请求只处理一个资源(此时就可以理解为TCP协议中的短连接,每个链接只获取一个资源,如需要多个就需要建立多个链接)

HTTP协议同时具备极强的扩展性,虽然浏览器请求的是http://www.sina.com的首页,但是新浪在HTML中可以链入其他服务器的资源,比如<img src="http://i1.sinaimg.cn/home/2013/1008/U8455P30DT20131008135420.png">,从而将请求压力分散到各个服务器上,并且,一个站点可以链接到其他站点,无数个站点互相链接起来,就形成了World Wide Web,简称WWW。

每个HTTP请求和响应都遵循相同的格式,一个HTTP包含Header和Body两部分,其中Body是可选的。

HTTP协议是一种文本协议,所以,它的格式也非常简单。

HTTP格式

HTTP GET请求的格式:

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

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

HTTP POST请求的格式:

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

body data goes here...

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

HTTP响应的格式:

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的大小,加快网络传输。

三次握手 四次挥手

在这里插入图片描述
简述三次握手的过程:

三次握手其实双方准备资源的过程,客户端调用connect并堵塞,三次握手成功后解堵塞。举例:客户端传输数据有一个特殊值syn11,服务器接收后+1后返回ask12给客户端,说明服务器已经接收到第一次的数据包,服务器已经准备好进行数据传输,第二次握手中服务器传输syn44,客户端+1并返回ack45,说明客户端户端也接收到了第二次的数据包,此时双方都做好资源的准备就可以开始进行通信。

简述四次挥手的过程:

四次挥手是将双方准备资源释放的过程,tcp协议是全双工的,所以关闭彼此的接收和发送通道才行。举例:客户端调用close(),发送数据包通知服务器结束发送数据,服务器接收到后返回一个数据,通知客户端已经收到第一次挥手的数据包,此时关闭的是客户端的发送通道和服务器的接收通道。服务器收到第一次挥手后recv()解堵塞并关闭发送通道,进行第三次挥手并通知客户端关闭接收通道,第四次挥手通知服务器客户端已经关闭接收通道。
问题一:为什么四次挥手不能改为三次?
因为第二次挥手是回复客服端确认收到第一次挥手的消息,第三次挥手是与第二次挥手不能合并是因为 发送消息的同时不一定会将recv进行解堵塞,所以如果合在一起 客户端收到应答包会等待很长时间。
问题二:为什么不是服务器先进行第一次挥手?
哪一方最后调用close(),等待一个超时时间,两种情况:

  • 1.服务器超时时间内没有收到对方数据就再一次发一个相同的包。
  • 2.客户端收到了消息,但是返回的数据服务器没收到,服务器就再一次发一个相同的包。

客户端接收到第三次挥手后需要等待2MSL(两倍的数据包在网络中存活的最长时间大概2分钟左右),所以哪一方最开始调用close,哪一方就要资源保留两分钟左右且不能重复使用,如果服务器进行第一次挥手,服务器字段大概两分钟内不能再次被使用,端口不能进行访问

实现简单的http 服务器

import socket


def service_client(new_socket):
    """为这个客户端返回数据"""

    # 1. 接收浏览器发送过来的请求 ,即http请求  
    # GET / HTTP/1.1
    # .....
    request = new_socket.recv(1024)
    print(request)

    # 2. 返回http格式的数据,给浏览器
    # 2.1 准备发送给浏览器的数据---header 服务器中换行一般是\r\n
    response = "HTTP/1.1 200 OK\r\n"
    response += "\r\n"
    # 2.2 准备发送给浏览器的数据---body
    response += "hahahhah"
    new_socket.send(response.encode("utf-8"))

    # 关闭套接字
    new_socket.close()
    

def main():
    """用来完成整体的控制"""
    # 1. 创建套接字 
    tcp_server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

    # 2. 绑定
    tcp_server_socket.bind(("", 7890))

    # 3. 变为监听套接字
    tcp_server_socket.listen(128)

    while True:
        # 4. 等待新客户端的链接
        new_socket, client_addr = tcp_server_socket.accept()

        # 5. 为这个客户端服务
        service_client(new_socket)

    # 关闭监听套接字
    tcp_server_socket.close()


if __name__ == "__main__":
    main()


返回inde.html页面.py

import socket


def service_client(new_socket):
    """为这个客户端返回数据"""

    # 1. 接收浏览器发送过来的请求 ,即http请求  
    # GET / HTTP/1.1
    # .....
    request = new_socket.recv(1024)
    print(">>>"*50)
    print(request)

    # 2. 返回http格式的数据,给浏览器
    # 2.1 准备发送给浏览器的数据---header
    response = "HTTP/1.1 200 OK\r\n"
    response += "\r\n"
    # 2.2 准备发送给浏览器的数据---boy
    # response += "hahahhah"
	# 不能使用 header+body response中是字符串  html_content是二进制文件
    
    f = open("./html/index.html", "rb")
    html_content = f.read()
    f.close()

    # 将response header发送给浏览器
    new_socket.send(response.encode("utf-8"))
    # 将response body发送给浏览器
    new_socket.send(html_content)

    # 关闭套接
    new_socket.close()
    

def main():
    """用来完成整体的控制"""
    # 1. 创建套接字
    tcp_server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    tcp_server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)

    # 2. 绑定
    tcp_server_socket.bind(("", 7890))

    # 3. 变为监听套接字
    tcp_server_socket.listen(128)

    while True:
        # 4. 等待新客户端的链接
        new_socket, client_addr = tcp_server_socket.accept()

        # 5. 为这个客户端服务
        service_client(new_socket)

    # 关闭监听套接字
    tcp_server_socket.close()


if __name__ == "__main__":
    main()

根据用户的需求返回相应的页面.py

import socket
import re


def service_client(new_socket):
    """为这个客户端返回数据"""

    # 1. 接收浏览器发送过来的请求 ,即http请求  
    # GET / HTTP/1.1
    # .....
    request = new_socket.recv(1024).decode("utf-8")
    # print(">>>"*50)
    # print(request)

    request_lines = request.splitlines()
    print("")
    print(">"*20)
    print(request_lines)

    # GET /index.html HTTP/1.1
    # get post put del
    file_name = ""
    ret = re.match(r"[^/]+(/[^ ]*)", request_lines[0])
    if ret:
        file_name = ret.group(1)
        # print("*"*50, file_name)
        if file_name == "/":
            file_name = "/index.html"




    # 2. 返回http格式的数据,给浏览器
    
    try:
        f = open("./html" + file_name, "rb")
    except:
        response = "HTTP/1.1 404 NOT FOUND\r\n"
        response += "\r\n"
        response += "------file not found-----"
        new_socket.send(response.encode("utf-8"))
    else:
        html_content = f.read()
        f.close()
        # 2.1 准备发送给浏览器的数据---header
        response = "HTTP/1.1 200 OK\r\n"
        response += "\r\n"
        # 2.2 准备发送给浏览器的数据---body
        # response += "hahahhah"  
		# 不能使用 header+body response中是字符串  html_content是二进制文件

        # 将response header发送给浏览器
        new_socket.send(response.encode("utf-8"))
        # 将response body发送给浏览器
        new_socket.send(html_content)


    # 关闭套接
    new_socket.close()
    

def main():
    """用来完成整体的控制"""
    # 1. 创建套接字
    tcp_server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    tcp_server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)

    # 2. 绑定
    tcp_server_socket.bind(("", 7890))

    # 3. 变为监听套接字
    tcp_server_socket.listen(128)

    while True:
        # 4. 等待新客户端的链接
        new_socket, client_addr = tcp_server_socket.accept()

        # 5. 为这个客户端服务
        service_client(new_socket)

    # 关闭监听套接字
    tcp_server_socket.close()


if __name__ == "__main__":
    main()


多进程、线程、协程实现http服务器

多进程实现http服务器.py

import socket
import re
import multiprocessing


def service_client(new_socket):
    """为这个客户端返回数据"""

    # 1. 接收浏览器发送过来的请求 ,即http请求  
    # GET / HTTP/1.1
    # .....
    request = new_socket.recv(1024).decode("utf-8")
    # print(">>>"*50)
    # print(request)

    request_lines = request.splitlines()
    print("")
    print(">"*20)
    print(request_lines)

    # GET /index.html HTTP/1.1
    # get post put del
    file_name = ""
    ret = re.match(r"[^/]+(/[^ ]*)", request_lines[0])
    if ret:
        file_name = ret.group(1)
        # print("*"*50, file_name)
        if file_name == "/":
            file_name = "/index.html"




    # 2. 返回http格式的数据,给浏览器
    
    try:
        f = open("./html" + file_name, "rb")
    except:
        response = "HTTP/1.1 404 NOT FOUND\r\n"
        response += "\r\n"
        response += "------file not found-----"
        new_socket.send(response.encode("utf-8"))
    else:
        html_content = f.read()
        f.close()
        # 2.1 准备发送给浏览器的数据---header
        response = "HTTP/1.1 200 OK\r\n"
        response += "\r\n"
        # 2.2 准备发送给浏览器的数据---boy
        # response += "hahahhah"

        # 将response header发送给浏览器
        new_socket.send(response.encode("utf-8"))
        # 将response body发送给浏览器
        new_socket.send(html_content)


    # 关闭套接
    new_socket.close()
    

def main():
    """用来完成整体的控制"""
    # 1. 创建套接字
    tcp_server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    tcp_server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)

    # 2. 绑定
    tcp_server_socket.bind(("", 7890))

    # 3. 变为监听套接字
    tcp_server_socket.listen(128)

    while True:
        # 4. 等待新客户端的链接
        new_socket, client_addr = tcp_server_socket.accept()

        # 5. 为这个客户端服务
        p = multiprocessing.Process(target=service_client, args=(new_socket,))
        p.start()
		# 进程复制资源后主线程的socket同样需要关闭
        new_socket.close()


    # 关闭监听套接字
    tcp_server_socket.close()


if __name__ == "__main__":
    main()


多线程实现http服务器.py

import socket
import re
import threading


def service_client(new_socket):
    """为这个客户端返回数据"""

    # 1. 接收浏览器发送过来的请求 ,即http请求  
    # GET / HTTP/1.1
    # .....
    request = new_socket.recv(1024).decode("utf-8")
    # print(">>>"*50)
    # print(request)

    request_lines = request.splitlines()
    print("")
    print(">"*20)
    print(request_lines)

    # GET /index.html HTTP/1.1
    # get post put del
    file_name = ""
    ret = re.match(r"[^/]+(/[^ ]*)", request_lines[0])
    if ret:
        file_name = ret.group(1)
        # print("*"*50, file_name)
        if file_name == "/":
            file_name = "/index.html"




    # 2. 返回http格式的数据,给浏览器
    
    try:
        f = open("./html" + file_name, "rb")
    except:
        response = "HTTP/1.1 404 NOT FOUND\r\n"
        response += "\r\n"
        response += "------file not found-----"
        new_socket.send(response.encode("utf-8"))
    else:
        html_content = f.read()
        f.close()
        # 2.1 准备发送给浏览器的数据---header
        response = "HTTP/1.1 200 OK\r\n"
        response += "\r\n"
        # 2.2 准备发送给浏览器的数据---boy
        # response += "hahahhah"

        # 将response header发送给浏览器
        new_socket.send(response.encode("utf-8"))
        # 将response body发送给浏览器
        new_socket.send(html_content)


    # 关闭套接
    new_socket.close()
    

def main():
    """用来完成整体的控制"""
    # 1. 创建套接字
    tcp_server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    tcp_server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)

    # 2. 绑定
    tcp_server_socket.bind(("", 7890))

    # 3. 变为监听套接字
    tcp_server_socket.listen(128)

    while True:
        # 4. 等待新客户端的链接
        new_socket, client_addr = tcp_server_socket.accept()

        # 5. 为这个客户端服务
        p = threading.Thread(target=service_client, args=(new_socket,))
        p.start()

        # new_socket.close()


    # 关闭监听套接字
    tcp_server_socket.close()


if __name__ == "__main__":
    main()


使用gevent来实现http服务器.py

import socket
import re
import gevent
from gevent import monkey

monkey.patch_all()


def service_client(new_socket):
    """为这个客户端返回数据"""

    # 1. 接收浏览器发送过来的请求 ,即http请求  
    # GET / HTTP/1.1
    # .....
    request = new_socket.recv(1024).decode("utf-8")
    # print(">>>"*50)
    # print(request)

    request_lines = request.splitlines()
    print("")
    print(">"*20)
    print(request_lines)

    # GET /index.html HTTP/1.1
    # get post put del
    file_name = ""
    ret = re.match(r"[^/]+(/[^ ]*)", request_lines[0])
    if ret:
        file_name = ret.group(1)
        # print("*"*50, file_name)
        if file_name == "/":
            file_name = "/index.html"




    # 2. 返回http格式的数据,给浏览器
    
    try:
        f = open("./html" + file_name, "rb")
    except:
        response = "HTTP/1.1 404 NOT FOUND\r\n"
        response += "\r\n"
        response += "------file not found-----"
        new_socket.send(response.encode("utf-8"))
    else:
        html_content = f.read()
        f.close()
        # 2.1 准备发送给浏览器的数据---header
        response = "HTTP/1.1 200 OK\r\n"
        response += "\r\n"
        # 2.2 准备发送给浏览器的数据---boy
        # response += "hahahhah"

        # 将response header发送给浏览器
        new_socket.send(response.encode("utf-8"))
        # 将response body发送给浏览器
        new_socket.send(html_content)


    # 关闭套接
    new_socket.close()
    

def main():
    """用来完成整体的控制"""
    # 1. 创建套接字
    tcp_server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    tcp_server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)

    # 2. 绑定
    tcp_server_socket.bind(("", 7890))

    # 3. 变为监听套接字
    tcp_server_socket.listen(128)

    while True:
        # 4. 等待新客户端的链接
        new_socket, client_addr = tcp_server_socket.accept()

        # 5. 为这个客户端服务
        gevent.spawn(service_client, new_socket)

        # new_socket.close()


    # 关闭监听套接字
    tcp_server_socket.close()


if __name__ == "__main__":
    main()


单进程-单线程-非堵塞并发为多个客户端服务

import socket
import time

tcp_server_tcp = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
tcp_server_tcp.bind(("", 7899))
tcp_server_tcp.listen(128)
tcp_server_tcp.setblocking(False)  # 设置套接字为非堵塞的方式

client_socket_list = list()

while True:

    # time.sleep(0.5)

    try:
        new_socket, new_addr = tcp_server_tcp.accept()
    except Exception as ret:
        print("---没有新的客户端到来---")
    else:
        print("---只要没有产生异常,那么也就意味着 来了一个新的客户端----")
        new_socket.setblocking(False)  # 设置套接字为非堵塞的方式
        client_socket_list.append(new_socket)
        
    for client_socket in client_socket_list:
        try:
            recv_data = client_socket.recv(1024)
        except Exception as ret:
            print(ret)
            print("----这个客户端没有发送过来数据----")
        else:
            print("-----没有异常-----")
            print(recv_data)
            if recv_data:
                # 对方发送过来数据
                print("----客户端发送过来了数据-----")
            else:
                # 对方调用close 导致了 recv返回
                client_socket.close()
                client_socket_list.remove(client_socket)
                print("---客户端已经关闭----")

tcp长连接和短连接

TCP在真正的读写操作之前,server与client之间必须建立一个连接,

当读写操作完成后,双方不再需要这个连接时它们可以释放这个连接,

连接的建立通过三次握手,释放则需要四次握手,

所以说每个连接的建立都是需要资源消耗和时间消耗的。

TCP短连接

模拟一种TCP短连接的情况:

client 向 server 发起连接请求
server 接到请求,双方建立连接
client 向 server 发送消息
server 回应 client
一次读写完成,此时双方任何一个都可以发起 close 操作
在步骤5中,一般都是 client 先发起 close 操作。当然也不排除有特殊的情况。

从上面的描述看,短连接一般只会在 client/server 间传递一次读写操作!

TCP长连接

再模拟一种长连接的情况:

client 向 server 发起连接
server 接到请求,双方建立连接
client 向 server 发送消息
server 回应 client
一次读写完成,连接不关闭
后续读写操作…
长时间操作之后client发起关闭请求

TCP长/短连接操作过程

短连接的操作步骤是:
建立连接——数据传输——关闭连接…建立连接——数据传输——关闭连接
在这里插入图片描述
长连接的操作步骤是:
建立连接——数据传输…(保持连接)…数据传输——关闭连接
在这里插入图片描述

TCP长/短连接的优点和缺点
  • 长连接可以省去较多的TCP建立和关闭的操作,减少浪费,节约时间。

*对于频繁请求资源的客户来说,较适用长连接。

*client与server之间的连接如果一直不关闭的话,会存在一个问题,

随着客户端连接越来越多,server早晚有扛不住的时候,这时候server端需要采取一些策略,

如关闭一些长时间没有读写事件发生的连接,这样可以避免一些恶意连接导致server端服务受损;

如果条件再允许就可以以客户端机器为颗粒度,限制每个客户端的最大长连接数,

这样可以完全避免某个蛋疼的客户端连累后端服务。

*短连接对于服务器来说管理较为简单,存在的连接都是有用的连接,不需要额外的控制手段。
*但如果客户请求频繁,将在TCP的建立和关闭操作上浪费时间和带宽。

TCP长/短连接的应用场景

*长连接多用于操作频繁,点对点的通讯,而且连接数不能太多情况。

每个TCP连接都需要三次握手,这需要时间,如果每个操作都是先连接,

再操作的话那么处理速度会降低很多,所以每个操作完后都不断开,

再次处理时直接发送数据包就OK了,不用建立TCP连接。

例如:数据库的连接用长连接,如果用短连接频繁的通信会造成socket错误,

*而且频繁的socket 创建也是对资源的浪费。

而像WEB网站的http服务一般都用短链接,因为长连接对于服务端来说会耗费一定的资源,

而像WEB网站这么频繁的成千上万甚至上亿客户端的连接用短连接会更省一些资源,

如果用长连接,而且同时有成千上万的用户,如果每个用户都占用一个连接的话,

那可想而知吧。所以并发量大,但每个用户无需频繁操作情况下需用短连好。

单进程-单线程-非堵塞-长链接
import socket
import re


def service_client(new_socket, request):
    """为这个客户端返回数据"""

    # 1. 接收浏览器发送过来的请求 ,即http请求  
    # GET / HTTP/1.1
    # .....
    # request = new_socket.recv(1024).decode("utf-8")
    # print(">>>"*50)
    # print(request)

    request_lines = request.splitlines()
    print("")
    print(">"*20)
    print(request_lines)

    # GET /index.html HTTP/1.1
    # get post put del
    file_name = ""
    ret = re.match(r"[^/]+(/[^ ]*)", request_lines[0])
    if ret:
        file_name = ret.group(1)
        # print("*"*50, file_name)
        if file_name == "/":
            file_name = "/index.html"

    # 2. 返回http格式的数据,给浏览器
    
    try:
        f = open("./html" + file_name, "rb")
    except:
        response = "HTTP/1.1 404 NOT FOUND\r\n"
        response += "\r\n"
        response += "------file not found-----"
        new_socket.send(response.encode("utf-8"))
    else:
        html_content = f.read()
        f.close()

        response_body = html_content

        response_header = "HTTP/1.1 200 OK\r\n"
        response_header += "Content-Length:%d\r\n" % len(response_body)
        response_header += "\r\n"

        response = response_header.encode("utf-8") + response_body

        new_socket.send(response)


def main():
    """用来完成整体的控制"""
    # 1. 创建套接字
    tcp_server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    tcp_server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)

    # 2. 绑定
    tcp_server_socket.bind(("", 7890))

    # 3. 变为监听套接字
    tcp_server_socket.listen(128)
    tcp_server_socket.setblocking(False)  # 将套接字变为非堵塞

    client_socket_list = list()
    while True:
        # 4. 等待新客户端的链接
        try:
            new_socket, client_addr = tcp_server_socket.accept()
        except Exception as ret:
            pass
        else:
            new_socket.setblocking(False)
            client_socket_list.append(new_socket)


        for client_socket in client_socket_list:
            try:
                recv_data = client_socket.recv(1024).decode("utf-8")
            except Exception as ret:
                pass
            else:
                if recv_data:
                    service_client(client_socket, recv_data)
                else:
                    client_socket.close()
                    client_socket_list.remove(client_socket)

    # 关闭监听套接字
    tcp_server_socket.close()


if __name__ == "__main__":
    main()


Web静态服务器-epoll

在这里插入图片描述
参考资料 : http://blog.csdn.net/xiajun07061225/article/details/9250579

IO 多路复用

就是我们说的select,poll,epoll,有些地方也称这种IO方式为event driven IO。

select/epoll的好处就在于单个process就可以同时处理多个网络连接的IO。

它的基本原理就是select,poll,epoll这个function会不断的轮询所负责的所有socket,当某个socket有数据到达了,就通知用户进程。

I/O 多路复用的特点:

通过一种机制使一个进程能同时等待多个文件描述符,而这些文件描述符(套接字描述符)其中的任意一个进入读就绪状态,epoll()函数就可以返回。 所以, IO多路复用,本质上不会有并发的功能,因为任何时候还是只有一个进程或线程进行工作,它之所以能提高效率是因为select\epoll 把进来的socket放到他们的 ‘监视’ 列表里面,当任何socket有可读可写数据立马处理,那如果select\epoll 手里同时检测着很多socket, 一有动静马上返回给进程处理,总比一个一个socket过来,阻塞等待,处理高效率。

当然也可以多线程/多进程方式,一个连接过来开一个进程/线程处理,这样消耗的内存和进程切换页会耗掉更多的系统资源。 所以我们可以结合IO多路复用和多进程/多线程 来高性能并发,IO复用负责提高接受socket的通知效率,收到请求后,交给进程池/线程池来处理逻辑。

epoll实现http.py
import socket
import re
import select


def service_client(new_socket, request):
    """为这个客户端返回数据"""

    # 1. 接收浏览器发送过来的请求 ,即http请求  
    # GET / HTTP/1.1
    # .....
    # request = new_socket.recv(1024).decode("utf-8")
    # print(">>>"*50)
    # print(request)

    request_lines = request.splitlines()
    print("")
    print(">"*20)
    print(request_lines)

    # GET /index.html HTTP/1.1
    # get post put del
    file_name = ""
    ret = re.match(r"[^/]+(/[^ ]*)", request_lines[0])
    if ret:
        file_name = ret.group(1)
        # print("*"*50, file_name)
        if file_name == "/":
            file_name = "/index.html"

    # 2. 返回http格式的数据,给浏览器
    
    try:
        f = open("./html" + file_name, "rb")
    except:
        response = "HTTP/1.1 404 NOT FOUND\r\n"
        response += "\r\n"
        response += "------file not found-----"
        new_socket.send(response.encode("utf-8"))
    else:
        html_content = f.read()
        f.close()

        response_body = html_content

        response_header = "HTTP/1.1 200 OK\r\n"
        response_header += "Content-Length:%d\r\n" % len(response_body)
        response_header += "\r\n"

        response = response_header.encode("utf-8") + response_body

        new_socket.send(response)


def main():
    """用来完成整体的控制"""
    # 1. 创建套接字
    tcp_server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    tcp_server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)

    # 2. 绑定
    tcp_server_socket.bind(("", 7890))

    # 3. 变为监听套接字
    tcp_server_socket.listen(128)
    tcp_server_socket.setblocking(False)  # 将套接字变为非堵塞

    # 创建一个epoll对象
    epl = select.epoll()

    # 将监听套接字对应的fd注册到epoll中
    epl.register(tcp_server_socket.fileno(), select.EPOLLIN)

    fd_event_dict = dict()

    while True:

        fd_event_list = epl.poll()  # 默认会堵塞,直到 os监测到数据到来 通过事件通知方式 告诉这个程序,此时才会解堵塞

        # [(fd, event), (套接字对应的文件描述符, 这个文件描述符到底是什么事件 例如 可以调用recv接收等)]
        for fd, event in fd_event_list:
            # 等待新客户端的链接
            if fd == tcp_server_socket.fileno():
                new_socket, client_addr = tcp_server_socket.accept()
                epl.register(new_socket.fileno(), select.EPOLLIN)
                fd_event_dict[new_socket.fileno()] = new_socket
            elif event==select.EPOLLIN:
                # 判断已经链接的客户端是否有数据发送过来
                recv_data = fd_event_dict[fd].recv(1024).decode("utf-8")
                if recv_data:
                    service_client(fd_event_dict[fd], recv_data)
                else:
                    fd_event_dict[fd].close()
                    epl.unregister(fd)
                    del fd_event_dict[fd]


    # 关闭监听套接字
    tcp_server_socket.close()


if __name__ == "__main__":
    main()


本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

python后端学习(七)HTTP协议、实现WEB服务器 的相关文章

  • Spark 请求最大计数

    我是 Spark 的初学者 我尝试请求允许我检索最常访问的网页 我的要求如下 mostPopularWebPageDF logDF groupBy webPage agg functions count webPage alias cntW
  • 围绕 readline 构建的 python 批处理的触发器选项卡完成

    背景 我有一个 python 程序 它导入并使用 readline 模块来构建自制的命令行界面 我有第二个 python 程序 围绕 Bottle 一个 Web 微框架构建 充当该 CLI 的前端 第二个 python 程序向第一个程序打开
  • python future 和元组解包

    实现像使用 future 进行元组解包这样的事情的优雅 惯用的方法是什么 我有这样的代码 a b c f x y g a b z h y c 我想将其转换为使用期货 理想情况下我想写一些类似的东西 a b c ex submit f x y
  • python 中的代表

    我实现了这个简短的示例来尝试演示一个简单的委托模式 我的问题是 这看起来我已经理解了委托吗 class Handler def init self parent None self parent parent def Handle self
  • 如何使用 Plotly 中的直方图将所有离群值分入一个分箱?

    所以问题是 我可以在 Plotly 中绘制直方图 其中所有大于某个阈值的值都将被分组到一个箱中吗 所需的输出 但使用标准情节Histogram类我只能得到这个输出 import pandas as pd from plotly import
  • 如何在 pytest 中将单元测试和集成测试分开

    根据维基百科 https en wikipedia org wiki Unit testing Description和各种articles https techbeacon com devops 6 best practices inte
  • Pandas 中允许重复列

    我将一个大的 CSV 包含股票财务数据 文件分割成更小的块 CSV 文件的格式不同 像 Excel 数据透视表之类的东西 第一列的前几行包含一些标题 公司名称 ID 等在以下列中重复 因为一家公司有多个属性 而不是一家公司只有一栏 在前几行
  • 如何计算numpy数组中元素的频率?

    我有一个 3 D numpy 数组 其中包含重复的元素 counterTraj shape 13530 1 1 例如 counterTraj 包含这样的元素 我只显示了几个元素 array 136 129 130 103 102 101 我
  • 对图像块进行多重处理

    我有一个函数必须循环遍历图像的各个像素并计算一些几何形状 此函数需要很长时间才能运行 在 24 兆像素图像上大约需要 5 小时 但似乎应该很容易在多个内核上并行运行 然而 我一生都找不到一个有据可查 解释充分的例子来使用 Multiproc
  • 首先对列表中最长的项目进行排序

    我正在使用 lambda 来修改排序的行为 sorted list key lambda item item lower len item 对包含元素的列表进行排序A1 A2 A3 A B1 B2 B3 B 结果是A A1 A2 A3 B
  • 将 matplotlib 颜色图集中在特定值上

    我正在使用 matplotlib 颜色图 seismic 绘制绘图 并且希望白色以 0 为中心 当我在不进行任何更改的情况下运行脚本时 白色从 0 下降到 10 我尝试设置 vmin 50 vmax 50 但在这种情况下我完全失去了白色 关
  • 创建嵌套字典单行

    您好 我有三个列表 我想使用一行创建一个三级嵌套字典 i e l1 a b l2 1 2 3 l3 d e 我想创建以下嵌套字典 nd a 1 d 0 e 0 2 d 0 e 0 3 d 0 e 0 b a 1 d 0 e 0 2 d 0
  • 使用 NumPy 将非均匀数据从文件读取到数组中

    假设我有一个如下所示的文本文件 33 346 1223 10 23 11 23 12 23 13 23 14 23 15 23 16 24 10 24 11 24 12 24 13 24 14 24 15 24 16 25 14 25 15
  • 如何在 OSX 上安装 numpy 和 scipy?

    我是 Mac 新手 请耐心等待 我现在使用的是雪豹 10 6 4 我想安装numpy和scipy 所以我从他们的官方网站下载了python2 6 numpy和scipy dmg文件 但是 我在导入 numpy 时遇到问题 Library F
  • 如何为每个屏幕添加自己的 .py 和 .kv 文件?

    我想为每个屏幕都有一个单独的 py 和 kv 文件 应通过 main py main kv 中的 ScreenManager 选择屏幕 设计应从文件 screen X kv 加载 类等应从文件 screen X py 加载 Screens
  • 迭代 my_dict.keys() 并修改字典中的值是否会使迭代器失效?

    我的例子是这样的 for my key in my dict keys my dict my key mutate 上述代码的行为是否已定义 假设my dict是一本字典并且mutate是一个改变其对象的方法 我担心的是 改变字典中的值可能
  • 无法在前端使用 JavaScript Fetch API 将文件上传到 FastAPI 后端

    我正在尝试弄清楚如何将图像发送到我的 API 并验证生成的token那是在header的请求 到目前为止 这就是我所处的位置 app post endreProfilbilde async def endreProfilbilde requ
  • 检查字典键是否有空值

    我有以下字典 dict1 city name yass region zipcode phone address tehsil planet mars 我正在尝试创建一个基于 dict1 的新字典 但是 它不会包含带有空字符串的键 它不会包
  • 从 Twitter API 2.0 获取 user.fields 时出现问题

    我想从 Twitter API 2 0 端点加载推文 并尝试获取标准字段 作者 文本 和一些扩展字段 尤其是 用户 字段 端点和参数的定义工作没有错误 在生成的 json 中 我只找到标准字段 但没有找到所需的 user fields 用户
  • 迭代 pandas 数据框的最快方法?

    如何运行数据框并仅返回满足特定条件的行 必须在之前的行和列上测试此条件 例如 1 2 3 4 1 1 1999 4 2 4 5 1 2 1999 5 2 3 3 1 3 1999 5 2 3 8 1 4 1999 6 4 2 6 1 5 1

随机推荐

  • zxing 二维码扫描优化

    先罗列优化点 1 优化扫描精度 增加解析成功率 hints put DecodeHintType TRY HARDER Boolean TRUE 2 生成图片 用于被解析 时不剪切图片 增加二维码图片的完整性 优化前 new PlanarY
  • js上拉加载更多

    感谢原作者
  • Unity3D相关知识点笔记汇总

    这篇文章将作为一些平时的小知识点笔记来记录 如果有错误望指出来 也欢迎大家在评论底下分享你们的笔记 1 检测点击或者触摸到UI public static bool CheckClickUI bool isClickUI false if
  • 医疗信息管理系统数据库--MySQL

    医疗信息管理系统数据库 MySQL 友情连接 1 学生成绩管理系统数据库设计 MySQL 2 邮件管理数据库设计 MySQL 3 点餐系统数据库设计 SQL Server 4 商品管理系统数据库设计 SQL Server 5 SQL Ser
  • 在WebView中对第三方H5页面的文本密码框添加自定义随机键盘

    前言 首先介绍一下这个需求的背景 由于公司是涉及到金融行业的需要与银行对接资金存管 出于保密性这里不直接列出公司名字和银行名字 从2018年国家对金融行业大整改以来 为了能够顺利通过备案 我们也跟着政府的脚步一步一步走向合规 好了 大致就是
  • 堡垒机-jumpserver环境搭建

    一 Jumpserver简单介绍 Jumpserver 是全球首款完全开源的堡垒机 使用 GNU GPL v2 0 开源协议 是符合 4A 的专业运维审计系统 Jumpserver 使用 Python Django 进行开发 遵循 Web
  • c语言链式栈课程设计,C语言实现链式栈(LinkStack)

    使用单链表来实现 push pop均在链表头部进行 linkStack h ifndef LINK STACK H define LINK STACK H include include include include typedef vo
  • 加密数字货币的开发技术介绍

    要问当前所有区块链应用中最火的是什么应用 非加密货币莫属 看看各个跟区块链相关的讨论组 整天热火朝天地讨论的是各种币的行情 即使是技术讨论组 除了一些热门讨论外 最吸引注意的莫过于本币的涨跌还有各种代币的ICO了 首先 加密数字货币是什么鬼
  • position absolute相关知识点

    前言 最近再看position相关知识点 发现有许多以前没有注意到的细节知识点 有不小的收获 本文就position absolute使用详细分析下 具体分析 position是CSS中比较重要的一个属性 常用于页面布局 它的值有4个 st
  • oracle数据库与postgre数据库之间的互相迁移

    oracle与postgre之间互相迁移之前要明白 postgreSQL中默认使用小写 oracleSQL中默认大写 迁移分成3个步骤 数据及结构迁移 迁移之后的类型及长度变化 不兼容的函数替换 1 数据及结构迁移 1 1数据大小写同步 o
  • JS 判断对象中是否包含某属性

    一 通过点或者方括号 我们在使用对象的时候 通过点或方括号可以获取对象的属性值 如果该对象自身不存在这个属性 就会返回undefined var obj name 小破船 doWhat 借箭 console log obj name 小破船
  • css linear-gradient 设置背景颜色渐变

    CSS3 渐变能够让背景颜色在两个或多个颜色之间平滑过渡 基本语法 background linear gradient direction color stop1 color stop2 direction 是指渐变的方向 color s
  • 迷宫问题寻宝(c++实现,求最短路径,显示路径)

    定义一个二维数组 int maze n m 它表示一个迷宫 其中的1表示道路不通 0表示可以走的路 3 表示宝藏 只能横着走或竖着走 不能斜着走 要求编程序找出找到宝藏的最短路路径 题目保证有解且只有一个最短路径 且只能从迷宫边缘进入迷宫
  • tomcat中仅启动指定的项目,不启动其它项目

    使用tomcat调试项目时 若部署过多项目会导致启动时间很长 这时指定自己想启动的项目就显得很有必要了 具体方法如下 一 打开tomcat中的server xml配置文件 在Host节点中添加或修改如下属性 deployXML false
  • QT系列第3节 QT中混合UI设计

    QT开发过程中 经常使用Qt designer设计器和代码方式结合来及进行ui设计 本节将介绍这两种方式混合进行ui开发 目录 1 工程添加图片资源 2 添加菜单 3 添加工具栏 4 简单文本编辑器实现 5 QT Creator常用快捷键
  • scrapy设置代理ip(精简版)

    在middlewares py文件中 添加下面的代码 import scrapy from scrapy import signals import random class ProxyMiddleware object def proce
  • 爬取淘宝价格

    爬取淘宝价格 from selenium import webdriver from lxml import etree from time import sleep 实例化一个浏览器对象 bro webdriver Chrome exec
  • [调用函数]

    注 梳理 整理 用来帮助自己学习 如有错误 请指出 1 编写一个函数 该函数接受两个整数作为参数并返回它们的和 在主函数中调用该函数并输出结果 示例输入 5 7 示例输出 12 解题思路 首先需要定义一个函数来实现两个整数的加法 函数的返回
  • 对于进程同步和异步的理解

    多进程并发执行具有异步的特性 进程异步就是指一个以上的进程在并发执行时具有的异步特型 就比如说两个进程之间指令的执行顺序是不确定的 具有很强的随机性 举个例子 现在有两个并发执行的进程 A 和 B 各自都有n条指令需要执行 然而 我的CPU
  • python后端学习(七)HTTP协议、实现WEB服务器

    HTTP协议简介 浏览器 gt 服务器发送的请求格式如下 GET HTTP 1 1 请求方式 路径 协议及版本 Host 127 0 0 1 8080 请求的地址 Connection keep alive 长连接 Accept text