软件要求
- 最新版本Netty
- JDK1.6以上
写一个Discard Server
在这个世界上最简单的协议不是 Hello world
,而是DISCARD
。该协议将一切接收到的数据全部舍弃,不做任何响应。因此,实现DISCARD
协议,你唯一需要做的事情是忽略所有接收到的数据
。
- 接下来,我们直接从
handler
的实现开始编写Discard服务,handler
主要用于处理Netty产生的IO事件。
package discard.server;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
/**
* @ClassName DiscardHandler
* @Description TODO
* @Author xiuc_shi
* @Date 2020/8/29 下午 1:17
* @Version 1.0
**/
public class DiscardHandler extends ChannelInboundHandlerAdapter {//(1)
@Override public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {//(2)
//舍弃接收到的消息
// ((ByteBuf)msg).release();//(3)
ByteBuf in = (ByteBuf)msg;
while (in.isReadable()) {
System.out.println((char)in.readByte());
System.out.flush();
}
}
@Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {//(4)
super.exceptionCaught(ctx, cause);
}
}
为了看到服务器是否真的接收到数据,我们直接将数据打印出来。
-
DiscardServerHandler
继承了ChannelInboundHandlerAdapter
,该类是ChannelInboundHandler
的一个实现。ChannelInboundHandler
提供了很多事件处理方法供你重写。 - 我们此处重写了
channelRead()
事件处理方法。当有新消息从客户端发出,该方法被调用来接收来自客户端的消息。在这个例子中,接收消息的类型是ByteBuf
。 - 对于DISCARD协议的实现,处理器会忽略接收到的消息。
ByteBuf
是一个引用计数对象(Reference-counted Object),因此必须通过调用release()
方法显式释放。通常,channelRead()
事件处理方法如下实现:
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) {
try {
// Do something with msg
} finally {
ReferenceCountUtil.release(msg);
}
}
- 当处理器在处理事件是发生异常或者出现IO错误时,
exceptionCaught()
事件处理方法会触发抛出Throwable
。大多数情况下,应当记录日志并且关闭相关的channel。实现不尽相同,根据你的需求,例如有时还希望在关闭连接之前发送错误码响应消息。
- 至此,我们已经实现了DISCARD服务器的第一步。接下来,写main方法用来启动
DiscardServerHandler
服务。
package discard.server;
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;
/**
* @ClassName Server
* @Description TODO
* @Author xiuc_shi
* @Date 2020/8/29 下午 1:39
* @Version 1.0
**/
public class DiscardServer {
private int port;
public DiscardServer(int port) {
this.port = port;
}
public void run() throws Exception{
EventLoopGroup bossGroup = new NioEventLoopGroup();//(1)
EventLoopGroup workerGroup = new NioEventLoopGroup();
try {
ServerBootstrap b = new ServerBootstrap();//(2)
b.group(bossGroup, workerGroup).channel(NioServerSocketChannel.class)//(3)
.childHandler(new ChannelInitializer() {//(4)
@Override protected void initChannel(SocketChannel socketChannel) throws Exception {
socketChannel.pipeline().addLast(new DiscardHandler());
}
}).option(ChannelOption.SO_BACKLOG, 128)//(5)
.childOption(ChannelOption.SO_KEEPALIVE, true);//(6)
//绑定端口,接收进来的连接
ChannelFuture f = b.bind(port).sync();//(7)
//等待服务器的socket关闭
//在本例中,这不会发生,但你可以优雅地关闭你的服务
f.channel().closeFuture().sync();
}finally {
workerGroup.shutdownGracefully();
bossGroup.shutdownGracefully();
}
}
public static void main(String[] args) throws Exception {
int port = 8080;
if(args.length > 0){
port = Integer.parseInt(args[0]);
}
new DiscardServer(port).run();
}
}
-
NioEventLoopGroup
是一个用于处理IO操作的多线程事件循环。Netty为不同的传输类型实现了多样的EventLoopGroup。我们在本例中实现的服务器端应用,使用了两个NioEventLoopGroup
。其一,通常称为boss
,接受一个进来的连接;其二,通常称为worker
,处理boss循环器已经接受并注册到worker中的连接。多少个线程,以及线程和Channel之间的映射取决于EventLoopGroup的实现,或者通过构造器配置。 -
ServerBootstrap
是一个用于配置服务器的帮助类。你可以使用channel直接配置服务器。然而,这是一个乏味的过程,在大多数情况下你不需要做。 - 在此处,我们指明使用
NioServerSocketChannel
类来实例化一个新的Channel来接受进来的连接。 - 此处指明的处理器总是由新接受的channel来评估。(The handler specified here will always be evaluated by a newly accepted Channel.)
ChannelInitializer
是一个特殊的处理器,它被用于帮助用户初始化一个新的channel。类似于你想通过添加一些处理器到channel的ChannelPipeline
以完成初始化。 - 你还能够设置参数来定制化你的Channel实现。本例实现的是TCP/IP服务器,因此可以设置socket options例如
tcpNoDelay
和keepAlive
。 -
option()
是服务于NioServerSocketChannel
,接受进来的连接。而childOption()
则是服务于父ServerChannel
接受的Channels。 - 最后,绑定端口然后开启服务。
测试结果
我们使用cmd,输入命令talnet localhost 8080发起请求,输入1,2,3,4
,控制台依次打印出来。