面试题答案
一键面试fd_set数据结构的作用
fd_set
是在多路复用I/O中用于表示一组文件描述符的结构体。它允许程序在一个或多个文件描述符上等待I/O事件,如可读、可写或异常情况。通过fd_set
,可以方便地管理和检查多个文件描述符的状态,避免逐个轮询文件描述符,从而提高效率。
初始化fd_set
在使用fd_set
之前,需要先进行初始化。在POSIX系统中,可以使用FD_ZERO
宏来清空一个fd_set
集合,将所有位都设置为0。示例代码如下:
#include <sys/select.h>
fd_set read_fds;
FD_ZERO(&read_fds);
添加文件描述符到fd_set
要将文件描述符添加到fd_set
集合中,可以使用FD_SET
宏。这个宏会将指定文件描述符对应的位设置为1。例如,将标准输入(文件描述符为0
)添加到read_fds
集合中:
FD_SET(0, &read_fds);
从fd_set中移除文件描述符
若要从fd_set
集合中移除文件描述符,可以使用FD_CLR
宏。它会将指定文件描述符对应的位设置为0。示例:
FD_CLR(0, &read_fds);
检查fd_set中的文件描述符状态
在select
函数返回后,可以使用FD_ISSET
宏来检查某个文件描述符是否在事件发生的集合中。如果指定的文件描述符在集合中,FD_ISSET
返回非零值,否则返回0。示例:
if (FD_ISSET(0, &read_fds)) {
// 标准输入有数据可读
}
在select函数中使用fd_set
select
函数使用fd_set
来监听多个文件描述符的事件。函数原型如下:
int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);
nfds
:需要检查的文件描述符集合中最大文件描述符值加1。readfds
:指向一个fd_set
结构体,用于检查可读性的文件描述符集合。writefds
:指向一个fd_set
结构体,用于检查可写性的文件描述符集合。exceptfds
:指向一个fd_set
结构体,用于检查异常情况的文件描述符集合。timeout
:设置select
等待的最长时间。如果设置为NULL
,select
将一直阻塞,直到有事件发生。
示例代码,监听标准输入和一个socket描述符的可读事件:
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <unistd.h>
#include <sys/select.h>
#include <arpa/inet.h>
#include <string.h>
#define PORT 8080
#define MAX_CLIENTS 10
int main() {
int server_fd, new_socket, valread;
struct sockaddr_in address;
int opt = 1;
int addrlen = sizeof(address);
char buffer[1024] = {0};
fd_set read_fds;
fd_set tmp_fds;
int activity;
// 创建socket
if ((server_fd = socket(AF_INET, SOCK_STREAM, 0)) == 0) {
perror("socket failed");
exit(EXIT_FAILURE);
}
// 设置socket选项
if (setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR | SO_REUSEPORT, &opt, sizeof(opt))) {
perror("setsockopt");
exit(EXIT_FAILURE);
}
address.sin_family = AF_INET;
address.sin_addr.s_addr = INADDR_ANY;
address.sin_port = htons(PORT);
// 绑定socket到指定地址和端口
if (bind(server_fd, (struct sockaddr *)&address, sizeof(address))<0) {
perror("bind failed");
exit(EXIT_FAILURE);
}
// 监听socket
if (listen(server_fd, MAX_CLIENTS) < 0) {
perror("listen");
exit(EXIT_FAILURE);
}
// 初始化文件描述符集合
FD_ZERO(&read_fds);
FD_ZERO(&tmp_fds);
// 添加标准输入和server_fd到集合
FD_SET(0, &read_fds);
FD_SET(server_fd, &read_fds);
while (1) {
tmp_fds = read_fds;
activity = select(server_fd + 1, &tmp_fds, NULL, NULL, NULL);
if ((activity < 0) && (errno!= EINTR)) {
printf("select error");
} else if (activity) {
if (FD_ISSET(server_fd, &tmp_fds)) {
if ((new_socket = accept(server_fd, (struct sockaddr *)&address, (socklen_t*)&addrlen))<0) {
perror("accept");
exit(EXIT_FAILURE);
}
FD_SET(new_socket, &read_fds);
}
if (FD_ISSET(0, &tmp_fds)) {
fgets(buffer, 1024, stdin);
printf("Input: %s", buffer);
}
// 处理其他客户端socket的可读事件
for (int i = 0; i < server_fd + 1; i++) {
if (FD_ISSET(i, &tmp_fds) && i!= server_fd && i!= 0) {
valread = read(i, buffer, 1024);
buffer[valread] = '\0';
printf("Message from client: %s\n", buffer);
}
}
}
}
return 0;
}
在上述代码中,通过select
函数使用fd_set
同时监听标准输入和socket连接的可读事件,实现了多路复用I/O。