select
函数是一个用于在一组文件描述符上进行异步I/O多路复用的系统调用。它可以同时监视多个文件描述符,等待其中任何一个文件描述符准备就绪,然后进行相应的操作。
以下是select
函数的原型:
#include
int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);
参数说明:
nfds
:待监视的最大文件描述符加1。readfds
:指向一个可读文件描述符集合的指针,用于指定要监视读事件的文件描述符。writefds
:指向一个可写文件描述符集合的指针,用于指定要监视写事件的文件描述符。exceptfds
:指向一个异常文件描述符集合的指针,用于指定要监视异常事件的文件描述符。timeout
:指向一个表示超时时间的结构体指针,用于设置select
的超时时间。如果为NULL
,select
将一直阻塞,直到有事件发生。返回值:
select
函数返回就绪文件描述符的总数。select
函数返回0。select
函数返回-1,并设置errno
来指示具体的错误类型。select
函数主要用于实现多路复用的I/O操作,它允许同时监视多个文件描述符,以避免使用阻塞式I/O时每个文件描述符都需要单独的线程。通过select
函数,可以有效地管理并发连接、处理I/O事件和提高系统性能。
下面是一个用C语言编写的使用select
函数实现的并发服务器的示例代码:
#include
#include
#include
#include
#include
#include
#define MAX_CLIENTS 10
#define BUFFER_SIZE 1024
int main() {
int server_fd, client_fds[MAX_CLIENTS], max_fd;
struct sockaddr_in server_addr, client_addr;
char buffer[BUFFER_SIZE];
// 创建服务器套接字
server_fd = socket(AF_INET, SOCK_STREAM, 0);
if (server_fd == -1) {
perror("Socket creation failed");
exit(EXIT_FAILURE);
}
// 初始化服务器地址结构
server_addr.sin_family = AF_INET;
server_addr.sin_addr.s_addr = INADDR_ANY;
server_addr.sin_port = htons(8080);
// 绑定服务器套接字到指定地址和端口
if (bind(server_fd, (struct sockaddr *)&server_addr, sizeof(server_addr)) == -1) {
perror("Bind failed");
exit(EXIT_FAILURE);
}
// 监听连接请求
if (listen(server_fd, MAX_CLIENTS) == -1) {
perror("Listen failed");
exit(EXIT_FAILURE);
}
printf("Server listening on port 8080\n");
fd_set read_fds, all_fds;
int i, fd;
FD_ZERO(&all_fds);
FD_SET(server_fd, &all_fds);
max_fd = server_fd;
// 初始化客户端套接字数组
for (i = 0; i < MAX_CLIENTS; i++) {
client_fds[i] = -1;
}
while (1) {
read_fds = all_fds;
// 使用 select 监听读事件
if (select(max_fd + 1, &read_fds, NULL, NULL, NULL) == -1) {
perror("Select failed");
exit(EXIT_FAILURE);
}
// 检查服务器套接字是否有新连接
if (FD_ISSET(server_fd, &read_fds)) {
socklen_t addr_len = sizeof(client_addr);
// 接受新连接
int new_fd = accept(server_fd, (struct sockaddr *)&client_addr, &addr_len);
if (new_fd == -1) {
perror("Accept failed");
exit(EXIT_FAILURE);
}
// 将新连接添加到客户端套接字数组
for (i = 0; i < MAX_CLIENTS; i++) {
if (client_fds[i] == -1) {
client_fds[i] = new_fd;
break;
}
}
// 更新最大文件描述符
if (new_fd > max_fd) {
max_fd = new_fd;
}
printf("New client connected. Socket fd: %d, IP address: %s, Port: %d\n",
new_fd, inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port));
}
// 检查客户端套接字是否有数据可读
for (i = 0; i < MAX_CLIENTS; i++) {
fd = client_fds[i];
if (fd != -1 && FD_ISSET(fd, &read_fds)) {
// 读取客户端数据
int bytes_read = read(fd, buffer, BUFFER_SIZE);
if (bytes_read <= 0) {
// 连接关闭或发生错误,移除客户端套接字
printf("Client disconnected. Socket fd: %d\n", fd);
close(fd);
FD_CLR(fd, &all_fds);
client_fds[i] = -1;
} else {
// 处理客户端数据
printf("Received data from client. Socket fd: %d\n", fd);
// 在此处添加处理客户端数据的代码
}
}
}
}
// 关闭服务器套接字
close(server_fd);
return 0;
}