I/O 多路複用
I/O型別:
接下來我們將介紹幾種常見的I/O模型及其區別
阻塞I/O:blocking I/O(如果沒有資訊,則阻塞)
非阻塞I/O:nonblocking I/O
多路複用I/O:I/O multiplexing (select and poll)
訊號I/O:signal driven I/O (SIGIO)
非同步I/O:asynchronous I/O (the POSIX aio_functions)
I/O多路複用:I/O多路複用技術通過把多個I/O的阻塞複用到同一個select的阻塞上,從而使得系統在單執行緒的情況下可以同時處理多個I/O請求。與傳統的多執行緒/多程序模型比,I/O多路複用的最大優勢是系統開銷小,系統不需要建立新的額外程序或者執行緒,也不需要維護這些程序和執行緒的執行,降底了系統的維護工作量,節省了系統資源。
API:select 、poll、epoll的解決方案
針對如下場合就需要用到IO複用:
當客戶處理多個描述符時候
一個同時處理多個套接字的時候
如果一個TCP伺服器既要處理監聽套接字,又要處理已連線套接字的時候
如果一個伺服器既要處理TCP又要處理UDP
如果一個伺服器要處理多個服務或者多個協議的時候
總結:如果一個執行緒中有多路徑阻塞I/O時,就可以用多路複用
Select:該函式會等待多個I/O事件(比如讀就緒,寫)的任何一個發生,並且只要有一個網路事件發生,select執行緒就會執行。如果沒有任何一個事件發生則阻塞。原型如下:
int select ( int nfds , fd_set *readfds , fd_set *writefds , fd_set *exceptfds , struct timeval *timeout ) ;
nfds:最大的監哨的檔案描述符
readfs:讀的檔案描述符
writes 寫的檔案描述符
exceptfds錯誤輸出檔案描述符
timeout:在指定時間內
struct timeout
{
long misec;
long sec;
}
特點:如果監哨的檔案描述符沒有狀態改變(沒有讀寫改變),則會阻塞。否則會喚醒並通知。每監哨一次時,重新設定檔案描述符集;返回時必須判斷原因 返回值:-1 失敗 errno錯誤的狀態
1、建立集合:
fd_set set
2、新增描述符到集合
FD_SET
從檔案描述符集中刪除描述符
FD_CLR
判斷檔案描述符中是否發生改變:
FD_ISSET
某檔案描述符狀態發生改變>0 失敗==0
清空描述符集:
FD_ZERO
多路I/O 的 伺服器實現 :
#include<stdio.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<arpa/inet.h>
//UDP--伺服器
//4關閉套接字
int main()
{
//1建立套接字
int sock=socket(AF_INET,SOCK_DGRAM,0);
if(sock<0)
{
perror("socket fail");
return -1;
}
//2繫結套接字
struct sockaddr_in myaddr;
myaddr.sin_family =AF_INET;
myaddr.sin_port =htons(7979);
myaddr.sin_addr.s_addr =INADDR_ANY;
if(bind(sock,(struct sockaddr*)&myaddr,sizeof(myaddr))<0)
{
perror("socket fail");
return -1;
}
//3收發訊息
int ilen=0;
char buf[100]="";
struct sockaddr_in caddr;
socklen_t addrlen=sizeof(caddr);
while(1)
{
ilen=recvfrom(sock,buf,99,0,(struct sockaddr*)&caddr,&addrlen);
if(ilen<=0)
break;
buf[ilen]='\0';
//輸出
printf("%s\n",buf);
//返回訊息
sendto(sock,"ok",2,0,(struct sockaddr*)&caddr,sizeof(caddr));
}
//關閉
close(sock);
return 0;
}
主要是在客戶端的程式碼 , 使用select 函式實現 :
#include<stdio.h>
#include<sys/socket.h>
#include<sys/types.h>
#include<arpa/inet.h>
#include<sys/stat.h>
#include<unistd.h>
#include<fcntl.h>
#include<string.h>
#include<sys/select.h>
#include<errno.h>
int main()
{
//建立套接字
int sock=socket(AF_INET,SOCK_DGRAM,0);
if(sock<0)
{
perror("fail\n");
return -1;
}
//繫結
//傳送
//建立檔案描述符集
fd_set rset;
FD_ZERO(&rset);
int maxfd=STDIN_FILENO>sock?STDIN_FILENO:sock; //鍵盤的描述符 0
//地址結構體:伺服器
struct sockaddr_in saddr;
bzero(&saddr,sizeof(saddr)); //清空
saddr.sin_family =AF_INET;
saddr.sin_port =htons(7979);
saddr.sin_addr.s_addr =inet_addr("192.168.8.138");
char buf[100]="";
int ilen=0;
while(1)
{
//設定檔案描述符集:從鍵盤上讀取 從套接字讀取
FD_SET(STDIN_FILENO,&rset);
FD_SET(sock,&rset);
//多路I/O複用
if(-1==select(maxfd+1,&rset,NULL,NULL,NULL)&&errno!=EINVAL)
{
perror("select fail");
break;
}
if(FD_ISSET(STDIN_FILENO,&rset)>0) //從鍵盤上讀取
{
read(STDIN_FILENO,buf,99);
//傳送
sendto(sock,buf,strlen(buf),0,(struct sockaddr*)&saddr,sizeof(saddr));
}
if(FD_ISSET(sock,&rset)>0) //從套接字讀取
{
//讀取
ilen=recv(sock,buf,99,0);
if(ilen<=0)
break;
buf[ilen]='\0';
printf("收到:%s\n",buf);
}
}
//關閉
close(sock);
return 0;
}