关于linux下IO多路复用函数select的使用

Linux下一切皆文件,指的是对所有的设备或者文件都按照文件的方式来操作及都是对文件描述符的操作。常见的文件描述符有针对普通文件的,也有针对设备和进程间通信的描述符。那么有这样一个问题,在一个进程中既可以从标准输入中读取数据并且打印,又可以从连接的网络套接字来获得客户端的请求? 这个问题也很好解决,可以利用多进程或者线程来解决,让每个进程或线程干不同的事情就行了,如果不用进程或线程该如何解决呢?

这就不得不提到一个函数,那就是select函数,我们先看一下select的函数接口:

#include

#include

#include

int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);

功能:

select函数的功能就是用来检测多个文件描述符,一旦有文件描述准备好,那么该函数就返回。

参数介绍:

nfds:要检测的大描述符的数值加 1.

readfds: 检测输入是否就绪的描述符集合,即文件描述符是否可读。

writefds: 检测输出是否就绪描述符集合,即文件描述符是否可写

exceptfds: 检测是否发生异常的文件描述符集合。

timeout: 超时检测,精确到微妙, 是一个相对时间。

struct timeval {

long tv_sec; /* seconds */

long tv_usec; /* microseconds */

};

返回值:

成功返回准备好的描述符个数。

失败返回-1。

超时返回0。

要检测的描述符集合数据类型fd_set是以位掩码的形式存在,就是1024个bit位,所以在linux上select检测描述符的个数FD_SETSIZE上限是1024个。对于这些我们没有必要关心,因为内核给我们提供了四个函数。

void FD_CLR(int fd, fd_set *set); 将某个描述符从一个描述符集合中取出。

int FD_ISSET(int fd, fd_set *set); 判断相应的描述是否在某个被检测的描述符中

void FD_SET(int fd, fd_set *set); 将需要检测的描述符添加到对应的描述符集合中

void FD_ZERO(fd_set *set); 将检测的描述符集合清空

使用限制:

1、select函数在每次重新调用时会将检测的描述符表清空,因此每次需要重新指定要检测的描述符。

2、当指定了超时时间时,重新调用select时超时时间会变成上一次调用剩余的时间。

下边是使用select进行描述符监听的基于tcp的服务器端的代码:

#include

#include

#include

#include

#include /p>

#include

#include

#include

#include

#include < 0)

#include

#include

void signal_handler(int signo);

int main(int argc, char *argv[])

{

char buf_1[64] = {0};

char buf[128] = {0};

int listenfd, connectfd;

struct sockaddr_in serveradd, clientadd;

unsigned int size = sizeof(clientadd);

fd_set readfds, temp;

int max_fd;

int ret, i = 0;

ssize_t bytes;

bzero(&serveradd, sizeof(serveradd));

bzero(&clientadd, sizeof(clientadd));

serveradd.sin_family = AF_INET;

serveradd.sin_port = htons(9999);

serveradd.sin_addr.s_addr = htonl(INADDR_ANY);

signal(SIGPIPE, signal_handler);

signal(SIGINT, SIG_IGN);

if ((listenfd = socket(AF_INET, SOCK_STREAM, 0)) < 0) {

perror("fail to create socket");

return -1;

}

if (bind(listenfd, (struct sockaddr*)&serveradd, sizeof(serveradd)) < 0) {

perror("fail to bind the server!");

close(listenfd);

return -1;

}

if (listen(listenfd, 20) < 0) {

perror("fail to listen");

close(listenfd);

return -1;

}

FD_ZERO(&readfds);

FD_ZERO(&temp);

FD_SET(listenfd, &readfds);

FD_SET(STDIN_FILENO, &readfds);

max_fd = listenfd;

temp = readfds;

while (1) {

readfds = temp;

ret = select(max_fd + 1, &readfds, NULL, NULL, NULL);

if (ret < 0) {

perror("fail to select...");

continue;

}

for (i = 0;i <= max_fd; i++){

if (FD_ISSET(i, &readfds)) {

if (i == listenfd) {

connectfd = accept(listenfd, (struct sockaddr*)&clientadd, &size);

printf("client addr: %s, port : %d\n", inet_ntoa(clientadd.sin_addr),ntohs(clientadd.sin_port));

max_fd = (max_fd > connectfd) ? max_fd: connectfd;

FD_SET(connectfd, &temp);

} else if (i == 0) {

fgets(buf_1, sizeof(buf_1), stdin);

puts(buf_1);

} else {

while (1) {

bytes = recv(i, buf, sizeof(buf), 0);

if (bytes == 0) {

close(i);

FD_CLR(i, &temp);

break;

}

sprintf(buf, "server recv %d bytes!\n", bytes);

send(i, buf, strlen(buf) + 1, 0);

}

}

}

}

}

close(listenfd);

return 0;

}

void signal_handler(int signo)

{

puts("the client is exit....");

exit(0);

}