目录
ServerSocketChannel和SocketChannel
从代码层面对NIO大致流程加深印象
服务端
public class ProcessNio {
public static void main(String[] args) throws IOException {
//serverSocketChannel -----> SocketChannel
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
// 得到selector
Selector selector = Selector.open();
// 绑定端口监听
serverSocketChannel.socket().bind(new InetSocketAddress(6666));
// 设置为非阻塞
serverSocketChannel.configureBlocking(false);
// 把serverSocketChannel注册到selector,事件为OP_ACCEPT
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
// 等待客户端连接
while(true){
//等待一秒,看是否有连接事件发生
if(selector.select(1000)==0){
System.out.println("等待一秒无事件发生");
continue;
}
//selector.select大于0,则返回SelectionKey,拿到有事件的集合
Set<SelectionKey> selectionKeys = selector.selectedKeys();
// 遍历事件
Iterator<SelectionKey> iterator = selectionKeys.iterator();
while (iterator.hasNext()){
SelectionKey key = iterator.next();
// 判断key是什么事件
if(key.isAcceptable()){
// 为这个连接的客户端生成一个SocketChannel
SocketChannel socketChannel = serverSocketChannel.accept();
socketChannel.configureBlocking(false);
// 注册到selector上并且声明buffer,关注OP_READ
socketChannel.register(selector,SelectionKey.OP_READ, ByteBuffer.allocate(1024));
}
if(key.isReadable()){
// 通过key获取到channel
SocketChannel channel = (SocketChannel)key.channel();
// 获取到key的buffer
ByteBuffer buffer = (ByteBuffer)key.attachment();
channel.read(buffer);
System.out.println("收到的数据为:"+new String(buffer.array()));
}
iterator.remove();
}
}
}
}
客户端
public class ClientNio {
public static void main(String[] args) throws IOException {
// 得到一个通道
SocketChannel channel = SocketChannel.open();
channel.configureBlocking(false);
channel.connect(new InetSocketAddress("127.0.0.1",6666));
if(!channel.isConnected()){
while (!channel.finishConnect()){
System.out.println("客户端连接需要时间,但是不会阻塞,可以做其他操作");
}
}
String str = "少壮不努力,老大徒伤悲";
ByteBuffer buffer = ByteBuffer.wrap(str.getBytes());
channel.write(buffer);
System.in.read();
}
}
SelectionKey
表示selector和channel的注册关系。
可以查看注册到selector的key和有事件发生的key,方法如下
selector.keys(); //全部
selector.selectedKeys(); //有事件发生
其他常用相关方法
ServerSocketChannel和SocketChannel
ServerSocketChannel在服务端监听新的客户端的连接。
SocketChannel网络IO通道具体负责进行读写操作。
零拷贝
优势:更少的上下文切换,更少的cpu缓存伪共享以及无cpu校验和计算。
从操作系统层面来说,内核数据只有一份(比如说不拷贝到socket buffer)就是零拷贝
从四次拷贝变为两次拷贝
四次拷贝是指:硬件数据进行DMA拷贝到内核,从内核缓冲拷贝(cpu拷贝)到用户缓冲,从用户缓冲拷贝(cpu拷贝)到socket缓冲,最后用DMA拷贝到协议栈
两次拷贝是指:从硬件驱动到内核缓冲的DMA拷贝(其实内核缓冲还需要将描述信息拷贝到socket缓冲,但由于数据量不大,所以忽略),从内核缓冲到协议栈的DMA拷贝
在方法transferTo()中就用到了零拷贝
为什么要有Netty
也就是说NIO有什么不好~
- NIO的类库和API繁杂
- 需要具有额外的线程和网络编程的能力才能写出很好的NIO程序
- 过程繁杂,需要对断线重连,失败缓存等问题做处理
- Selector的空轮询导致CPU爆满
传统阻塞IO服务模型
每个请求需要独立的线程处理。并发量大时,占用很大的系统资源
采用阻塞IO的方式,比如read,去造成线程资源浪费
Reactor模式
使用IO复用监听事件,收到事件后分发。
核心组成包括:Reactor和Handlers
Reactor:负责监听和分发事件,分发到适当的处理程序对IO做出反应
Handlers:处理IO事件的实际事件
模式分类:单Reactor单线程,单Reactor多线程,主从Reactor多线程(netty基于此模式做出改进)
单Reactor单线程:处理线程只有一个,会造成阻塞,cpu不能充分利用。
单Reactor多线程:在这里,handler只负责响应事件,不做具体业务处理,具体处理由后边work线程池的线程处理,并将结果返回给handler,handler可以把结果返回给client。reactor在这里是单线程,在高并发场景中容易出现性能瓶颈。由于数据共享和访问,需要保证不出现线程安全问题。
主从Reactor多线程:在单Reactor多线程基础上,把reactor分为处理连接和处理读写,处理读写的reactor又可以为多线程,编程复杂度较高,在nginx、netty、memcached中都有广泛使用。
Netty模型工作原理
右下角的pipeline可以简单的理解为包含了channel,里边还包含了很多处理器。