Netty学习——入门实例之实现时间协议服务

时间协议服务介绍

时间协议会发送一条包含32位整数的消息,而不接收任何请求,并在消息发送后关闭连接。在本例中,将演示如何构造和发送消息,以及如何在完成时关闭连接。
由于时间协议将忽略任何收到的消息,而是在建立连接后立即发送消息,因此我们这里不使用channelRead()方法,而是使用channelActive()方法,像下面代码这样:

package server;

import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;

/**
 * 时间服务器处理类
 * @author 胡海龙
 *
 */
public class TimeServerHandler extends ChannelInboundHandlerAdapter {

	@Override
	public void channelActive(final ChannelHandlerContext ctx) throws Exception {	//(1)
		final ByteBuf time = ctx.alloc().buffer(4);									//(2)
		time.writeInt((int)(System.currentTimeMillis() / 1000L + 2208988800L));
		final ChannelFuture f = ctx.writeAndFlush(time);							//(3)
		f.addListener(new ChannelFutureListener() {
			public void operationComplete(ChannelFuture future) throws Exception {
				assert f == future;
				ctx.close();
			}
		});																			//(4)
	}

	@Override
	public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
		cause.printStackTrace();
		ctx.close();
	}
}

代码解析

  1. channelActive()方法将会在连接建立时被调用。
  2. 要发送一条新的消息时我们需要分配一个新的缓冲区来包含该消息,由于我们要写一个32位的整数,因此我们需要容量至少为4字节字节缓冲器。通过ChannelHandlerContextalloc()方法可以获取当前字节缓冲分配器,并分配一个新的缓冲区。
  3. 构造并刷新消息。注意:使用Java NIO来操作I/O时常会用到filp()方法,而netty是不需要的,这是因为ByteBuf有俩个指针,一个用作读取操作,一个用作写入操作。当我们像字节缓冲器中写入数据时写入操作指针的索引会增加,而读取操作指针的索引不会改变。
    另外需要注意的一点是ChannelHandlerContextwrite()方法和writeAndFlush()方法返回的ChannelFuture表示未发生的I/O操作,这意味着可能没有执行任何请求操作,因为Netty中所有的操作都是异步的,例如下面的代码可能在发送消息前关闭:
Channel ch = ...;
ch.writeAndFlush(message);
ch.close();

因此我们需要在ChannelFuture完成后调用close()方法
4. 如何实现ChannelFuture完成后调用关闭方法?这里我们为它添加一个监听的匿名内部类,在操作完成后关闭该Channel。也可以使用预定义的监听器——f.addListener(ChannelFutureListener.CLOSE);来简化代码。

服务端代码

服务端代码和前面丢弃协议和Echo协议的服务端代码基本相同,只要改一下childHandler()方法里的处理类即可。

package 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;

/**
 * 时间服务器
 * 
 * @author 胡海龙
 *
 */
public class TimeServer {

	public void run(int port) throws InterruptedException {
		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
						protected void initChannel(SocketChannel ch) throws Exception {
							ch.pipeline().addLast(new TimeServerHandler());
						}
					}).option(ChannelOption.SO_BACKLOG, 128).childOption(ChannelOption.SO_KEEPALIVE, true);
			ChannelFuture f = b.bind(port).sync();
			f.channel().closeFuture().sync();
		} finally {
			bossGroup.shutdownGracefully();
			workerGroup.shutdownGracefully();
		}
	}

	public static void main(String[] args) throws InterruptedException {
		new TimeServer().run(8080);
		;
	}
}

到这里服务端的代码已经完成,这时候我们可以使用rdate命令来验证我们的服务(没有rdate命令环境的可以使用下面的编写客户端验证)。我使用Ubuntu下的终端验证该服务,截图如下:

在这里插入图片描述

编写客户端

上面我们使用了rdate命令来验证,除此之外我们也可以使用客户端来处理接收到的消息。接下来是Netty创建客户端的部分。
Netty的服务端和客户端最大的区别就是使用不同的BootstrapChannel。代码如下:
TimeClient.java

package server;

import io.netty.bootstrap.Bootstrap;
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.NioSocketChannel;

/**
 * 时间协议客户端
 * 
 * @author 胡海龙
 *
 */
public class TimeClient {

	public static void main(String[] args) throws InterruptedException {
		String host = "127.0.0.1";
		int port = 8080;
		EventLoopGroup workerGroup = new NioEventLoopGroup();
		try {
			Bootstrap b = new Bootstrap(); 									// (1)
			b.group(workerGroup); 											// (2)
			b.channel(NioSocketChannel.class); 								// (3)
			b.option(ChannelOption.SO_KEEPALIVE, true); 					// (4)
			b.handler(new ChannelInitializer<SocketChannel>() {

				@Override
				protected void initChannel(SocketChannel ch) throws Exception {
					ch.pipeline().addLast(new TimeClientHandler());
				}
			});
			ChannelFuture f = b.connect(host, port).sync(); 				// (5)
			f.channel().closeFuture().sync();
		} finally {
			workerGroup.shutdownGracefully();
		}
	}
}

代码解析

  1. Bootstrap类似于ServerBootstrap,只不过它只适用于非服务端的通道。
  2. 如果只指定了一个EventLoopGroup它将同时用作boos和worker组,但是boss组并不在客户端工作。
  3. NioSocketChannel被用作创建客户端的通道。
  4. 需要注意的是这里我们不再使用childOption()方法来设置而是直接使用option()方法,因为在客户端SocketChannel没有父级。
  5. 最后使用connect()方法,而不是使用bind()方法。

TimeClientHandler.java

package server;

import java.util.Date;

import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;

/**
 * 时间协议客户端处理类
 * 
 * @author 胡海龙
 *
 */
public class TimeClientHandler extends ChannelInboundHandlerAdapter {

	@Override
	public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
		ByteBuf buf = (ByteBuf) msg;
		try {
			long currentTimeMillis = (buf.readUnsignedInt() - 2208988800L) * 1000L;
			System.out.println(new Date(currentTimeMillis));
			ctx.close();
		} finally {
			buf.release(); // 释放
		}
	}

	@Override
	public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
		cause.printStackTrace();
		ctx.close();
	}

}

运行客户端验证,如下图:
在这里插入图片描述
参考连接:https://netty.io/wiki/user-guide-for-4.x.html

posted @ 2022-06-02 08:39  胡海龙  阅读(48)  评论(0)    收藏  举报
www.huhailong.vip