日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 编程语言 > python >内容正文

python

python非阻塞多线程socket_Python实现web服务器之 单进程单线程非阻塞实现并发及其原理...

發(fā)布時間:2023/12/13 python 29 豆豆
生活随笔 收集整理的這篇文章主要介紹了 python非阻塞多线程socket_Python实现web服务器之 单进程单线程非阻塞实现并发及其原理... 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

在Python實現(xiàn)web服務(wù)器入門學(xué)習(xí)多進(jìn)程、多線程實現(xiàn)并發(fā)HTTP服務(wù)器中,我們知道可以分別通過多進(jìn)程、多線程的方式實現(xiàn)并發(fā)服務(wù)器,那么,是否可以通過單進(jìn)程單線程的程序?qū)崿F(xiàn)類似功能呢?

實際上,在Python多任務(wù)學(xué)習(xí)分別通過yield關(guān)鍵字、greenlet以及gevent實現(xiàn)多任務(wù)中,我們知道gevent可以通過協(xié)程的方式實現(xiàn)多任務(wù),且相較于yield關(guān)鍵字和greenlet而言,gevent屏蔽了很多實現(xiàn)細(xì)節(jié),使用起來簡單方便。

一、gevent實現(xiàn)并發(fā)HTTP服務(wù)器

下面代碼以gevent實現(xiàn)并發(fā)HTTP服務(wù)器(即一種單進(jìn)程、單線程、非阻塞的方式):

from gevent import monkey

import gevent

import socket

import re

monkey.patch_all()

def serve_client(new_client_socket):

"""為這個客戶端返回數(shù)據(jù)"""

# 6.接收瀏覽器發(fā)送過來的http請求

request = new_client_socket.recv(1024).decode("utf-8")

# 7.將請求報文分割成字符串列表

request_lines = request.splitlines()

print(request_lines)

# 8.通過正則表達(dá)式提取瀏覽器請求的文件名

file_name = None

ret = re.match(r"^[^/]+(/[^ ]*)", request_lines[0])

if ret:

file_name = ret.group(1)

print("file_name:", file_name)

if file_name == "/":

file_name = "/index.html"

# 9.返回http格式的應(yīng)答數(shù)據(jù)給瀏覽器

try:

f = open("./Charisma" + file_name, "rb")

except Exception:

response = "HTTP/1.1 404 NOT FOUND\r\n"

response += "\r\n"

response += "-----file not found-----"

new_client_socket.send(response.encode("utf-8"))

else:

# 9.1 讀取發(fā)送給瀏覽器的數(shù)據(jù)-->body

html_content = f.read()

f.close()

# 9.2 準(zhǔn)備發(fā)送給瀏覽器的數(shù)據(jù)-->header

response = "HTTP/1.1 200 OK\r\n"

response += "\r\n"

# 將response header發(fā)送給瀏覽器--先以utf-8格式編碼

new_client_socket.send(response.encode("utf-8"))

# 將response body發(fā)送給瀏覽器--直接是以字節(jié)形式發(fā)送

new_client_socket.send(html_content)

# 10. 關(guān)閉此次服務(wù)的套接字

new_client_socket.close()

def main():

"""用來完成程序整體控制"""

# 1.創(chuàng)建套接字

tcp_server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

# 通過設(shè)定套接字選項解決[Errno 98]錯誤

tcp_server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)

# 2.綁定端口

tcp_server_socket.bind(("", 7899))

# 3.變?yōu)楸O(jiān)聽套接字

tcp_server_socket.listen(128)

while True:

# 4.等待新客戶端連接

new_client_socket, client_addr = tcp_server_socket.accept()

# 5.為連接上的客戶端服務(wù)

# 創(chuàng)建一個greenlet并不會導(dǎo)致其立即得到切換執(zhí)行,

# 還需要在其父greenlet(在哪個程序控制流中創(chuàng)建該greenlet,

# 則這個程序控制流就是父greenlet)中遇到正確的阻塞延時類操作或調(diào)用greenlet對象的join()方法

#(此處不需要使用join()函數(shù),因為主程序由于死循環(huán)的緣故不會在greenlet執(zhí)行結(jié)束前退出)

greenlet = gevent.spawn(serve_client, new_client_socket)

# 關(guān)閉監(jiān)聽套接字

tcp_server_socket.close()

if __name__ == "__main__":

main()

至此,本文和Python實現(xiàn)web服務(wù)器入門學(xué)習(xí)筆記(3)——多進(jìn)程、多線程實現(xiàn)并發(fā)HTTP服務(wù)器給出了三種實現(xiàn)并發(fā)HTTP服務(wù)器的方式,對比之后,可以發(fā)現(xiàn):

對于進(jìn)程、線程實現(xiàn)方式,服務(wù)器都是在客戶端數(shù)量大于1時開辟新的程序執(zhí)行控制流(分別叫子進(jìn)程、子線程),且程序控制流之間一般相對獨(dú)立,不會因為一個的阻塞而導(dǎo)致其他程序控制流無法執(zhí)行,以避免在單進(jìn)程且單線程的程序中,先建立請求的客戶端因各種原因引起程序阻塞而導(dǎo)致其他客戶端的請求得不到執(zhí)行。如:上述通過進(jìn)程和線程實現(xiàn)并發(fā)服務(wù)器程序中的accept()、recv()方法均為阻塞類操作。

對于協(xié)程實現(xiàn)方式,服務(wù)器為實現(xiàn)并發(fā),雖然也會開辟新的程序執(zhí)行控制流(這里叫g(shù)reenlet,程序執(zhí)行控制流可以承擔(dān)很多身份,比如:進(jìn)程、線程、greenlet,關(guān)于greenlet的詳細(xì)說明,具體請見Python多任務(wù)學(xué)習(xí)筆記(10)——分別通過yield關(guān)鍵字、greenlet以及gevent實現(xiàn)多任務(wù)),但是這些程序執(zhí)行控制流之間通過確定的時序作相互間切換實現(xiàn)并發(fā),切換時機(jī)為程序中所有延時阻塞類操作的地方,指定程序控制流切換執(zhí)行時機(jī)的方式有兩種:

程序規(guī)模很小時,對于所有延時阻塞類操作,如:time.sleep(),socket.accept(),socket.recv(),使用gevent模塊中的同名操作做替換,如:gevent.sleep(),gevent.accept(),gevent.recv();

程序規(guī)模很大,且多處使用了涉及延時阻塞類操作時,不用挨個做模塊替換,通過gevent.monkey模塊中的patch_all()函數(shù)改變所有的阻塞類操作的行為,使得每當(dāng)程序遇到阻塞類操作則切換至其他greenlet。

對于協(xié)程實現(xiàn)方式,在主程序執(zhí)行控制流中,通過gevent.spawn()這一類方法創(chuàng)建一個greenlet之后,主程序執(zhí)行控制流自動成為該greenlet的父greenlet,如果程序中僅存在這兩個greenlet,則程序也會在遇到正確的阻塞延時類操作時,在二者之間切換執(zhí)行,請比較下面兩段代碼:

1. 程序未正確指定阻塞延時類操作:

import gevent

import time

def foo():

print('Explicit context switch to foo!')

gevent.sleep(0.0)

print('Explicit context switch to foo again!')

def main():

greenlet = gevent.spawn(foo)

print('Explicit execution in main!')

time.sleep(0.0)

print('Explicit context switch to main again!')

time.sleep(0.0)

print("The end of main!")

# 確保主程序(即主greenlet)等待子greenlet執(zhí)行完畢之后才退出

greenlet.join()

if __name__ == '__main__':

main()

上述代碼的運(yùn)行結(jié)果為:

Explicit execution in main!

Explicit context switch to main again!

The end of main!

Explicit context switch to foo!

Explicit context switch to foo again!

2. 程序正確指定了阻塞延時類操作:

import gevent

def foo():

print('Explicit context switch to foo!')

gevent.sleep(0.0)

print('Explicit context switch to foo again!')

def main():

greenlet = gevent.spawn(foo)

print('Explicit execution in main!')

gevent.sleep(0.0)

print('Explicit context switch to main again!')

gevent.sleep(0.0)

# greenlet.join()

print("The end of main!")

if __name__ == '__main__':

main()

上述代碼運(yùn)行結(jié)果為:

Explicit execution in main!

Explicit context switch to foo!

Explicit context switch to main again!

Explicit context switch to foo again!

The end of main!

對比上述兩段代碼,我們知道:

創(chuàng)建一個greenlet并不會導(dǎo)致其立即得到切換執(zhí)行,還需要在其父greenlet(在哪個程序控制流中創(chuàng)建該greenlet,則這個程序控制流就是父greenlet)中遇到正確的阻塞延時類操作或調(diào)用greenlet對象的join()方法;

即使不調(diào)用greenlet對象的join()方法,只要使用正確的阻塞延時類操作,程序依然可以按照期望的順序執(zhí)行完畢。

二、單進(jìn)程單線程非阻塞實現(xiàn)并發(fā)原理

實際上,從單進(jìn)程、單線程、非阻塞這幾個關(guān)鍵字就可以發(fā)現(xiàn),要想通過單進(jìn)程、單線程實現(xiàn)并發(fā),首要是要解決單進(jìn)程、單線程的程序可能面對的程序阻塞問題,因為這一般會無謂地耗費(fèi)時間。鄭州哪個人流醫(yī)院好 http://www.csyhjlyy.com/

那么,自然地,我們會想到:是否可以讓原本阻塞的操作不阻塞?答案是肯定的:對于socket對象中的accept()、recv()等方法,其原本都是阻塞類操作,可以通過調(diào)用socket對象的setblocking()方法設(shè)置其為非阻塞模式。

然而,問題在于:在將socket對象設(shè)置為非阻塞模式的情況下,在調(diào)用其accept()、recv()方法時,如果未能立刻正確返回,則程序會拋出異常。故此時需要進(jìn)行異常捕捉和處理,保證程序不被中斷。

基于上述討論,下面代碼簡單演示了單進(jìn)程單線程非阻塞實現(xiàn)并發(fā)的原理:

import socket

import time

def initialize(port):

# 1.創(chuàng)建服務(wù)器端TCP協(xié)議socket

tcp_server_sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

# 2.綁定本地IP和端口

tcp_server_sock.bind(("", port))

# 3.設(shè)置套接字為監(jiān)聽狀態(tài)

tcp_server_sock.listen(128)

# 4.設(shè)置套接字為非阻塞狀態(tài)

tcp_server_sock.setblocking(False)

return tcp_server_sock

def non_blocking_serve(tcp_server_sock):

# 定義一個列表,用于存放已成功連接但是未完成數(shù)據(jù)發(fā)送的客戶端

client_sock_list = list()

while True:

try:

new_client_sock, new_client_addr = tcp_server_sock.accept()

except Exception as exception:

print(exception)

else:

print("新客戶端", new_client_sock, "已成功連接!")

new_client_sock.setblocking(False)

client_sock_list.append(new_client_sock)

for client_sock in client_sock_list:

try:

recv_data = client_sock.recv(1024)

except Exception as exception:

print(exception)

else:

if recv_data:

# 表明客戶端發(fā)來了數(shù)據(jù)

print(recv_data)

else:

# 客戶端已調(diào)用close()方法,recv()返回為空

client_sock_list.remove(client_sock)

client_sock.close()

print("客戶端", client_sock, "已斷開連接!")

def main():

tcp_server_sock = initialize(8888)

non_blocking_serve(tcp_server_sock)

if __name__ == '__main__':

main()

對于上述代碼,需要說明的幾點(diǎn)是:

程序24行定義的列表client_sock_list用于存放已成功連接但是未完成數(shù)據(jù)發(fā)送的客戶端對象;

程序36行遍歷列表client_sock_list,挨個通過recv()方法通過非阻塞方式接收數(shù)據(jù),而recv()方法正確返回有兩種情況:

客戶端將數(shù)據(jù)正確發(fā)送了過來,此時recv_data變量非空;

客戶端完成了此次請求,主動先斷開了連接,此時recv_data為空。

程序45行將已經(jīng)完成請求的客戶端移出列表client_sock_list,避免列表過長產(chǎn)生無效遍歷,導(dǎo)致程序性能下降。

總結(jié)

以上是生活随笔為你收集整理的python非阻塞多线程socket_Python实现web服务器之 单进程单线程非阻塞实现并发及其原理...的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。