1. 程式人生 > >I/O 多路複用

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;
}