一、基本概念
(服務器多客戶端模型)
定義:
? ? ? ? 單線程或單進程同時監測若干個文件描述符是否可以執行IO操作的能力
作用:
? ? ? ? 應用程序通常需要處理來自多條事件流中的事件,比如我現在用的電腦,需要同時處理鍵盤鼠標的輸入、中斷信號等等事件,再比如web服務器如nginx,需要同時處理來來自N個客戶端的事件。
? ? ? ? 邏輯控制流在時間上的重疊叫做 并發(時間段是并行,但時間點上是串行的)
并行:
? ? ? ? 在一個時間點上同時運行。
? ??
? ? ? ? 而CPU單核在同一時刻只能做一件事情,一種解決辦法是對CPU進行時分復用(多個事件流將CPU切割成多個時間片,不同事件流的時間片交替進行)。在計算機系統中,我們用線程或者進程來表示一條執行流,通過不同的線程或進程在操作系統內部的調度,來做到對CPU處理的時分復用。這樣多個事件流就可以并發進行,不需要一個等待另一個太久,在用戶看起來他們似乎就是并行在做一樣。使用并發處理的成本:
? ? ? ? 線程/進程創建成本
? ? ? ? CPU切換不同線程/進程成本 Context Switch ? ? ? ?? ? ? (上下文切換:頁表、寄存器、緩存)
? ? ? ???多線程的資源競爭? ? ? ?
有沒有一種可以在單線程/進程中處理多個事件流的方法呢?一種答案就是IO多路復用。
因此IO多路復用解決的本質問題是在用更少的資源完成更多的事。
二、IO模型?
1、阻塞IO ?
2、非阻塞IO ?EAGAIN ?忙等待 errno
3、信號驅動IO ?SIGIO 用的相對少(了解)
4、并行模型 進程,線程?
2.1阻塞IO?
最常用 默認設置
?
2.2非阻塞IO?
在阻塞IO的基礎上調整其為不再阻塞等待。
?? ? 在程序執行階段調整文件的執行方式為非阻塞:
?? ??? ??? ?===》fcntl() ===>動態調整文件的阻塞屬性?
int fcntl(int fd, int cmd, ... /* arg */ );操作對象 進行何種操作 看第二個參數是否需要
?? 功能:修改指定文件的屬性信息。
?? ?參數:fd 要調整的文件描述符
?? ??? ? ?cmd 要調整的文件屬性宏名稱
?? ??? ? ?... 可變長的屬性值參數。
?? ?返回值:成功 ?不一定,看cmd;?失敗 ?-11.獲得原設備狀態標志位
2.在原標志位基礎上加上 非阻塞
3.當是FILE *文件時,使用fileno,轉換為標志位
4.當寫0,是只讀
eg:修改文件的非阻塞屬性:
? ? ? ? int flag ;
? ? ? ? flag ?= fcntl(fd,F_GETFL,0); ?///獲取fd文件的默認屬性到flag變量中。
? ? ? ? flag ?= flag | O_NONBLOCK; ? ?///將變量的值調整并添加非阻塞屬性
? ? ? ? fcntl(fd,F_SETFL,flag); ? ? ? ///將新屬性flag設置到fd對應的文件生效。? ? ? ? 以上代碼執行后的阻塞IO將變成非阻塞方式。
缺點:CPU占用率高??
2.3信號驅動IO(了解)?
文件描述符需要追加 O_ASYNC//信號通知 標志。
? ? 設備有io事件可以執行時,內核發送SIGIO信號。
? ??
? ? ? ? 1.追加標志
? ? ? ? int flag ;
? ? ? ? flag ?= fcntl(fd,F_GETFL,0);
? ? ? ? fcntl(fd,F_SETFL,flag | O_ASYNC); ? ?//設置為異步
? ? ? ? 2.設置信號接收者
? ? ? ? fcntl(fd,F_SETOWN,getpid());//常用設置
? ? ? ? 3.對信號進行捕獲
? ? ? ? signal(SIGIO,myhandle);
2.4并行模型?
?1.進程
?? ??? ?2.線程
?IO 多路復用 ===》并發服務器 ===》TCP協議?? ?
?? ??? ?3、select循環服務器 ===> 用select函數來動態檢測有數據流動的文件描述符?
2.5 IO多路復用
2.5.1 select函數
select:
?
- 創建fd集合
- 文件描述符加入集合
- select等待事件到來
- 找到對應的fd,進行讀寫操作
- 清除標志位
int select(int nfds, fd_set *readfds, fd_set *writefds,檢測讀 一般不檢測fd_set *exceptfds,錯誤struct timeval *timeout);超時控制,NULL為阻塞,填秒數/毫秒,都寫0,非阻塞工作,只掃一圈,都沒有準備好,返-1;
功能:完成指定描述符集合中有效描述符的動態檢測。
? ? ? ? ? 該函數具有阻塞等待功能,在函數執行完畢后
? ? ? ? ? 目標測試集合中將只保留最后有數據的描述符。? ? 參數:nfds 描述符的上限值,一般是鏈接后描述符的最大值+1;
? ? ? ? ? ? ? ?readfds 只讀描述符集
? ? ? ? ? ? ? ?writefds 只寫描述符集
? ? ? ? ? ? ? ?exceptfds 異常描述符集
? ? ? ? ? ? ? ?以上三個參數都是 fd_set * 的描述符集合類型
? ? ? ? ? ? ? ?timeout ?檢測超時 如果是NULL表示一直檢測不超時 。? ? 返回值:超時 0
? ? ? ? ? ? ? ? ? 失敗 ?-1
? ? ? ? ? ? ? ? ? 成功 >0
為了配合select函數執行,有如下宏函數:
? ? ? ? void FD_CLR(int fd, fd_set *set);
? ? ? ? 功能:將指定的set集合中編號為fd的描述符號刪除。? ? ? ? int ?FD_ISSET(int fd, fd_set *set);//是否就緒
? ? ? ? 功能:判斷值為fd的描述符是否在set集合中,
? ? ? ? ? ? ? 如果在則返回真,否則返回假。? ? ? ? void FD_SET(int fd, fd_set *set);
? ? ? ? 功能:將指定的fd描述符,添加到set集合中。? ? ? ? void FD_ZERO(fd_set *set);
? ? ? ? 功能:將指定的set集合中所有描述符刪除。?
?
2.5.2 epoll函數?
epoll:
?
創建fd集合(二叉樹)
加入關心的文件描述符
epoll_wait ? ? ? ? ? ? ? ? 當 epoll_wait 成功時,它返回準備就緒的文件描述符的數量,如果超時則返回 0。如果發生錯誤,則返回 -1 并設置相應的 errno。
epoll把準備就緒的fd放入rev集合(數組)
1.int epoll_create(int size);
2.int epoll_ctl(int epfd, ? ? ? int op, ? ? ? ? int fd, ? ? ? ? struct epoll_event *event);
? ? ? ? ? ? ? ? ? ? ? ? 哪個集合 ? 何種操作 ? ? ? ? ? 放入誰 ? ? ? ?監視事件,用戶自定義變量
3.int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?-1?
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?0 非阻塞
epoll 解決了select和poll的幾個性能上的缺陷:
①不限制監聽的描述符個數(poll也是),只受進程打開描述符總數的限制;
②監聽性能不隨著監聽描述 符數的增加而增加,是O(1)的,不再是輪詢描述符來探測事件,而是由描述符主動上報事件;
③使用共享內存的方式,不在用戶和內核之間反復傳遞監聽的描述 符信息;
④返回參數中就是觸發事件的列表,不用再遍歷輸入事件表查詢各個事件是否被觸發。
epoll顯著提高性能的前提是:監聽大量描述符,并且每次觸發事件的描述符文件非常少。
epoll的另外區別是:①epoll創建了描述符,記得close;? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?②支持水平觸發和邊沿觸發。
?僅TCP使用(由對方的通信套接字)
getpeername函數是一個網絡編程中常用的函數,它用于獲取與某個套接字關聯的遠程協議地址。這個函數在網絡通信中非常有用,尤其是在需要獲取連接對方的IP地址和端口號時。
int getpeername(int sockfd, struct sockaddr *peeraddr, socklen_t *addrlen);
sockfd 是需要獲取遠程協議地址的套接字描述符。
peeraddr 是一個指向struct sockaddr結構的指針,該結構將被填充遠程地址信息。
addrlen 是一個指向socklen_t類型的變量,初始時表示peeraddr指向的緩沖區的大小,函數返回時,它包含遠程地址的實際大小。
getpeername(conn,(SA),&cli,&len);