函数原型:
#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);
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);
#include <sys/select.h>
int pselect(int nfds, fd_set *readfds, fd_set *writefds,
fd_set *exceptfds, const struct timespec *timeout,
const sigset_t *sigmask);
参数解释:
-
nfds ---- 所有文件描述符的最大范围, 比最大的文件描述符大一
-
fd_set *readfds ---- 结构体指针,此项中应该包含文件描述符,监视其可读状态,有一个文件可读就返回一个大于0的数,文件描述符到文件末尾也是可以读的,都不可读根据timeout参数进行等待,判断超时则返回0, 发生错误返回负数,可写入NULL,不关注可读状态
-
fd_set *writefds ---- 监视集合内的文件描述符是否有空间可写入,其他同上
-
fd_set *exceptfds ---- 同上,监视文件错误异常
-
struct timeval *timeout :超时时间,会使select 进入三种状态
(1)传入值为NULL,select 进入阻塞态,直到监视的文件描述符发生状态变化(ready)(实例: timeout == NULL )
(2)若将两个时间值设为0秒和0毫秒,就变成一个纯粹的非阻塞函数,不管文件描述符是否有变化,都立刻返回继续执行,文件无变化返回0,有变化返回一个正值 ( timeout->tv_sec == 0 &&timeout->tv_usec == 0不等待)
(3)timeout的值大于0,这就是等待的超时时间,即select在timeout时间内阻塞,时间内发生变化会返回正值,否则返回0
-
结构体 struct fd_set :集合中存放的是文件描述符(file descriptor),即文件句柄,可根据宏来人为操作,fd_set类型变量每一位代表了一个描述符。我们也可以认为它只是一个由很多二进制位构成的数组
-
(1)void FD_CLR(int fd, fd_set *set); ---- 将一个给定的文件描述符从集合中删除
(2)int FD_ISSET(int fd, fd_set *set); ---- 检查集合中指定的文件描述符是否可以读写
(3) void FD_SET(int fd, fd_set *set); ---- 将一个给定的文件描述符加入集合之中
(4)void FD_ZERO(fd_set *set); ---- 清空所有的描述符
-
结构体 struct timeval : 包含秒和微秒两个变量,long tv_sec; 与 long tv_usec;
代码实例:
#include <iostream>
#include "common_n.h"
#include <stdio.h>
#include <sys/ioctl.h>
#include <stdlib.h>
using namespace std;
#define MAX_CLIENT 200
#define PORT 8888
int client_cnt;
int client_socketfd[MAX_CLIENT + 5];
int max_fd;
void do_accept(int listen_socket) {
int new_socket;
new_socket = accept(listen_socket, NULL, NULL);
if (new_socket < 0) {
perror("accept : new_socket");
return ;
}
cout << "----加入成功----" << endl;
for (int i = 0; i < MAX_CLIENT; i++) {
if (client_socketfd[i] != -1) continue;
client_socketfd[i] = new_socket;
client_cnt ++;
break;
}
}
void do_recv(fd_set rfds) {
int client_fd;
int leave = 0;
for (int i = 0; i < client_cnt; i++) {
if (client_socketfd[i] == -1) continue;
client_fd = client_socketfd[i];
if (FD_ISSET(client_fd, &rfds)) {
char buff[1024] = {0};
char ip[20] = {0};
struct sockaddr_in addr;
socklen_t len = sizeof(addr);
getpeername(client_fd, (struct sockaddr*)&addr, &len);
int t = recv(client_fd, buff, 1024, 0);
if (t <= 0) {
client_socketfd[i] = -1;
leave++;
cout << "someone has leave" << endl;
continue;
} else {
cout << inet_ntoa(addr.sin_addr) << "-->" << buff << endl;
t = send(client_fd, buff, strlen(buff), 0);
if (t > 0) cout << "send : " << buff << endl;
else cout << "send failed" << endl;
}
}
}
client_cnt -= leave;
}
int main() {
int listen_socket;
listen_socket = socket_create(PORT);
if (listen_socket < 0) {
perror("socket_create : listen_socket");
exit(0);
}
for (int i = 0; i < MAX_CLIENT; i++) client_socketfd[i] = -1;
struct timeval tm;
fd_set rfds;
max_fd = listen_socket;
while (1) {
FD_ZERO(&rfds);
FD_SET(listen_socket, &rfds);
tm.tv_sec = 10;
tm.tv_usec = 0;
for (int i = 0; i < client_cnt; i++) {
if (client_socketfd[i] == -1) continue;
FD_SET(client_socketfd[i], &rfds);
if (max_fd < client_socketfd[i]) max_fd = client_socketfd[i];
}
int reval = select(max_fd + 1, &rfds, NULL, NULL, &tm);
if (reval < 0) {
perror("select");
break;
} else if (reval == 0) {
cout << "----本轮超时结束----\n" << endl;
continue;
}
if (FD_ISSET(listen_socket, &rfds)) {
do_accept(listen_socket);
} else {
do_recv(rfds);
}
}
close(listen_socket);
return 0;
}
利用select实现I/O复用,实现accept的非阻塞运行。