在UNIX/linux中有4中IO模型,分別為:
1、 阻塞IO
2、 非阻塞IO
3、 IO多路復用
4、 信號驅動IO
這幾種IO模型,阻塞IO是長用到的,并且操作相對簡單,但是缺點在于效率低下,尤其是在,同時操作多個IO的時候,不能隨時的處理各個IO操作。而非阻塞IO可以解決這個問題,但是同樣也存在的問題,因為使用非阻塞IO模型的時候,就需要對每個IO操作進行輪訓操作,而實際上輪空的幾率是很大的,多以非阻塞IO模型是在那種輪空幾率相對較小的的時候才會使用。因為輪訓是會占用相當多的cpu時間片的。基于這種考慮多路復用模型變產生了。
多路復用模型是對多個IO操作進行檢測,返回可操作集合,這樣就可以對其進行操作了。這樣就避免了阻塞IO不能隨時處理各個IO和非阻塞占用系統資源的確定。下面就多路復用中兩個常用的系統調用select和poll進行簡單的說明。
1、 select
select函數的原型為:
#include <sys/select.h>
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
int select(int nfds,
fd_set *readfds,
fd_set *writefds,
fd_set *exceptfds,
struct timeval *timeout);
select的使用需要維護三類文件描述符集合,分別是:
readfds:用于存放我們要檢測的可讀的文件描述符。
writefds:用于存放我們要檢測的可寫的文件描述符。
exceptfds:用于存放我們要檢測的是否發生異常的文件描述符。
在使用select的時候需要注意,在調用select后者幾個集合就會變化,變為是操作的集合,即可讀寫或發生異常,所以在調用select之前需要保存以前的集合。
系統為我們提供了下面四個宏來操作這些文件描述符集合:
void FD_SET(int fd, fd_set *set);
void FD_CLR(int fd, fd_set *set);
I int FD_ISSET(int fd, fd_set *set);
void FD_ZERO(fd_set *set);
其中FD_SET用來將一個文件描述符加入到文件描述符集合里,FD_CLR是刪除,FD_ISSET是判斷文件描述符是否在這個集合里,而FD_ZERO是將這個集合清零。
下面我們以一個簡單的例子來說明select的使用。
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/select.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#define N 64
int main(int argc, char *argv[])
{
if(argc != 2)
{
printf(“format: server <server ip> <server port>”);
exit(0);
}
int i, servsock, connfd, maxfd;
char buf[N];
fd_set rdfd,rdfd1;
struct sockaddr_in servaddr;
if((servsock = socket(PF_INET, SOCK_STREAM, 0)) < 0)
{
perror("socket");
exit(1);
}
memset(&servaddr, 0, sizeof(servaddr));
servaddr.sin_family = PF_INET;
servaddr.sin_addr.s_addr = inet_addr("192.168.1.203");
servaddr.sin_port = htons(8889);
if(bind(servsock, (struct sockaddr *)&servaddr, sizeof(servaddr)) < 0)
{
perror("bind");
exit(1);
}
listen(servsock, 5);
maxfd = servsock;
FD_ZERO(&rdfd1);
FD_SET(0, &rdfd1);
FD_SET(servsock, &rdfd1);
for(;;)
{
rdfd = rdfd1;
if(select(maxfd+1, &rdfd, NULL, NULL, NULL) < 0)
{
perror("select");
exit(1);
}
for(i = 0; i <= maxfd; i++)
{
if(FD_ISSET(i, &rdfd))
{
if( i == STDIN_FILENO)
{
fgets(buf, N, stdin);
printf("%s", buf);
}
else if(i == servsock)
{
connfd = accept(servsock, NULL, NULL);
printf("New Connection %d is comming\n", connfd);
send(connfd, buf, strlen(buf), 0);
sleep(1);
close(connfd);
}
}
}
}
return 0;
}
上面這段程序在select會阻塞,這個時候我們可以定義一個時間結構體變量,如下:
struct timeval timeout;
timeval的原型為:
struct timeval {
long tv_sec; /* seconds */
long tv_usec; /* microseconds */
};
這個變量可以提供一個微秒級的時間,我們可以使用這個作為超時處理。使用方式就是把這個變量作為select系統調用的后一個參數。
2、 poll
poll的原型為:
#include <poll.h>
int poll(struct pollfd *fds, nfds_t nfds, int timeout);
poll的使用不需要分別維護幾個表,只需要維護一個結構體數組,這個結構體為:
struct pollfd {
int fd; /* file descriptor */
short events; /* requested events */
short revents; /* returned events */
};
這個結構體幾個成員分別是我們要監測的文件描述符,和我們請求的事件及后返回的事件。
poll事件的類型為:
POLLIN There is data to read.
POLLPRI There is urgent data to read .
POLLOUT Writing now will not block.
POLLERR Error condition (output only).
POLLHUP Hang up (output only).
POLLNVAL Invalid request: fd not open (output only).
上面這些事件下面三種在events中沒有用的,只能在revents中才能出現。我們可以調用poll并且查看每個文件描述符的返回狀態進行相應的操作。
下面以一個簡單的實力說明poll的使用:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <poll.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/types.h>
#include <errno.h>
#include <string.h>
#define OPEN_MAX openmax
#define BUFSIZE 1024
int main(int argc, char **argv)
{
if(argc != 3)
{
printf("format: server-poll <server ip> <port>");
exit(0);
}
int openmax = getdtablesize();
int listensock, connsock;
struct sockaddr_in servaddr;
struct pollfd fds[OPEN_MAX];
int i, count = 0, ready, reval;
char readbuf[BUFSIZE];
for(i = 0; i < OPEN_MAX; i++)
{
fds[i].fd = -1;
}
if((listensock = socket(AF_INET, SOCK_STREAM, 0)) < 0)
{
perror("socket");
exit(0);
}
memset(&servaddr, 0, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = inet_addr(argv[1]);
servaddr.sin_port = htons(atoi(argv[2]));
if(bind(listensock, (struct sockaddr*)&servaddr, sizeof(servaddr)) < 0)
{
perror("bind");
exit(1);
}
listen(listensock, 5);
fds[0].fd = 0;
fds[0].events = POLLIN;
count++;
fds[1].fd = listensock;
fds[1].events = POLLIN;
count++;
for(;;)
{
memset(readbuf, 0, BUFSIZE);
ready = poll(fds, count, 0);
for(i = 0; i < count; i++)
{
if(fds[i].revents & POLLIN)
{
if(fds[i].fd == 0)
{
fgets(readbuf, BUFSIZE, stdin);
printf("read buf:%s\n", readbuf);
}
else if(fds[i].fd == listensock)
{
connsock = accept(listensock, NULL, NULL);
printf("client connect\n");
fds[count].fd = connsock;
fds[count].events = POLLIN;
count++;
}
else
{
reval = recv(fds[i].fd, readbuf, BUFSIZE, 0);
printf("recv buf:%s\n", readbuf);
send(fds[i].fd, readbuf, reval, 0);
}
}
}
}
}
return 0;
}