Netty基本使用

Netty

Netty就是封装了JDK的NIO网络库。用官网的话说是,Netty是一个异步事件驱动的网络应用框架,用于快速开发可维护的高性能服务器和客户端。

为什么使用Netty而不用jdk

  1. JDK中使用的话需要对NIO编程有足够的了解,编程也比较复杂,不小心就会出现很多bug
  2. Netty底层的I/O模型可以随意更换,而不需要改变太多的代码,只需要改一些参数就可以了。
  3. Netty解决了jdk的一些bug,比如select空轮询导致cpu100
  4. Netty自带拆包解包,异常检测机制,可以让用户只关心业务逻辑
  5. 自带很多协议栈,比如http协议,google的protobuf协议

简单使用

首先如果想要使用Netty,建议首先要了解一下NIO网络编程,可以不熟悉,但是一定要了解。对理解起来会有帮助.
首先看一下服务端怎么实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
// 首先定义两个事件循环组,一个是用来接受新建连接,一个用来处理已经建立好的连接
int port = 9999;
EventLoopGroup bossGroup = new NioEventLoopGroup();
EventLoopGroup workerGroup = new NioEventLoopGroup();
try{
/**
* 是一个启动 Nio服务器的一个辅助类,可以在这个服务中直接使用Channel
*/
ServerBootstrap boot = new ServerBootstrap();
// 将这两个 事件循环组加进去
boot = boot.group(bossGroup,workerGroup)
// 设置为NIO模式
.channel(NioServerSocketChannel.class)
// 设置事件处理程序
.childHandler(new ChannelInitializer<SocketChannel>() {
protected void initChannel(SocketChannel ch) throws Exception {
// 这里添加的类是真正处理业务逻辑的类
ch.pipeline().addLast(new DiscardServerHandler());
}
})
/**
* 指定实现的通道实现的配置参数,这里是TCP/IP服务器
* 所以允许设置Socket的参数选项比如tcpNodeloy,keepAlive
*
* SO_BACKLOG 是一个数量设置
* 在linux内核中,有两个队列,一个是正在连接队列,一个是已经连接队列
* SO_BACKLOG 是设置这个缓冲区大小的,也就是这两个队列的大小
* 设置的值为两个队列最大能接受的值,当两个队列的大小超过这个值,新来的连接将会被抛弃
*
*/
.option(ChannelOption.SO_BACKLOG,128)// 设置tcp缓冲区大小
/**
* option()是提供个NioServerSocketChannel用来接受进来的连接
* childOption()是提供给由父管道ServerChannel接受到的连接.
*/
.childOption(ChannelOption.SO_KEEPALIVE,true);
// 调用bind()方法启动服务器,sync()就是异步调用.变为后台线程.
ChannelFuture f = boot.bind(port).sync();
/**
* 这里一直会等待,知道socket关闭
*/
// Thread.sleep(Integer.MAX_VALUE)
// 相当于阻塞进程.
f.channel().closeFuture().sync();
}finally {
/**
* 关闭
*/
workerGroup.shutdownGracefully();
bossGroup.shutdownGracefully();
}
}

只需要这些代码就可以建立好一个服务器,当然了,还需要写一个实现业务逻辑,就是说要写一个Handler,这里我们实现一个最简单的网络程序,也就是接收到数据什么都不做,只是简单的打印出来.下面看Handler的实现.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
public class DiscardServerHandler extends ChannelInboundHandlerAdapter {

/**
* 在有数据可以读取的时候调用
*/
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
try {
ByteBuf in = (ByteBuf)msg;
System.out.println(in.toString(CharsetUtil.UTF_8));

}finally {
ReferenceCountUtil.release(msg);
}
}

/**
* 发生异常时候调用,关闭连接
*/
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
/**
* 当出现Throwable对象的时候,当netty抛异常时候调用此方法,可以在其中设置异常逻辑,比如发送错误码
*/
cause.printStackTrace();
ctx.close();
}
}

上面可以很清除的看到覆盖了父类的方法.channelRead(). 上面说过Netty是事件驱动的框架.这里就体现出来了.当socket可读的时候就会调用此方法,这里不执行任何逻辑,只是简单的丢弃.所以说它是异步的事件驱动的.

之后就可以运行服务器.然后用telnet访问本地的9999端口就可以发送消息.

到这里一个简单的服务器就搭建好了. 接下来再详细讲解一下其中各个组件的功能与作用.

BootStrap

这是一个引导类,通过设置BootStrap类的开始,该类提供了一个用于应用程序网络层的配置容器

Channel

底层网络传输api必须提供给应用I/O操作接口,Netty自己对JDK的channel进行了进一步的封装,使得网络编程有更好的可扩展性. Channel 定义了与 socket 丰富交互的操作集:bind, close, config, connect, isActive, isOpen, isWritable, read, write 等等。

ChannelHandler

ChannelHandler支持很多种协议,并且提供用于进行数据处理的地方.ChannelHandler由特定的一些事件触发,触发之后会调用不同的方法,用户只需要自己实现ChannelHandler的一些方法就好了.当然实际编程中一般应该实现ChannelInboundHandlerAdapter和ChannelOutboundHandlerAdapter类,这两个类已经默认实现了ChannelHandler的方法.用户只需要在复写自己感兴趣的事件,然后进行处理就可以了.

ChannelPipeline

ChannelPipeline提供了一个容器ChannelHandler链,并提供了一个API用于管理沿着这条链进行的入站和出站的事件流动.每一个Channel都有自己的ChannelPipeline.是在创建Channel的时候自动创建ChannelPipeline的. ChannelHandler 是如何安装在 ChannelPipeline? 主要是实现了ChannelHandler 的抽象 ChannelInitializer。ChannelInitializer子类 通过 ServerBootstrap 进行注册。当它的方法 initChannel() 被调用时,这个对象将安装自定义的 ChannelHandler 集到 pipeline。当这个操作完成时,ChannelInitializer 子类则 从 ChannelPipeline 自动删除自身。

EventLoop

EventLoop用于处理Channel的I/O操作,一个单一的EventLoop通常会处理多个Channel事件.

ChannelFuture

因为Netty中所有的事件都是异步的,因为一个操作无法立即返回,这个时候就有了ChannelFuture能够在之后确定它的结果.ChannelFuture的addListener()方法可以添加注册事件,当操作完成之后会被通知.

总结

上面是对Netty的一个最基本的认识.总之Netty是一个强大的基于事件驱动的一个网络编程框架,使用它能够很好的建立一个稳定的网络应用程序.