select函数
使用该函数可以将多个文件描述符集中到一起监视,监视项如下
- 是否存在套接字接收数据
- 无需阻塞传输数据的套接字有哪些
- 哪些套接字发生了异常
select 函数的调用过程如下:
1. 设置文件描述符
监视文件描述符可以视为监视套接字,使用fd_set
数组变量执行此操作,数组是存有 0 和 1 的位数组。
最左边的位表示文件描述符 0 所在位置,若为 1,表示该文件描述符是监视对象。
在fd_set
变量中注册或更改值的操作由下列宏完成:
FD_ZERO(fd_set * fdset)
将 fd_set 变量的所有位初始化位0
FD_SET(int fd, fd_set * fdset)
在参数 fd_set 指向的变量中注册文件描述符 fd 的信息
FD_CLR(int fd, fd_set * fdset)
从参数 fdset 指向的变量中清除文件描述符 fd 的信息
FD_ISSET(int fd, fd_set * fdset)
若参数 fdset 指向的变量中包含文件描述符 fd 的信息,则返回真
2. 设置检查(监视)范围及超时
#include <sys/select.h>
#include <sys/time.h>
int select(int maxfd, fd_set * readset, fd_set * writeset, fd_set * exceptset, const struct timeval * timeout);
成功时返回大于0的值, 该值是发生事件的文件描述符的数目, 失败时返回 -1,超时返回 0
参数解释:
maxfd: 监视对象文件描述符的数量
readset: 将所有关注“是否存在待读取数据”的文件描述符注册到fd_set变量,并传递其地址值
writeset: 将所有关注“是否可传输无阻塞数据”的文件描述符注册到fd_set变量,并传递其地址值
exceptset: 将所有关注“是否发生异常”的文件描述符注册到fd_set变量,并传递其地址值
timeout: 调用 select 后,为防止陷入无限阻塞的状态,传递超时信息
文件描述符的监视范围
与第一个参数有关,该参数要求传递监视对象文件描述符数量,需要得到注册在fd_set
中的文件描述符数,只需将最大的文件描述符值+1 传递到 select 函数即可,加 1 是因为文件描述符的值从 0 开始
超时时间与最后一个参数有关,timeval
结构体如下:
struct timeval
{
long tv_sec; //seconds
long tv_usec; //microseconds
}
设置超时时间是为了防止监视的文件描述符一直未发生变化,造成一直阻塞。
3. 调用 select 后查看结果
select
调用完成后,向其传递的fd_set
变量中将发生变化。原来为 1 的所有位都变为 0,但发生变化的文件描述符对应位除外,因此可以认为值仍为 1 的位置上的文件描述符发生了变化。
#define BUF_SIZE 30
int main(int argc, char *argv[]) {
fd_set reads, temps;
int result, str_len;
char buf[BUF_SIZE];
struct timeval timeout;
FD_ZERO(&reads);
FD_SET(0, &reads);
while (1) {
temps = reads;
timeout.tv_sec = 5;
timeout.tv_usec = 0;
result = select(1, &temps, 0, 0, &timeout);
printf("result = %d", result);
if (result == -1) {
puts("select error!!!");
break;
} else if (result == 0) {
puts("time out");
} else {
if (FD_ISSET(0, &temps)) {
str_len = read(0, buf, BUF_SIZE);
buf[str_len] = 0;
printf("message from console: %s", buf);
}
}
}
return 0;
}
4.I/O复用服务器端
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <sys/time.h>
#include <sys/select.h>
#define BUF_SIZE 30
void error_handling(char *message);
int main(int argc, char *argv[]) {
int serv_sock, cln_sock;
struct sockaddr_in serv_adr, clnt_adr;
struct timeval timeout;
fd_set reads, cpy_reads;
socklen_t adr_sz;
int fd_max, str_len, fd_num, i;
char buf[BUF_SIZE];
if(argc != 2) {
printf("usage: %s <port>\n", argv[0]);
exit(1);
}
serv_sock = socket(PF_INET, SOCK_STREAM, 0);
memset(&serv_adr, 0, sizeof(serv_adr));
serv_adr.sin_family = AF_INET;
serv_adr.sin_addr.s_addr = htonl(INADDR_ANY);
serv_adr.sin_port = htons(atoi(argv[1]));
if (bind(serv_sock, (struct sockaddr *)&serv_adr, sizeof(serv_adr)) == -1) {
error_handling("bind error !!!");
}
if (listen(serv_sock, 5) == -1) {
error_handling("listen error !!!");
}
FD_ZERO(&reads);
FD_SET(serv_sock, &reads);
fd_max = serv_sock;
printf("serv_sock is %d \n", fd_max);
while (1) {
cpy_reads = reads;
timeout.tv_sec = 5;
timeout.tv_usec = 5000;
if ((fd_num = select(fd_max + 1, &cpy_reads, 0, 0, &timeout)) == -1) {
break;
}
if (fd_num == 0) {
continue;
}
for (i = 0; i < fd_max + 1; i++) {
if (FD_ISSET(i, &cpy_reads)) {
if (i == serv_sock) { // connection request
adr_sz = sizeof(clnt_adr);
cln_sock = accept(serv_sock, (struct sockaddr *)&clnt_adr, &adr_sz);
FD_SET(cln_sock, &reads);
if (fd_max < cln_sock) { //保证fd_max比新注册的文件描述符的值大
fd_max = cln_sock;
}
printf("connected clinent: %d \n", cln_sock);
} else { // read message
str_len = read(i, buf, BUF_SIZE);
if (str_len == 0) {
FD_CLR(i, &reads);
close(i);
printf("close client: %d \n", i);
} else {
write(i, buf, str_len); // echo
}
}
}
}
}
close(serv_sock);
return 0;
}
void error_handling(char *message) {
fputs(message, stderr);
fputc('\n', stderr);
exit(1);
}