01 NIO网络通信框架 Netty server和client的api

storm 进程间通信用的是netty,和生产线上的storm topology经常报netty的错误,所有有必要好好把netty好好记录一下。

netty的service和client,helloworld,需要四个类。netty把网络链接和业务处理分离开来了。直接上代码

01:EchoServer service端,负责链接客户端

02:EchoClient client端,连接service段。

03:EchoClientHandler client端的消息处理的抽象

04:EchoServerHandler server端消息处理的抽象

//**********************************下面是代码*********************************************************************************
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;

/**
 * netty服务端
 * 
 * @author mosi
 * 
 */
public class EchoServer {
	private final int port;

	public EchoServer(int port) {
		this.port = port;
	}

	/**
	 * • 创建ServerBootstrap实例来引导绑定和启动服务器 
* • 创建NioEventLoopGroup对象来处理事件,如接受新连接、接收数据、写数据等等
* • 指定InetSocketAddress,服务器监听此端口
* • 设置childHandler执行所有的连接请求
* • 都设置完毕了,最后调用ServerBootstrap.bind() 方法来绑定服务器
* * @throws Exception */ public void start() throws Exception { // 所以指定NioEventLoopGroup来接受和处理新连接 EventLoopGroup group = new NioEventLoopGroup(); try { /** * 先看看ServerBootstrap提供了哪些方法
* • group(...),设置EventLoopGroup事件循环组
* • channel(...),设置通道类型
* • channelFactory(...),使用ChannelFactory来设置通道类型
* • localAddress(...),设置本地地址,也可以通过bind(...)或connect(...)
* • option(ChannelOption, * T),设置通道选项,若使用null,则删除上一个设置的ChannelOption
* • childOption(ChannelOption, T),设置子通道选项
* • attr(AttributeKey, T),设置属性到Channel,若值为null,则指定键的属性被删除
* • childAttr(AttributeKey, T),设置子通道属性
* • handler(ChannelHandler),设置ChannelHandler用于处理请求事件
* • childHandler(ChannelHandler),设置子ChannelHandler
* • clone(),深度复制ServerBootstrap,且配置相同
* • bind(...),创建一个新的Channel并绑定
*/ ServerBootstrap b = new ServerBootstrap(); // Specifies NIO transport, local socket address // Adds handler to channel pipeline b = b.group(group);// 所以指定NioEventLoopGroup来接受和处理新连接 b = b.channel(NioServerSocketChannel.class);// 指定通道类型为NioServerSocketChannel b = b.localAddress(port);// 设置InetSocketAddress让服务器监听某个端口已等待客户端连接。 // 接下来,调用childHandler放来指定连接后调用的ChannelHandler b.childHandler(new ChannelInitializer() {// 这个方法传ChannelInitializer类型的参数 // 这个方法就是用来设置ChannelHandler @Override protected void initChannel(Channel ch) throws Exception { ch.pipeline().addLast(new EchoServerHandler()); } }); // Binds server, waits for server to close, and releases resources // 最后绑定服务器等待直到绑定完成,调用sync()方法会阻塞直到服务器完成绑定 ChannelFuture f = b.bind().sync(); System.out.println(EchoServer.class.getName() + "started and listen on “" + f.channel().localAddress()); // 然后服务器等待通道关闭,因为使用sync(),所以关闭操作也会被阻塞 f.channel().closeFuture().sync(); } finally { // 现在你可以关闭EventLoopGroup和释放所有资源,包括创建的线程。 group.shutdownGracefully().sync();// 因为使用sync(),所以关闭操作也会被阻塞 } } public static void main(String[] args) throws Exception { new EchoServer(65535).start(); } }
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;


import java.net.InetSocketAddress;


/**
 * 创建启动一个客户端包含下面几步:
 * • 创建Bootstrap对象用来引导启动客户端
 * • 创建EventLoopGroup对象并设置到Bootstrap中,EventLoopGroup可以理解为是一个线程池,这个线程池用来处理连接、接受数据  * 、发送数据
 * • 创建InetSocketAddress并设置到Bootstrap中,InetSocketAddress是指定连接的服务器地址
 * • 添加一个ChannelHandler,客户端成功连接服务器后就会被执行
 * • 调用Bootstrap.connect()来连接服务器
 * • 最后关闭EventLoopGroup来释放资源
 *   * @author mosi  *   */ public class EchoClient { private final String host; private final int port; public EchoClient(String host, int port) { this.host = host; this.port = port; } /** * 创建Bootstrap实例使用new关键字,下面是Bootstrap的方法:
* • group(...),设置EventLoopGroup,EventLoopGroup用来处理所有通道的IO事件
* • channel(...),设置通道类型
* • channelFactory(...),使用ChannelFactory来设置通道类型
* • localAddress(...),设置本地地址,也可以通过bind(...)或connect(...)
* • option(ChannelOption, T),设置通道选项,若使用null,则删除上一个设置的ChannelOption
* • attr(AttributeKey, T),设置属性到Channel,若值为null,则指定键的属性被删除
* • handler(ChannelHandler),设置ChannelHandler用于处理请求事件
* • clone(),深度复制Bootstrap,Bootstrap的配置相同
* • remoteAddress(...),设置连接地址
* • connect(...),连接远程通道
* • bind(...),创建一个新的Channel并绑定
* @throws Exception */ public void start() throws Exception { EventLoopGroup group = new NioEventLoopGroup(); try { Bootstrap b = new Bootstrap(); b.group(group).channel(NioSocketChannel.class) .remoteAddress(new InetSocketAddress(host, port)) .handler(new ChannelInitializer() { @Override protected void initChannel(SocketChannel ch) throws Exception { ch.pipeline().addLast(new EchoClientHandler()); } }); ChannelFuture f = b.connect().sync(); f.channel().closeFuture().sync(); } finally { group.shutdownGracefully().sync(); } } public static void main(String[] args) throws Exception { new EchoClient("127.0.0.1", 65535).start(); } }
import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufUtil;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.util.CharsetUtil;


/**
 * 客户端的业务逻辑的实现依然很简单,更复杂的用法将在后面章节详细介绍。和编写服务器的ChannelHandler一样,
 * 在这里将自定义一个继承SimpleChannelInboundHandler的ChannelHandler来处理业务 ;
 * 通过重写父类的三个方法来处理感兴趣的事件:
 * • channelActive():客户端连接服务器后被调用
 * • channelRead0():从服务器接收到数据后调用
 * • exceptionCaught():发生异常时被调用
 *   * @author mosi  *   */ public class EchoClientHandler extends SimpleChannelInboundHandler { /** * 可能你会问为什么在这里使用的是SimpleChannelInboundHandler而不使用ChannelInboundHandlerAdapter * ?
* 主要原因是ChannelInboundHandlerAdapter在处理完消息后需要负责释放资源 * 。在这里将调用ByteBuf.release()来释放资源。 * SimpleChannelInboundHandler会在完成channelRead0后释放消息
* ,这是通过Netty处理所有消息的ChannelHandler实现了ReferenceCounted接口达到的。
* 为什么在服务器中不使用SimpleChannelInboundHandler呢?
* 因为服务器要返回相同的消息给客户端 ,在服务器执行完成写操作之前不能释放调用读取到的消息,
* 因为写操作是异步的,一旦写操作完成后,Netty中会自动释放消息。 */ @Override public void channelActive(ChannelHandlerContext ctx) throws Exception { System.out.println("active"); ctx.write(Unpooled.copiedBuffer("Netty rocks!", CharsetUtil.UTF_8)); } @Override protected void channelRead0(ChannelHandlerContext ctx, ByteBuf msg) throws Exception { System.out.println("Client received: " + ByteBufUtil.hexDump(msg.readBytes(msg.readableBytes()))); } @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { cause.printStackTrace(); ctx.close(); } }
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;


/**
 * 实现服务端,业务逻辑的抽象。Netty使用多个Channel
 * Handler来达到对事件处理的分离,因为可以很容的添加、更新、删除业务逻辑处理handler
 * 。Handler很简单,它的每个方法都可以被重写,它的所有的方法中只有channelRead方法是必须要重写的。
 *
 * @author mosi
 * 
 */
public class EchoServerHandler extends ChannelInboundHandlerAdapter {


	/**
	 * 真正处理每个消息的回调方法
	 */
	@Override
	public void channelRead(ChannelHandlerContext ctx, Object msg)
			throws Exception {
		System.out.println("Server received: " + msg);
		ctx.write(msg);
	}


	/**
	 * 处理结束的方法。释放资源之类的
	 */
	@Override
	public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
		ctx.writeAndFlush(Unpooled.EMPTY_BUFFER).addListener(
				ChannelFutureListener.CLOSE);
	}


	/**
	 * 出异常的回调。释放资源.捕获服务器的异常,比如客户端连接服务器后强制关闭,服务器会抛出"客户端主机强制关闭错误",
	 * 通过重写exceptionCaught方法就可以处理异常,比如发生异常后关闭ChannelHandlerContext。
	 */
	@Override
	public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause)
			throws Exception {
		cause.printStackTrace();
		ctx.close();
	}


}



你可能感兴趣的:(netty)