python server send event_[Python之路] 多种方式实现并发Web Server
下面我們使用Python來實(shí)現(xiàn)并發(fā)的Web Server,其中采用了多進(jìn)程、多線程、協(xié)程、單進(jìn)程單線程非阻塞的方式。
一、使用子進(jìn)程來實(shí)現(xiàn)并發(fā)Web Server
importsocketimportreimportmultiprocessingdefhandle_request(new_socket):#接收請(qǐng)求
recv_msg = ""recv_msg= new_socket.recv(1024).decode("utf-8")if recv_msg == "":print("recv null")
new_socket.close()return
#從請(qǐng)求中解析出URI
recv_lines =recv_msg.splitlines()print(recv_lines.__len__())#使用正則表達(dá)式提取出URI
ret = re.match(r"[^/]+(/[^ ]*)", recv_lines[0])ifret:#獲取URI字符串
file_name = ret.group(1)#如果URI是/,則默認(rèn)返回index.html的內(nèi)容
if file_name == "/":
file_name= "/index.html"
try:#根據(jù)請(qǐng)求的URI,讀取相應(yīng)的文件
fp = open("." + file_name, "rb")except:#找不到文件,響應(yīng)404
response_msg = "HTTP/1.1 404 NOT FOUND\r\n"response_msg+= "\r\n"response_msg+= "
----file not found----
"new_socket.send(response_msg.encode("utf-8"))else:html_content=fp.read()
fp.close()#響應(yīng)正確 200 OK
response_msg = "HTTP/1.1 200 OK\r\n"response_msg+= "\r\n"
#返回響應(yīng)頭
new_socket.send(response_msg.encode("utf-8"))#返回響應(yīng)體
new_socket.send(html_content)#關(guān)閉該次socket連接
new_socket.close()defmain():#創(chuàng)建TCP SOCKET實(shí)例
tcp_server_socket =socket.socket(socket.AF_INET, socket.SOCK_STREAM)## 設(shè)置重用地址
#tcp_server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
#綁定地址(默認(rèn)本機(jī)IP)和端口
tcp_server_socket.bind(("", 7890))#監(jiān)聽
tcp_server_socket.listen(128)#循環(huán)接收客戶端連接
whileTrue:
new_socket, client_addr=tcp_server_socket.accept()#啟動(dòng)一個(gè)子進(jìn)程來處理客戶端的請(qǐng)求
sub_p = multiprocessing.Process(target=handle_request, args=(new_socket,))
sub_p.start()#這里要關(guān)閉父進(jìn)程中的new_socket,因?yàn)閯?chuàng)建子進(jìn)程會(huì)復(fù)制一份new_socket給子進(jìn)程
new_socket.close()#關(guān)閉整個(gè)SOCKET
tcp_server_socket.close()if __name__ == "__main__":
main()
我們使用進(jìn)程來實(shí)現(xiàn)并發(fā)的Web Server,也就是將Accept到new_socket傳遞給子進(jìn)程去處理,處理函數(shù)還是handle_request。
但是這里注意,子進(jìn)程會(huì)從父進(jìn)程中將所有的變量進(jìn)行拷貝,也就是說父進(jìn)程和子進(jìn)程中各有一份new_socket,而在Linux下,socket對(duì)應(yīng)的也是一個(gè)文件描述符,而這兩個(gè)new_socket實(shí)際上是指向同一個(gè)fd的。所以我們將new_socket交給子進(jìn)程后,父進(jìn)程就可以馬上關(guān)閉自己的new_socket了,當(dāng)子進(jìn)程服務(wù)完畢后,關(guān)閉子進(jìn)程中的new_socket,這樣對(duì)應(yīng)的FD才會(huì)正真關(guān)閉,此時(shí)才會(huì)觸發(fā)四次揮手。所以父進(jìn)程代碼中藍(lán)色部分new_socket.close()非常重要。
二、使用線程來實(shí)現(xiàn)并發(fā)Web Server
在第一節(jié)中,我們使用進(jìn)程來實(shí)現(xiàn)并發(fā),但是進(jìn)程對(duì)資源消耗很大,一般不推薦使用。所以這里我們使用線程來實(shí)現(xiàn)并發(fā),很簡(jiǎn)單,我們將?multiprocessing.Process 替換為 threaing.Thread就可以了:
importsocketimportreimportthreadingdefhandle_request(new_socket):#接收請(qǐng)求
recv_msg = ""recv_msg= new_socket.recv(1024).decode("utf-8")if recv_msg == "":print("recv null")
new_socket.close()return
#從請(qǐng)求中解析出URI
recv_lines =recv_msg.splitlines()print(recv_lines.__len__())#使用正則表達(dá)式提取出URI
ret = re.match(r"[^/]+(/[^ ]*)", recv_lines[0])ifret:#獲取URI字符串
file_name = ret.group(1)#如果URI是/,則默認(rèn)返回index.html的內(nèi)容
if file_name == "/":
file_name= "/index.html"
try:#根據(jù)請(qǐng)求的URI,讀取相應(yīng)的文件
fp = open("." + file_name, "rb")except:#找不到文件,響應(yīng)404
response_msg = "HTTP/1.1 404 NOT FOUND\r\n"response_msg+= "\r\n"response_msg+= "
----file not found----
"new_socket.send(response_msg.encode("utf-8"))else:html_content=fp.read()
fp.close()#響應(yīng)正確 200 OK
response_msg = "HTTP/1.1 200 OK\r\n"response_msg+= "\r\n"
#返回響應(yīng)頭
new_socket.send(response_msg.encode("utf-8"))#返回響應(yīng)體
new_socket.send(html_content)#關(guān)閉該次socket連接
new_socket.close()defmain():#創(chuàng)建TCP SOCKET實(shí)例
tcp_server_socket =socket.socket(socket.AF_INET, socket.SOCK_STREAM)## 設(shè)置重用地址
#tcp_server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
#綁定地址(默認(rèn)本機(jī)IP)和端口
tcp_server_socket.bind(("", 7890))#監(jiān)聽
tcp_server_socket.listen(128)#循環(huán)接收客戶端連接
whileTrue:
new_socket, client_addr=tcp_server_socket.accept()#啟動(dòng)一個(gè)線程來處理客戶端的請(qǐng)求
t = threading.Thread(target=handle_request, args=(new_socket,))
t.start()#關(guān)閉整個(gè)SOCKET
tcp_server_socket.close()if __name__ == "__main__":
main()
我們發(fā)現(xiàn),除了將子進(jìn)程的創(chuàng)建過程替換成了線程的創(chuàng)建過程,后面的new_socket.close()也被刪除了,這是因?yàn)榫€程是公用進(jìn)程資源的,new_socket不會(huì)被復(fù)制,所以socket對(duì)應(yīng)的FD,只有一個(gè)new_socket指向他。
如果此時(shí)我們?nèi)匀辉谶@里關(guān)閉new_socket,那么在線程再使用new_socket就會(huì)報(bào)錯(cuò)。如下信息:
Exception in thread Thread-5:
Traceback (most recent call last):
File "D:\Anaconda3_530\lib\threading.py", line 917, in _bootstrap_inner
self.run()
File "D:\Anaconda3_530\lib\threading.py", line 865, in run
self._target(*self._args, **self._kwargs)
File "D:/pycharm_project/leo1127/thread_web_server.py", line 44, in handle_request
new_socket.send(response_msg.encode("utf-8"))
OSError: [WinError 10038] 在一個(gè)非套接字上嘗試了一個(gè)操作。
三、使用協(xié)程來實(shí)現(xiàn)并發(fā)Web Server
使用進(jìn)程和線程來實(shí)現(xiàn)的并發(fā)Web Server,當(dāng)并發(fā)訪問量很大時(shí),資源消耗都很高。所以這里使用協(xié)程來實(shí)現(xiàn)并發(fā)服務(wù)器。
importsocketimportreimportgeventfrom gevent importmonkey
monkey.patch_all()defhandle_request(new_socket):#接收請(qǐng)求
recv_msg = ""recv_msg= new_socket.recv(1024).decode("utf-8")if recv_msg == "":print("recv null")
new_socket.close()return
#從請(qǐng)求中解析出URI
recv_lines =recv_msg.splitlines()print(recv_lines.__len__())#使用正則表達(dá)式提取出URI
ret = re.match(r"[^/]+(/[^ ]*)", recv_lines[0])ifret:#獲取URI字符串
file_name = ret.group(1)#如果URI是/,則默認(rèn)返回index.html的內(nèi)容
if file_name == "/":
file_name= "/index.html"
try:#根據(jù)請(qǐng)求的URI,讀取相應(yīng)的文件
fp = open("." + file_name, "rb")except:#找不到文件,響應(yīng)404
response_msg = "HTTP/1.1 404 NOT FOUND\r\n"response_msg+= "\r\n"response_msg+= "
----file not found----
"new_socket.send(response_msg.encode("utf-8"))else:html_content=fp.read()
fp.close()#響應(yīng)正確 200 OK
response_msg = "HTTP/1.1 200 OK\r\n"response_msg+= "\r\n"
#返回響應(yīng)頭
new_socket.send(response_msg.encode("utf-8"))#返回響應(yīng)體
new_socket.send(html_content)#關(guān)閉該次socket連接
new_socket.close()defmain():#創(chuàng)建TCP SOCKET實(shí)例
tcp_server_socket =socket.socket(socket.AF_INET, socket.SOCK_STREAM)## 設(shè)置重用地址
#tcp_server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
#綁定地址(默認(rèn)本機(jī)IP)和端口
tcp_server_socket.bind(("", 7890))#監(jiān)聽
tcp_server_socket.listen(128)#循環(huán)接收客戶端連接
whileTrue:
new_socket, client_addr=tcp_server_socket.accept()#啟動(dòng)一個(gè)協(xié)程來處理客戶端的請(qǐng)求
gevent.spawn(handle_request, new_socket)#關(guān)閉整個(gè)SOCKET
tcp_server_socket.close()if __name__ == "__main__":
main()
使用gevent來實(shí)現(xiàn)協(xié)程,并發(fā)處理請(qǐng)求。
四、使用單進(jìn)程單線程非阻塞模擬并發(fā)(非并行)
前面我們使用的多進(jìn)程和多線程來處理并發(fā),是因?yàn)閟ocket.recv()是阻塞的,每次accept一個(gè)連接,就需要交給一個(gè)新的進(jìn)程或線程去處理,從而不影響下一個(gè)socket連接。
但是我們可以通過單進(jìn)程單線程和非阻塞的方式來完成并發(fā)socket的處理:
importsocketdefmain():#創(chuàng)建TCP SOCKET實(shí)例
tcp_server_socket =socket.socket(socket.AF_INET, socket.SOCK_STREAM)## 設(shè)置重用地址
#tcp_server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
#綁定地址(默認(rèn)本機(jī)IP)和端口
tcp_server_socket.bind(("", 7890))#監(jiān)聽
tcp_server_socket.listen(128)#將accept設(shè)置為非阻塞,這里設(shè)置一次,后面不管調(diào)多少次accept都是非阻塞的
tcp_server_socket.setblocking(False)#定義一個(gè)列表,將每次連接的socket加入該列表
client_socket_list =list()#循環(huán)接收客戶端連接
whileTrue:try:
new_socket, client_addr=tcp_server_socket.accept()exceptException as ret:#當(dāng)沒有客戶端鏈接的時(shí)候,拋出異常
pass
else:#當(dāng)有客戶端鏈接的時(shí)候
#將new_socket.recv()設(shè)置為非阻塞的
new_socket.setblocking(False)#將new_socket加入列表
client_socket_list.append(new_socket)#遍歷socket列表,檢查每一個(gè)socket是否有數(shù)據(jù)到達(dá),或者客戶端是否斷開
for client_socket inclient_socket_list:try:
recv_content=client_socket.recv(1024)exceptException as ret:#異常,表示該客戶端沒有發(fā)數(shù)據(jù)過來
pass
else:#正常,表示客戶端發(fā)了數(shù)據(jù),或者客戶端斷開連接(斷開連接會(huì)導(dǎo)致recv正常返回)
ifrecv_content:#有數(shù)據(jù),調(diào)用請(qǐng)求處理代碼
print("處理請(qǐng)求")else:#客戶端斷開時(shí),服務(wù)器也關(guān)閉連接
client_socket.close()#將已關(guān)閉的鏈接提出列表
client_socket_list.remove(client_socket)#關(guān)閉整個(gè)SOCKET
tcp_server_socket.close()
上面代碼只有主干部分(省略了請(qǐng)求處理部分),主要是說明在單進(jìn)程單線程情況下,如何將accept和recv分開,并且都用非阻塞的方式來處理,這樣每次查看是否有客戶端鏈接進(jìn)來的時(shí)候,都會(huì)去檢查所有已鏈接的socket是否有數(shù)據(jù)發(fā)送過來。
注意:socket.recv(1024)一定要給參數(shù),讀取的字節(jié)數(shù)。否則會(huì)一直報(bào)異常。
在這種方式中,我們使用單進(jìn)程單線程模擬了并發(fā)處理socket連接的功能,但這些socket連接的處理不是并行的。當(dāng)一個(gè)socket處理數(shù)據(jù)時(shí)間比較長(zhǎng)時(shí),也會(huì)造成整個(gè)程序的等待。
五、短連接和長(zhǎng)連接
在第四節(jié)中,我們使用單進(jìn)程單線程非阻塞的形式實(shí)現(xiàn)了并發(fā)處理socket連接。其中省略了實(shí)際處理的部分,我們將其補(bǔ)充上:
importsocketimporttimeimportredefhandle_request(new_socket, recv_msg):#從請(qǐng)求中解析出URI
recv_lines =recv_msg.splitlines()#使用正則表達(dá)式提取出URI
ret = re.match(r"[^/]+(/[^ ]*)", recv_lines[0])ifret:#獲取URI字符串
file_name = ret.group(1)#如果URI是/,則默認(rèn)返回index.html的內(nèi)容
if file_name == "/":
file_name= "/index.html"
try:#根據(jù)請(qǐng)求的URI,讀取相應(yīng)的文件
fp = open("." + file_name, "rb")except:#找不到文件,響應(yīng)404
response_msg = "HTTP/1.1 404 NOT FOUND\r\n"response_msg+= "\r\n"response_msg+= "
----file not found----
"new_socket.send(response_msg.encode("utf-8"))else:html_content=fp.read()
fp.close()
response_body=html_content#響應(yīng)正確 200 OK
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#返回響應(yīng)數(shù)據(jù)
new_socket.send(response)defmain():#創(chuàng)建TCP SOCKET實(shí)例
tcp_server_socket =socket.socket(socket.AF_INET, socket.SOCK_STREAM)## 設(shè)置重用地址
#tcp_server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
#綁定地址(默認(rèn)本機(jī)IP)和端口
tcp_server_socket.bind(("", 7890))#監(jiān)聽
tcp_server_socket.listen(128)#將accept設(shè)置為非阻塞,這里設(shè)置一次,后面不管調(diào)多少次accept都是非阻塞的
tcp_server_socket.setblocking(False)#定義一個(gè)列表,將每次連接的socket加入該列表
client_socket_list =list()#循環(huán)接收客戶端連接
whileTrue:
time.sleep(0.5)try:
new_socket, client_addr=tcp_server_socket.accept()exceptException as ret:#當(dāng)沒有客戶端鏈接的時(shí)候,拋出異常
pass
else:print("一個(gè)新的客戶端連接。。。。")#當(dāng)有客戶端鏈接的時(shí)候
#將new_socket.recv()設(shè)置為非阻塞的
new_socket.setblocking(False)#將new_socket加入列表
client_socket_list.append(new_socket)print(client_socket_list.__len__())#遍歷socket列表,檢查每一個(gè)socket是否有數(shù)據(jù)到達(dá),或者客戶端是否斷開
for client_socket inclient_socket_list:try:
recv_content= client_socket.recv(1024).decode("utf-8")exceptException as ret:#異常,表示該客戶端沒有發(fā)數(shù)據(jù)過來
pass
else:#正常,表示客戶端發(fā)了數(shù)據(jù),或者客戶端斷開連接(斷開連接會(huì)導(dǎo)致recv正常返回)
ifrecv_content:#有數(shù)據(jù),調(diào)用請(qǐng)求處理代碼
handle_request(client_socket, recv_content)else:#recv正常返回,且數(shù)據(jù)為空,表示客戶端斷開了鏈接
#將該socket踢出列表
client_socket_list.remove(client_socket)#服務(wù)器也關(guān)閉連接
client_socket.close()#關(guān)閉整個(gè)SOCKET
tcp_server_socket.close()if __name__ == "__main__":
main()
特別注意的是,在請(qǐng)求處理函數(shù)handle_request中,我們將請(qǐng)求內(nèi)容作為參數(shù)一并傳遞進(jìn)去。然后在返回200 OK的時(shí)候,在響應(yīng)頭中添加了Content-Length字段,這個(gè)字段用于告訴客戶端,此次發(fā)送的響應(yīng)體有多大。當(dāng)客戶端收完指定大小的數(shù)據(jù),就認(rèn)為這次服務(wù)器發(fā)送的數(shù)據(jù)已經(jīng)發(fā)送完畢。他就可以繼續(xù)發(fā)送下一個(gè)新的請(qǐng)求。
在handle_request中可以看到,new_socket.close()已經(jīng)被刪除,也就是說服務(wù)器不會(huì)自動(dòng)關(guān)閉連接,而直到客戶端斷開連接之前,服務(wù)器都保持長(zhǎng)連接。斷開連接由客戶端來發(fā)起。
總結(jié)
以上是生活随笔為你收集整理的python server send event_[Python之路] 多种方式实现并发Web Server的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 为什么电网智慧工程首页没有了?
- 下一篇: vba 在光标插入文字_VBA学习入门方