天天看點

linux伺服器高并發處理,Linux高并發伺服器的架構簡介

Linux的伺服器軟體,大多是基于epoll事件機制的多程序架構。

其中有一個主程序master,負責監控其他程序的狀态。如果其他程序異常退出,則把它從新啟動起來。

其他的程序是工作程序worker,負責具體的業務處理。

worker程序的主函數是一個基于epoll事件機制的循環,監控它負責的socket連接配接的讀寫狀态,讀取資料并且調用相關的各個協定解析子產品進行處理,應答使用者的請求。

之是以用多程序結構,是個别worker程序的異常退出不會導緻整個伺服器程式的退出,而是會被master程序再次啟動起來繼續服務,比多線程的穩定性要高。

帶來的問題之一就是,跨程序的資料通信比較繁瑣。一般是關鍵資訊通過AF_UNIX域的socketpair傳遞,如果是視訊之類的大量資料則使用共享記憶體。

典型的Linux高并發伺服器,就是著名的nginx,作者俄羅斯程式員Igor Syseov。

1,主程序master,

主程序在初始化結束之後的工作比較簡單,主要是監控工作程序的狀态,并及時重新開機異常退出的工作程序。

再就是接受使用者的指令,例如reload,quit之類的操作。

Linux的子程序退出時會給父程序發送SIGCHILD信号,然後父程序可以使用wait系統調用擷取子程序的退出狀态。

信号的傳遞是異步的,需要用Linux的signal()函數挂載自定義的信号處理函數,然後就可以捕獲子程序的SIGCHILD信号了。

主程序master發現子程序worker退出後,就可以用fork()系統調用再建立一個子程序,以維持worker程序的個數。

其他時間master隻需要關注标準輸入STDIN,及時擷取使用者的指令。

2,worker程序,

worker程序的主循環是一個死循環,不斷的用epoll_wait()系統調用監控socket連接配接的狀态。

如果有資料可讀,則讀取資料,并調用相關的子產品進行處理。

如果有新連接配接要接入,則使用accept()函數擷取連接配接的檔案描述符,并為該使用者建立連接配接的上下文,該上下文代表伺服器上的一個使用者。

同時這個主循環還要處理定時器問題,有時候給使用者發送的資料需要控制帶寬,就按定時器每多少毫秒發送多少位元組,以保證帶寬不會暴漲暴跌。

定時器需要頻繁的添加、删除、查找,是以多是使用紅黑樹作為管理結構。

定時器的精度取決于epoll_wait的逾時時間。

下圖為Linux man手冊關于epoll_wait的介紹,它的最後一個參數就是逾時的毫秒數。

即使沒有等到新資料到達,在逾時之後也會傳回,這時可以處理定時器。極端情況下,定時器的最粗精度取決于這個逾時的時間。

linux伺服器高并發處理,Linux高并發伺服器的架構簡介

工作程序的主循環代碼大概這樣:

while(!exit) { //如果沒有退出标志則循環

int ret = epoll_wait(epfd, events, max events, timeout);

if (ret < 0) {

//出錯處理

break;

}

//正常傳回時的ret傳回值是觸發的事件個數

int i;

for (i =0; i < ret; i ++) {

//周遊處理所有的觸發事件

}

//處理到時間的定時器事件

}

像http之類的具體協定的處理,都是在事件的回調函數裡做的。

epoll_event的結構分兩個部分,一個是具體的epoll事件的标志,例如EPOLLIN表示讀,EPOLLOUT表示寫。

另一部分是使用者自定義的上下文資料的指針。各子產品的資料結構可以放在這個指針裡。

如下圖,可以把自定義資料結構挂在e->data.ptr裡,其中e為struct epoll_event類型的指針。

linux伺服器高并發處理,Linux高并發伺服器的架構簡介

假設自定義的連接配接上下文結構是:

struct connection {

int fd; // socket檔案描述符

int (*read)(struct connnection* c);

//讀資料的函數指針

int (*write)(struct connection* c);

//寫資料的函數指針

uint8_t* readbuf; //讀緩沖區

int readbuf_size; //目前讀緩沖區大小

uint8_t* writebuf; //寫緩沖區

int writebuf_size; //寫緩沖區大小

void* data; //下一級子產品的結構體

};

那麼工作程序worker的主循環while()裡,周遊觸發的事件的for循環的處理就是:

for (i =0; i < ret; i++) {

struct epoll_event* e = &events[i];

struct connection * c = e->data.ptr;

int ret2 = c->read(c);

...

}

這隻是示例代碼,nginx的這部分代碼還是比較複雜的。

最精簡的事件架構記得是redis自帶的libae,大概幾百行。

libevent架構則特别的複雜。

linux伺服器高并發處理,Linux高并發伺服器的架構簡介

舉報/回報