目录
IO多路复用技术(其他IO模型详见:IO模型篇章)
selector会替代应用程序,轮询一批注册到selector中的文件描述符(套接字,标准输入输
出),只要有部分文件描述符的数据准备就绪,就通知用户程序。
linux系统的selector由select,poll,epoll方式实现;
select 的缺点:
1,每次调用 select,都需要把 fd 集合从用户态拷贝到内核态,这个开销在 fd 很多时会很大
2,每次调用 select 都需要在内核遍历传递进来的所有 fd,这个开销在 fd 很多时也很大
3,select 支持的文件描述符数量,默认是1024
epoll(Linux 2.6内核正式引入,用于代替 select 和 poll 系统调用):
1,内核与用户空间共享一块内存
2,通过回调解决遍历问题
3,fd 没有限制,可以支撑10万连接
JAVA NIO
NIO:非阻塞IO,多路复用技术;
Selector:Channel的管理器
Selector允许单线程处理多个 Channel
Selector是一个对象,Channel可以注册到Selector,Selector监听各个Channel上发生的事
件,并且能够根据事件情况决定Channel读写。
Channel
Java NIO中Channel主要有如下几种类型:
FileChannel:从文件读取数据的;(通过FileInputStream,FileOutputStream,
RandomAccessFile中获取)
DatagramChannel:读写UDP网络协议数据
SocketChannel:读写TCP网络协议数据;
ServerSocketChannel:可以监听TCP连接;
读取文件
RandomAccessFile file = new RandomAccessFile("test.txt", "rw");
FileChannel fileChannel = file.getChannel();
ByteBuffer buf = ByteBuffer.allocate(1024);
int bytesRead = fileChannel.read(buf);
网络编程中的使用
底层实现代码
...
// 监听socketServer的文件描述符
listen_fd = tcp_server_listen(SERV_PORT);
...
// 把上述文件描述符,添加到文件描述符集合中
struct pollfd event_set[INIT_SIZE];
event_set[0].fd = listen_fd;
event_set[0].events = POLLRDNORM;
...
for (;;) {
//调用poll函数,轮询文件描述符集合,进行事件检测
if ((ready_number = poll(event_set, INIT_SIZE, -1)) < 0) {
error(1, errno, "poll failed ");
}
if (event_set[0].revents & POLLRDNORM) {
socklen_t client_len = sizeof(client_addr);
//socketServer监听,如果有链接事件,创建socket链接描述符
connected_fd = accept(listen_fd, (struct sockaddr *) &client_addr, &client_len);
//把socket链接描述符,添加到描述符集合中;
for (i = 1; i < INIT_SIZE; i++) {
if (event_set[i].fd < 0) {
event_set[i].fd = connected_fd;
event_set[i].events = POLLRDNORM;
break;
}
}
}
// 链接描述符,读写数据
for (i = 1; i < INIT_SIZE; i++) {
if (event_set[i].revents & (POLLRDNORM | POLLERR)) {
if ((n = read(socket_fd, buf, MAXLINE)) > 0) {
if (write(socket_fd, buf, n) < 0) {
error(1, errno, "write error");
}
} else if (n == 0 || errno == ECONNRESET) {
close(socket_fd); event_set[i].fd = -1;
} else {
error(1, errno, "read error");
}
}
}
}
Java封装之后的使用代码
ServerSocketChannel ssc = ServerSocketChannel.open();
ssc.socket().bind(new InetSocketAddress(port));
while(true){
SocketChannel sc = ssc.accept();
ByteBuffer buffer = ByteBuffer.wrap("data".getBytes());
sc.write(buffer);
}
Buffer
读写数据的缓存空间。
应用程序不能直接对Channel进行读写操作,而必须通过Buffer来进行。