了解http協(xié)議
http請(qǐng)求頭
GET / HTTP/1.1
Host: www.baidu.com
Connection: keep-alive
Pragma: no-cache
Cache-Control: no-cache
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/76.0.3809.100 Safari/537.36
Sec-Fetch-Mode: navigate
Sec-Fetch-User: ?1
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3
Sec-Fetch-Site: none
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.9,en;q=0.8
最主要的頭兩行分析如下:
- GET表示一個(gè)讀取請(qǐng)求,將從服務(wù)器獲得網(wǎng)頁(yè)數(shù)據(jù),/表示URL的路徑,URL總是以/開(kāi)頭,/就表示首頁(yè),最后的HTTP/1.1指示采用的HTTP協(xié)議版本是1.1。
- 目前HTTP協(xié)議的版本就是1.1,但是大部分服務(wù)器也支持1.0版本,主要區(qū)別在于1.1版本允許多個(gè)HTTP請(qǐng)求復(fù)用一個(gè)TCP連接,以加快傳輸速度。
- Host: www.baidu.com表示請(qǐng)求的域名是www.baidu.com。如果一臺(tái)服務(wù)器有多個(gè)網(wǎng)站,服務(wù)器就需要通過(guò)Host來(lái)區(qū)分瀏覽器請(qǐng)求的是哪個(gè)網(wǎng)站。
http響應(yīng)頭
HTTP/1.1 200 OK
Bdpagetype: 2
Bdqid: 0x8ef7ae5901149cf7
Cache-Control: private
Connection: Keep-Alive
Content-Encoding: gzip
Content-Type: text/html;charset=utf-8
Date: Wed, 28 Aug 2019 01:59:49 GMT
Expires: Wed, 28 Aug 2019 01:59:48 GMT
Server: BWS/1.1
Set-Cookie: BDSVRTM=249; path=/
Set-Cookie: BD_HOME=1; path=/
Set-Cookie: H_PS_PSSID=1426_21111_20697_29522_29518_29099_29568_29220_26350; path=/; domain=.baidu.com
Strict-Transport-Security: max-age=172800
X-Ua-Compatible: IE=Edge,chrome=1
Transfer-Encoding: chunked
說(shuō)明:
- 200表示一個(gè)成功的響應(yīng),后面的OK是說(shuō)明。
- Content-Type指示響應(yīng)的內(nèi)容,這里是text/html表示HTML網(wǎng)頁(yè)。
- 請(qǐng)求頭和響應(yīng)頭通過(guò)\r\n來(lái)?yè)Q行。
- 響應(yīng)頭和body響應(yīng)體中也通過(guò)\r\n來(lái)分隔。
簡(jiǎn)單的http服務(wù)器
有多簡(jiǎn)單呢?運(yùn)行程序后打開(kāi)瀏覽器,只能顯示hello world。
import socket
def service_client(new_socket):
# 接受瀏覽器發(fā)過(guò)來(lái)的http請(qǐng)求
# GET / HTTP/1.1
request = new_socket.recv(1024)
print(request)
# 返回http響應(yīng)
resposne = "HTTP/1.1 200 OK\r\n"
resposne += "\r\n"
resposne += "
hello world
"
new_socket.send(resposne.encode("utf-8"))
# 關(guān)閉套接字
new_socket.close()
def main():
# 創(chuàng)建套接字
http_server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 防止端口被占用無(wú)法啟動(dòng)程序
http_server.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)
# 綁定端口
http_server.bind(("", 80))
# 變?yōu)楸O(jiān)聽(tīng)套接字
http_server.listen(128)
while True:
# 等在新客戶端連接
client, info = http_server.accept()
# 為這個(gè)客戶端服務(wù)
service_client(client)
if __name__ == "__main__":
main()
單進(jìn)程http服務(wù)器
它上之前有一個(gè)升級(jí)就是可以返回靜態(tài)的html頁(yè)面。
import socket
import re
def service_client(new_socket):
# 接受瀏覽器發(fā)過(guò)來(lái)的http請(qǐng)求
# GET / HTTP/1.1
request = new_socket.recv(1024).decode("utf-8")
# print(request)
request_lines = request.splitlines()
req = re.match(r"[^/]+(/\S*)", request_lines[0])
file_name: str = ""
if req:
file_name = req.group(1)
if file_name == "/":
file_name = "/index.html"
print(file_name)
# print(request_lines)
# 返回http響應(yīng)
try:
# 打開(kāi)要請(qǐng)求的html文件,并返回給客戶端。網(wǎng)頁(yè)在當(dāng)前路徑的html文件夾里面。
with open("./html" + file_name, "r", encoding="utf-8") as f:
resposne = "HTTP/1.1 200 OK\r\n"
resposne += "\r\n"
# resposne += "
hello world
"
resposne += f.read()
except Exception as e:
resposne = "HTTP/1.1 400 NOT FOUND\r\n"
resposne += "\r\n"
resposne += "--file not found--"
new_socket.send(resposne.encode("utf-8"))
# 關(guān)閉套接字
new_socket.close()
def main():
# 創(chuàng)建套接字
http_server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 防止端口被占用無(wú)法啟動(dòng)程序
http_server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
# 綁定端口
http_server.bind(("", 80))
# 變?yōu)楸O(jiān)聽(tīng)套接字
http_server.listen(128)
while True:
# 等在新客戶端連接
client, info = http_server.accept()
# 為這個(gè)客戶端服務(wù)
service_client(client)
if __name__ == "__main__":
main()
多進(jìn)程服務(wù)器
http服務(wù)器是完成了,但是如果同時(shí)有好多人訪問(wèn)的話它反應(yīng)就會(huì)非常慢,所有又在之前的基礎(chǔ)上做了升級(jí),增加服務(wù)器的并發(fā)能力。
import socket
import re
from multiprocessing import Process
def service_client(new_socket):
# 接受瀏覽器發(fā)過(guò)來(lái)的http請(qǐng)求
# GET / HTTP/1.1
request = new_socket.recv(1024).decode("utf-8")
# print(request)
request_lines = request.splitlines()
req = re.match(r"[^/]+(/\S*)", request_lines[0])
file_name: str = ""
if req:
file_name = req.group(1)
if file_name == "/":
file_name = "/index.html"
print(file_name)
# print(request_lines)
# 返回http響應(yīng)
try:
with open("./html" + file_name, "r", encoding="utf-8") as f:
resposne = "HTTP/1.1 200 OK\r\n"
resposne += "\r\n"
# resposne += "
hello world
"
resposne += f.read()
except Exception as e:
resposne = "HTTP/1.1 400 NOT FOUND\r\n"
resposne += "\r\n"
resposne += "--file not found--"
new_socket.send(resposne.encode("utf-8"))
# new_socket.send(body)
# 關(guān)閉套接字
new_socket.close()
def main():
# 創(chuàng)建套接字
http_server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 防止端口被占用無(wú)法啟動(dòng)程序
http_server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
# 綁定端口
http_server.bind(("", 80))
# 變?yōu)楸O(jiān)聽(tīng)套接字
http_server.listen(128)
while True:
# 等在新客戶端連接
client, info = http_server.accept()
# 開(kāi)啟一個(gè)子進(jìn)程為這個(gè)客戶端服務(wù)
p = Process(target=service_client,args=(client,))
p.start()
# 子進(jìn)程會(huì)復(fù)制主進(jìn)程發(fā)的資源,故把主進(jìn)程的socket關(guān)閉。
client.close()
if __name__ == "__main__":
main()
多線程服務(wù)器
我們知道進(jìn)程耗費(fèi)資源是非常大的,所以這次使用了耗費(fèi)資源小的線程來(lái)實(shí)現(xiàn)多任務(wù)。
import socket
import re
from threading import Thread
def service_client(new_socket):
# 接受瀏覽器發(fā)過(guò)來(lái)的http請(qǐng)求
# GET / HTTP/1.1
request = new_socket.recv(1024).decode("utf-8")
# print(request)
request_lines = request.splitlines()
req = re.match(r"[^/]+(/\S*)", request_lines[0])
file_name: str = ""
if req:
file_name = req.group(1)
if file_name == "/":
file_name = "/index.html"
print(file_name)
# print(request_lines)
# 返回http響應(yīng)
try:
with open("./html" + file_name, "r", encoding="utf-8") as f:
resposne = "HTTP/1.1 200 OK\r\n"
resposne += "\r\n"
# resposne += "
hello world
"
resposne += f.read()
except Exception as e:
resposne = "HTTP/1.1 400 NOT FOUND\r\n"
resposne += "\r\n"
resposne += "--file not found--"
new_socket.send(resposne.encode("utf-8"))
# new_socket.send(body)
# 關(guān)閉套接字
new_socket.close()
def main():
# 創(chuàng)建套接字
http_server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 防止端口被占用無(wú)法啟動(dòng)程序
http_server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
# 綁定端口
http_server.bind(("", 80))
# 變?yōu)楸O(jiān)聽(tīng)套接字
http_server.listen(128)
while True:
# 等在新客戶端連接
client, info = http_server.accept()
# 開(kāi)啟一個(gè)子線程為這個(gè)客戶端服務(wù)
p = Thread(target=service_client, args=(client,))
p.start()
if __name__ == "__main__":
main()
gevent協(xié)程版的服務(wù)器
協(xié)程在一個(gè)線程中執(zhí)行,減少了線程之間的切換,多線程的升級(jí)版,擁有更好的處理能力。
import socket
import re
import gevent
from gevent import monkey
monkey.patch_all()
def service_client(new_socket):
# 接受瀏覽器發(fā)過(guò)來(lái)的http請(qǐng)求
# GET / HTTP/1.1
request = new_socket.recv(1024).decode("utf-8")
# print(request)
request_lines = request.splitlines()
req = re.match(r"[^/]+(/\S*)", request_lines[0])
file_name: str = ""
if req:
file_name = req.group(1)
if file_name == "/":
file_name = "/index.html"
print(file_name)
# print(request_lines)
# 返回http響應(yīng)
try:
with open("./html" + file_name, "r", encoding="utf-8") as f:
resposne = "HTTP/1.1 200 OK\r\n"
resposne += "\r\n"
# resposne += "
hello world
"
resposne += f.read()
except Exception as e:
resposne = "HTTP/1.1 400 NOT FOUND\r\n"
resposne += "\r\n"
resposne += "--file not found--"
new_socket.send(resposne.encode("utf-8"))
# new_socket.send(body)
# 關(guān)閉套接字
new_socket.close()
def main():
# 創(chuàng)建套接字
http_server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 防止端口被占用無(wú)法啟動(dòng)程序
http_server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
# 綁定端口
http_server.bind(("", 80))
# 變?yōu)楸O(jiān)聽(tīng)套接字
http_server.listen(128)
while True:
# 等在新客戶端連接
client, info = http_server.accept()
# 為這個(gè)客戶端服務(wù)
gevent.spawn(service_client, client)
if __name__ == "__main__":
main()
單進(jìn)程非堵塞版長(zhǎng)連接的服務(wù)器
從這個(gè)例子中來(lái)引入epoll版,它的性能應(yīng)該要比協(xié)程的好,與之前所有服務(wù)器的不同之處就是采用了長(zhǎng)連接,通過(guò)響應(yīng)頭中的Content-Length來(lái)指定響應(yīng)體的長(zhǎng)度,從而讓瀏覽器知道頁(yè)面數(shù)據(jù)傳輸完成以后自動(dòng)在同一個(gè)套接字連接中繼續(xù)發(fā)送其他資源文件的請(qǐng)求,效率較高。之前的都是短連接,只要傳輸完當(dāng)前文件就關(guān)閉這個(gè)套接字。
import socket
import re
def service_client(new_socket: object, request: str):
# 接受瀏覽器發(fā)過(guò)來(lái)的http請(qǐng)求
# GET / HTTP/1.1
# request = new_socket.recv(1024).decode("utf-8")
# print(request)
request_lines = request.splitlines()
req = re.match(r"[^/]+(/\S*)", request_lines[0])
file_name: str = ""
if req:
file_name = req.group(1)
if file_name == "/":
file_name = "/index.html"
print(file_name)
# print(request_lines)
# 返回http響應(yīng)
try:
with open("./html" + file_name, "r", encoding="utf-8") as f:
resposne_body: str = f.read()
resposne_header: str = "HTTP/1.1 200 OK\r\n"
resposne_header += "Content-Length:%d\r\n" % len(resposne_body)
resposne_header += "\r\n"
# resposne += "
hello world
"
resposne = resposne_header + resposne_body
except Exception as e:
resposne = "HTTP/1.1 400 NOT FOUND\r\n"
resposne += "\r\n"
resposne += "--file not found--"
new_socket.send(resposne.encode("utf-8"))
# new_socket.send(body)
# 關(guān)閉套接字
# new_socket.close()
def main():
# 創(chuàng)建套接字
http_server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 防止端口被占用無(wú)法啟動(dòng)程序
http_server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
# 綁定端口
http_server.bind(("", 80))
# 變?yōu)楸O(jiān)聽(tīng)套接字
http_server.listen(128)
# 設(shè)置套接字為非堵塞方式
http_server.setblocking(False)
socket_list: list = []
while True:
try:
# 等在新客戶端連接
client, info = http_server.accept()
# 為這個(gè)客戶端服務(wù)
# gevent.spawn(service_client, client)
except Exception as e:
# print(e)
pass
else:
client.setblocking(False)
socket_list.append(client)
for socket_client in socket_list:
try:
recv_data: str = socket_client.recv(1024).decode("utf-8")
except Exception as e:
# print(e)
pass
else:
if recv_data:
service_client(socket_client, recv_data)
else:
socket_list.remove(socket_client)
socket_client.close()
if __name__ == "__main__":
main()
eopll版的服務(wù)器
這個(gè)版本是性能最高的服務(wù)器。它的基本原理就是select,poll,epoll這個(gè)function會(huì)不斷的輪詢所負(fù)責(zé)的所有socket,當(dāng)某個(gè)socket有數(shù)據(jù)到達(dá)了,就通知用戶進(jìn)程。select/epoll的好處就在于單個(gè)process就可以同時(shí)處理多個(gè)網(wǎng)絡(luò)連接的IO。
import socket
import re
import select
def service_client(new_socket: object, request: str):
# 接受瀏覽器發(fā)過(guò)來(lái)的http請(qǐng)求
# GET / HTTP/1.1
# request = new_socket.recv(1024).decode("utf-8")
# print(request)
request_lines = request.splitlines()
req = re.match(r"[^/]+(/\S*)", request_lines[0])
file_name: str = ""
if req:
file_name = req.group(1)
if file_name == "/":
file_name = "/index.html"
print(file_name)
# print(request_lines)
# 返回http響應(yīng)
try:
with open("./html" + file_name, "r", encoding="utf-8") as f:
resposne_body: str = f.read()
resposne_header: str = "HTTP/1.1 200 OK\r\n"
resposne_header += "Content-Length:%d\r\n" % len(resposne_body)
resposne_header += "\r\n"
# resposne += "
hello world
"
resposne = resposne_header + resposne_body
except Exception as e:
resposne = "HTTP/1.1 400 NOT FOUND\r\n"
resposne += "\r\n"
resposne += "--file not found--"
new_socket.send(resposne.encode("utf-8"))
# new_socket.send(body)
# 關(guān)閉套接字
# new_socket.close()
def main():
# 創(chuàng)建套接字
http_server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 防止端口被占用無(wú)法啟動(dòng)程序
http_server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
# 綁定端口
http_server.bind(("", 80))
# 變?yōu)楸O(jiān)聽(tīng)套接字
http_server.listen(128)
# 設(shè)置套接字為非堵塞方式
http_server.setblocking(False)
# 創(chuàng)建一個(gè)epoll對(duì)象
epl = select.epoll()
# 將監(jiān)聽(tīng)套接字對(duì)應(yīng)的fd(文件描述符)注冊(cè)到epoll中
epl.register(http_server.fileno(), select.EPOLLIN)
# 存儲(chǔ)fd文件描述符和套接字的對(duì)應(yīng)關(guān)系
fd_event_dict: dict = {}
while True:
# 默認(rèn)會(huì)堵塞,知道os檢測(cè)到數(shù)據(jù)到來(lái),通過(guò)事件通知方式告訴這個(gè)程序,此時(shí)才會(huì)解堵塞
fd_event_list: list = epl.poll() # [(套接字對(duì)應(yīng)的文件描述符,這個(gè)文件描述符是什么事件),...]
for fd, event in fd_event_list:
# 如果是監(jiān)聽(tīng)套接字有數(shù)據(jù)過(guò)來(lái),即等待新的客戶端連接
if fd == http_server.fileno():
client, info = http_server.accept()
# 將新的套接字注冊(cè)到epoll中
epl.register(client.fileno(), select.EPOLLIN)
# 把文件描述符和套接字的對(duì)應(yīng)關(guān)系存入字典
fd_event_dict[client.fileno()] = client
elif event == select.EPOLLIN:
# 判斷已連接的套接字是否有數(shù)據(jù)發(fā)過(guò)來(lái)
recv_data: str = 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]
if __name__ == '__main__':
main()
I/O 多路復(fù)用的特點(diǎn):
通過(guò)一種機(jī)制使一個(gè)進(jìn)程能同時(shí)等待多個(gè)文件描述符,而這些文件描述符(套接字描述符)其中的任意一個(gè)進(jìn)入讀就緒狀態(tài),epoll()函數(shù)就可以返回。 所以, IO多路復(fù)用,本質(zhì)上不會(huì)有并發(fā)的功能,因?yàn)槿魏螘r(shí)候還是只有一個(gè)進(jìn)程或線程進(jìn)行工作,它之所以能提高效率是因?yàn)閟elect\epoll 把進(jìn)來(lái)的socket放到他們的 '監(jiān)視' 列表里面,當(dāng)任何socket有可讀可寫(xiě)數(shù)據(jù)立馬處理,那如果select\epoll 手里同時(shí)檢測(cè)著很多socket, 一有動(dòng)靜馬上返回給進(jìn)程處理,總比一個(gè)一個(gè)socket過(guò)來(lái),阻塞等待,處理高效率。
更多文章、技術(shù)交流、商務(wù)合作、聯(lián)系博主
微信掃碼或搜索:z360901061

微信掃一掃加我為好友
QQ號(hào)聯(lián)系: 360901061
您的支持是博主寫(xiě)作最大的動(dòng)力,如果您喜歡我的文章,感覺(jué)我的文章對(duì)您有幫助,請(qǐng)用微信掃描下面二維碼支持博主2元、5元、10元、20元等您想捐的金額吧,狠狠點(diǎn)擊下面給點(diǎn)支持吧,站長(zhǎng)非常感激您!手機(jī)微信長(zhǎng)按不能支付解決辦法:請(qǐng)將微信支付二維碼保存到相冊(cè),切換到微信,然后點(diǎn)擊微信右上角掃一掃功能,選擇支付二維碼完成支付。
【本文對(duì)您有幫助就好】元
