Netty 编程模型,netty应用入门。
NIO
服务端:ServerSocketChannel监听多个SocketChannel的连接
客户端:SocketChannel , 连接到服务器,读写数据即可
selector和ServerSocketChannel配合使用:实现io多路复用
netty
netty对nio进行一层封装,简化了代码的编写,更专注于业务。
- 由专门线程监听连接,接受连接后,将channel分配给worker线程处理,处理流程定义在pipeline中,pipeline中定义各种in或者out的handler
服务端
// 链式编程:配置服务器端,线程,pipeline,端口
ServerBootstrap server=new ServerBootstrap()
.group(new NioEventLoopGroup()) // boss和worker共用一个事件循环组,构造方法默认是1个线程池。group方法有重载,可以分别传入不同的事件循环组
.channel(NioServerSocketChannel.class) // 配置创建服务端socket类型
.childHandler(new ChannelInitializer<NioSocketChannel>() {
// childHandler 说明是建立连接后的channel,区别于handler,在客户端用的就是handler
protected void initChannel(NioSocketChannel ch) {
// 具体业务handler
ch.pipeline().addLast(new StringDecoder());
ch.pipeline().addLast(new SimpleChannelInboundHandler<String>() {
@Override
protected void channelRead0(ChannelHandlerContext ctx, String msg) {
System.out.println(msg);
}
});
}
});
ChannelFuture bind = server.bind(8080);
ChannelFuture sync = bind.sync(); // 等待服务器启动完成
sync.addListener(new ChannelFutureListener(){
@Override
public void operationComplete(ChannelFuture future) throws Exception {
if (future.isSuccess()) {
System.out.println("服务器启动成功");
} else {
System.out.println("服务器启动失败");
}
}
});
Channel channel = sync.channel(); // 获取到对应的ServerSocketChannel,有时候需要获取绑定成功后的channel的一些信息
System.out.println(channel.localAddress());
// 添加关闭监听器
channel.closeFuture().addListener(new ChannelFutureListener() {
@Override
public void operationComplete(ChannelFuture future) {
// 服务器关闭时执行
System.out.println("服务器已关闭");
// 通常在这里关闭EventLoopGroup,以释放资源
future.channel().eventLoop().shutdownGracefully();
}
});
- ServerBootstrap: 服务器启动引导类,配置以及启动 常用设置方法有:group,channel,childHandler option
和childOption:option用于设置NioServerSocketChannel网络设置相关,childOption用于设置客户端连接后对应channel网络设置相关,可以用ChannelOption对应常量设置,每调用一次设置一个网络相关参数。如.option(ChannelOption.SO_BROADCAST, true)表示UDP开启广播模式。 - NioEventLoopGroup:NIO对应的事件循环组,包含多个NioEventLoop(一个线程),当服务器有事件发生时,交由这些线程处理,group就是一个netty定制化的线程池
- NioServerSocketChannel: NIO的socketChannel,监听连接请求。还可以配置为
OioServerSocketChannel:对应BIO,事件循环组对应改成Oio,BIO中EventLoopGroup的每个线程只能处理一个连接,只要哪个连接没有退出,其他请求无法共享这个线程。EpollServerSocketChannel: 绕过 JDK,直接调用 Linux 的epoll系统调用,从而在linux效率比java实现的NIO高一点。 - ChannelInitializer:Channel初始化器,当新连接建立时,初始化Channel对应的pipeline
- Pipeline和Handler:Pipeline是一个双向链表结构,里面维护了多个Handler(处理器)。连接后的channel的发生的事件都会按照顺序拦截流经处理链。
ChannelFuture 的特点
-
非阻塞 + 回调机制:可以添加监听器(
GenericFutureListener),在 Future 完成时自动触发回调。间接继承了JDK的future,所以也有其部分特性,也支持获取返回值,不过实际实际返回值是空(void),channel只关注成功与否。 -
netty中每个future几乎都和一个channel相关,每个channel都会绑定到一个
EventLoop(即一个线程),和这个channel相关的操作都会在这个线程内完成,包括future的回调。 -
只读:只能获取结果、检查状态(成功/失败),不能设置结果。和Promise区分,Promise可写的。
-
常见api:
sync():直到对应事件执行完毕 addListener():添加一个监听器 isSuccess():是否执行成功
怎么理解Event Loop?
-
事件循环,一个(或少量)线程,通过一个无限循环,不断监听和处理“事件
-
netty中的事件
Inbound Event(入站事件) :从网络 → 应用(服务器) Outbound Event(出站事件) :从应用(服务器) → 网络
客户端
NioEventLoopGroup eventExecutors = new NioEventLoopGroup();
Bootstrap client=new Bootstrap()
.group(eventExecutors)
.channel(NioSocketChannel.class)
.handler(new ChannelInitializer<Channel>() {
// 用的是handler
@Override
protected void initChannel(Channel ch) {
ch.pipeline().addLast(new StringEncoder());
}
});
ChannelFuture connect = client.connect("127.0.0.1", 8080);
ChannelFuture sync = connect.sync(); // 阻塞等待连接建立成功
Channel channel = sync.channel(); // 连接成功后获取channel对象
channel.writeAndFlush(new Date() + ": hello world!"); // 写入数据并刷新
channel.closeFuture().addListener(future -> {
System.out.println("客户端关闭");
});
System.out.println(channel.localAddress());
channel.close();
eventExecutors.shutdownGracefully(); // 优雅关闭线程池,group内部的线程是非守护线程
-
NioSocketChannel : 对应TCP编程
NioDatagramChannel : 对应的UDP编程