導航:首頁 > IDC知識 > 並發伺服器epoll

並發伺服器epoll

發布時間:2021-02-28 13:13:27

1、epoll編程,如何實現高並發伺服器開發?

<

2、linux epoll並發伺服器怎麼主動發數據給客戶端

udp還是tcp,如抄果是tcp需要你把服襲務器當成客戶端主動連接客戶端的指定埠,listen後才能send。要麼你就手動使用raw socket直接從最上層到最底層直接構造tcp回應包發送,不用bind listen都可以。

3、高並發的UDP服務端需不需要epoll

 服務程序最為關鍵的設計是並發服務模型,當前有以下幾種典型的模型:

- 單進程服務,使用非阻塞IO

使用一個進程服務多個客戶,通常與客戶通信的套接字設置為非阻塞的,阻塞只發生在select()、poll()、epoll_wait()等系統調用上面。這是一種行之有效的單進程狀態機式服務方式,已被廣泛採用。

缺點是它無法利用SMP(對稱多處理器)的優勢,除非啟動多個進程。此外,它嘗試就緒的IO文件描述符後,立即從系統調用返回,這會導致大量的系統調用發生,尤其是在較慢的位元組傳輸時。

select()本身的實現也是有局限的:能打開的文件描述符最多不能超過FD_SETSIZE,很容易耗盡;每次從select()返回的描述符組中掃描就緒的描述符需要時間,如果就緒的描述符在末尾時更是如此(epoll特別徹底修復了這個問題)。

- 多進程服務,使用阻塞IO

也稱作 accept/fork 模型,每當有客戶連線時產生一個新的進程為之服務。這種方式有時是必要的,比如可以通過操作系統獲得良好的內存保護,可以以不同的用戶身份運行程序,可以讓服務運行在不同的目錄下面。但是它的缺點也很明顯:進程比較占資源,進程切換開銷太大,共享某些信息比較麻煩。Apache 1.3就使用了這種模型,MaxClients數很容易就可以達到。

4、服務端並發問題以及epoll和select的區別

問題的引出:當需要讀兩個以上的I/O的時候,如果使用阻塞式的I/O,那麼可能長時間的阻塞在一個描述符上面,另外的描述符雖然有數據但是不能讀出來,這樣實時性不能滿足要求,大概的解決方案有以下幾種:
1.使用多進程或者多線程,但是這種方法會造成程序的復雜,而且對與進程與線程的創建維護也需要很多的開銷。(Apache伺服器是用的子進程的方式,優點可以隔離用戶)
2.用一個進程,但是使用非阻塞的I/O讀取數據,當一個I/O不可讀的時候立刻返回,檢查下一個是否可讀,這種形式的循環為輪詢(polling,注意這個不是指poll函數),這種方法比較浪費CPU時間,因為大多數時間是不可讀,但是仍花費時間不斷反復執行read系統調用。
3.非同步I/O(asynchronous I/O),當一個描述符准備好的時候用一個信號告訴進程,但是由於信號個數有限,多個描述符時不適用(真的嗎???)。
4.一種較好的方式為I/O多路轉接(I/O multiplexing)(貌似也翻譯多路復用),先構造一張有關描述符的列表(epoll中為隊列),然後調用一個函數,直到這些描述符中的一個准備好時才返回,返回時告訴進程哪些I/O就緒。select和epoll這兩個機制都是多路I/O機制的解決方案,select為POSIX標准中的,而epoll為Linux所特有的。

5、4G內存伺服器epoll並發量最大能達到多少

並發量與其他配置也息息相關,況且4G的內存已經很低端了。建議你可以去伺服器廠商(正睿)的網上直接咨詢一下(最好當成購買用戶,你懂的),幾分鍾就清楚了!

6、epoll可以解決多個socket的連接,為什麼高並發伺服器還要用進程池或者線程池呢?

socket接受線程:C語言為了高並發所以選擇了epoll。當程序啟動的時候(g_net_update.c文件中main函數,會啟動一個thread見函數create_accept_task)這個thread就處理一件事情,只管接收客戶端的連接,當有連接進來的時候 通過epoll_ctl函數,把socket fd 加入到epoll裡面去,epoll設置監聽事件EPOLLIN | EPOLLET; 主要是監聽的是加入到epoll中的socket是否可讀(因為我的需求是客戶端連上了server就會馬上向server發送一份數據的)。其它的部分在主線程中處理。

主線程:是一個無線循環,epoll_wait 函數相當於把客戶端的連接從epoll中拿出來(因為我們監聽的是EPOLLIN | EPOLLET)說明這個時候客戶端有數據發送過來)。再通過recv_buffer_from_fd 函數把客戶端發送過來的數據讀出來。然後其他的一切就拋給線程池去處理。

線程池:(代碼中我會在池裡面創建15個線程) 雙向鏈表。加入線程就是在鏈表後面加一個鏈表項,鏈表的前面會一個一個被拿出來處理。主要是malloc 函數free函數,sem_wait函數sem_post的處理(sem_wait 會阻塞當值大於0是會減一,sem_post是值加一)。typedef void* (FUNC)(void arg, int index);是我們自定義的線程的邏輯處理部分,arg是參數,index是第幾個線程處理(我們隱形的給每個線程都標了號),例如代碼中的respons_stb_info,更加具體可以看看代碼裡面是怎麼實現的。聰明的你也可以改掉這塊的內容改成動態線程池,當某個時刻的處理比較多的時候能夠動態的增加線程,而不像我代碼裡面的是固定的。

資料庫連接池:按照我的需求在處理客戶端請求數據的時候是要訪問資料庫的。就是一下子創建出一堆的數據連接。要訪問資料庫的時候先去資料庫連接池中找出空閑的連接,具體可以看下代碼。使用的時候可以參考下database_process.c文件(代碼中資料庫連接池和線程池中的個數是一樣的)。這里我想說下get_db_connect_from_pool這個函數,我用了隨機數,我是為了不想每次都從0開始去判斷哪個連接沒有用到。為了資料庫連接池中的每個鏈接都能等概率的使用到,具體的還是可以看下代碼的實現。

7、epoll並發量最大能達到多少

按照題主的意思 是根據內存去算一個最大並發的連接數. 那麼首先要找出來單個連接消耗內存的地方.
第一個首先是socket buffer. read 和write 分別有一個, 默認大小在

復制代碼
代碼如下:

/proc/sys/net/ipv4/tcp_rmem (for read)
/proc/sys/net/ipv4/tcp_wmem (for write)

默認大小都是87K和16K, 最低是4K和4K, 最高是2M,2M, 實際使用默認值最低也要保留8K,8K.

然後是邏輯IO緩沖區
就是比如你監聽了recv事件 事件來了 你要有內存可用(一般都是socket建立起就分配好,斷開才會釋放的).
這個內存是自己寫socket程序時候自己控制的, 最低也要4K,4K, 實際使用8K,8K至少.
現在設定一個優化方案和使用場景, 首先假設4G內存全部為空閑(系統和其他進程也要內存的….
假如網路包的大小都可以控制在4K以下, 假設所有連接的網路都不會擁堵, 或者擁堵時候的總量在4K以下:
一個連接的內存消耗是4+4+4+4=16K
4G/16K=26.2萬並發
假如網路包的大小都可以控制在8K以下, 假設所有連接的網路都不會擁堵, 或者擁堵時候的總量在8K以下
一個socket的內存佔用介於 24K ~ 32K之間, 保守的按照32K算
4G/32K=13.1萬並發, 這個在生產環境作為一個純網路層面的內存消耗, 是可以作為參考的.
假如使用默認配置, 假如所有連接的網路都出現嚴重擁堵, 不考慮邏輯上的發送隊列的佔用,
使用默認配置是2M+2M+8+8 ~= 4M
4G/4M=1024並發 ( …
如果考慮到發送隊列也擁堵的話 自己腦補.
如果只是為了跑分 為了並發而優化, 沒有常駐的邏輯緩沖區 並且socket的網路吞吐量很小並且負載平滑, 把socket buffer size設置系統最低.
那麼是
4G/8K = 52.4萬並發 這個應該是極限值了.

8、多核Linux伺服器開發,創建多少個epoll合適

多核伺服器和多個epoll沒什麼關系,多核能力還是留給CPU計算型任務吧,至於網路IO,一個epoll實例輕松處理10K以上並發連接。只遇到過後續處理數據的瓶頸,沒遇過epoll接入和收發數據的瓶頸。

9、linux並發伺服器中epoll+多線程分別怎麼理

某個線程處理某個特定事件吧
通過epoll檢測一些事件,事件觸發時,創建一個線程來專職處理這個事件

10、高並發的伺服器有什麼模式

服務程序最為關鍵的設計是並發服務模型,當前有以下幾種典型的模型:

  - 單進程服務,使用非阻塞IO

使用一個進程服務多個客戶,通常與客戶通信的套接字設置為非阻塞的,阻塞只發生在select()、poll()、epoll_wait()等系統調用上面。這是一種行之有效的單進程狀態機式服務方式,已被廣泛採用。

缺點是它無法利用SMP(對稱多處理器)的優勢,除非啟動多個進程。此外,它嘗試就緒的IO文件描述符後,立即從系統調用返回,這會導致大量的系統調用發生,尤其是在較慢的位元組傳輸時。

select()本身的實現也是有局限的:能打開的文件描述符最多不能超過FD_SETSIZE,很容易耗盡;每次從select()返回的描述符組中掃描就緒的描述符需要時間,如果就緒的描述符在末尾時更是如此(epoll特別徹底修復了這個問題)。

- 多進程服務,使用阻塞IO

也稱作 accept/fork 模型,每當有客戶連線時產生一個新的進程為之服務。這種方式有時是必要的,比如可以通過操作系統獲得良好的內存保護,可以以不同的用戶身份運行程序,可以讓服務運行在不同的目錄下面。但是它的缺點也很明顯:進程比較占資源,進程切換開銷太大,共享某些信息比較麻煩。Apache 1.3就使用了這種模型,MaxClients數很容易就可以達到。

- 多線程服務,使用阻塞IO

也稱之 accept/pthread_create模型,有新客戶來時創建一個服務線程而不是服務進程。這解決了多進程服務的一些問題,比如它佔用資源少,信息共享方便。但是麻煩在於線程仍有可能消耗光,線程切換也需要開銷。

- 混合服務方式
所謂的混合服務方式,以打破服務方和客戶方之間嚴格的1:1關系。基本做法是:

新客戶到來時創建新的工作線程,當該工作線程檢測到網路IO會有延遲時停止處理過程,返回給Server一個延遲處理狀態,同時告訴 Server被延遲的文件描述符,延遲超時時間。Server會在合適的時候返回工作線程繼續處理。注意這里的工作線程不是通過 pthread_create()創建的,而是被包裝在專門用於處理延遲工作的函數里。

這里還有一個問題,工作線程如何檢測網路IO會有延遲?方法有很多,比如設置較短的超時時間調用poll(),或者甚至使用非阻塞IO。如果是套接字,可以設置SO_RCVTIMEO和SO_SNDTIMEO選項,這樣更有效率。
除了延遲線程,Server還應提供了未完成線程的支持。
如有有特別耗費時間的操作,你可以在完成部分工作後停止處理,返回給Server一個未完成狀態。這樣Server會檢查工作隊列是否有別的線程,如果有則讓它們運行,否則讓該工作線程繼續處理,這可以防止某些線程挨餓。

典型的一個混合服務模型開源實現ServerKit

Serverkit的這些線程支持功能可簡化我們的服務程序設計,效率上應該也是有保證的。

2. 隊列(queue)

ServerKit提供的隊列是一個單向鏈表,隊列的存取是原子操作,如果只有一個執行單元建議不要用,因為原子操作的開銷較大。

3. 堆(heap)

malloc()分配內存有一定的局限,比如在多線程的環境里,需要序列化內存分配操作。ServerKit提供的堆管理函數,可快速分配內存,可有效減少分配內存的序列化操作,堆的大小可動態增長,堆有引用計數,這些特徵比較適合多線程環境。目前ServerKit堆的最大局限是分配單元必須是固定大小。

4. 日誌記錄

日誌被保存在隊列,有一個專門的線程處理隊列中的日誌記錄:它或者調用syslog()寫進系統日誌,或者通過UDP直接寫到遠程機器。後者更有效。

5. 讀寫鎖

GNU libc也在pthreads庫里實現了讀寫鎖,如果定義了__USE_UNIX98就可以使用。不過ServerKit還提供了讀寫鎖互相轉換的函數,這使得鎖的應用更為彈性。比如擁有讀鎖的若干個線程對同一個hash表進行檢索,其中一個線程檢索到了數據,此時需要修改它,一種辦法是獲取寫鎖,但這會導致釋放讀鎖和獲取寫鎖之間存在時間窗,另一種辦法是使用ServerKit提供的函數把讀鎖轉換成寫鎖,無疑這種方式更有效率。

除了以上這些功能,ServerKit還提供了資料庫連接池的管理(當前只支持MySQL)和序列化(Sequences),如感興趣可參見相關的API文檔。

二、ServerKit服務模塊編寫

ServerKit由3部分組成:server程序,負責載入服務模塊、解析配置文件、建立資料庫連接池;libserver,動態鏈接庫,提供所有功能的庫支持,包括server本身也是調用這個庫寫的;API,編程介面,你編寫的服務模塊和ServerKit框架進行對話的介面。

ServerKit需要libConfuse解析配置文件,所以出了安裝ServerKit,還需要安裝libConfuse。關於libConfuse可參考 http://www.nongnu.org/confuse/ 。

下面我們看一個簡單的服務模塊FOO:

#include <confuse.h>
#include <server.h>

static long int sleep_ration;

static int FOO_construct()
{
fprintf(stderr, "FOO_construct\n");

return 1;
}

static int FOO_prestart(cfg_t *configuration)
{
fprintf(stderr, "FOO_prestart\n");

return 1;
}

static void * FOO_operator(void *foobar)
{
fprintf(stderr, "FOO_operator\n");

for(;;) sleep(sleep_ration);

return NULL;
}

static void FOO_report(void)
{
fprintf(stderr, "FOO_report\n");
}


static cfg_opt_t FOO_config[] = {
CFG_SIMPLE_INT("sleep_ration", &sleep_ration),
CFG_END()
};

static char *FOO_authors[] = {"Vito Caputo <[email protected]>", NULL};


SERVER_MODULE(FOO,0,0,1,"Example mole that does nothing but sleep")按以下方法編譯:

$ gcc -c -fPIC -pthread -D_REENTRANT -g FOO.c
$ gcc -shared -lserver -lconfuse -lpthread -g -e __server_mole_main -o FOO.so FOO.o

-e選項指定程序運行入口,這使得你可以直接在命令行敲 ./FOO.so 運行模塊。
server程序根據環境變數SERVER_PERSONALITY_PATH定位主目錄,並查找主目錄下的c11n作為配置文件,動態載入的模塊需放在主目錄下的moles目錄。

$ export SERVER_PERSONALITY_PATH=`pwd`
$ mkdir moles
$ cp FOO.so moles
$ vi c11n

c11n的內容:

identity = "any_id"

FOO {
sleep_ration = 1;
}

identity標識server實例,用ps可看到程序名稱形如server.identity,本例為server.any_id。
執行server啟動服務程序。

三、ServerKit其他功能缺陷
缺乏daemon模式;
只能運行在Linux box;
DB pool只支持MySQL;
Heap管理內存的功力有限

與並發伺服器epoll相關的知識