歡迎來到Linux教程網
Linux教程網
Linux教程網
Linux教程網
Linux教程網 >> Linux編程 >> Linux編程 >> Linux Socket 編程I/O Multiplexing

Linux Socket 編程I/O Multiplexing

日期:2017/3/1 9:47:48   编辑:Linux編程

Linux Socket 編程中I/O Multiplexing 主要通過三個函數來實現:select, poll,epoll來實現。I/O Multiplexing,先構造一張有關描述符的列表,然後調用一個函數,直到這些描述符中的一個已准備好進行I/O時,該函數才返回。在返回時,它告訴進程哪些描述符已准備好可以進行I/O。本文具體介紹一下select 和poll的用法,給出簡單的demo代碼,簡要分析一下這兩個函數的使用易出錯的地方。

#include<sys/select.h>

int select(int maxfdp1, fd_set *restrict readfds, fd_set *restrict writefds,fd_set *restrict exceptfds, struct timeval* restrict tvptr);

//Returns: count of ready descriptors, 0 on timeout, -1 on error

  中間三個參數readfds、writefds和exceptfds是指向描述符集的指針。這三個描述符集說明了我們關心的可讀、可寫或出於異常條件的各個描述符,設置為NULL則表示不關心。每個描述符集存放在一個fd_set數據類型中。這種數據類型為每一可能的描述符保持一位。描述符集的函數接口(可能實現為宏)包括:調用FD_ZERO將一個指定的fd_set變量的所有位設置為0;調用FD_SET設置一個fd_set變量的指定位;調用FD_CLR將一指定位清楚;調用FD_ISSET測試一指定位是否設置。

#include <sys/select.h>

int FD_ISSET(int fd, fd_set *fdset);

  //Returns: nonzero if fd is in set, 0 otherwise

void FD_CLR(int fd, fd_set *fdset);

void FD_SET(int fd, fd_set *fdset);

void FD_ZERO(fd_set *fdset); 

  文件描述符集fdset中的文件描述符的個數是有限制的,最大值由FD_SETSIZE指定,一般為1024.

  Select 最後一個參數用於設置超時值,當select監聽達到超時值時還未有關心的事件發生則返回,函數返回值為0.

struct timeval{

  long tv_sec;//second

  long tv_usec;//minisecond

} 

  超時參數如果設置為 NULL 則無限等待。

  下面來是一個簡單的select Echo server:

// simpleEcho.cpp
#include <stdio.h> #include <sys/socket.h> #include <netinet/in.h> #include <unistd.h> #include <arpa/inet.h> #include <sys/types.h> #include <vector> #include <string.h> #include <stdlib.h> #include <fcntl.h> #define SEVER_PORT 1314 #define MAX_LINE_LEN 1024 using namespace std; int main() { struct sockaddr_in cli_addr, server_addr; socklen_t sock_len; vector<int> client(FD_SETSIZE,-1); fd_set rset,allset; int listenfd, connfd, sockfd, maxfd, nready, ix,maxid, nrcv,one; char addr_str[INET_ADDRSTRLEN],buf[MAX_LINE_LEN]; bzero(&server_addr,sizeof server_addr); server_addr.sin_family = AF_INET; server_addr.sin_addr.s_addr = htonl(INADDR_ANY); server_addr.sin_port = htons(SEVER_PORT); listenfd = socket(AF_INET,SOCK_STREAM,0); one = 1; setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR,&one, sizeof(one)); if(bind (listenfd ,(struct sockaddr *)&server_addr ,sizeof server_addr) < 0 ) { printf("socket bind error" ); return 0; } listen(listenfd ,10); FD_ZERO(&allset); FD_SET(listenfd ,&allset ); maxfd = listenfd ; maxid = -1 ; while(1 ) { rset = allset; //! nready = select (maxfd + 1, &rset,NULL,NULL,NULL); if(nready < 0 ) { printf("select error! \n" ); exit(1 ); } if(FD_ISSET (listenfd , &rset )) { sock_len = sizeof cli_addr; connfd = accept (listenfd ,(struct sockaddr *)&cli_addr , &sock_len); printf("recieve from : %s at port %d\n" , inet_ntop(AF_INET,&cli_addr .sin_addr ,addr_str ,INET_ADDRSTRLEN ),cli_addr .sin_port ); for(ix = 0 ; ix < static_cast< int>(client .size()); ix++) { if(client[ix] < 0 ) { client[ix] = connfd ; break; } } printf("client[%d] = %d\n" ,ix ,connfd ); if( FD_SETSIZE == ix) { printf("too many client! \n" ); exit(1 ); } if( connfd > maxfd) { maxfd = connfd; } FD_SET(connfd, &allset ); if(ix > maxid ) { maxid = ix; } if(--nready == 0) { continue; } } for(ix = 0 ; ix <= maxid; ix++) //<= { if((sockfd = client [ix ]) < 0) { continue; } if(FD_ISSET (sockfd ,&rset )) { if( 0 == (nrcv = read(sockfd,buf,MAX_LINE_LEN ))) { close(sockfd); client[ix] = -1 ; FD_CLR(sockfd ,&allset ); } else { printf("RECIEVE: %s \n" ,buf ); write(sockfd,buf,nrcv); } } if(--nready == 0) { break; } } } return 0; }

  在使用select 的時候要注意兩點:

    第一個參數需要是當前所關心的文件描述符中最大的一個+1

    第二需要注意的是select的中間3個參數采用了“value-result”(UNP1的說法)的方式,設置了關心的文件描述符進行select,select返回之後對應描述的fdset中只有有事件發生的對應fd會被設置,其它關心但是沒有事件發生的描述符將會從fdset中清除掉,如果不進行重新賦值,下次select就不會關注這些描述符了,因此上述代碼中allset每次對rset進行復制。

  來看看如果只在while(1) 之前設置rset,在while(1) 中不在每次select之前賦值會發生什麼,在控制台輸入: strace ./simpleEcho,另外打開一個控制台窗口輸入:nc localhost 1314,這作為一個連接Echo server 的 client,然後輸入你想發往Echo Server內容。關鍵我們來看一下Echo server的情況:

Copyright © Linux教程網 All Rights Reserved