Netty 服务端开发可能涉及下面这些类,对第一次接触Netty的同学一下接触这么多类,千万别它震慑,其实Netty Server端或者Client代码总体比较固定,我们几乎只需要编写自己的编码器、解码器。
先感受Netty 开发服务端程序步骤
配置 EventLoopGroup
可以理解为线程池,一个接受客户端连接,一个处理IO读写请求
配置Channel为NioServerSocketChannel
如果基于传统阻塞IO,可以使用OioServerSocketChannel
配置消息处理器责任链
这里需要定义业务自己的处理逻辑,当然也可以使用内置的处理器,一般需要和内置的处理器集合起来使用
绑定端口
释放资源
实现需要引入依赖
<dependency>
<groupId>io.nettygroupId>
<artifactId>netty-allartifactId>
<version>4.1.68.Finalversion>
dependency>
Netty 实现EchoServer ,实际上我们只实现了 NettyEchoServerHandler,将接收到的消息原封不动的发出去。
Netty内置了许多编码器、解码器如StringDecoder、StringEncoder,当然还有许多其他编码器,解码器。
ch.pipeline().addLast 可以初步推断这是一个责任链模式,这就意味着,许多处理器可以结合起来使用,使得开发者很容易在内置处理器基础上,扩展新功能。
public class NettyEchoServer {
private int port;
public NettyEchoServer(int port) {
this.port = port;
}
public void run() throws Exception {
EventLoopGroup bossGroup = new NioEventLoopGroup();
EventLoopGroup workerGroup = new NioEventLoopGroup();
try {
ServerBootstrap b = new ServerBootstrap();
b.group(bossGroup, workerGroup)
.channel(NioServerSocketChannel.class)
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
public void initChannel(SocketChannel ch) throws Exception {
ch.pipeline().addLast("decoder", new StringDecoder());
ch.pipeline().addLast("encoder", new StringEncoder());
ch.pipeline().addLast(new NettyEchoServerHandler());
}
});
ChannelFuture f = b.bind(port).sync();
f.channel().closeFuture().sync();
} finally {
workerGroup.shutdownGracefully();
bossGroup.shutdownGracefully();
}
}
public static void main(String[] args) throws Exception {
int port = 8080; // 设置服务端监听端口
new NettyEchoServer(port).run();
}
}
public class NettyEchoServerHandler extends SimpleChannelInboundHandler<String> {
@Override
protected void channelRead0(ChannelHandlerContext ctx, String msg) throws Exception {
System.out.println("Server received: " + msg);
ctx.writeAndFlush( msg);
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
cause.printStackTrace();
ctx.close();
}
}
回顾下 NIO 大概流程
NIO如何实现EchoServer关键代码,相比Netty 直观感受Netty几个优势。当然随着深入学习,你将会感受到Netty的强大,本文只是入门。
NIO需要自己将Buffer 解析成我们的消息结构,Netty内置了许多处理器,消息处理更简单
NIO 扩展性不好,当然我们该程序没有好好设计,而netty 我们就很容易在 pipeline 添加我们的处理器,扩展很容易。
性能方面
Netty使用不同的EventLoopGroup 接受连接和处理IO读写,开发者只需要调整相关参数即可, NIO 可能依赖开发者处理。
public static void main(String[] args) throws IOException {
// 打开选择器
Selector selector = Selector.open();
// 打开服务器套接字通道
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
serverSocketChannel.configureBlocking(false); // 设置为非阻塞模式
serverSocketChannel.socket().bind(new InetSocketAddress(PORT));
// 将服务器套接字通道注册到选择器上,并且只关心接受事件
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
System.out.println("NIO Echo Server started on port " + PORT);
while (true) {
// 阻塞直到有至少一个注册的选择键就绪
selector.select();
// 获取就绪的选择键集合
Set<SelectionKey> selectedKeys = selector.selectedKeys();
Iterator<SelectionKey> iterator = selectedKeys.iterator();
while (iterator.hasNext()) {
SelectionKey key = iterator.next();
if (key.isAcceptable()) { // 新的连接请求
ServerSocketChannel ssc = (ServerSocketChannel) key.channel();
SocketChannel client = ssc.accept();
if (client == null){
return; // 可能没有可用的客户端
}
client.configureBlocking(false);
// 将新连接注册到 Selector 上,监听读取事件,并附带 ReadHandler
client.register(selector, SelectionKey.OP_READ | SelectionKey.OP_WRITE, ByteBuffer.allocate(BUFFER_SIZE));
}
if (key.isReadable()) { // 可读数据
SocketChannel client = (SocketChannel) key.channel();
ByteBuffer output = (ByteBuffer) key.attachment();
client.read(output);
}
if(key.isWritable()){
SocketChannel client = (SocketChannel) key.channel();
ByteBuffer output = (ByteBuffer) key.attachment();
output.flip();
client.write(output);
output.compact();
}
iterator.remove(); // 处理完后移除选择键
}
}
}