首頁  >  文章  >  php框架  >  淺析Swoole server

淺析Swoole server

coldplay.xixi
coldplay.xixi轉載
2021-03-11 09:52:081938瀏覽

淺析Swoole server

一.基礎知識

#1.1 Swoole

Swoole是一個面向生產環境的php非同步網路通訊引擎, php開發人員可以利用Swoole開發出高效能的server服務。 Swoole的server部分, 內容很多, 也涉及很多的知識點, 本文僅對其server進行簡單的概述, 具體的實現細節在後續的文章中再進行詳細介紹。

 推薦(免費):swoole

1.2 網路程式設計

##1. 網路通訊是指在一台(或多台)機器上啟動一個(或多個)進程, 監聽一個(或多個)端口, 按照某種協議(可以是標準協議http, dns; 也可以是自行定義的協議)與客戶端交換資訊。

2. 目前的網路程式設計多是在tcp, udp或更上層的協定之上進行程式設計。 Swoole的server部分是基於tcp以及udp協定的。

3. 利用udp進行程式設計較為簡單, 本文主要介紹tcp協定之上的網路程式設計

4. TCP網路程式設計主要涉及4種事件

 連線建立: 主要是指客戶端發起連線(connect)以及服務端接受連線(accept)

 訊息到達: 服務端接受到客戶端發送的資料,該事件是TCP網路程式設計最重要的事件,服務端對於該類別事件進行處理時, 可以採用阻塞式或非阻塞式,除此之外, 服務端還需要考慮分包, 應用層緩衝區等問題

 訊息傳送成功: 傳送成功是指應用層將資料成功傳送到核心的套接字發送緩衝區中,並不是指客戶端成功接受資料。對於低流量的服務而言,數據通常一次性即可發送完,並不需要關心此類事件。如果一次性無法將全部資料傳送到核心緩衝區,則需要關心訊息是否成功傳送(阻塞式程式設計在系統呼叫(write, writev, send等)傳回後即是傳送成功, 非阻塞式程式設計則需要考慮實際寫入的資料是否與預期一致)

 連線斷開: 需要考慮客戶端斷開連線(read回傳0)以及服務端斷開連線(close, shutdown)

5. tcp建立連線的過程如下圖

#● 圖中, ACK、SYN表示標誌位元, seq、ack為tcp套件的序號以及確認序號

6. tcp斷開連接的過程如下圖

 

● 上圖考慮的是客戶端主動斷開連線的情況, 服務端主動斷開連線也類似

● 圖中, FIN、ACK表示標誌位元, seq、ack為tcp包的序號以及確認序號

1.3 進程間通訊

1. 進程之間的通訊有無名管道(pipe), 有名管道(fifo), 訊號(signal), 信號量(semaphore), 套接字(socket), 共享記憶體(shared memory)等方式

2. Swoole中採用unix域套接字(套接字的一種)用於多進程之間的通訊(指Swoole內部進程之間)

 

1.4 socketpair

1. socketpair用於建立一個套接字對, 類似pipe , 不同的是pipe是單向通信, 雙向通信需要創建兩次, socketpair調用一次即可實現雙向通信, 除此之外, 由於使用的是套接字, 還可以定義數據交換的方式

2. socketpair系統呼叫

    呼叫成功後sv[0], sv[1]分別儲存一個檔案描述子
  • 向sv[0]寫入, 可以從sv[1]讀取
  • 向sv[1]寫入, 可以從sv[0]讀取
  • 進程呼叫socketpair後, fork子進程, 子進程會預設繼承sv[0], sv[1]這兩個檔案描述子, 進而可以實現父子進程間通訊。例如, 父進程向sv[0]寫入, 子程序從sv[1]讀取; 子程序向sv[1]寫入, 父進程從sv[0]讀取

1.5 守護程式(daemon)

#1. 守護程式是一種特殊的後台程式, 它脫離於終端機, 用於週期性的執行某種任務

2. 進程組

    每個進程都屬於一個進程組
  • 每個進程組都有一個進程組號, 也就是該組組長的進程號(PID)
  • 一個程序只能為自己或其子程序設定進程組號
3. 會話

  • 一個會話可以包含多個進程組, 這些進程組中最多只能有一個前台進程組(也可以沒有), 其餘為後台進程組
  • 一個會話最多只能有一個控制終端機
  • 使用者透過終端登入或網路登入, 會建立一個新的會話
  • 行程呼叫系統呼叫setsid可以建立一個新的會話, 呼叫setsid的行程不能是某個行程組的組長。 setsid呼叫完成後, 該進程成為這個會話的首進程(領頭進程), 同時變成一個新的進程組的組長, 如果該進程之前有控制終端, 該進程與終端的聯繫也被斷開

4. 建立守護程式的方式

  • fork子程式後, 父程式退出, 子程式執行setsid, 子程式即可成為守護程式。這種方式下, 子程序是會話的領頭進程, 可以重新打開終端, 此時可以再次fork, fork產生的子進程無法再打開終端(只有會話的領頭進程才能打開終端)。第二次fork並不是必須的, 只是為了防止子程序再次打開終端
  • linux提供了daemon函數(該函數並不是系統調用, 為庫函數)用於創建守護程序

1.6 Swoole tcp server範例

  • #上述程式碼在cli模式下執行時, 經過詞法分析, 語法分析產生opcode , 進而交由zend虛擬機器執行
  • zend虛擬機器在執行到$serv->start()時, 啟動Swoole server
  • 上述程式碼中設定的事件回呼是在worker進程中執行, 後文會詳細介紹Swoole server模型

 

#二. Swoole server


2.1 base模式

1. 說明

  • base模式採用多進程模型, 這種模型與nginx一致, 每個進程只有一個執行緒, 主進程負責管理工作進程,工作進程負責監聽埠, 接受連線, 處理請求以及關閉連線
  • 多個行程同時監聽埠, 會有驚群問題, linux 3.9之前版本的核心, Swoole沒有解決驚群問題
  • linux 內核3.9及其後續版本提供了新的套接字參數SO_REUSEPORT, 該參數允許多個進程綁定到同一個端口, 內核在接受到新的連接請求時, 會喚醒其中一個進行處理, 內核層面也會做負載平衡, 可以解決上述的驚群問題, Swoole也已經加入了這個參數
  • base模式下, reactor_number參數並沒有實際作用
  • 如果worker進程數設定為1, 則不會fork出worker進程, 主程序直接處理請求, 這種模式適合調試

#2. 啟動過程

    ##php程式碼執行到$serv- >start()時,主程序進入int swServer_start(swServer *serv)函式, 此函式負責啟動server
  • 在函式swServer_start中會呼叫swReactorProcess_start, 這個函式會fork出多個worker程序
  • 主行程與worker行程各自進入自己的事件循環, 處理各類事件

#2.2 process模式

#1. 說明

    #這種模式為多進程多執行緒, 有主進程, manager進程, worker進程, task_worker進程
  • 主進程下有多個執行緒, 主執行緒負責接受連接, 之後交給react線程處理請求。 react執行緒負責接收資料包, 並將資料轉發給worker進程進行處理, 之後處理worker進程返回的資料
  • manager進程, 該進程為單線程, 主要負責管理worker進程, 類似於nginx中的主進程, 當worker進程異常退出時, manager進程負責重新fork出一個worker進程
  • worker進程, 該進程為單線程, 負責具體處理請求
  • task_worker進程, 用於處理比較耗時的任務, 預設不開啟
  • worker進程與主進程中的react執行緒使用域套接字進行通訊, worker進程之間不進行通訊
2.啟動過程

    Swoole server啟動入口: swServer_start函數

    如果設定了daemon模式, 在必要的參數檢查完後, 先將自己變為守護程序再fork manager進程, 進而創建reactor線程
  • 主進程先fork出manager進程, manager進程負責fork出worker進程以及task_worker進程。 worker進程之後進入int swWorker_loop
  • (swServer *serv, int worker_id), 也就是進入自己的事件循環, task_worker也是一樣, 進入自己的事件循環

主程序pthread_create出react執行緒, 主執行緒與react執行緒各自進入自己的事件循環, reactor執行緒執行static int swRea-torThread_loop (swThreadParam *param), 等待處理事件

#3. 結構圖

  • Swoole process模式結構如下圖所示,

上圖並沒有考慮task_worker進程, 在預設情況下, task_worker進程數為0

 

三.請求處理流程(process模式)


3.1 reactor執行緒與worker進程之間的通信

1. Swoole master進程與worker進程之間的通訊如下圖所示

  • Swoole使用SOCK_DGRAM, 而非SOCK_STREAM , 這裡是因為每個reactor線程負責處理多個請求, reactor接收到請求後會將信息轉發給worker進程, 由worker進程負責處理,如果使用SOCK_STREAM, worker進程無法對tcp進行分包, 進而處理請求
  • swFactoryProcess_start函數中會根據worker進程數建立對應個數的套接字對, 用於reactor執行緒與worker進程通訊(詳見swPipeUnsock_create函數)

2. 假設reactor線程有2個, worker進程有3個, 則reactor與worker之間的通信如下圖所示

  • #每個reactor線程負責監聽幾個worker行程, 每個worker行程只有一個reactor執行緒監聽(reactor_num <= worker_num)。 Swoole預設使用worker_process_id % reactor_num對worker進程進行分配, 交給對應的reactor線程進行監聽
  • reactor線程收到某個worker進程的資料後會進行處理, 值得注意的是, 這個reactor線程可能並不是發送請求的那個reactor線程。

3. reactor執行緒與worker進程通訊的資料包

#3.2 請求處理

# 1. master行程中的主執行緒負責監聽埠(listen), 接受連線(accept

, 產生一個fd), 接受連線後將請求指派給reactor執行緒, 預設透過fd % reactor_number進行分配, 之後透過
    epoll_ctl
  • 將fd加入到對應reactor線程中, 剛加入時監聽寫事件, 因為新接受連接創建的套接字寫緩衝區為空, 故而一定可寫,會被立刻觸發, 進而reactor線程進行一些初始化操作
  • 存在多個線程同時操作一個epollfd(透過系統呼叫
  • epoll_create創建)的情況
  • #多個執行緒同時呼叫
  • epoll_ctl是執行緒安全的(對應一個epollfd), 一個執行緒正在執行, 其他執行緒會被阻塞(因為需要同時操作epoll底層的紅黑樹)多個執行緒同時呼叫epoll_wait
  • 也是執行緒安全的, 但是一個事件可能會被多個執行緒同時接收到, 實際上不建議多個執行緒同時
  • epoll_wait一個epollfd。 Swoole中也是不存在這種情況的, Swoole中每個reactor線程都有自己的epollfd一個線程調用epoll_wait, 一個線程調用epoll_ctl, 根據man手冊, 如果epoll_ctl新加入的fd已經準備好, 會使得執行epoll_wait
  • 的執行緒變成非阻塞狀態(可以透過man 
epoll_wait

檢視相關內容)

2. reactor線程中fd的寫事件被觸發, reactor線程負責處理, 發現是首次加入, 沒有資料可寫, 則會開啟讀事件監聽, 準備接受客戶端發送的資料

3. reactor線程讀取到用戶的請求資料, 
    一個請求的資料接收完後
  • , 將資料轉發給worker進程, 默認是透過fd % worker_number進行分配
  • reactor發送給worker進程的資料包, 會包含一個頭部, 頭部中記錄了reactor的資訊
  • 如果發送的資料過大, 則需要將資料進行分片, 限於篇幅, 資料分片, 後續再進行詳細講述

可能存在多個reactor線程同時向同一個worker進程發送資料的情況, 故而Swoole採用SOCK_DGRAM模式與worker進程進行通訊, 透過每個資料包的包頭, worker進程可以區分出是由哪個reactor線程發送的資料, 也可以知道是哪個請求

  • 4. worker進程收到reactor發送的資料包後, 進行處理, 處理完成後, 將請求結果發送給主程序

worker進程發送給主進程的資料包, 也會包含一個頭, 當reactor線程收到封包後, 能夠知道對應的reactor線程, 請求的fd等信息

#######5. 主程序收到worker進程發送的數據包, 這個會觸發某個reactor線程進行處理## #
  • 這個reactor線程並不一定是之前發送請求給worker進程的那個reactor線程
  • 主進程的每個reactor線程都負責監聽worker進程發送的數據包, 每個worker發送的資料包只會由一個reactor線程進行監聽, 故而只會觸發一個reactor線程

6.reactor線程處理worker進程發送的請求處理結果, 如果是直接傳送資料給客戶端, 則可以直接傳送, 如果需要改變這個這個連線的監聽狀態(例如close), 則需要先找到監聽這個連線的reactor執行緒, 進而改變這個連線的監聽狀態(透過呼叫epoll_ctl)

  • reactor處理執行緒與reactor監聽執行緒可能並不是同一個執行緒
  • reactor監聽執行緒負責監聽客戶端發送的資料, 進而轉送worker程序
  • reactor處理執行緒負責監聽worker程序傳送給主程序的資料, 進而將資料傳送給客戶端
## 

#四. gdb偵錯

4.1 process模式啟動

4.2 base模式啟動

1. 本文主要介紹了Swoole server的兩種模式: base模式、process模式, 詳細講解了兩種模式的網路程式設計模型, 並重點介紹了process模式下, 進程間通訊的方式、請求的處理流程等

2. process模式下, 為什麼不直接在主進程中創建多個執行緒, 由執行緒直接進行處理請求(可以避免進程間通訊的開銷), 而是創建出manager進程, 再由manager進程創建出worker進程, 由worker進程處理請求?

  • 個人覺得可能是php對多線程的支援不是很友好, phper大都也只是進行單線程編程
  • ZendVM 提供的TSRM 雖然也是支持多線程環境,但實際上這是一個按線程隔離內存的方案, 多線程並沒有意義

3. process模式下, 主進程中的每個reactor線程都可以同時處理多個請求, 多個請求是並發處理的, 我們從2個維度看

  • 從主程序的角度看, 主程序同時處理多個請求, 當一個請求包全部接收完後, 轉發給worker進程進行處理
  • 從某個worker進程的角度看, 這個worker進程收到的請求是串行的, 預設情況下, worker進程也是串行處理請求, 如果單個請求阻塞(Swoole的worker進程會回調phper寫的事件處理函數, 該函數可能阻塞), 後續的請求也無法處理, 這個就是排頭阻塞問題, 這種情況下可以使用Swoole的協程, 通過協程的調度, 單一請求阻塞時, worker進程可以繼續處理其他請求

#4. 使用Swoole建立tcp server時, 由於tcp是位元組流的協定, 需要分包, 而Swoole在不清楚客戶端與服務端通訊協定的情況下, 無法進行分包, process模式下, reactor交給worker程序的資料也只能是位元組流的, 需要使用者自行處理。當然, 一般情況也不需要自行建構協定, 使用tcp server, Swoole已經支援Http, Https等協定

以上是淺析Swoole server的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述:
本文轉載於:csdn.net。如有侵權,請聯絡admin@php.cn刪除
上一篇:Swoole與HTTP下一篇:Swoole與HTTP