一、單循環服務器
特點:
1.可以處理多個客戶端 (不能同時)
2.效率不高
//單循環服務器:
socket
bind
listen
while (1)
{connfd = accept();//通信
}特點:簡單 可以處理多客戶端 不能同時
二、并發服務器 ? ?--- 同時可以處理多個客戶端
1、設置一個選項(開啟一個功能) ---讓地址重用
int setsockopt(int sockfd, int level, int optname,const void *optval, socklen_t optlen);
功能:設置socket的屬性參數: @sockfd --- 要設置的socket @level --- 設置socket層次 //socket本身 tcp ip @optname --- 選項名字 @optval --- 選項值 @optlen --- 長度 設置一個選項(開啟一個功能) ---讓地址重用 地址重用:
//并發服務器 --- 同時處理多個客戶端?
socket
bind
listen
while (1)
{
connfd = ?accept();
//通信?
//進程
// ---專門創建一個子進程 --- 負責通信過程 ? ?
//線程
// ---專門創建一個線程 --- 負責通信過程 ??
}
//并發服務器 --- 同時處理多個客戶端
socket
bind
listen
while (1)
{connfd = accept();//通信 //進程// ---專門創建一個子進程 --- 負責通信過程 //線程// ---專門創建一個線程 --- 負責通信過程
}
將兩個任務:
1.負責完成連接操作?
accept ??
2.負責通信的?
多進程 和 多線程
并發服務器 ---多進程方式的效率 肯定 低于 多線程
三、更高效的方式:多路IO復用
多路IO?
I ? --- input
O ? --- output
復用?
一個進程?
一個線程 可以處理多路IO?
//這多路IO 復用了這個一個進程 或是線程
比較:
//并發服務器 --- 同時處理多個客戶端?
socket
bind
listen
while (1)
{
connfd = ?accept();
//通信?
//進程
// ---專門創建一個子進程 --- 負責通信過程 ? ?
//線程
// ---專門創建一個線程 --- 負責通信過程 ??
}
//一路io
accept
//另一路io
負責通信多路IO?
把負責通信的那個進程 或 線程?
用來處理多個客戶端的通信/多進程或多線程的并發服務器
一個客戶端 ----> 一個進程 或 線程?
//IO多路復用?
N個客戶端 -----> 一個進程 或 線程提高并發的能力
?? ??? ?socket ?
|
bind
|
listen
|
accept
|
do_client1 do_client2 ?do_client3
socket ?
|
bind
|
listen
|
accept
|
do_client1 ? do_client2 ?do_client3
| ? | ? |
cli1 cli2 cli3 cli4 cli5 cli6 cli7 cli8 cli9 ?
四、多路IO復用功能的相關函數
select
poll
epoll
五、IO處理的模型
阻塞IO模型:i -- 讀 scanfgetcharfgetsreadrecvo -- 寫寫有阻塞 //管道 讀端存在 寫管道
阻塞IO模型:
以讀為例:
讀操作--->內核中讀取數據--->如果沒有數據,一直等到,直到有數據--->之后將數據帶回到用戶空間 ?? ? ??
非阻塞IO模型:
以讀為例:
讀操作--->內核中讀取數據--->如果沒有數據,不等,直接返回用戶空間
阻塞 與 非阻塞 是操作對象的特性?
5.1設置非阻塞的函數 fcntl
//設置非阻塞
fcntl
#include <unistd.h>
#include <fcntl.h>int fcntl(int fd, int cmd, ... /* arg */ );
功能:維護文件描述符
參數:@fd --- 要操作的fd@cmd --- 要做的一些操作 //command@... --- 可變參數
返回值 取決于所做的操作
int printf(const char *format, ...);printf("hello world!\n");
printf("a = %d\n",a);
printf("a = %d b = %c\n",a,b);設置非阻塞
int flags;
flags = fcntl(fd,F_GETFL,0); //讀
flags = flags | O_NONBLOCK; //修改
fcntl(fd,F_SETFL,flags); //寫 點對點聊天client <----------> serverfgetswrite/send -----> recv/read printf fgetsread<------writeprintf
5.2信號驅動IO:
信號驅動IO//signal
1.fcntl --- 設置 信號接受者 flags = fcntl(fd,F_GETFL); //獲得當前標志 fcntl(fd,F_SETFL,flags|O_ASYNC); O_ASYNC //開啟異步操作 //同步 //A->B//異步 //A//B
2.將該程序 和 SIGIO信號關聯起來 fcntl(fd,F_SETOWN,pid);//OWNER
3.設置信號處理函數 signal(SIGIO)如果 程序要處理多路IO 缺點:處理的數量有限
IO多路復用方式?
//可以處理多路IO?
//不需要 阻塞
//不需要 輪詢?
因此
六、IO多路復用方式:
6.1slect函數;
select
0 --- 讀
1 --- 寫
2 --- 出錯int select(int nfds, fd_set *readfds, fd_set *writefds,fd_set *exceptfds, struct timeval *timeout);功能:實現IO多路復用 @nfds //是關心的文件描述符中最大的那個文件描述符 + 1
@readfds //代表 要關心 的 讀操作的文件描述符的集合
@writefds //代表 要關心 的 寫操作的文件描述符的集合
@exceptfds //代表 要關心 的 異常的文件描述符的集合
@timeout //超時 --- 設置一個超時時間 //NULL 表示select是一個阻塞調用 //設置時間 // 0 --- 非阻塞 // n (>0) --- 阻塞n這么長時間 //注意: 這個值 每次 自動在往下減少 --直到減少到0struct timevalstruct timeval {long tv_sec; /* seconds */long tv_usec; /* microseconds */};struct timeval t = {0,0};返回值:成功 返回就緒的文件描述符的數量 失敗 -1
6.2select函數使用的整體思路:
//select使用時的整體思路:
1.建立一張表 監控fd_set readfds; //一張表 FD_ZERO(&readfds); //清空這張表
2.將要監控的文件描述符 添加表中 FD_SET(0,&readfds);FD_SET(fd,&readfds);3. nfds = fd + 1;
select(nfs,&readfds,NULL,NULL,NULL)void FD_CLR(int fd, fd_set *set); //將fd從set集合中清除
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集合清空
6.3使用:
多路IO復用的服務器: //實現并發 --- 可以同時處理多個客戶端listenfd = socket
bind
listen
//connfd = accept
//1.準備表fd_set readfds;FD_ZERO(&readfds);
//2.添加要監控的文件描述符 FD_SET(listenfd,&reafds);
//3.準備參數 maxfds = listenfd + 1;fd_set backfds;
while (1)
{ backfds = readfds;int ret = select(maxfds,&backfds,NULL,NULL,NULL);if (ret > 0){int i = 0;for (i = 0; i < maxfds;++i){if (FD_ISSET(i,&backfds)){if (i == listenfd) //連接 {connfd = accept();//connfd 要被添加到 監控表FD_SET(connfd,&readfds);if (connfd + 1 > maxfds)maxfds = connfd + 1;}else //負責與客戶端通信 {// i = ?//fd 此時就緒 printf("buf = %s\n",buf);if (strncmp(buf,"quit",4) == 0){FD_CLR(i,&readfds); //清除對應的客戶端的fdclose(i); }}}}}
}//優化
int i = maxfds;
for (i = maxfds-1; i >= 0; --i)
{if (FD_ISSET(i,&readfds)){maxfds = i + 1;}
}
不足:? ?
?1. 最大監聽數受限:`FD_SETSIZE` 默認 1024(Linux)
2. 每次調用需重置 fd_set:內核會修改集合,必須每次重新 `FD_SET`
3. 用戶態與內核態拷貝開銷大
4. 返回后仍需遍歷所有 fd 才能知道哪個就緒
5. 效率隨 fd 數量增長下降明顯