BIO 、NIO 、OIO
- BIO就是OIO,BIO是阻塞IO模型(Block-I/O)
- NIO是非阻塞IO模型(Non-Block I/O),
- 有人将NIO称作(New-I/O),所以也将BIO称之为(Old-I/O),简称OIO
- 下文中开始称之为OIO,因为Netty使用了这一简称,为了不对代码造成混淆,所以也是用OIO的命名方式
jdk-API中的OIO
- 定义:网络链接进入时,直接阻塞线程进行I/O读取与写入,链接关闭之前,这个线程都不会做其他工作。有比较详细的可参读作者的另外一篇博文OIO介绍
- 这里就直接使用多线程的版本代码:
public class JDK_OIOServer{
public void start(int port) throws IOException {
// 初始化ServerSocket 并且绑定端口
final ServerSocket serverSocket = new ServerSocket(port);
for(;;){
//获取远程客户端的链接
final Socket clientSocket = serverSocket.accept();
//启用线程
new Thread(new Runnable(){
@override
public void run(){
try{
//获得写输出流
OutputStream out= client.getOutputStream();
//写入字符并设置字符集
out.wirte("Hello",getBytes(Charset.forName("UTF-8")));
//冲刷数据
out.flush();
}
catch (IOException e){
e.printStackTrace();
}
finally{
//关闭客户端链接
try{
clientSocket.close();
}catch(IOException e){
e.printStackTrace();
}
}
}
}).start();
}
}
}
jdk-API中的NIO
- 定义:核心为一个选择器,将所有链接和服务器监听对象都注册进这个选择器中,监听这个选择器是否有事件发生,如果有事件发生则进行动作,理论上是一个事件驱动的模型,但是同时还是阻塞的。详细可见作者的另外一篇博客NIO
- 代码:
public class JDK_NIOServer{
public void start(int port){
//得到serverChannel对象
ServerSocketChannel serverChannel = ServerSocketChannel.open();
//配置为非阻塞状态
serverChannel.configureBlocking(false);
//得到选择器
Selector selector = Selector.open();
//得到serverChannel中的serverSocket,网络链接真实发生的地方就是serverSocket
ServerSocket serverSocket = serverChannel.channel();
//初始化地址和端口,这里使用了本地回环地址
InetSocketAddress address = new InetSocketAddress(port);
//serverSocket绑定地址
serverSocket.bind(address);
//最后将serverChannel注册进选择器,并且监听OP_ACCEPT事件,也就是网络链接发生事件
serverChannel.register(selector,SelectionKey.OP_ACCEPT);
for(;;){
try{
//阻塞自己,监听事件发生,
//select()方法也有一个参数,可以接受时间,这样当前线程不会被完全阻塞
//加时间参数的方法适用于不需要及时响应的服务器环境
selector.select();
}catch(IOException e){
e.printStackTrace();
break;
}
//获取到选择器中发生时间的key的集合
Set<SelectionKey> keys = selector.selectedKeys();
//获取key集合的迭代器
Iterator<SelectionKey> iterators = keys.iterator();
while(iterators.hasNext()){
//获取key
SelectKey key = iterators.next();
try{
//如果是链接进入事件
if(key.isAcceptable()){
//这里这么做是因为可能有多个ServerSocketChannel同时工作在一个选择器
//key中包含了很多信息
ServerSocketChannel sChanel =(ServerSocketChannel) key.channel();
//调用ServerSocketChannel的accept方法获取当前客户端链接Channel
SocketChannel cChanel = sChannel.accept();
//配置为非阻塞
cChannel.configureBlocking(false);
//将channel注册进选择器,并且分配一个1024字节的缓冲区
cChannel.register(selector,SelectionKey.OP_READ | SelectionKey.OP_WRITE,BufferByte.allocate(1024));
}
//写事件发生
if(key.isWriteable()){
//获得到当前的channel,注意,读事件写事件的channel都是客户端链接Channel,
SocketChannel cChannel = (SocketChannel)key.channel();
//获得到链接的字符缓冲
ByteBuffer buffer = (ByteBuffer) key.attachment();
while(buffer.hasRemaining()){
if(cChannel.write(buffer)==0){
break;
}
}
//关闭channel
cChannel.close();
}
}catch(IOException e ){
key.cancel();
try{
key.channel.close();
}catch(IOException e){
e.printStackTrace();
}
}finally{
//最终移除掉这个key,防止重复操作
iterators.remove();
}
}
}
}
}
Netty中的OIO
- 直接上代码分析吧,尽量每一行代码都注释,但是如果不了解Netty工作流程的人可能看起来会有一些难度
public class Netty_OIOServer{
public void start(int port){
//创建内容为Hi的字节缓冲流
final ByteBuf buf = Unpooled.unreleasableBuffer(Unpooled.copiedBuffer("Hi!\r\n",Charset.forName("UTF-8")));
//创建一个EventLoopGroup组
EventLoopGroup evtLoopGroup = new OioEventLoopGroup();
try{
//bootStrap是netty服务器管理的核心组件
ServerBootStrap bootStrap = new ServerBootStrap();
bootStrap.group(evtLoopGroup)//添加组
//使用什么ServerSocketChannel作为服务器接受链接事件的通道
.channel(OioServerSocketChannel.class)
//绑定地址
.localAddress(new InetSocketAddress(port))
//添加回调函数,使用ChannelInitialize初始化
//其实就是当有链接进入时,回用什么进行操作
.childHandler(new ChannelInitialize<SocketChannel>(){
//链接进入时回创建一个SocketChannel
//此时调用ChannelInitialize的initChannel进行初始化
@override
public void initChannel(SocketChannel ch) throws Exception {
//获得SocketChannel,并且对SocketChannel的pipeline添加回调函数
//继承自ChannelInboundHandler,或者ChannelOutboundHandler
ch.pipeline.addLast(new ChannelInboundHandler{
@override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
ctx.writeAndFlush(buf.duplicate())
.addListener(ChannelFutureListen.CLOSE);
}
})
}
})
}
}
}
Netty中的NIO
public class Netty_OIOServer{
public void start(int port){
//创建内容为Hi的字节缓冲流
final ByteBuf buf = Unpooled.unreleasableBuffer(Unpooled.copiedBuffer("Hi!\r\n",Charset.forName("UTF-8")));
//创建一个EventLoopGroup组
EventLoopGroup evtLoopGroup = new NioEventLoopGroup();
try{
//bootStrap是netty服务器管理的核心组件
ServerBootStrap bootStrap = new ServerBootStrap();
bootStrap.group(evtLoopGroup)//添加组
//使用什么ServerSocketChannel作为服务器接受链接事件的通道
.channel(NioServerSocketChannel.class)
//绑定地址
.localAddress(new InetSocketAddress(port))
//添加回调函数,使用ChannelInitialize初始化
//其实就是当有链接进入时,回用什么进行操作
.childHandler(new ChannelInitialize<SocketChannel>(){
//链接进入时回创建一个SocketChannel
//此时调用ChannelInitialize的initChannel进行初始化
@override
public void initChannel(SocketChannel ch) throws Exception {
//获得SocketChannel,并且对SocketChannel的pipeline添加回调函数
//继承自ChannelInboundHandler,或者ChannelOutboundHandler
ch.pipeline.addLast(new ChannelInboundHandler{
@override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
ctx.writeAndFlush(buf.duplicate())
.addListener(ChannelFutureListen.CLOSE);
}
})
}
})
}
}
}
总结
这里没有详细的将Netty的工作流程说的很详细,但是大家可以做一个小对比
- Netty相对于传统的jdk-API实现来说,更加的模块化,我们仅需要在不同的阶段添加我们的处理逻辑就可以了,而不需要实现整个通信流程,然后在通信流程的不同阶段实现我们的逻辑
- Netty的OIO、NIO实现几乎一摸一样,说明Netty本身抽象程度足够高,我们仅需要知道Netty的流程就可以,而不需要知道OIO模型或者NIO模型的不同之处
- 上面两点已经足够减少我们对于项目模型的构建复杂程度,而只需要专心于服务器逻辑的实现就好