一、select函数
#include<sys/select.h>
#include<sys/time.h>
// 功能:将多个文件描述符集中到一起同一监视:是否存在套接字传接收数据?无需阻塞传输数据的套接字?哪些套接字发生了异常?
// 参数:
// maxfd--监视对象文件描述符数量
// readset--将所有关注“是否存在读取数据”的文件描述符注册到 fd_set 型变量,并传递其地址值
// writeset--将所有关注“是否可传输无阻塞数据”的文件描述符注册到 fd_set 型变量,并传递其地址值
// exceptset--将所有关注“是否发生异常”的文件描述符注册到 fd_set 型变量,并传递其地址值
// timeout--调用 select 函数后,为防止陷入无线阻塞状态,传递超时信息
// 返回值:发生错误时返回 -1,超时返回 0,因发生关注的事件返回时,返回发生事件的文件描述符
int select(int maxfd,fd_set* readset,fd_set* writeset,fd_set* exceptset,const struct timeval* timeout);
struct timeval{
long tv_sec;
long tv_usec;
}
- 设置文件描述符:fd_set 数组是存有 0 和 1 的位数组,最左端的位监视的是文件描述符 0,该位为 1 表示监视,为 0 表示不监视。在 fd_set 变量中注册或更改监视情况的操作由以下宏完成
FD_ZERO(fd_set* fdset) 将fd_set变量的所有位置零
FD_SET(int fd,fd_set* fdset) 在参数fdset指向的变量中注册文件描述符fd的信息
FD_CLR(int fd,fd_set* fdset) 从参数fdset指向的变量中清除文件描述符fd的信息
FD_ISSET(int fd,fd_set* fdset) 若参数fdset指向的变量中包含文件描述符fd的信息,则返回
- 设置监视范围:只需将最大的文件描述符值加 1 再传递给 select 函数的第一个参数即可
- 设置超时:将秒数填入 tv_sec 成员,将微秒数传入 tv_usec 成员,然后将结构体的地址值传给 select 函数的最后一个参数
- 调用 select 函数后查看结果:fd_set 变量中位置上仍为 1 时,表示对应的文件描述符发生了变化
#include<stdio.h>
#include<unistd.h>
#include<sys/time.h>
#include<sys/select.h>
#define BUF_SIZE 30
int main(int argc, char* argv[]) {
fd_set reads, temps;
char buf[BUF_SIZE];
struct timeval timeout;
FD_ZERO(&reads);
FD_SET(0, &reads); // 监视标准输入
while (1) {
temps = reads;
// 调用select函数后tv_sec和tv_usec都会被替换成超时前剩余时间,所有每次调用select函数之前都需要初始化timeval
timeout.tv_sec = 5;
timeout.tv_usec = 0;
int result = select(1, &temps, 0, 0, &timeout);
if (result == -1) {
puts("select() error");
break;
}
else if (result == 0) {
puts("time out");
}
else {
// 验证发生变化的文件描述符是否为标准输入
if (FD_ISSET(0, &temps)) {
int str_len = read(0, buf, BUF_SIZE);
buf[str_len] = 0;
printf("Message from console: %s", buf);
}
}
}
return 0;
}
运行结果:
二、实现 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 100
void error_handling(char* message) {
fputs(message, stderr);
fputc('\n', stderr);
exit(1);
}
int main(int argc, char* argv[]) {
int serv_sock, clnt_sock;
struct sockaddr_in serv_addr, clnt_addr;
char buf[BUF_SIZE];
struct timeval timeout;
fd_set reads, cpy_reads;
if (argc != 2) {
printf("Usage : %s <port> \n", argv[0]);
exit(1);
}
serv_sock = socket(PF_INET, SOCK_STREAM, 0);
if (serv_sock == -1)
error_handling("socket() error");
memset(&serv_addr, 0, sizeof(serv_addr));
serv_addr.sin_family = AF_INET;
serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
serv_addr.sin_port = htons(atoi(argv[1]));
if (bind(serv_sock, (struct sockaddr*)&serv_addr, sizeof(serv_addr)) == -1)
error_handling("bind() error");
if (listen(serv_sock, 5) == -1)
error_handling("listen() error");
FD_ZERO(&reads);
FD_SET(serv_sock, &reads);
int fd_max = serv_sock;
while (1) {
cpy_reads = reads;
timeout.tv_sec = 5;
timeout.tv_usec = 5000;
int fd_num;
if ((fd_num = select(fd_max + 1, &cpy_reads, 0, 0, &timeout)) == -1)
break;
if (fd_num == 0)
continue;
for (int i = 0; i < fd_max + 1; i++) {
if (FD_ISSET(i, &cpy_reads)) {
if (i == serv_sock) // connection request
{
socklen_t clnt_addr_size = sizeof(clnt_addr);
clnt_sock = accept(serv_sock, (struct sockaddr*)&clnt_addr, &clnt_addr_size);
FD_SET(clnt_sock, &reads);
if (fd_max < clnt_sock) {
fd_max = clnt_sock;
}
printf("Connected client: %d \n", clnt_sock);
}
else {
int str_len = read(i, buf, BUF_SIZE);
if (str_len == 0) {
FD_CLR(i, &reads);
close(i);
printf("closed client: %d \n", i);
}
else {
write(i, buf, str_len);
}
}
}
}
}
close(serv_sock);
return 0;
}
客户端代码见文章:https://blog.youkuaiyun.com/Lee_01/article/details/87870121
运行结果:
参考书籍:《TCP/IP网络编程》尹圣雨 著,金果哲 译