深入浅出网络编程与Swoole内核
在阿里云PHP技術沙龍專場中,阿里云邀請到php-nsq作者,pecl、Swoole開發組成員吳振宇分享了Swoole進程模型的原理與Swoole協程實現的原理。并結合具體開發案例講解了Swoole在網絡編程中的應用。
本次直播視頻精彩回顧,戳這里!
直播回顧:https://yq.aliyun.com/live/965
PPT分享:https://yq.aliyun.com/download/3528
以下內容根據演講嘉賓視頻分享以及PPT整理而成。
Socket編程
網絡編程又可稱為Socket編程。編程分為基于Server端開發與基于Client端開發兩部分。基于Server端的編程由四大步驟組成,開發者首先創建Socket,利用bind與listen函數綁定監聽地址及相應的端口,最后使用accept函數接受來自監聽端的請求。Client端的操作較為簡便,開發者在創建Socket后使用connect函數對服務器端進行連接即可實現。
下圖所示為Client端與Server端的協作示意圖。Client端首先向Server端發起帶有SYN標識的握手請求,Server端接受到請求后,返回給Client端帶有SYN與ACK標識的請求并將Client端中的RCVD文件加載至隊列中,在三次握手完成之后,該文件描述符將被添加至accept隊列中等待下一步邏輯處理。
下圖所示為Socket編程的實現代碼
在Socket編程中,Socket的讀寫狀態判斷十分重要。Socket可讀條件分為以下四條:
- 該套接字接收緩沖區中的數據字節數大于等于套接字接收緩存區低水位。
- 該連接的讀半部關閉(也就是接收了FIN的TCP連接)。
- 有新鏈接到達可讀,該套接字是一個listen的監聽套接字,并且目前已經完成的連接數不為0。
- 有一個Socket有異常錯誤條件待處理.對于這樣的Socket讀操作將不會阻塞,并且返回一個錯誤(-1),errno則設置成明確的錯誤條件。
以上條件中,第一條件與第三條件較為重要。對于TCP和UDP套接字而言,緩沖區低水位的值默認為1,在默認情況下,緩沖區中的數據均為可讀。當為Socket收到connect請求,執行了三次握手的第一步接收SYN請求后,Socket便處于可讀狀態。對這樣的套接字進行accept操作通常不會阻塞。
對應于Socket可讀條件的判斷,Socket可寫條件也分為以下四條:
- 該套接字發送緩沖區中的可用空間字節數大于等于套接字發送緩存區低水位標記時,并且該套接字已經成功連接。
- 該連接的寫半部關閉。
- 使用非阻塞的connect套接字已建立連接,或者connect已經以失敗告終。
- 有一個錯誤的套接字待處理。
下圖舉了生活中與網絡阻塞類似的生活事例來展示該過程。在用戶到手機店修手機的過程中,用戶在手機店不做任何事,等待老板將手機修好類似于網絡同步阻塞過程;用戶在店中做些其他工作,不時詢問老板手機是否修好類似于同步非阻塞過程;用戶回到家中,等待手機店老板修好后的電話類似于異步阻塞過程;用戶回到家中做其他事情,等待老板修好后的電話類似于多路IO 復用、異步非阻塞過程。
在一款應用開發初期,應用的用戶不多,服務器相對的要求同樣不高,此時開發者可以使用多進程策略進行應用的開發,以此加快開發效率。下圖所示為多進程同步阻塞開發的偽代碼。
當業務量擴大,系統需要進行優化時,開發者可以對每個子進程中的套接字進行監聽,其偽代碼如下圖所示。
IO復用與Reactor
當系統的用戶及業務量擴大到一定規模時,開發者可以使用多路IO復用、Reactor及異步非阻塞等方法對系統進行改進。如下圖所示,在這些系統調用中,Select方法存在內存開銷大,支持文件描述符數量少的缺點。目前Epoll系統調用方式占據開發的主流位置,Epoll方式采用了紅黑樹的數據結構模式,同時擁有就緒列表rdlist,當套接字中存在可讀或可寫的事件時,該事件將被直接添加到就緒列表當中,從而使系統省去了輪詢所有套接字屬性的過程,提高了系統的執行效率。
(1)操作系統調度原理
操作系統進程調用時分為正在運行,阻塞運行及等待運行三個狀態。在處理進程的過程中,內核會不斷發生中斷,比如三次握手過程中,當ACK發送時,內核會觸發中斷,系統此時需要放下正在執行的任務,去處理TCP的任務。處理完成后,系統結束中斷處理并恢復運行被打斷的進程。下圖所示為操作系統進程調度的一些方法。
在三次握手中,系統執行以下三個步驟完成操作系統的調度:
1.網卡收到數據:網卡收到SYN消息,觸發內核中斷,系統將直接打斷當前執行的進程,同時CPU將會把套接字加入到Socket Queue隊列當中進行存儲。
2.中斷回調:若當前沒有新的連接,accept將阻塞到系統調用上,并將套接字注冊到Wait Queue上。
3.系統中斷回調:當新的連接產生時,Wait Queue隊列將觸發回調函數,將相應數據加載至rdlist列表中。
若網卡收到ACK消息,則繼續觸發內核中斷,內核完成標準的三次握手,將連接從半連接隊列移入連接隊列,于是 listen Socket有可讀事件,內核調用listen Socket的Wait Queue的喚醒回調函數,將之前阻塞的accept進程置為 Ready調度狀態。
(2)Epoll的在調度中的作用
Epoll主要用來監聽Socket的可讀可寫過程,在Epoll創建時,開發者需要傳對應文件描述符EPOLLIN與EPOLLOUT作為可讀與可寫的參數標志,epoll_wait函數擁有accept的功能,會在事件發送后提醒開發者。下圖羅列了Epoll中的參數與主要方法。
?
將Socket創建與accept過程轉化為Epoll的代碼示意圖如下所示。首先將fd作為描述符加入創建好的Epoll中,同時把開發者想要監聽的可讀可寫事件也注冊入Epoll之中。當listen fd監聽到事件時,使用accept方法將該fd描述符設為可讀事件,并再次將其加入到Epoll的監聽數組中,此時代表真正的客戶端連接已接入。
Swoole進程模型與Reactor
Reactor模型的創建與使用較為簡單,其中含有以下四個方法:
- Add方法:添加一個Socket到Reactor之中。
- Set方法:修改Socket對應的事件,如可讀可寫事件等。
- Del方法:從Reactor中移除相應的對象。
- Callback方法:事件發生后回調指定的函數方法。
Swoole目前使用較多的模式為單線程模式與進程模式。在單線程模式中,系統使用Worker監聽accept與鏈接,當Worker掛掉后會對系統產生一些影響。進程模式的Swoole解決了這些問題。下圖為兩種模式的詳細對比。
在進程模式中,系統采用MainReactor線程監聽accept,線程將出現的問題拋給Worker進程進行處理,這樣即使單個Worker進程掛掉也不會對系統產生任何的影響。下圖所示為進程模式的系統結構示意圖。
下圖展示了對Swoole模式的調用代碼示意。在用戶使用客戶端去連接服務器的過程中,系統首先注冊可讀可寫與超時三個狀態回調函數。客戶端與服務器連接成功時,套接字變為可寫狀態,系統調用可寫狀態的回調函數,在回調函數中處理相關的數據。
Swoole協程實現原理
Swoole協程是由事件驅動與棧切換兩步共同實現完成的。
在C語言環境中,事件的調用往往使用堆棧進行處理。在堆棧中,指針EBP指向堆棧棧底,指針ESP指向堆棧棧頂,在函數調用之后,每個EBP的返回值會返回上一個EBP的地址。以此來進行事件調用的檢索。下圖所示為C語言中的事件調用示意圖。
在PHP中的函數調用步驟如下圖所示。PHP首先通過詞法分析與語法分析將代碼編譯成語法樹,語法樹中的每一個語法會被編譯入opcode,語法中的每一個函數會以oparray的形式存入結構體EG中,EG結構體使用函數表對這些函數進行存儲。
當函數調用時,結構體中的call對應指針ESP,prev對應于指針EBP。當用戶調取函數時,系統會向zend VM中為每一個方法申請一個堆棧的內存。當系統中一個函數調用其他函數時,會調用code下方儲存的地址,調用方法的opcode從function存儲的成員中找到并進行編譯與執行。當觸發了opcode后,系統會申請一個新的內存來進行新的內存分配。下圖為PHP調用示意圖。
下圖所示為在PHP函數調用中壓棧的過程及函數中存在的opcode。FCALL與DO_FCALL負責函數的調用,當堆棧中第一個opcode執行時,將進行參數壓棧的操作。觸發函數調用時,將執行DO_FCALL操作,系統將會把下一個函數的調用地址壓入堆棧。當調用有結果后系統會將返回值返回入CALL FRAME中。
下圖所示為Swoole協程代碼。協程代碼包括兩個執行網絡IO操作的go函數,當系統執行connect操作時觸發網絡IO操作,并將當前的PHP調用棧先保存起來。在當前調用棧保存好后,系統順次執行下面的函數調用。當connect遇到IO函數時,系統會跳出當前任務去執行堆棧中儲存的任務。
在Swoole2.0中使用C函數進行線程任務的協程。當開發者調用setjmp時,函數的返回值為0并調起first函數。當調用longjmp時,setjmp也同樣被調起,此時返回值為1。Swoole2.0利用該代碼實現了PHP執行的跳轉,代碼示意圖如下。
Swoole2.0協程時序圖與代碼展示如下圖所示。setjump方法設置當前函數堆棧,當有網絡事件產生時,系統將首先對產生的事件進行注冊,并在有事件通知時跳回執行中的代碼,以此完成代碼協程過程。
Swoole4.0通過實現C堆棧對Swoole2.0中的問題進行了改進。在Swoole4.0中用戶直接調用MySQL中的鏈接直接就可以形成網絡協程。下圖所示為Swoole4.0內核系統架構示意圖。
Swoole4.0的時序調度與Swoole2.0差別不大,不同的是Swoole4.0使用匯編指令對C棧與堆棧進行了存儲。在協程創建時,系統會產生C棧與PHP棧,兩個堆棧間會進行通信,通過這種方法解決了C棧銷毀后的一些問題。下圖展現了Swoole4.0的時序圖。
當系統鏈接數量增多后會出現一些問題,開發者通過設置心跳參數與心跳收回可以保證系統服務器的資源不會被浪費。下圖列舉了Swoole網絡編程對系統進行優化的方式。
總結
下圖為吳老師分享的內容的關鍵詞總結。
原文鏈接
本文為云棲社區原創內容,未經允許不得轉載。
總結
以上是生活随笔為你收集整理的深入浅出网络编程与Swoole内核的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: K8s中Pod健康检查源代码分析
- 下一篇: 阿里云安全肖力:云的六大安全基因助力企业