NIO
入门代码
nio服务端代码,可以设置为非阻塞,每个socketchannel表示一个客户端连接的管道,发到一个集合中,循环获取客户端发送的消息
缺点:
- 如果连接太多,集合会越来越多
- 如果集合太多,但实际发送消息的客户端很少,每次全部循环,性能损耗大
public class NioServer {
public static void main(String[] args) throws IOException {
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
// 绑定9000端口
serverSocketChannel.socket().bind(new InetSocketAddress(9000));
// 设置成非阻塞默认是true
serverSocketChannel.configureBlocking(false);
List<SocketChannel> clientChannelList = new ArrayList<>();
while (true) {
SocketChannel clientChannel = serverSocketChannel.accept();
if (clientChannel != null) {
// 设置成非阻塞
clientChannel.configureBlocking(false);
clientChannelList.add(clientChannel);
}
for (SocketChannel socketChannel : clientChannelList) {
ByteBuffer byteBuffer = ByteBuffer.allocate(128);
// 读取客户端发送的消息(目前是非阻塞)
int len = socketChannel.read(byteBuffer);
if (len > 0) {
System.out.println("收到客户端消息:" + new String(byteBuffer.array()));
}
}
}
}
}
优化版本
使用Selector多路复用器,监听指定事件,发生事件时会放到单独的集合中,这样循环就只循环有事件发生的集合就行
public class NioSelectorServer {
public static void main(String[] args) throws IOException {
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
// 绑定9000端口
serverSocketChannel.socket().bind(new InetSocketAddress(9000));
// 设置成非阻塞默认是true
serverSocketChannel.configureBlocking(false);
// 多路复用器
Selector selector = Selector.open();
// 多路复用器上监听连接事件,发生连接会发到一个selector的集合中
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
while (true) {
// 阻塞直到有事件发生
selector.select();
// 发生的事件
Set<SelectionKey> selectionKeys = selector.selectedKeys();
// 递归事件
Iterator<SelectionKey> iterator = selectionKeys.iterator();
while (iterator.hasNext()) {
// 获取当前事件
SelectionKey selectionKey = iterator.next();
if (selectionKey.isAcceptable()) {
// 如果是连接事件,获取对应的连接管道
ServerSocketChannel clientChannel = (ServerSocketChannel)selectionKey.channel();
SocketChannel socketChannel = clientChannel.accept();
// 设置为非阻塞模式
socketChannel.configureBlocking(false);
// 监听读事件(客户端发送数据)
socketChannel.register(selector, SelectionKey.OP_READ);
} else if (selectionKey.isReadable()) {
// 如果客户端发送请求,获取对应的连接管道
SocketChannel socketChannel = (SocketChannel)selectionKey.channel();
ByteBuffer byteBuffer = ByteBuffer.allocate(128);
// 读取客户端发送的消息(目前是非阻塞)
int len = socketChannel.read(byteBuffer);
if (len > 0) {
System.out.println("收到客户端消息:" + new String(byteBuffer.array()));
}
}
// 处理完的事件,移出
iterator.remove();
}
}
}
}
源码
jdk1.4
nio使用linux的select()或poll()来实现,这两个方法与上面写的入门代码类似,都是循环所有连接,判断客户端是否发送数据。select()对客户端连接数有限制(好像只支持1024个连接),poll()对客户端连接数没有限制
jdk1.5
nio使用linux的epoll实现,epoll基于事件监听机制,可以监听指定事件,如果发生该事件,放到指定集合中,这样只需要遍历这个发生过事件的集合就行
epoll函数
java调用selector的方法时,底层实际使用linux的epoll实现
epoll利用操作系统的中断实现事件监听
redis底层
redis底层就是使用epoll实现多路复用器,监听客户端不同事件(连接,发送数据等…)