1. 程式人生 > >深入淺出網路程式設計與Swoole核心

深入淺出網路程式設計與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網路程式設計對系統進行優化的方式。

總結

下圖為吳老師分享的內容的關鍵詞總結。


原文連結
本文為雲棲社群原創內容,未經