Open JDK 在 1.6 版本以后就取消了 NIO 在 Linux 下的 select 模型,只提供 epoll 模型,那么这里我们就模拟一下 NIO 在 Linux 下调用 select 模型
Linux 下的 select 函数
NAME
select, pselect, FD_CLR, FD_ISSET, FD_SET, FD_ZERO - synchronous I/O multiplexing
SYNOPSIS
/* According to POSIX.1-2001, POSIX.1-2008 */
#include <sys/select.h>
/* According to earlier standards */
#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:ndfs 应该设置为三个集合中编号最高的文件描述符加1。表示集合中的每个文件描述符都会被检查,直到达到这个限制
- readfds:监听需要读的文件描述符集合
- writefds:监听需要写的文件描述符集合
- exceptfds:监听异常事件的文件描述符集合
- timeout:监听超时时间
select 代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
void start() {
// 创建服务端结构体
struct sockaddr_in my_addr;
my_addr.sin_family = AF_INET;
my_addr.sin_port = htons(8080);
my_addr.sin_addr.s_addr = htonl(INADDR_ANY);
// 创建客户端结构体
struct sockaddr_in client_addr;
char client_ip[INET_ADDRSTRLEN] = "";
int clientfd = 0;
// 创建 socket
int listenfd = socket(AF_INET, SOCK_STREAM, 0);
bind(listenfd, (struct sockaddr*)&my_addr, sizeof(my_addr));
listen(listenfd, 128);
printf("listen client @port=%d...\n", 8080);
int lastfd = listenfd;
int i;
// 创建读事件集合
fd_set read_set, total_set;
FD_ZERO(&read_set);
FD_SET(listenfd, &total_set); // 将 listenfd 加入读事件集合
while(1) {
read_set = total_set;
// 返回三种事件的集合
// select 会阻塞监听
int z = select(lastfd + 1, &read_set, NULL, NULL, NULL);
if (z > 0) {
// 判断 listenfd 是否在 read_set 集合中
if (FD_ISSET(listenfd, &read_set)) {
socklen_t client_len = sizeof(client_addr);
clientfd = accept(listenfd, (struct sockaddr*)&client_addr, &client_len);
// 打印连接信息
inet_ntop(AF_INET, &client_addr.sin_addr, client_ip, INET_ADDRSTRLEN);
printf("----------------------------------\n");
printf("client ip=%s, port=%d\n", client_ip, ntohs(client_addr.sin_port));
// 将新的 clientfd 加入集合
FD_SET(clientfd, &total_set);
lastfd = clientfd;
// 只有一个连接事件的时候,不在往下执行
if (0 == --z) {
continue;
}
}
for (i = listenfd + 1; i <= lastfd; i++) {
// 判断 i 是否在 read_set 集合中,也就是 i 发送了信息,发生了读事件
if (FD_ISSET(i, &read_set)) {
char recv_buf[1024] = "";
int rs = read(i, recv_buf, sizeof(recv_buf));
// 0 表示对方关闭了连接
if (rs == 0) {
// 关闭 i
close(i);
// 将 i 从 total_set 中移除
FD_CLR(i, &total_set);
} else {
printf("%s\n", recv_buf);
// write(0, recv_buf, rs);
}
}
}
}
}
}
int main() {
start();
return 0;
}
编译
gcc select.c -o select.out
执行
./select.out
listen client @port=8080...
测试
nc 127.0.0.1 8080
test
服务端显示:
listen client @port=8080...
----------------------------------
client ip=127.0.0.1, port=6742
test