Netty 官网
可以到官网下载,也可以直接使用 maven 依赖:
1 2 3 4 5 6 7 8
| <dependencies> <dependency> <groupId>io.netty</groupId> <artifactId>netty-all</artifactId> <version>4.1.30.Final</version> <scope>compile</scope> </dependency> </dependencies>
|
DISCARD 协议
官网的入门示例使用的协议是:DISCARD,最简单的协议,因为它只接收数据(然后扔掉……)而不作响应,类似 Linux 下的无底洞 /dev/null。
因为使用的是 DISCARD 协议,所以唯一要做的就是无视所有接收到的数据:
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
| import io.netty.buffer.ByteBuf; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelInboundHandlerAdapter; import io.netty.util.CharsetUtil; import io.netty.util.ReferenceCountUtil;
public class DiscardServerHandler extends ChannelInboundHandlerAdapter {
@Override public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { ((ByteBuf) msg).release();
}
@Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { cause.printStackTrace(); ctx.close(); } }
|
到目前为止一切正常,我们已经实现了一半的 DISCARD 协议服务端。剩下的工作就是写 main 方法,去启动带有 DiscardServerHandler 处理器的服务端:
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 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77
| import io.netty.bootstrap.ServerBootstrap; import io.netty.channel.ChannelFuture; import io.netty.channel.ChannelInitializer; import io.netty.channel.ChannelOption; import io.netty.channel.EventLoopGroup; import io.netty.channel.nio.NioEventLoopGroup; import io.netty.channel.socket.SocketChannel; import io.netty.channel.socket.nio.NioServerSocketChannel;
public class DiscardServer {
private int port;
public DiscardServer(int port) { this.port = port; }
public void run() throws Exception { EventLoopGroup bossGroup = new NioEventLoopGroup(); EventLoopGroup workGroup = new NioEventLoopGroup();
try { ServerBootstrap serverBootstrap = new ServerBootstrap(); serverBootstrap.group(bossGroup, workGroup) .channel(NioServerSocketChannel.class) .childHandler(new ChannelInitializer<SocketChannel>() { @Override protected void initChannel(SocketChannel socketChannel) { socketChannel.pipeline().addLast(new DiscardServerHandler()); } }) .option(ChannelOption.SO_BACKLOG, 128) .childOption(ChannelOption.SO_KEEPALIVE, true);
ChannelFuture channelFuture = serverBootstrap.bind(port).sync();
channelFuture.channel().closeFuture().sync(); } finally { workGroup.shutdownGracefully(); bossGroup.shutdownGracefully(); } }
public static void main(String[] args) throws Exception { int port; if (args.length > 0) { port = Integer.parseInt(args[0]); } else { port = 9000; } new DiscardServer(port).run(); } }
|
为了能看到效果,改造一下 channelRead 方法,让服务端在接收到消息时能显示出来:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| @Override public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
ByteBuf buf = (ByteBuf) msg; try { System.out.print(buf.toString(CharsetUtil.US_ASCII));
} finally { buf.release(); } }
|
为了方便,客户端直接用 telnet 进行测试:
ECHO 协议
到目前为止,服务端还没有发回过响应(因为是 DISCARD 协议)。
让我们实现 ECHO 协议,写一个可以“回音”的服务端。
只要改造一下 channelRead 方法就好了:
1 2 3 4 5
| @Override public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { ctx.write(msg); ctx.flush(); }
|
ChannelHandlerContext 的对象提供了各种操作可以让你触发各种 I/O 事件和操作。这里我们调用了 write 方法将收到的消息逐字写回。
注意,这里我们并没有像 DISCARD 例子那样释放收到的消息,因为 Netty 已经在你写回消息时为你自动释放了(和流的概念差不多)。
调用 write 并没有立刻将消息写回,而是在内部缓存起来了,还需要调用 flush 才能写回。你也可以使用更简洁的 writeAndFlush(msg) 方法。