大併發服務器設計目標前端
分佈式:react
C/S結構: 任何網絡系統均可以抽象爲C/S結構(客戶端, 服務端)linux
網絡I/O+服務器高性能編程技術+數據庫
超出數據庫鏈接數: 數據庫併發鏈接數10個, 應用服務器這邊有1000個併發請求, 將會有990個請求失敗. 解決辦法: 增長一箇中間層DAL(數據庫訪問控制層), 一個隊列進行排隊
超出時限: 數據庫併發鏈接數10個, 數據庫1秒鐘以內最能處理1000個請求, 應用服務器這邊有10000個併發請求, 會出現0-10秒的等待. 若是系統規定響應時間5秒, 則該系統不能處理10000個併發請求, 這時數據庫併發能力5000, 數據出現瓶頸.git
提升數據庫的併發能力github
緩存更新(緩存同步):redis
當緩存服務器內存不足時, 緩存換頁: 內存不足, 將不活躍的數據換出內存. 算法FIFO, LRU(least recently used, 最後一次訪問時間距離如今最久的換出), LFU(least frequently used 最近最不頻繁使用的換出)算法
nosql: 反sql, 主要用來存放非關係數據, sql存儲對數據一致性要求較高的數據, nosql存儲對數據一致性要求較低的數據, 基於key-value數據, 將nosql看成緩存來使用.
將cache與應用服務器放到同一臺機器中, 則這個緩存不是全局緩存, 是局部緩存, 另外一臺機器沒法訪問這個緩存. 若是部署在獨立機器上面, 而且使用分佈式緩存機制, 則各個機器均可訪問
分佈式緩存如: redis, memcachedsql
對數據庫寫操做, 會把數據庫加鎖, 則對數據庫的讀操做會阻塞 --> 把數據庫讀寫分離, 對大部分服務器對數據庫讀操做比寫操做多, 對數據庫進行負載均衡, 讀寫分離. 使用replication機制, 一些數據庫只作讀操做, 一些數據庫只作寫操做數據庫
若數據庫容量太大會影響併發, 對數據分區編程
應用服務器的負載均衡:
循環中執行操做, 每處理完一個請求就關閉鏈接, 短連接模式. 循環式服務器只能使用短連接而不能使用長鏈接. 若使用長鏈接則在處理完請求執行write後, 不關閉鏈接回到read處, 而整個程序是一個單線程的程序, 若再有客戶端鏈接則執行不到accept.
單線程程序, 不是真正意義上的併發服務器, 沒法充分利用多核cpu, 不適合執行時間較長的服務
one connection per process(一個鏈接一個進程)
one connection pre thread(一個鏈接一個線程)
長鏈接, 可以處理多個客戶端, 適合執行時間比較長的服務
預先建立進程或者預先建立線程, 與併發服務器相似, 但減少的建立進程/線程的開銷, 能夠提升響應速度
問題: 當一個客戶端鏈接過來後因爲多個子進程處於循環狀態中, 多個進程的accept都有返回, 那麼只有一個進程的返回值是正確的, 其餘進程返回值失敗. --> 驚羣現象
單線程輪詢多個客戶端
reactor使用select/poll/epoll來實現. 併發處理多個請求, 其實是在一個線程中完成的, 單線程輪詢, 沒法充分利用多核cpu.
可是併發量比並髮式服務器要多, 由於併發式服務器可以建立的進程/線程數量有限
不適合執行時間比較長的服務, 因此爲了讓客戶感受是在"併發"處理而不是"循環"處理, 每一個請求必須在相對較短期內執行. 解決方案: 有限狀態機, 但並不使用, 有更好的解決方案
沒有充分利用多核cpu
每一個請求過來都建立一個線程出來, 可以充分利用多cpu.
若是有不少的請求過來可能就會產生不少線程出來
每一個鏈接過來都在一個工做線程中完成, 可以充分利用cpu. 一共兩個線程, 一個reactor線程, 一個工做線程. 不如直接利用併發式模型
第5種方案的改進
reactor在一個線程中(IO線程), 讀取請求包, 把請求包丟到線程池中處理. 線程池中取出一個工做線程來處理請求包. 即便請求計算量大比較耗費cpu也不要緊, 由於是在線程池中線程執行的, 不會影響到IO線程.
線程池中線程不負責數據發送, 要響應數據包必須丟到IO線程中發送, 或者說異步調用IO線程的發送方法發送數據
單個線程處理網絡IO
reactors in threads(one loop per thread)
reactors in process
每一個線程/進程都有一個Reactor
好比有mainReactor, subReactor_1, subReactor_2, mainReactor收到conn_1, 放入subReactor_1中並負責conn_1的IO, mainReactor接收到conn_2, 放入subReactor_2並負責conn_2的IO, mainReactor接收到的conn輪詢放到subReactor
多個事件循環, 每一個reactor都是一個線程或進程. 若是隻有一個線程處理網絡IO可能會產生瓶頸, 一個reactor可適應一個千兆網口. 如有三個網口加上manReactor共4個subReactor. 這些subReactor能夠看做是IO的線程池, 這些線程池中線程的個數通常是固定的, 根據千兆網卡的數量來設置reactor的數量
多個線程處理網絡IO
這裏multiple reactors不能用進程來實現, 由於若是用進程則後面的線程池沒有辦法共享(進程沒有辦法共享線程池). 多個subReactor共享一個線程池
理論上proactor比reactor效率要高一些
異步I/O可以讓I/O操做與計算重疊. 充分利用DMA特性
linux異步IO:
glibc aio(aio_*), 有bug
kernel native aio(io_), 也不完美. 目前僅支持O_DIRECT方式來對磁盤讀寫, 跳過系統緩存. 要本身實現緩存, 難度不小
boost asio實現proactor, 實際上不是真正意義上的異步I/O, 底層是用epoll來實現的, 模擬異步I/O
linux下沒有比較成熟的異步I/O, windows下可基於完成端口來實現異步I/O
同步: I/O並無完成, 需從內核緩衝區 拉 到應用緩存區中
異步: I/O完成, 直接把數據 推 到應用緩存區的過程
muduo庫支持4, 7, 8, 9模型, 模型9 multiple reactors + threadpool是最好的併發方式
linux能同時啓動多少個線程?
對於32bit linux, 一個進程的地址空間是4G, 其中用戶態能訪問3G左右, 而一個線程的默認棧(stack)大小是10M, 心算可知, 一個進程大約最多支持300個線程左右
多線程能提升併發度嗎?
若是指的是"併發鏈接數", 不能
假如單純採用thread per connection的模型, 那麼併發鏈接數大約300, 這遠遠低於基於事件的單線程程序所能輕鬆達到的併發鏈接數(幾千上萬, 甚至幾萬). 所謂"基於事件", 指的是用IO multiplexing event loop的編程模型, 又稱reactor模式
多線程能提升吞吐量嗎?
對於計算密集型服務, 不能
若是要在一個8核的機器上壓縮100個1G的文本文件, 每一個core的處理能力爲200MB/s, 那麼"每次起8個進程, 一個進程壓縮一個文件"與"只啓動一個進程(8線程併發壓縮一個文件)", 這兩種方式總耗時至關, 可是第二種方式可以較快的拿到第一個壓縮完的文件, 可提升響應速度
多線程能夠提升響應時間嗎?
能夠, 如壓縮100個1G文件時, 只啓動一個進程(8線程併發)可較快拿到第一個壓縮完的文件
多線程若是讓I/O和計算重疊, 下降latency(遲延)
例: 日誌(logging), 多個線程寫日誌, 因爲文件操做比較慢, 服務器會等在IO上, 讓cpu空閒, 增長響應時間
解決辦法: 單獨用一個logging線程負責寫磁盤文件, 經過BlockQueue提供對外接口, 別的線程要寫日誌的時候往隊列一塞就行, 這樣服務線程的計算和logging線程的磁盤IO就能夠重疊
若是異步IO成熟的話, 可使用proactor模式, 讓同一個線程的IO和計算重疊, 充分利用DMA特性
線程池大小的選擇?
若是池中執行任務時, 密集計算所佔時間比重P(0<P<=1), 而系統一共有C個cpu, 爲了讓C個cpu跑滿而不過載, 線程池大小的經驗公式T=C/P, 即T*P=C(讓cpu恰好跑滿). P太小, 如小於0.2, 如0.001, 此時則需根據測試選擇固定的線程數量
假設C=8, P=1.0, 線程池的任務徹底密集計算, 只要8個活動線程就能讓cpu飽和
假設C=8, P=0.5, 線程池的任務有一半是計算, 一半是IO, 那麼T=16, 也就是16個"50%繁忙的線程能讓8個cpu忙個不停"
線程分類:
IO線程(這裏特指網絡IO), reactor
計算線程, cpu
第三方庫所用線程, 如logging, 又好比database, 不能放入計算線程中
多路IO轉接服務器也叫多任務IO服務器. 該類服務器實現的主旨思想是, 再也不有應用程序本身見識客戶端鏈接, 取而代之有內核替應用程序監視文件
linux下有三種IO複用模型: (1) select; (2) poll; (3) epoll
若是客戶端關閉套接字close, 而服務端調用了一層write, 服務端會接收一個RST segment(TCP傳輸層)
若是服務器再次調用write, 這時就會產生SIGPIPE信號, 若是沒有忽略該信號, 默認處理方式退出整個進程
TIME_WAIT狀態在一段時間內, 內核會保留一些內核資源, 對大併發服務器的影響, 應該儘量在服務器端避免出現TIME_WAIT狀態
若是服務器主動斷開鏈接(先於client調用close), 服務端就會進入TIME_WAIT, 這樣在必定時間範圍內內核會hold住內存資源
協議設計上, 應該讓客戶端主動斷開鏈接, 這樣就把TIME_WAIT狀態分散到大量的客戶端
問題: 若是客戶端不活躍, 一些惡意客戶端不斷開鏈接佔用服務端資源, 這時服務端要有個機制來踢掉不活躍的鏈接close, 這樣也會後來
select缺點:
int select( int nfds, // 監聽全部的文件描述符中, 最大的文件描述符+1 fd_set *readfds, // 監聽的文件描述符"可讀"事件 fd_set *writefds, // 監聽的文件描述符"可寫"事件 fd_set *exceptfds, // 監聽的文件描述符"異常"事件 struct timeval *timeout); // 返回值: // 成功: 所監聽的全部的監聽集合中, 知足條件的總數 // 失敗: -1 void FD_ZERO(fd_set *set); // 將set清空 void FD_CLR(int fd, fd_set *set); // 將fd從set中清除出去 void FD_SET(int fd, fd_set *set); // 將fd設置到set集合中 int FD_ISSET(int fd, fd_set *set); // 判斷fd是否在set集合中 // 返回值: 知足--> 1 FD_SETSIZE // 一個進程中select所能操做的文件描述符的最大數目. 通常狀況下被定義爲1024. 修改需從新編譯內核
文件描述符突破1024, 修改配置文件
監聽和返回集合分離
搜索範圍變小
監聽同一文件描述符的兩種事件時, 可定義兩個相同的文件描述符, event設置不一樣便可
poll模式至關於epoll的ET(電平觸發模式)
read 可能並無把connf所對應的緩衝區的數據都讀完, 那麼connfd仍然是處於活躍狀態, 也就是說下一次poll時, connfd對應的POLLIN事件仍然會觸發, 須要再次調用read接收請求, 這沒有問題, 可是應用層發來的數據包恰好分包(一個數據包須要兩次read才能接受徹底, 粘包問題)
應該將讀到的數據保存在connfd的應用層緩衝區, 也就是說對每個已鏈接的套接字分配一個應用層緩衝區, read時只管把數據追加到應用層緩衝區末尾, 下一次讀時也最加到末尾, 解析協議到應用緩衝區
write 對請求數據進行應答, 若是應答數據過大, 如應答一萬個字節, 只write了一千個字節connfd對應的內核發送滿了, write調用不會阻塞(connfd是非阻塞套接字, 阻塞模式不會出現這個問題, 但阻塞把整個線程阻塞住了, 下降程序的併發性), 這時不能把未發送的數據丟棄掉
這時也應該有一個應用層的發送緩衝區, 數據沒有write完, 應該關注connfd的POLLOUT事件, POLLOUT事件觸發條件是connfd對應的內核緩衝區能夠容納數據
不能一開始accept接收到connfd時就關注POLLOUT事件, 一開始connfd對應的發送緩衝區沒有數據, 又沒有發送數據, 這時會一直觸發POLLOUT事件, 會出現busy loop忙等待
int poll( struct pollfd *fds, // 輸入輸出參數, 結構體指針, 每次都須要把關注事件從用戶緩衝區拷貝到內核緩衝區, 效率底 nfds_t nfds, // 監聽文件描述符的個數 int timeout // 超時時間, 負數阻塞等待直至發生事件 ); // timeout: // -1: 阻塞等, #define INFTIM -1, linux中沒有定義此宏 // 0: 當即返回, 不阻塞 // >0: 等待指定毫秒數, 如當前系統時間精度不夠毫秒, 向上取值 struct pollfd { int fd; /* file descriptor, 文件描述符 */ short events; /* requested events, 請求的事件 */ short revents; /* returned events, 返回的事件 */ };
epoll是linux下多路複用I/O接口select和poll的加強版本, 他能顯著提升程序在大量併發鏈接中只有少許活躍的狀況下的系統CPU利用率, 由於它會複用文件描述符集合來傳遞結果, 而不用迫使開發者每次等待事件以前都必須從新準備要被監聽的文件描述符集合. 另外一點緣由就是獲取事件的時候, 它無須遍歷整個被監聽的描述符集, 只要遍歷那些被內核IO事件異步喚醒二加入Read列的描述符集合就能夠
目前epoll是linux大規模併發網絡程序中的熱門首選模型, 適用於鏈接描述符多, 監聽描述符少的狀況
EPOLLIN事件
內核中的socket接收緩衝區 爲空 低電平
內核中的socket接收緩衝區 不爲空 高電平
即便緩衝區沒有讀完, 仍然會觸發事件, 由於處於高電平狀態直至數據讀完
EPOLLOUT事件
內核中的socket發送緩衝區 不滿 高電平
內核中的socket發送緩衝區 滿 低電平
兩種觸發模式: (1)Level-Triggered; (2)Edge-Triggered
LT電平觸發
高電平觸發
ET邊沿觸發
低電平 -> 高電平 觸發
高電平 -> 低電平 觸發
若是採用LT, 那何時關注EPOLLOUT事件? 會不會形成busy-loop?
獲得套接字當即關注會出現busy loop, 由於這時內核緩衝區是空, 一直處於可發送狀態
read: 同poll
write:: 同poll
若是採用ET, 那何時關注EPOLLOUT事件?會不會形成busy-loop?
獲得套接字當即關注不會出現busy loop, 由於這時內核緩衝區是空, 一直處於可發送狀態(高電平狀態), 電平並無發生變化
read: 必定要讀到EAGAIN, 表示數據都讀完, 若是有1000個數據, 只讀取10個字節, 那麼從此無論對方發送多少個數據過來, 因爲是ET模式因此都不會再觸發了
write: 寫數據後不須要再關注EPOLLOUT事件, 由於在接收文件描述符時, 已經關注EPOLLOUT了. 發送數據要把應用層緩衝區發送完, 若不能發送完必定要發送至返回EAGAIN.
如應用層要發200個字節, 內核緩衝區可容納100個字節:
(1)只發送50個字節, 應用層還剩餘50個字節沒有發送, 可是沒有發送至EAGAIN, 一直處於可發送狀態至關於一直處於高電平狀態, 就永遠不會再發送剩餘150個字節
(2)發送100個字節把內核緩衝區填滿, 處於不可發送狀態至關於底電平狀態, 對方接收走數據後至關於高電平狀態, 會觸發EPOLLOUT事件, 剩餘100個字節才能夠再發送
epoll下ET模式爲什麼必定要用要用非阻塞的模式
ET 模式是一種邊沿觸發模型,在它檢測到有 I/O 事件時,經過 epoll_wait 調用會獲得有事件通知的文件描述符,每於每個被通知的文件描述符,如可讀,則必須將該文件描述符一直讀到空,讓 errno 返回 EAGAIN 爲止,不然下次的 epoll_wait 不會返回餘下的數據,會丟掉事件。而若是你的文件描述符若是不是非阻塞的,那這個一直讀或一直寫勢必會在最後一次阻塞。
普通模型
epoll --> 服務器 --> 監聽 --> fd --> 可讀 --> epoll返回 --> read --> 小寫轉大寫 --> write --> epoll繼續監聽
反應堆模型
epoll --> 服務器 --> 監聽 --> cfd --> 可讀 --> epoll返回 --> read --> cfd從樹上摘下 --> 設置監聽cfd寫時間(滑動窗口), 操做 --> 小寫轉大寫 --> 等待epoll_wait返回 --> 回寫客戶端 --> cfd從樹上摘下 --> 設置監聽cfd讀事件, 操做 --> epoll繼續監聽
#include <sys/epoll.h> // 至關於內核開闢一個空間處理關注事件 int epoll_create(int size); // epoll可以處理的文件描述符個數, 這個值如今能夠忽略, epoll所能管理文件描述符的個數依賴系統資源的限制 int epoll_create1(int flags); // 能夠指定標誌位, 如EPOLL_CLOEXEC // 向內核中的空間添加或刪除事件 int epoll_ctl( int epfd, // epoll_create的句柄 int op, // 動做, EPOLL_CTL_ADD-->註冊新的fd到epfd中, EPOLL_CTL_MOD-->修改已註冊到epfd中的事件, EPOLL_CTL_DEL-->刪除epfd中的fd事件 int fd, struct epoll_event *event); // 內核要監聽的事件 // events: 輸出參數, 關注事件已經從epoll_ctl中傳遞進去加入, 不須要再把關注的事件拷貝到內核, 與poll相比效率提升, 不須要每次從用戶空間拷貝到內核空間 int epoll_wait( int epfd, struct epoll_event *events, // 輸出參數, 用來存儲內核獲得事件的集合, 不像在poll中在全部已鏈接套接字數組中進行遍歷, 返回的套接字都是活躍的 int maxevents, // 告之這個events的大小, 不能大於epoll_create時的size int timeout ); // timeout: 超時時間 // -1: 阻塞 // 0: 當即返回 // >0: 指定毫秒數 typedef union epoll_data { // 最大值8個字節 void *ptr; int fd; uint32_t u32; uint64_t u64; } epoll_data_t; struct epoll_event { uint32_t events; /* Epoll events, 關注事件 */ epoll_data_t data; /* User data variable, 用戶數據 */ }; // events: // EPOLLIN: 表示對應的文件描述符能夠讀(包括對端SOCKET正常關閉) // EPOLLOUT: 表示對應的文件描述符能夠寫 // EPOLLERR: 表示對應的文件描述符發送錯誤 // EPOLLET: 將EPOLL設置問邊沿觸發模式 客戶端主動斷開鏈接返回POLLIN 服務端主動斷開鏈接返回POLLIN|POLLHUP兩個事件 events能夠是如下幾個宏的集合: EPOLLIN :表示對應的文件描述符能夠讀(包括對端SOCKET正常關閉); EPOLLOUT:表示對應的文件描述符能夠寫; EPOLLPRI:表示對應的文件描述符有緊急的數據可讀(這裏應該表示有帶外數據到來); EPOLLERR:表示對應的文件描述符發生錯誤; EPOLLHUP:表示對應的文件描述符被掛斷; EPOLLET: 將EPOLL設爲邊緣觸發(Edge Triggered)模式,這是相對於水平觸發(Level Triggered)來講的。 EPOLLONESHOT:只監聽一次事件,當監聽完此次事件以後,若是還須要繼續監聽這個socket的話,須要再次把這個socket加入到EPOLL隊列裏 EPOLLIN == POLLIN); EPOLLOUT == POLLOUT); EPOLLPRI == POLLPRI); EPOLLERR == POLLERR); EPOLLHUP == POLLHUP); EPOLLRDHUP == POLLRDHUP);
EMFILE: 進程打開的文件描述符超出上限
騰出來一個文件描述符用於接收,
出現一個EMFILE後, 若是沒有退出程序, 事件沒有處理(可用套接字滿了), 監聽套接字仍然處於活躍狀態(高電平狀態), 下一次調用poll時, accept仍會觸發可讀事件, 調用accept仍然會返回EMFILE錯誤, 會出現busy loop狀態,
返回一個EMFILE後, 因爲沒有accept成功, listenfd仍然處於活躍狀態, 下一次poll時仍然返回EMFILE, 處於busy loop狀態
accept返回EMFILE的處理
若是是epoll模型使用ET模式, accept時沒有辦法把文件描述符返回出來(沒有從高電平到底電平的轉換), 由於文件描述符打開滿了, accept返回失敗, 監聽描述符不會再觸發, 至關於一直處於高電平狀態, 電平沒有變化不會出現busy loop. 可是漏讀一次事件, 後續新的客戶端鏈接過來一直處於高電平狀態
select監聽fd受限,使用的是內核設定的long數組
poll監聽fd不受限,使用的是pollfd的動態數組
select本質上是經過設置或者檢查存放fd標誌位的數據結構來進行下一步處理. 這樣所帶來的缺點是:
poll本質上和select沒有區別, 它將用戶傳入的數組拷貝到內核空間, 而後查詢每一個fd對應的設備狀態, 若是設備就緒則在設備等待隊列中加入一項並繼續遍歷, 若是遍歷完全部fd後沒有發現就緒設備, 則掛起當前進程, 直到設備就緒或者主動超時, 被喚醒後它又要再次遍歷fd. 這個過程經歷了屢次無謂的遍歷.
它沒有最大鏈接數的限制, 緣由是它是基於鏈表來存儲的, 可是一樣有一個缺點:
大量的fd的數組被總體複製於用戶態和內核地址空間之間, 而無論這樣的複製是否是有意義.
poll還有一個特色是「水平觸發」, 若是報告了fd後, 沒有被處理, 那麼下次poll時會再次報告該fd.
在前面說到的複製問題上, epoll使用mmap減小複製開銷.
還有一個特色是, epoll使用「事件」的就緒通知方式, 經過epoll_ctl註冊fd, 一旦該fd就緒, 內核就會採用相似callback的回調機制來激活該fd, epoll_wait即可以收到通知
epoll改進select缺陷主要在: (1)紅黑樹; (2)rdlist(就緒描述符列表)
紅黑樹是用來存儲這些描述符的,由於紅黑樹的特性,就是良好的插入,查找,刪除性能O(lgN)。
當內核初始化epoll的時候(當調用epoll_create的時候內核也是個epoll描述符建立了一個文件,畢竟在Linux中一切都是文件,而epoll面對的是一個特殊的文件,和普通文件不一樣),會開闢出一塊內核高速cache區,這塊區域用來存儲咱們要監管的全部的socket描述符,固然在這裏面存儲必定有一個數據結構,這就是紅黑樹,因爲紅黑樹的接近平衡的查找,插入,刪除能力,在這裏顯著的提升了對描述符的管理。
rdlist就緒描述符鏈表這是一個雙鏈表,epoll_wait()函數返回的也是這個就緒鏈表。
當內核建立了紅黑樹以後,同時也會創建一個雙向鏈表rdlist,用於存儲準備就緒的描述符,當調用epoll_wait的時候在timeout時間內,只是簡單的去管理這個rdlist中是否有數據,若是沒有則睡眠至超時,若是有數據則當即返回並將鏈表中的數據賦值到events數組中。這樣就可以高效的管理就緒的描述符,而不用去輪詢全部的描述符。因此當管理的描述符不少可是就緒的描述符數量不多的狀況下若是用select來實現的話效率可想而知,很低,可是epoll的話確實是很是適合這個時候使用。
對與rdlist的維護:當執行epoll_ctl時除了把socket描述符放入到紅黑樹中以外,還會給內核中斷處理程序註冊一個回調函數,告訴內核,當這個描述符上有事件到達(或者說中斷了)的時候就調用這個回調函數。這個回調函數的做用就是將描述符放入到rdlist中,因此當一個socket上的數據到達的時候內核就會把網卡上的數據複製到內核,而後把socket描述符插入就緒鏈表rdlist中。
實現原理:當一個socket描述符的中斷事件發生,內核會將數據從網卡複製到內核,同時將socket描述符插入到rdlist中,此時若是調用了epoll_wait會把rdlist中的就緒的socekt描述符複製到用戶空間,而後清理掉這個rdlist中的數據,最後epoll_wait還會再次檢查這些socket描述符,若是是工做在LT模式下,而且這些socket描述符上還有數據沒有讀取完成,那麼L就會再次把沒有讀完的socket描述符放入到rdlist中,因此再次調用epoll_wait的時候是會再次觸發的,而ET模式是不會這麼幹的。
select: 單個進程所能打開的最大鏈接數有FD_SETSIZE宏定義, 其大小是32個整數的大小(在32位的機器上, 大小就是32*32, 同理64位機器上FD_SETSIZE爲32*64), 固然咱們能夠對進行修改, 而後從新編譯內核, 可是性能可能會受到影響, 這須要進一步的測試.
poll: 本質上和select沒有區別, 可是它沒有最大鏈接數的限制, 緣由是它是基於鏈表來存儲的, 1G內存的機器上能夠打開10萬左右的鏈接, 2G內存的機器能夠打開20萬左右的鏈接
epoll: 雖然鏈接數有上限, 可是很大, 1G內存的機器上能夠打開10萬左右的鏈接, 2G內存的機器能夠打開20萬左右的鏈接
select: 由於每次調用時都會對鏈接進行線性遍歷, 因此隨着FD的增長會形成遍歷速度慢的「線性降低性能問題」.
poll: 同上
epoll: 由於epoll內核中實現是根據每一個fd上的callback函數來實現的, 只有活躍的socket纔會主動調用callback, 因此在活躍socket較少的狀況下使用epoll沒有前面二者的線性降低的性能問題. 可是已鏈接套接字不太大而且套接字都很是活躍時, 可能會有性能問題, 由於epoll內部實現更復雜, 須要更復雜的代碼邏輯. epoll優點在於大併發.
select: 內核須要將消息傳遞到用戶空間, 都須要內核拷貝動做
poll: 同上
epoll: epoll經過內核和用戶空間共享一塊內存來實現的.
基於管道, 邊緣觸發時父進程一次顯示4個字符, 觸發時一次顯示所有字符: epoll_pipe
阻塞epoll: epoll_blocking_server.c
非阻塞epoll, 邊緣觸發而且非阻塞模式減小epoll_wait函數調用次數: epoll_nonblocking_server.c
客戶端固定一次發送10個字符: epoll_client.c
epoll反應堆: epoll_reactor.c
#!/bin/sh set -x # 追蹤全部執行命令 SOURCE_DIR=`pwd` # 反斜號, 執行命令 BUILD_DIR=${BUILD_DIR:-build} # BUILD_DIR判斷, 爲空取build值 # 下面反斜號後面不能有註釋空格 mkdir -p $BUILD_DIR \ && cd $BUILD_DIR \ && cmake $SOURCE_DIR \ && make $*