前面咱們介紹了網絡一些基本的概念,雖說這些很難吧,可是至少要作到理解吧。有了以前的基礎,咱們來正式揭開Netty這神祕的面紗就會簡單不少。編程
public class PrintServer { public void bind(int port) throws Exception { EventLoopGroup bossGroup = new NioEventLoopGroup(); //1 EventLoopGroup workerGroup = new NioEventLoopGroup(); //2 try { ServerBootstrap b = new ServerBootstrap(); //3 b.group(bossGroup, workerGroup) //4 .channel(NioServerSocketChannel.class) //5 .option(ChannelOption.SO_BACKLOG, 1024) //6 .childHandler(new ChannelInitializer<SocketChannel>() { //7 @Override protected void initChannel(SocketChannel ch) throws Exception { ch.pipeline().addLast(new PrintServerHandler()); } }); ChannelFuture f = b.bind(port).sync(); //8 f.channel().closeFuture().sync(); //9 } finally { // 優雅退出,釋放線程池資源 bossGroup.shutdownGracefully(); workerGroup.shutdownGracefully(); } } /** * @param args * @throws Exception */ public static void main(String[] args) throws Exception { int port = 8080; new TimeServer().bind(port); } }
咱們來分析一下上面的這段代碼(下面的每一點對應上面的註釋)數組
1~2:首先咱們建立了兩個NioEventLoopGroup實例,它是一個由Netty封裝好的包含NIO的線程組。爲何建立兩個?我想通過前面的學習你們應該都清楚了。對,由於Netty的底層是IO多路複用,bossGroup 是用於接收客戶端的鏈接,原理就是一個實現的Selector的Reactor線程。而workerGroup用於進行SocketChannel的網絡讀寫。緩存
3:建立一個ServerBootstrap對象,能夠把它想象成Netty的入口,經過這類來啓動Netty,將所須要的參數傳遞到該類當中,大大下降了的開發難度。網絡
4:將兩個NioEventLoopGroup實例綁定到ServerBootstrap對象中。異步
5:建立Channel(典型的channel有NioSocketChannel,NioServerSocketChannel,OioSocketChannel,OioServerSocketChannel,EpollSocketChannel,EpollServerSocketChannel),這裏建立的是NIOserverSocketChannel,它的功能能夠理解爲當接受到客戶端的鏈接請求的時候,完成TCP三次握手,TCP物理鏈路創建成功。並將該「通道」與workerGroup線程組的某個線程相關聯。ide
6:設置參數,這裏設置的SO_BACKLOG,意思是客戶端鏈接等待隊列的長度爲1024.oop
7:創建鏈接後的具體Handler。就是咱們接受數據後的具體操做,例如:記錄日誌,對信息解碼編碼等。學習
8:綁定端口,同步等待成功編碼
9:等待服務端監聽端口關閉線程
public class PrintServerHandler extends ChannelHandlerAdapter { @Override public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { ByteBuf buf = (ByteBuf) msg; //1 byte[] req = new byte[buf.readableBytes()]; buf.readBytes(req); //將緩存區的字節數組複製到新建的req數組中 String body = new String(req, "UTF-8"); System.out.println(body); String response= "打印成功"; ByteBuf resp = Unpooled.copiedBuffer(response.getBytes()); ctx.write(resp); //2 } @Override public void channelReadComplete(ChannelHandlerContext ctx) throws Exception { ctx.flush(); //3 } @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) { ctx.close(); } }
PrintServerHandler 繼承 ChannelHandlerAdapter ,在這裏它的功能爲 打印客戶端發來的數據而且返回客戶端打印成功。
咱們只須要實現channelRead,exceptionCaught,前一個爲接受消息具體邏輯的實現,後一個爲發生異常後的具體邏輯實現。
1:咱們能夠看到,接受的消息被封裝爲了Object ,咱們將其轉換爲ByteBuf ,前一章的講解中也說明了該類的做用。咱們須要讀取的數據就在該緩存類中。
2~3:咱們將寫好的數據封裝到ByteBuf中,而後經過write方法寫回到客戶端,這裏的3調用flush方法的做用爲,防止頻繁的發送數據,write方法並不直接將數據寫入SocketChannel中,而是把待發送的數據放到發送緩存數組中,再調用flush方法發送數據。
public class PrintClient { public void connect(int port, String host) throws Exception { EventLoopGroup group = new NioEventLoopGroup(); //1 try { Bootstrap b = new Bootstrap(); //2 b.group(group) //3 .channel(NioSocketChannel.class) //4 .option(ChannelOption.TCP_NODELAY, true) //5 .handler(new ChannelInitializer<SocketChannel>() { //6 @Override public void initChannel(SocketChannel ch) throws Exception { ch.pipeline().addLast(new PrintClientHandler()); } }); ChannelFuture f = b.connect(host, port).sync(); //7 f.channel().closeFuture().sync(); //8 } finally { // 優雅退出,釋放NIO線程組 group.shutdownGracefully(); } } /** * @param args * @throws Exception */ public static void main(String[] args) throws Exception { int port = 8080; new TimeClient().connect(port, "127.0.0.1"); } }
咱們繼續來分析一下上面的這段代碼(下面的每一點對應上面的註釋)
1:區別於服務端,咱們在客戶端只建立了一個NioEventLoopGroup實例,由於客戶端你並不須要使用I/O多路複用模型,須要有一個Reactor來接受請求。只須要單純的讀寫數據便可
2:區別於服務端,咱們在客戶端只須要建立一個Bootstrap對象,它是客戶端輔助啓動類,功能相似於ServerBootstrap。
3:將NioEventLoopGroup實例綁定到Bootstrap對象中。
4:建立Channel(典型的channel有NioSocketChannel,NioServerSocketChannel,OioSocketChannel,OioServerSocketChannel,EpollSocketChannel,EpollServerSocketChannel),區別與服務端,這裏建立的是NIOSocketChannel.
5:設置參數,這裏設置的TCP_NODELAY爲true,意思是關閉延遲發送,一有消息就當即發送,默認爲false。
6:創建鏈接後的具體Handler。注意這裏區別與服務端,使用的是handler()而不是childHandler()。handler和childHandler的區別在於,handler是接受或發送以前的執行器;childHandler爲創建鏈接以後的執行器。
7:發起異步鏈接操做
8:當代客戶端鏈路關閉
public class PrintClientHandler extends ChannelHandlerAdapter { private static final Logger logger = Logger .getLogger(TimeClientHandler.class.getName()); private final ByteBuf firstMessage; /** * Creates a client-side handler. */ public TimeClientHandler() { byte[] req = "你好服務端".getBytes(); firstMessage = Unpooled.buffer(req.length); //1 firstMessage.writeBytes(req); } @Override public void channelActive(ChannelHandlerContext ctx) { ctx.writeAndFlush(firstMessage); //2 } @Override public void channelRead(ChannelHandlerContext ctx, Object msg) //3 throws Exception { ByteBuf buf = (ByteBuf) msg; byte[] req = new byte[buf.readableBytes()]; buf.readBytes(req); String body = new String(req, "UTF-8"); System.out.println("服務端迴應消息 : " + body); } @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) { //4 // 釋放資源 System.out.println("Unexpected exception from downstream : " + cause.getMessage()); ctx.close(); } }
PrintClientHandler 繼承 ChannelHandlerAdapter ,在這裏它的功能爲 發送數據並打印服務端發來的數據。
咱們只須要實現channelActive,channelRead,exceptionCaught,第一個爲創建鏈接後當即執行,後兩個與一個爲接受消息具體邏輯的實現,另外一個爲發生異常後的具體邏輯實現。
1:將發送的信息封裝到ByteBuf中。
2:發送消息。
3:接受客戶端的消息並打印
4:發生異常時,打印異常信息,釋放客戶端資源
這是一個入門程序,對應前面所講的I/O多路複用模型以及NIO的特性,能頗有效的理解該模式的編程方式。若是這幾段代碼看着很費勁,那麼能夠看看以前博主的Netty基礎系列。
若是博主哪裏說得有問題,但願你們提出來,一塊兒進步~