首頁 php框架 Swoole 淺析Swoole server

淺析Swoole server

Mar 11, 2021 am 09:52 AM

淺析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中文網其他相關文章!

本網站聲明
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn

熱AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover

AI Clothes Remover

用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool

Undress AI Tool

免費脫衣圖片

Clothoff.io

Clothoff.io

AI脫衣器

Video Face Swap

Video Face Swap

使用我們完全免費的人工智慧換臉工具,輕鬆在任何影片中換臉!

熱工具

記事本++7.3.1

記事本++7.3.1

好用且免費的程式碼編輯器

SublimeText3漢化版

SublimeText3漢化版

中文版,非常好用

禪工作室 13.0.1

禪工作室 13.0.1

強大的PHP整合開發環境

Dreamweaver CS6

Dreamweaver CS6

視覺化網頁開發工具

SublimeText3 Mac版

SublimeText3 Mac版

神級程式碼編輯軟體(SublimeText3)