关闭

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

标签: netty
257人阅读 评论(0) 收藏 举报
分类:

<p>
storm 进程间通信用的是netty,和生产线上的storm topology经常报netty的错误,所有有必要好好把netty好好记录一下。</p><p>netty的service和client,helloworld,需要四个类。netty把网络链接和业务处理分离开来了。直接上代码</p><p>01:EchoServer				service端,负责链接客户端</p><p>02:EchoClient				client端,连接service段。</p><p>03:EchoClientHandler		client端的消息处理的抽象</p><p>04:EchoServerHandler		server端消息处理的抽象</p>
//**********************************下面是代码*********************************************************************************
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实例来引导绑定和启动服务器 <br>
	 * • 创建NioEventLoopGroup对象来处理事件,如接受新连接、接收数据、写数据等等 <br>
	 * • 指定InetSocketAddress,服务器监听此端口<br>
	 * • 设置childHandler执行所有的连接请求 <br>
	 * • 都设置完毕了,最后调用ServerBootstrap.bind() 方法来绑定服务器<br>
	 * 
	 * @throws Exception
	 */
	public void start() throws Exception {
		// 所以指定NioEventLoopGroup来接受和处理新连接
		EventLoopGroup group = new NioEventLoopGroup();
		try {
			/**
			 * 先看看ServerBootstrap提供了哪些方法<br>
			 * • group(...),设置EventLoopGroup事件循环组<br>
			 * • channel(...),设置通道类型<br>
			 * • channelFactory(...),使用ChannelFactory来设置通道类型<br>
			 * • localAddress(...),设置本地地址,也可以通过bind(...)或connect(...)<br>
			 * • option(ChannelOption<T>,
			 * T),设置通道选项,若使用null,则删除上一个设置的ChannelOption<br>
			 * • childOption(ChannelOption<T>, T),设置子通道选项<br>
			 * • attr(AttributeKey<T>, T),设置属性到Channel,若值为null,则指定键的属性被删除<br>
			 * • childAttr(AttributeKey<T>, T),设置子通道属性<br>
			 * • handler(ChannelHandler),设置ChannelHandler用于处理请求事件<br>
			 * • childHandler(ChannelHandler),设置子ChannelHandler<br>
			 * • clone(),深度复制ServerBootstrap,且配置相同<br>
			 * • bind(...),创建一个新的Channel并绑定<br>
			 */
			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<Channel>() {// 这个方法传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;


/**
 * 创建启动一个客户端包含下面几步:<br>
 * • 创建Bootstrap对象用来引导启动客户端<br>
 * • 创建EventLoopGroup对象并设置到Bootstrap中,EventLoopGroup可以理解为是一个线程池,这个线程池用来处理连接、接受数据
 * 、发送数据<br>
 * • 创建InetSocketAddress并设置到Bootstrap中,InetSocketAddress是指定连接的服务器地址<br>
 * • 添加一个ChannelHandler,客户端成功连接服务器后就会被执行<br>
 * • 调用Bootstrap.connect()来连接服务器<br>
 * • 最后关闭EventLoopGroup来释放资源<br>
 * 
 * @author mosi
 * 
 */
public class EchoClient {


<span>	</span>private final String host;
<span>	</span>private final int port;


<span>	</span>public EchoClient(String host, int port) {
<span>		</span>this.host = host;
<span>		</span>this.port = port;
<span>	</span>}


<span>	</span>/**
<span>	</span> * 创建Bootstrap实例使用new关键字,下面是Bootstrap的方法:<br>
<span>	</span> * • group(...),设置EventLoopGroup,EventLoopGroup用来处理所有通道的IO事件<br>
<span>	</span> * • channel(...),设置通道类型<br>
<span>	</span> * • channelFactory(...),使用ChannelFactory来设置通道类型<br>
<span>	</span> * • localAddress(...),设置本地地址,也可以通过bind(...)或connect(...)<br>
<span>	</span> * • option(ChannelOption<T>, T),设置通道选项,若使用null,则删除上一个设置的ChannelOption<br>
<span>	</span> * • attr(AttributeKey<T>, T),设置属性到Channel,若值为null,则指定键的属性被删除<br>
<span>	</span> * • handler(ChannelHandler),设置ChannelHandler用于处理请求事件<br>
<span>	</span> * • clone(),深度复制Bootstrap,Bootstrap的配置相同<br>
<span>	</span> * • remoteAddress(...),设置连接地址<br>
<span>	</span> * • connect(...),连接远程通道<br>
<span>	</span> * • bind(...),创建一个新的Channel并绑定<br>
<span>	</span> * 
<span>	</span> * @throws Exception
<span>	</span> */
<span>	</span>public void start() throws Exception {
<span>		</span>EventLoopGroup group = new NioEventLoopGroup();
<span>		</span>try {
<span>			</span>Bootstrap b = new Bootstrap();
<span>			</span>b.group(group).channel(NioSocketChannel.class)
<span>					</span>.remoteAddress(new InetSocketAddress(host, port))
<span>					</span>.handler(new ChannelInitializer<SocketChannel>() {
<span>						</span>@Override
<span>						</span>protected void initChannel(SocketChannel ch)
<span>								</span>throws Exception {
<span>							</span>ch.pipeline().addLast(new EchoClientHandler());
<span>						</span>}
<span>					</span>});
<span>			</span>ChannelFuture f = b.connect().sync();
<span>			</span>f.channel().closeFuture().sync();
<span>		</span>} finally {
<span>			</span>group.shutdownGracefully().sync();
<span>		</span>}
<span>	</span>}


<span>	</span>public static void main(String[] args) throws Exception {
<span>		</span>new EchoClient("127.0.0.1", 65535).start();
<span>	</span>}
}
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来处理业务 ;<br>
 * 通过重写父类的三个方法来处理感兴趣的事件:<br>
 * • channelActive():客户端连接服务器后被调用 <br>
 * • channelRead0():从服务器接收到数据后调用 <br>
 * • exceptionCaught():发生异常时被调用<br>
 * 
 * @author mosi
 * 
 */
public class EchoClientHandler extends SimpleChannelInboundHandler<ByteBuf> {
<span>	</span>/**
<span>	</span> * 可能你会问为什么在这里使用的是SimpleChannelInboundHandler而不使用ChannelInboundHandlerAdapter
<span>	</span> * ? <br>
<span>	</span> * 主要原因是ChannelInboundHandlerAdapter在处理完消息后需要负责释放资源
<span>	</span> * 。在这里将调用ByteBuf.release()来释放资源。
<span>	</span> * SimpleChannelInboundHandler会在完成channelRead0后释放消息<br>
<span>	</span> * ,这是通过Netty处理所有消息的ChannelHandler实现了ReferenceCounted接口达到的。<br>
<span>	</span> * 为什么在服务器中不使用SimpleChannelInboundHandler呢?<br>
<span>	</span> * 因为服务器要返回相同的消息给客户端 ,在服务器执行完成写操作之前不能释放调用读取到的消息,<br>
<span>	</span> * 因为写操作是异步的,一旦写操作完成后,Netty中会自动释放消息。
<span>	</span> */
<span>	</span>@Override
<span>	</span>public void channelActive(ChannelHandlerContext ctx) throws Exception {
<span>		</span>System.out.println("active");
<span>		</span>ctx.write(Unpooled.copiedBuffer("Netty rocks!", CharsetUtil.UTF_8));
<span>	</span>}


<span>	</span>@Override
<span>	</span>protected void channelRead0(ChannelHandlerContext ctx, ByteBuf msg)
<span>			</span>throws Exception {
<span>		</span>System.out.println("Client received: "
<span>				</span>+ ByteBufUtil.hexDump(msg.readBytes(msg.readableBytes())));
<span>	</span>}


<span>	</span>@Override
<span>	</span>public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause)
<span>			</span>throws Exception {
<span>		</span>cause.printStackTrace();
<span>		</span>ctx.close();
<span>	</span>}
}
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 {


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


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


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


}



0
0

查看评论
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
    个人资料
    • 访问:108289次
    • 积分:2501
    • 等级:
    • 排名:第14765名
    • 原创:90篇
    • 转载:161篇
    • 译文:31篇
    • 评论:8条
    最新评论