一,准備工作
1,netty-all-4.1.5.Final.jar(官網下載)
2,eclipse
二,步驟概要
1,服務器開發
(1),創建Server類
該類是程序的主入口,有main方法,服務器開啟也是在此執行。
該類主要是提供了channel鏈接,綁定了端口。
該類需要new一個Initalizer類來完成服務器開啟。
(2),創建Initalizer類
該類是初始化類,主要是創建了傳輸通道ChannelPipeline,然后在通道中加入了一些列的Handler,其中解碼和編碼Handler是Netty自帶的輔助類,在最后假如了自定義業務控制器類。
(3),創建Handler類
該類是自定義的業務控制器,需要的邏輯都在這里實現,例如收到信息如何處理,斷開連接如何發送消息等。
2,客戶端開發
和服務器端開發類似,不同在於:
(1),client類(對應服務器的server類)只需要一個EventLoopGroup,而服務器類需要兩個。
(2),編碼解碼器要和服務器一致。
三,具體實現
1,項目樹形圖
服務器端和客戶端分別有三個類,HelloClient是客戶端主入口,HelloServer是服務器端主入口。
2,服務器代碼
(1),HelloServer
package org.example.hello; import io.netty.bootstrap.ServerBootstrap; import io.netty.channel.ChannelFuture; import io.netty.channel.EventLoopGroup; import io.netty.channel.nio.NioEventLoopGroup; import io.netty.channel.socket.nio.NioServerSocketChannel; public class HelloServer { /** * 服務端監聽的端口地址 */ private static final int portNumber = 7878; public static void main(String[] args) throws InterruptedException { //開啟兩個事件循環組,事件循環組會自動構建EventLoop,服務器一般開啟兩個,提高效率 EventLoopGroup bossGroup = new NioEventLoopGroup(); EventLoopGroup workerGroup = new NioEventLoopGroup(); try { //Netty的引導類,用於簡化開發 ServerBootstrap b = new ServerBootstrap(); //把事件循環組加入引導程序 b.group(bossGroup, workerGroup); //開啟socket b.channel(NioServerSocketChannel.class); //加入業務控制器,這里是加入一個初始化類,其中包含了很多業務控制器 b.childHandler(new HelloServerInitializer()); // 服務器綁定端口監聽 ChannelFuture f = b.bind(portNumber).sync(); // 監聽服務器關閉監聽 f.channel().closeFuture().sync(); // 可以簡寫為 /* b.bind(portNumber).sync().channel().closeFuture().sync(); */ } finally { //Netty優雅退出 bossGroup.shutdownGracefully(); workerGroup.shutdownGracefully(); } } }
(1),HelloServerInitializer
初始化傳輸通道
package org.example.hello; import io.netty.channel.ChannelInitializer; import io.netty.channel.ChannelPipeline; import io.netty.channel.socket.SocketChannel; import io.netty.handler.codec.DelimiterBasedFrameDecoder; import io.netty.handler.codec.Delimiters; import io.netty.handler.codec.string.StringDecoder; import io.netty.handler.codec.string.StringEncoder; //繼承Netty提供的初始化類,只要復寫其中的方法就可以了 public class HelloServerInitializer extends ChannelInitializer<SocketChannel> { @Override protected void initChannel(SocketChannel ch) throws Exception { //開啟傳輸通道,這個通道的作用就是管理控制器,形成一個責任鏈式管理 ChannelPipeline pipeline = ch.pipeline(); // 以("\n")為結尾分割的 解碼器 pipeline.addLast("framer", new DelimiterBasedFrameDecoder(8192, Delimiters.lineDelimiter())); // 字符串解碼 和 編碼 pipeline.addLast("decoder", new StringDecoder()); pipeline.addLast("encoder", new StringEncoder()); // 加入自定義的Handler pipeline.addLast("handler", new HelloServerHandler()); //初始化類一般都是先加入編碼解碼器來解讀傳輸來的消息,然后加入自定義類來處理業務邏輯 } }
(1),HelloServerHandler
定義業務邏輯
package org.example.hello; import java.net.InetAddress; import io.netty.channel.Channel; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.SimpleChannelInboundHandler; import io.netty.channel.group.ChannelGroup; import io.netty.channel.group.DefaultChannelGroup; import io.netty.util.concurrent.GlobalEventExecutor; //繼承Netty提供的通道傳入處理器類,只要復寫方法就可以了,簡化開發 public class HelloServerHandler extends SimpleChannelInboundHandler<String> { //獲取現有通道,一個通道channel就是一個socket鏈接在這里 public static ChannelGroup channels = new DefaultChannelGroup(GlobalEventExecutor.INSTANCE); //有新鏈接加入,對外發布消息 @Override public void handlerAdded(ChannelHandlerContext ctx) throws Exception { // (2) Channel incoming = ctx.channel(); for (Channel channel : channels) { channel.writeAndFlush("[SERVER] - " + incoming.remoteAddress() + " 加入\n"); } channels.add(ctx.channel()); } //有鏈接斷開,對外發布消息 @Override public void handlerRemoved(ChannelHandlerContext ctx) throws Exception { // (3) Channel incoming = ctx.channel(); for (Channel channel : channels) { channel.writeAndFlush("[SERVER] - " + incoming.remoteAddress() + " 離開\n"); } channels.remove(ctx.channel()); } //消息讀取有兩個方法,channelRead和channelRead0,其中channelRead0可以讀取泛型,常用 //收到消息打印出來,並返還客戶端消息 @Override protected void channelRead0(ChannelHandlerContext ctx, String msg) throws Exception { // 收到消息直接打印輸出 System.out.println(ctx.channel().remoteAddress() + " Say : " + msg); // 返回客戶端消息 - 我已經接收到了你的消息 ctx.writeAndFlush("Received your message !\n"); } /* * * 覆蓋 channelActive 方法 在channel被啟用的時候觸發 (在建立連接的時候) * * channelActive 和 channelInActive 在后面的內容中講述,這里先不做詳細的描述 * */ @Override public void channelActive(ChannelHandlerContext ctx) throws Exception { System.out.println("RamoteAddress : " + ctx.channel().remoteAddress() + " active !"); ctx.writeAndFlush( "Welcome to " + InetAddress.getLocalHost().getHostName() + " service!\n"); super.channelActive(ctx); } }
3,客戶端代碼
(1),HelloClient
package org.example.hello; import io.netty.bootstrap.Bootstrap; import io.netty.channel.Channel; import io.netty.channel.EventLoopGroup; import io.netty.channel.nio.NioEventLoopGroup; import io.netty.channel.socket.nio.NioSocketChannel; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; public class HelloClient { public static String host = "127.0.0.1"; public static int port = 7878; /** * @param args * @throws InterruptedException * @throws IOException */ public static void main(String[] args) throws InterruptedException, IOException { EventLoopGroup group = new NioEventLoopGroup(); try { Bootstrap b = new Bootstrap(); b.group(group) .channel(NioSocketChannel.class) .handler(new HelloClientInitializer()); // 連接服務端 Channel ch = b.connect(host, port).sync().channel(); // 控制台輸入 BufferedReader in = new BufferedReader(new InputStreamReader(System.in)); //也可以用while循環 for (;;) { String line = in.readLine(); if (line == null) { continue; } /* * 向服務端發送在控制台輸入的文本 並用"\r\n"結尾 * 之所以用\r\n結尾 是因為我們在handler中添加了 DelimiterBasedFrameDecoder 幀解碼。 * 這個解碼器是一個根據\n符號位分隔符的解碼器。所以每條消息的最后必須加上\n否則無法識別和解碼 * */ ch.writeAndFlush(line + "\r\n"); } } finally { // The connection is closed automatically on shutdown. group.shutdownGracefully(); } } }
(2),HelloClientInitializer
package org.example.hello; import io.netty.channel.ChannelInitializer; import io.netty.channel.ChannelPipeline; import io.netty.channel.socket.SocketChannel; import io.netty.handler.codec.DelimiterBasedFrameDecoder; import io.netty.handler.codec.Delimiters; import io.netty.handler.codec.string.StringDecoder; import io.netty.handler.codec.string.StringEncoder; public class HelloClientInitializer extends ChannelInitializer<SocketChannel> { @Override protected void initChannel(SocketChannel ch) throws Exception { ChannelPipeline pipeline = ch.pipeline(); /* * 這個地方的 必須和服務端對應上。否則無法正常解碼和編碼 * * 解碼和編碼 我將會在下一張為大家詳細的講解。再次暫時不做詳細的描述 * * */ pipeline.addLast("framer", new DelimiterBasedFrameDecoder(8192, Delimiters.lineDelimiter())); pipeline.addLast("decoder", new StringDecoder()); pipeline.addLast("encoder", new StringEncoder()); // 客戶端的邏輯 pipeline.addLast("handler", new HelloClientHandler()); } }
(3),HellClientHandler
package org.example.hello; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.SimpleChannelInboundHandler; public class HelloClientHandler extends SimpleChannelInboundHandler<String> { @Override protected void channelRead0(ChannelHandlerContext ctx, String msg) throws Exception { System.out.println("Server say : " + msg); } @Override public void channelActive(ChannelHandlerContext ctx) throws Exception { System.out.println("Client active "); super.channelActive(ctx); } @Override public void channelInactive(ChannelHandlerContext ctx) throws Exception { System.out.println("Client close "); super.channelInactive(ctx); } }
四,運行測試
服務器右鍵運行程序
客戶端右鍵運行程序
服務器端console:
客戶端console:
切換console: