DISCARD(丢弃)服务器

发表时间:2017-09-13 14:45:53 浏览量( 26 ) 留言数( 0 )

学习目标:

1、了解DISCARD的示例

2、理解并会实现本示例


学习过程:

   Netty的官方文档有详细的入门示例,今天我们会在这个官方的用户文档的基础上面进行讲解,让大家对Netty有一个深入的了解。明天我们再继续写一些示例:写个丢弃服务器

一、直接DISCARD(丢弃)

    为什么官方文档会先介绍一个直接DISCARD的示例呢,除了简单之外,我觉得还有一个原因就是有些如果没有释放资源,在生产环境上面很容易就会内存溢出导致系统卡死,当然有很多方法和handler处理类都会自动释放的,只是大家必须要引起注意,如果没有释放很容易会出错的。

   这个协议将会丢掉任何收到的数据,而不响应。为了实现 DISCARD 协议,你只需忽略所有收到的数据。让我们先写一个Handler,handler 是由 Netty 生成用来处理 I/O 事件的。

    代码如下:

public class DiscardServerHandler extends ChannelInboundHandlerAdapter { // (1)

	@Override
	public void channelRead(ChannelHandlerContext ctx, Object msg) { // (2)
		// 默默地丢弃收到的数据
		((ByteBuf) msg).release(); // (3)

	}

	@Override
	public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) { // (4)
		// 当出现异常就关闭连接
		cause.printStackTrace();
		ctx.close();
	}
}

    代码解析:

    1.DiscardServerHandler 继承自 ChannelInboundHandlerAdapter,这个类实现了 ChannelInboundHandler接口,ChannelInboundHandler 提供了许多事件处理的接口方法,然后你需要覆盖这些方法,这样会很麻烦,现在只需要继承 ChannelInboundHandlerAdapter 类就行了,重写必要的方法即可,其他的都直接使用父类即可。

    2.这里我们重写了 chanelRead() 事件处理方法。每当从客户端收到新的数据时,这个方法会在收到消息时被调用,这个例子中,收到的消息的类型是 ByteBuf。

    3.为了实现 DISCARD 协议,处理器不得不忽略所有接受到的消息。ByteBuf 是一个引用计数对象,这个对象必须显示地调用 release() 方法来释放。请记住处理器的职责是释放所有传递到处理器的引用计数对象。通常,channelRead() 方法的实现就像下面的这段代码:

		try {

		} finally {
			ReferenceCountUtil.release(msg); 
		}

    4.exceptionCaught() 事件处理方法是当出现 Throwable 对象才会被调用,即当 Netty 由于 IO 错误或者处理器在处理事件时抛出的异常时。在大部分情况下,捕获的异常应该被记录下来并且把关联的 channel 给关闭掉。然而这个方法的处理方式会在遇到不同异常的情况下有不同的实现,比如你可能想在关闭连接之前发送一个错误码的响应消息。

    我们在写一个服务的启动类,这个在我们昨天已经写过了,基本也就是那个模板:

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<SocketChannel>() { // (4)
						@Override
						public void initChannel(SocketChannel ch) throws Exception {
							ch.pipeline().addLast(new DiscardServerHandler());
						}
					}).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;
		if (args.length > 0) {
			port = Integer.parseInt(args[0]);
		} else {
			port = 8080;
		}
		new DiscardServer(port).run();
	}
}

   其他的说明我们这里也就不再啰嗦了。看看不同的地方:

    5.你可以设置这里指定的 Channel 实现的配置参数。我们正在写一个TCP/IP 的服务端,因此我们被允许设置 socket 的参数选项比如tcpNoDelay 和 keepAlive。请参考 ChannelOption 和详细的 ChannelConfig 实现的接口文档以此可以对ChannelOption 的有一个大概的认识。

    6.你关注过 option() 和 childOption() 吗?option() 是提供给NioServerSocketChannel 用来接收进来的连接。childOption() 是提供给由父管道 ServerChannel 接收到的连接,在这个例子中也是 NioServerSocketChannel。

   因为我们还没有写一个客户端类来发送数据,所以我们这里可以先使用一下telnet命令

二、打印

    上面的例子没有上面输出的反应,所以我们下面把读到的内容打印出来。我们已经知道 channelRead() 方法是在数据被接收的时候调用。让我们放一些代码到 DiscardServerHandler 类的 channelRead() 方法。

    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) {
        ByteBuf in = (ByteBuf) msg;
        try {
            while (in.isReadable()) { // (1)
                System.out.print((char) in.readByte());
                System.out.flush();
            }
        } finally {
            ReferenceCountUtil.release(msg); // (2)
        }
    }

1.这个低效的循环事实上可以简化为:System.out.println(in.toString(io.netty.util.CharsetUtil.US_ASCII))

2.你可以在这里调用 in.release()。

    现在我们已经编写出我们第一个服务端,我们需要测试一下他是否真的可以运行。最简单的测试方法是用 telnet 命令。例如,你可以在命令行上输入telnet localhost 8080或者其他类型参数。

1、先启动服务器类

2、启动dos命令,使用telnet 命令想服务端发送内容

再客户端输入内容,可以看到服务端同步的输出内容了。但是客户端并没有东西回显。

测试结果:

attcontent/2ed5f541-d3bf-49b0-987c-0c0541a0a4d3.png

三、应答客户端

    和 discardServer 唯一不同的是把在此之前我们实现的 channelRead() 方法,返回所有的数据替代打印接收数据到控制台上的逻辑。因此,需要把 channelRead() 方法修改如下:

    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) {
        ctx.write(msg); // (1)
        ctx.flush(); // (2)
    }

    ChannelHandlerContext 对象提供了许多操作,使你能够触发各种各样的 I/O 事件和操作。这里我们调用了 write(Object) 方法来逐字地把接受到的消息写入。请注意不同于 DISCARD 的例子我们并没有释放接受到的消息,这是因为当写入的时候 Netty 已经帮我们释放了。

    ctx.write(Object) 方法不会使消息写入到通道上,他被缓冲在了内部,你需要调用 ctx.flush() 方法来把缓冲区中数据强行输出。或者你可以用更简洁的。

    cxt.writeAndFlush(msg) 以达到同样的目的。

    如果你再一次运行 telnet 命令,你会看到服务端会发回一个你已经发送的消息。