python非阻塞多線程socket_Python實現web服務器之 單進程單線程非阻塞實現并發及其原理...

在Python實現web服務器入門學習多進程、多線程實現并發HTTP服務器中,我們知道可以分別通過多進程、多線程的方式實現并發服務器,那么,是否可以通過單進程單線程的程序實現類似功能呢?

實際上,在Python多任務學習分別通過yield關鍵字、greenlet以及gevent實現多任務中,我們知道gevent可以通過協程的方式實現多任務,且相較于yield關鍵字和greenlet而言,gevent屏蔽了很多實現細節,使用起來簡單方便。

一、gevent實現并發HTTP服務器

下面代碼以gevent實現并發HTTP服務器(即一種單進程、單線程、非阻塞的方式):

from gevent import monkey

import gevent

import socket

import re

monkey.patch_all()

def serve_client(new_client_socket):

"""為這個客戶端返回數據"""

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

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

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

request_lines = request.splitlines()

print(request_lines)

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

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格式的應答數據給瀏覽器

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 讀取發送給瀏覽器的數據-->body

html_content = f.read()

f.close()

# 9.2 準備發送給瀏覽器的數據-->header

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

response += "\r\n"

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

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

# 將response body發送給瀏覽器--直接是以字節形式發送

new_client_socket.send(html_content)

# 10. 關閉此次服務的套接字

new_client_socket.close()

def main():

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

# 1.創建套接字

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

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

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

# 2.綁定端口

tcp_server_socket.bind(("", 7899))

# 3.變為監聽套接字

tcp_server_socket.listen(128)

while True:

# 4.等待新客戶端連接

new_client_socket, client_addr = tcp_server_socket.accept()

# 5.為連接上的客戶端服務

# 創建一個greenlet并不會導致其立即得到切換執行,

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

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

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

greenlet = gevent.spawn(serve_client, new_client_socket)

# 關閉監聽套接字

tcp_server_socket.close()

if __name__ == "__main__":

main()

至此,本文和Python實現web服務器入門學習筆記(3)——多進程、多線程實現并發HTTP服務器給出了三種實現并發HTTP服務器的方式,對比之后,可以發現:

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

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

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

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

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

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執行完畢之后才退出

greenlet.join()

if __name__ == '__main__':

main()

上述代碼的運行結果為:

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()

上述代碼運行結果為:

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!

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

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

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

二、單進程單線程非阻塞實現并發原理

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

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

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

基于上述討論,下面代碼簡單演示了單進程單線程非阻塞實現并發的原理:

import socket

import time

def initialize(port):

# 1.創建服務器端TCP協議socket

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

# 2.綁定本地IP和端口

tcp_server_sock.bind(("", port))

# 3.設置套接字為監聽狀態

tcp_server_sock.listen(128)

# 4.設置套接字為非阻塞狀態

tcp_server_sock.setblocking(False)

return tcp_server_sock

def non_blocking_serve(tcp_server_sock):

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

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:

# 表明客戶端發來了數據

print(recv_data)

else:

# 客戶端已調用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()

對于上述代碼,需要說明的幾點是:

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

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

客戶端將數據正確發送了過來,此時recv_data變量非空;

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

程序45行將已經完成請求的客戶端移出列表client_sock_list,避免列表過長產生無效遍歷,導致程序性能下降。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/446033.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/446033.shtml
英文地址,請注明出處:http://en.pswp.cn/news/446033.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

企業知識庫與知識管理:如何統一戰略與實踐

在知識密集型的現代企業中,知識已經成為了一種寶貴的資產。如何有效地管理和利用這一資產,成為企業持續發展與創新的關鍵。企業知識庫與知識管理作為知識經濟的兩大支柱,它們的重要性不言而喻。但很多時候,我們發現企業的知識管理…

音視頻處理 ffmpeg中級開發 AAC編碼

介紹 編碼流程類似于視頻編碼,1,查找編碼器;2,設定參數,打開編碼器;3,數據編碼編碼函數 avcodec_encode_audio2 已經被棄用FFmpeg 過時 Api 匯總整理 - 灰色飄零 - 博客園 未成功使用 舊版本i…

虛擬機為Ubuntu分配空間

當虛擬機里面的創建的ubuntu鏡像需要更大的空間,將ubuntu關掉之后,對應調整硬盤的空間大小,由先前的20G上調至50G,但是先前的20G內存空間映射的位置是/dev/sda,后面增加的這段內存空間30G映射到/dev/sda1因此&#xff…

為什么人會擺高姿態_Yo , 你為什么喜歡沖浪?

“你為什么喜歡沖浪?” 那天木木突然問我。我愣住了。此時一道碧波恰從防潑堤(jetty)的那頭升起,木木轉頭望去,視線追著那道浪緩緩向西,直至它破碎成白色的浪花。我瞥見他眼神中的光亮,就和小孩…

音視頻處理 ffmpeg初級開發 命令行工具-實用命令

參考鏈接 ffmpeg Documentation作者:smallest_one 鏈接:FFmpeg命令行工具-實用命令 - 簡書 目錄 1,help命令使用 1.1 ffmpeg命令的語法結構1.2 獲取詳細的help信息1.3 打印幫助或者支持能力的信息1.4 全局選項1.5 文件選項1.6 視頻/音頻/字…

不同的電腦打印預覽不同怎么解決_條碼打印軟件中標簽預覽正常打印無反應怎么解決...

在使用條碼打印軟件制作標簽時,有客戶反饋,標簽打印預覽正常的,但是打印無反應,咨詢是怎么回事?今天針對這個情況,可以參考以下方法進行解決。一、預覽正常情況下,打印沒反應(1)在條碼打印軟件中設計好標簽之后&#…

python安裝scrapy_Python安裝Scrapy的種種

這幾天沒什么事,決定把自己抓代理的小工具用scrapy改寫。然而安裝的時候卻出現以下問題,反復失敗:Unable to find vcvarsall.bat經過一番查找,找到了這個文件:\Lib\distutils\_msvccompiler.py它里邊長這樣&#xff1a…

MP4文件格式的相關內容

參考鏈接 FFmpeg中mp4的demuxer(mov.c)代碼閱讀 - 簡書mp4文件格式解析 - 簡書mp4封裝格式各box類型講解及IBP幀計算_青丶空゛的博客-CSDN博客5分鐘入門MP4文件格式 - 程序猿小卡 - 博客園?關于M4A文件的隨機訪問 - 云社區 - 騰訊云 MP4文件格式相關內容 MP4文件由許多box組…

華三交換機如何進入配置_學校機房項目交換機的如何配置,理解這篇,交換機配置不再難...

弱電項目中,交換機的配置是無法避免的,大部分的項目都有可能會涉及到,尤其是機房等網絡項目,本期我們就通過一個實際項目案例來詳細了解交換機在項目中的應用配置,如果我們平時對交換機配置不熟,這個案例可…

百度地圖遷徙大數據_百度地圖大數據:五一高速擁堵不似預期,廣深成熱門遷出入地...

五一假期在即,你是否做好了“出行功課”?高速擁堵水平降低、公眾出門不出城、公園成踏青賞景熱門目的地……在全國疫情防控仍未松懈的時刻,2020年的五一或許注定與往年不同。近日,百度地圖發布2020五一假期安全出行大數據&#xf…

音視頻的基礎知識 視頻播放器原理/封裝格式/視頻音頻編碼數據/視頻像素數據/音頻采樣數據

參考鏈接 FFMpeg視頻播放器的制作-雷霄驊(去除電流音版本)_嗶哩嗶哩_bilibili 視頻播放器原理 播放視頻文件的流程YUV是一張屏幕中像素點的數值封裝格式 MP4 RMVB TS FLV AVI將視頻和音頻碼流按照一定的格式存儲在一個文件中封裝格式分析工具&#xf…

科立捷7代寫頻軟件_天大廈大“兩碩士論文雷同”通報,代寫買賣論文

澎湃新聞記者 薛莎莎天津大學、廈門大學7月10日晚就“兩碩士論文雷同”一事,分別發出調查處理通報。通報稱,涉事兩名學生存在由他人代寫、買賣論文的學術作假的行為,均撤銷其所獲碩士學位,收回、注銷碩士學位證書。澎湃新聞注意到…

FFMpeg命令行基礎

參考鏈接 FFMpeg視頻播放器的制作-雷霄驊(去除電流音版本)_嗶哩嗶哩_bilibili音視頻處理 ffmpeg初級開發 命令行工具-實用命令_MY CUP OF TEA的博客-CSDN博客 介紹 FFMpeg是視頻播放和轉碼的內核 使用 win中ffmpeg.exe用于視頻轉碼簡單命令&#xff1…

悲觀鎖和樂觀鎖_面試必備之樂觀鎖與悲觀鎖

何謂悲觀鎖與樂觀鎖樂觀鎖對應于生活中樂觀的人總是想著事情往好的方向發展,悲觀鎖對應于生活中悲觀的人總是想著事情往壞的方向發展。這兩種人各有優缺點,不能不以場景而定說一種人好于另外一種人。大家可以點擊加群【JAVA架構知識學習討論群】47398464…

Microsoft Visual Studio2019環境下搭建FFmpeg開發環境

參考鏈接 《基于 FFmpeg SDL 的視頻播放器的制作》課程的視頻_雷霄驊的博客-CSDN博客_雷霄驊ffmpeg視頻教程小學期課程資料 - 基于FFmpegSDL的視頻播放器的制作.zip_免費高速下載|百度網盤-分享無限制輔助參考鏈接使用VS2019創建項目,添加文件和庫地址_MY CUP OF …

vue process.env獲取不到_從文檔開始,重學vue(下)源碼級別

此篇文章主要是從應用及源碼層面講解vue部分常用api,閱讀起來可能略有難度,新手可以看《從文檔開始,重學vue(上)》示例代碼均在vue-cli3中完成Vue.extend()可以使用 extend 創建一個子類,該方法通常用于構建全局組件,如彈框組件等,下面我們就用它來制作個全局alert組件吧首先我…

Microsoft Visual Studio2019環境下搭建SDL開發環境

參考鏈接 《基于 FFmpeg SDL 的視頻播放器的制作》課程的視頻_雷霄驊的博客-CSDN博客_雷霄驊ffmpeg視頻教程小學期課程資料 - 基于FFmpegSDL的視頻播放器的制作.zip_免費高速下載|百度網盤-分享無限制輔助參考鏈接VS自動鏈接到Windows上隨vcpkg安裝的SDL2庫 | 碼農俱樂部 - G…

不關注公眾號可以獲取openid嗎_微信公眾號粉絲遷移

目錄 [toc] 微信公眾號遷移 正常的公眾號遷移直接通過微信操作就可以,如下圖。但是因為udb數據里面存的是遷移前公眾號的openid以及unionid,需要自行獲取新舊openid以及unionid。 舊的用戶信息要在遷移之前獲取,第三步點擊同意之后就公眾號的接口就調不通…

建筑專業規范大全 2020版_房屋建筑工程現行規范標準目錄匯編(2020版)—建筑電氣...

房屋建筑工程現行規范標準目錄匯編(2020版)建筑電氣規范編號規范名稱GB 50034-2013建筑照明設計標準GB 50052-2009供配電系統設計規范GB 50053-201320kV及以下變電所設計規范GB 50057-2010建筑物防雷設計規范GB 50147-2010電氣裝置安裝工程 高壓電器施工及驗收規范GB 50148-201…

基于Microsoft Visual Studio2019環境編寫ffmpeg視頻解碼代碼

舊代碼 舊代碼使用了很多過時的API,這些API使用后,vs會報編譯器警告 (級別 3) C4996的錯誤即 函數被聲明為已否決 報 C4996的錯誤 // test_ffmpeg.cpp : 此文件包含 "main" 函數。程序執行將在此處開始并結束。 // #define SDL_MAIN_HANDLED …