本章介绍java
获取Netty4最新版本bootstrap
设置运行环境来构建和运行netty程序服务器
建立一个基于Netty的服务器和客户端异步
拦截和处理异常socket
编写和运行Netty服务器和客户端ide
----学习Netty是如何拦截和处理异常,服务器和客户端的启动以及分离通道的处理程序----oop
1.NETTY介绍学习
一个Netty程序的工做图以下:this
1.客户端链接到服务器 .net
2.创建链接后,发送或接收数据
3.服务器处理全部的客户端链接
2. 1编写一个应答服务器
写一个Netty服务器主要由两部分组成:
配置服务器功能,如线程、端口
实现服务器处理程序,它包含业务逻辑,决定当有一个请求链接或接收数据时该作什么
2.1.1启动服务器
经过建立ServerBootstrap对象来启动服务器,而后配置这个对象的相关选项,如端口、线程模式、事件循环,而且添加逻辑处理程序用来处理业务逻辑。
package netty.example; 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; public class EchoServer { private final int port; public EchoServer(int port) { this.port = port; } public void start() throws Exception { EventLoopGroup group = new NioEventLoopGroup(); try { //create ServerBootstrap instance ServerBootstrap b = new ServerBootstrap(); //Specifies NIO transport, local socket address //Adds handler to channel pipeline b.group(group).channel(NioServerSocketChannel.class).localAddress(port) .childHandler(new ChannelInitializer<Channel>() { @Override protected void initChannel(Channel ch) throws Exception { ch.pipeline().addLast(new EchoServerHandler()); } }); //Binds server, waits for server to close, and releases resources ChannelFuture f = b.bind().sync(); System.out.println(EchoServer.class.getName() + "started and listen on " + f.channel().localAddress()); f.channel().closeFuture().sync(); } finally { group.shutdownGracefully().sync(); } } public static void main(String[] args) throws Exception { new EchoServer(65535).start(); } }
a.启动服务器应先建立一个ServerBootstrap对象,由于使用NIO,因此指定NioEventLoopGroup来接受和处理新链接,指定通道类型为NioServerSocketChannel,设置InetSocketAddress让服务器监听某个端口已等待客户端链接。
b. 接下来,调用childHandler用来指定链接后调用的ChannelHandler,这个方法传ChannelInitializer类型的参数,ChannelInitializer是个抽象类,因此须要实现initChannel方法,这个方法就是用来设置ChannelHandler。
c.最后绑定服务器 等待直到绑定完成,调用sync()方法会阻塞直到服务器完成绑定,而后服务器等待通道关闭,由于使用sync(),因此关闭操做也会被阻塞。如今你能够关闭EventLoopGroup和释放全部资源,包括建立的线程。
若是这个例子使用OIO方式传输,你须要指定OioServerSocketChannel
本小节重点内容:
建立ServerBootstrap实例来引导绑定和启动服务器
建立NioEventLoopGroup对象来处理事件,如接受新链接、接收数据、写数据等等
指定InetSocketAddress,服务器监听此端口
设置childHandler执行全部的链接请求
都设置完毕了,最后调用ServerBootstrap.bind() 方法来绑定服务器
2.1.2 实现服务器业务逻辑
Netty使用futures和回调概念,它的设计容许你处理不一样的事件类型。
你的channelHandler必须继承ChannelInboundHandlerAdapter而且重写channelRead方法,这个方法在任什么时候候都会被调用来接收数据,在这个例子中接收的是字节。
下面是handler的实现,其实现的功能是将客户端发给服务器的数据返回给客户端:
package netty.example; import io.netty.buffer.Unpooled; import io.netty.channel.ChannelFutureListener; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelInboundHandlerAdapter; 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); } @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { cause.printStackTrace(); ctx.close(); } }
Netty使用多个ChannelHandler来达到对事件处理的分离,由于能够很容易的添加、更新、删除业务逻辑处理handler。Handler很简单,它的每一个方法均可以被重写,它的全部的方法中只有channelRead方法是必需要重写的。
2.1.3 捕获异常
重写ChannelHandler的exceptionCaught方法能够捕获服务器的异常,好比客户端链接服务器后强制关闭,服务器会抛出"客户端主机强制关闭错误",经过重写exceptionCaught方法就能够处理异常,好比发生异常后关闭ChannelHandlerContext。
2.2 编写应答程序的客户端
服务器写好了,如今来写一个客户端链接服务器。应答程序的客户端包括如下几步:
链接服务器
写数据到服务器
等待接受服务器返回相同的数据
关闭链接
2.2.1 引导客户端
引导客户端启动和引导服务器很相似,客户端需同时指定host和port来告诉客户端链接哪一个服务器。看下面代码:
package netty.example; 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 io.netty.example.echo.EchoClientHandler; import java.net.InetSocketAddress; public class EchoClient { private final String host; private final int port; public EchoClient(String host, int port) { this.host = host; this.port = port; } 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<SocketChannel>() { @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("localhost", 20000).start(); } }
建立启动一个客户端包含下面几步:
建立Bootstrap对象用来引导启动客户端
建立EventLoopGroup对象并设置到Bootstrap中,EventLoopGroup能够理解为是一个线程池,这个线程池用来处理链接、接受数据、发送数据
建立InetSocketAddress并设置到Bootstrap中,InetSocketAddress是指定链接的服务器地址
添加一个ChannelHandler,客户端成功链接服务器后就会被执行
调用Bootstrap.connect()来链接服务器
最后关闭EventLoopGroup来释放资源
2.2.2 实现客户端的业务逻辑
和编写服务器的ChannelHandler同样,在这里将自定义一个继承SimpleChannelInboundHandler的ChannelHandler来处理业务
经过重写父类的三个方法来处理感兴趣的事件:
channelActive():客户端链接服务器后被调用
channelRead0():从服务器接收到数据后调用
exceptionCaught():发生异常时被调用
package netty.example; 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; public class EchoClientHandler extends SimpleChannelInboundHandler<ByteBuf> { @Override public void channelActive(ChannelHandlerContext ctx) throws Exception { 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(); } }
可能你会问为何在这里使用的是SimpleChannelInboundHandler而不使用ChannelInboundHandlerAdapter?
缘由是ChannelInboundHandlerAdapter在处理完消息后须要负责释放资源。在这里将调用ByteBuf.release()来释放资源。SimpleChannelInboundHandler会在完成channelRead0后释放消息,这是经过Netty处理全部消息的ChannelHandler实现了ReferenceCounted接口达到的。
为何在服务器中不使用SimpleChannelInboundHandler呢?
由于服务器要返回相同的消息给客户端,在服务器执行完成写操做以前不能释放调用读取到的消息,由于写操做是异步的,一旦写操做完成后,Netty中会自动释放消息。
2.3 编译和运行echo(应答)程序客户端和服务器