【Linux網路程式設計】Reactor模式與Proactor模式
Reactor模式
Reactor 模式是指主執行緒即 IO 處理單元只負責監聽檔案描述符上是否有事件發生,有則立刻將該事件通知給工作執行緒即邏輯單元,除此之外,主執行緒不做任何其它實質性的動作。讀寫資料,接受新的連線,以及處理客戶請求均在工作執行緒中完成。
同步 IO 模型實現 Reactor 模式的工作流程:
- 主執行緒往 epoll 核心事件表中註冊 socket 上的讀就緒事件。
- 主執行緒呼叫 epoll_wait 等待 socket 上有資料可讀。
- 當 socket 上有資料可讀時,epoll_wait 通知主執行緒,主執行緒則將 socket 可讀事件放入請求佇列。
- 睡眠在請求佇列上的工作執行緒被喚醒,它從 socket 上讀取資料,並處理客戶請求,然後往 epoll 核心事件表中註冊寫就緒事件。
- 主執行緒呼叫 epoll_wait 等待 socket 可寫。
- 當 socket 可寫時,epoll_wait 通知主執行緒,主執行緒將 socket 可寫事件放入請求佇列中。
- 睡眠在請求佇列上的某個工作執行緒被喚醒,它往 socket 上寫入伺服器處理客戶請求的結果。
如下為 Reactor 模式的工作流程圖:
工作執行緒從請求佇列中取出事件後,將根據事件的型別來決定如何處理它:對於可讀事件則執行讀資料和處理請求的操作;對於可寫事件則執行寫資料的操作。因此並沒有所謂的“讀工作執行緒”和“寫工作執行緒”。
Proactor 模式
與 Reactor 模式不同,Proactor 模式將所有的 IO 操作交由主執行緒與核心去處理,工作執行緒僅僅負責業務邏輯。
非同步 IO 模型(以 aio_read 與 aio_write 為例)實現 Proactor 模式的工作流程:
- 主執行緒呼叫 aio_read 函式向核心註冊 socket 讀完成事件,並告訴核心使用者的讀緩衝區的位置,以及讀操作完成時如何通知應用程式。
- 主執行緒繼續處理其它邏輯。
- 當 socket 上的資料被讀入使用者緩衝區後,核心將嚮應用程式傳送一個訊號,以通知應用程式資料已經可用。
- 應用程式預先定義好的訊號處理函式選擇一個工作執行緒來處理客戶請求。工作執行緒處理完客戶請求後,呼叫 aio_write 函式向核心註冊 socket 上的寫完成事件,並告訴核心使用者寫緩衝區的位置,以及寫操作完成時如何通知應用程式。
- 主執行緒繼續處理其它邏輯。
- 當使用者緩衝區中的資料寫入 socket 後,核心嚮應用程式傳送一個訊號,以通知應用程式資料已經傳送完畢。
- 應用程式預先定義好的訊號處理函式選擇一個工作執行緒來做善後處理,比如決定是否關閉 socket。
如下為 Proactor 模式的工作流程圖:
連線 socket 上的讀寫事件是透過 aio_read / aio_write 向核心註冊的,因此核心將透過訊號來嚮應用程式報告連線 socket 上的讀寫事件。所以,主執行緒中的 epoll_wait 呼叫僅用來檢測監聽 socket 上的連線請求事件,而不能用來檢測連線 socket 上的讀寫事件。
Reactor 模型程式碼
#include "webserver.h"
WebServer::WebServer(int port, int trigMode, int timeoutMs) : port_(port), timeoutMs_(timeoutMs),
isClose_(false), epoller_(new Epoller()) {
InitEventMode_(trigMode);
if(!InitSocket_()) { isClose_ = true };
}
WebServer::~WebServer() {
close(listenFd_);
isClose_ = true;
}
void WebServer::Start() {
while (!isClose_) {
int eventCnt = epoller_->Wait(timeoutMs_);
for (int i = 0; i < eventCnt; ++i) {
int fd = epoller_->GetEventFd(i); // 獲取事件對應的fd
uint32_t events = epoller_->GetEvents(i); // 獲取事件的型別
if (fd == listenFd_) {
DealListen_();
} else if (events & EPOLLIN) {
DealRead_(); // 子執行緒中執行
} else if (events & EPOLLOUT) {
DealWrite_(); // 子執行緒中執行
} else if (events & (EPOLLRDHUP | EPOLLHUP | EPOLLERR)) {
CloseConn_();
} else {
perror("Unexpected event");
}
}
}
}
void WebServer::DealListen_() {
struct sockaddr_in cliaddr;
socklen_t len = sizeof(cliaddr);
do {
int cfd = accept(listenFd_, (struct sockaddr*)&cliaddr, &len);
if (cfd < 0) { return ; }
// 獲取客戶端資訊
char cliIp[16];
inet_ntop(AF_INET, &cliaddr.sin_addr.s_addr, cliIp, sizeof(cliIp));
unsigned short cliPort = ntohs(cliaddr.sin_port);
// 輸出客戶端的資訊
printf("client's ip is %s, and port is %d\n", cliIp, cliPort );
SetFdNonblock(cfd);
epoller_->AddFd(cfd, connEvent_ | EPOLLIN);
} while (listenEvent_ & EPOLLET);
}
void WebServer::InitEventMode_(int trigMode) {
listenEvent_ = EPOLLRDHUP;
connEvent_ = EPOLLRDHUP | EPOLLONESHOT; // 註冊EPOLLONESHOT,防止多執行緒同時操作一個socket的情況
switch ((trigMode))
{
case 0:
break;
case 1:
connEvent_ |= EPOLLET;
break;
case 2:
listenEvent_ |= EPOLLET;
break;
case 3:
connEvent_ |= EPOLLET;
listenEvent_ |= EPOLLET;
break;
default:
connEvent_ |= EPOLLET;
listenEvent_ |= EPOLLET;
break;
}
}
bool WebServer::InitSocket_() {
// 建立socket
listenFd_= socket(AF_INET, SOCK_STREAM, 0);
if (listenFd_ == -1) {
perror("socket");
return false;
}
// 設定埠複用
int optval = 1;
setsockopt(listenFd_, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof(optval));
struct sockaddr_in saddr;
saddr.sin_family = AF_INET;
saddr.sin_addr.s_addr = INADDR_ANY;
saddr.sin_port = htons(port_);
// 繫結
int ret = bind(listenFd_, (struct sockaddr*)&saddr, sizeof(saddr));
if (ret == -1) {
perror("bind");
return false;
}
// 監聽
ret = listen(listenFd_, 128);
if (ret == -1) {
perror("listen");
return false;
}
// 將listenFd_註冊至epoll事件表中
ret = epoller_->AddFd(listenFd_, listenEvent_ | EPOLLIN);
if (ret == 0) {
perror("Add listen error");
return false;
}
// 設定listenFd_非阻塞
SetFdNonblock(listenFd_);
return true;
}
int WebServer::SetFdNonblock(int fd) {
assert(fd > 0);
return fcntl(fd, F_SETFL, fcntl(fd, F_GETFL, 0) | O_NONBLOCK);
}