91超碰碰碰碰久久久久久综合_超碰av人澡人澡人澡人澡人掠_国产黄大片在线观看画质优化_txt小说免费全本

溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

Swoole server的案例分析

發布時間:2021-03-11 10:27:13 來源:億速云 閱讀:101 作者:小新 欄目:編程語言

這篇文章將為大家詳細講解有關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建立連接的過程如下圖

Swoole server的案例分析

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

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

Swoole server的案例分析

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

● 圖中, 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系統調用

Swoole server的案例分析

  • 調用成功后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示例

Swoole 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函數

Swoole server的案例分析

  • 如果設置了daemon模式, 在必要的參數檢查完后, 先將自己變為守護進程再fork manager進程, 進而創建reactor線程

  • 主進程先fork出manager進程, manager進程負責fork出worker進程以及task_worker進程。worker進程之后進入int swWorker_loop
    (swServer *serv, int worker_id), 也就是進入自己的事件循環, task_worker也是一樣, 進入自己的事件循環

Swoole server的案例分析

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

Swoole server的案例分析

3. 結構圖

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

Swoole server的案例分析

上圖并沒有考慮task_worker進程, 在默認情況下, task_worker進程數為0

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


3.1 reactor線程與worker進程之間的通信

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

Swoole server的案例分析

  • 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之間的通信如下圖所示

Swoole server的案例分析

  • 每個reactor線程負責監聽幾個worker進程, 每個worker進程只有一個reactor線程監聽(reactor_num <= worker_num)。Swoole默認使用worker_process_id % reactor_num對worker進程進行分配, 交給對應的reactor線程進行監聽

  • reactor線程收到某個worker進程的數據后會進行處理, 值得注意的是, 這個reactor線程可能并不是發送請求的那個reactor線程。

3. reactor線程與worker進程通信的數據包

Swoole server的案例分析

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查看相關內容)

Swoole server的案例分析

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模式啟動

Swoole server的案例分析

4.2 base模式啟動

Swoole server的案例分析

五. 總結及思考

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的案例分析”這篇文章就分享到這里了,希望以上內容可以對大家有一定的幫助,使各位可以學到更多知識,如果覺得文章不錯,請把它分享出去讓更多的人看到。

向AI問一下細節

免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

AI

灵台县| 东源县| 五大连池市| 英山县| 咸丰县| 句容市| 延长县| 云安县| 察雅县| 普兰店市| 高邑县| 丰顺县| 蒙阴县| 甘泉县| 武陟县| 天气| 响水县| 建瓯市| 越西县| 通海县| 麦盖提县| 潞城市| 亳州市| 宜丰县| 十堰市| 新津县| 大悟县| 龙口市| 大兴区| 林周县| 红原县| 阳新县| 闵行区| 巩留县| 甘洛县| 孟州市| 拜泉县| 拉孜县| 辽宁省| 勐海县| 靖宇县|