這篇隨筆暫時不講原理,首先搭建起一個簡單的可以實現通信的Demo。之后的一系列隨筆會進行一些原理上的分享。
不過在這之前大家最好了解一下Netty的線程模型和NIO編程模型,會對它的整體邏輯有所了解。
更新一篇關於NIO的博客:手動搭建I/O網絡通信框架3:NIO編程模型,升級改造聊天室
首先創建好項目后在pom.xml引入Netty依賴
<dependency> <groupId>io.netty</groupId> <artifactId>netty-all</artifactId> </dependency>
用Netty搭建一個WebSocket服務器整體上需要三樣東西,不管是不是用的SpringBoot框架,這三樣東西是必不可少的。
1.啟動服務器的類(NettyServer),會進行一些初步的配置工作。
2.助手類(Handler),有自己定義的助手類,也有Netty提供的一些基本的助手類,比如對Http、WebSocket支持的助手類。
3.初始化器(Initializer),我們下面使用的是主從線程模型,從線程組里會分配出不同channel去處理不同客戶端的請求,而每個channel里就會有各種助手類去實現一些功能。初始化器的作用就是對各種助手類進行綁定。
服務器啟動類:
public class NettyServer { private static int port; public NettyServer(int port) { this.port = port; } public static void start() throws InterruptedException {//在main方法里調用這個方法,並用構造函數設置端口號 //創建主線程組,接收請求 EventLoopGroup bossGroup = new NioEventLoopGroup(); //創建從線程組,處理主線程組分配下來的io操作 EventLoopGroup workerGroup = new NioEventLoopGroup(); //創建netty服務器 try { ServerBootstrap serverBootstrap = new ServerBootstrap(); serverBootstrap.group(bossGroup, workerGroup)//設置主從線程組 .channel(NioServerSocketChannel.class)//設置通道 .childHandler(new NettyServerInitializer());//子處理器,用於處理workerGroup中的操作 //啟動server ChannelFuture channelFuture = serverBootstrap.bind(port).sync(); //監聽關閉channel channelFuture.channel().closeFuture().sync(); } finally { bossGroup.shutdownGracefully();//關閉主線程 workerGroup.shutdownGracefully();//關閉從線程 } } }
初始化器:
public class NettyServerInitializer extends ChannelInitializer<SocketChannel> { @Override protected void initChannel(SocketChannel socketChannel) throws Exception { ChannelPipeline pipeline= socketChannel.pipeline(); //以下三個是Http的支持 //http解碼器 pipeline.addLast(new HttpServerCodec()); //支持寫大數據流 pipeline.addLast(new ChunkedWriteHandler()); //http聚合器 pipeline.addLast(new HttpObjectAggregator(1024*62)); //websocket支持,設置路由 pipeline.addLast(new WebSocketServerProtocolHandler("/ws")); //添加自定義的助手類 pipeline.addLast(new NettyHandler()); } }
自定義助手類:
這個類就是業務的核心,客戶端的請求會在這里處理。比如客戶端連接、客戶端發送消息、給客戶端發送消息等等。
自定義助手類需要重寫的方法可以根據自己的需求重寫,這里就不把每個方法都重寫一遍了,完整的大家可以去找找文檔看看。
如果需要在助手類中用到@Autowire注解,可以參考這個博客,網上有很多說明,這里就不再重復了https://blog.csdn.net/weixin_30828379/article/details/95009595
public class NettyHandler extends SimpleChannelInboundHandler<TextWebSocketFrame> {//TextWebSocketFrame是netty用於處理websocket發來的文本對象 //所有正在連接的channel都會存在這里面,所以也可以間接代表在線的客戶端 public static ChannelGroup channelGroup = new DefaultChannelGroup(GlobalEventExecutor.INSTANCE); //在線人數 public static int online; //接收到客戶都發送的消息 @Override public void channelRead0(ChannelHandlerContext ctx, TextWebSocketFrame msg) throws Exception { SendAllMessages(ctx,send_message);//send_message是我的自定義類型,前后端分離往往需要統一數據格式,可以先把對象轉成json字符串再發送給客戶端 } //客戶端建立連接 @Override public void handlerAdded(ChannelHandlerContext ctx) throws Exception { channelGroup.add(ctx.channel()); online=channelGroup.size(); System.out.println(ctx.channel().remoteAddress()+"上線了!"); } //關閉連接 @Override public void handlerRemoved(ChannelHandlerContext ctx) throws Exception { channelGroup.remove(ctx.channel()); online=channelGroup.size(); System.out.println(ctx.channel().remoteAddress()+"斷開連接"); } //出現異常 @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { cause.printStackTrace(); } //給某個人發送消息 private void SendMessage(ChannelHandlerContext ctx, Send_Message msg) { ctx.channel().writeAndFlush(new TextWebSocketFrame(JSON.toJSONString(msg))); } //給每個人發送消息,除發消息人外 private void SendAllMessages(ChannelHandlerContext ctx,Send_Message msg) { for(Channel channel:channelGroup){ if(!channel.id().asLongText().equals(ctx.channel().id().asLongText())){ channel.writeAndFlush(new TextWebSocketFrame(JSON.toJSONString(msg))); } } } }
前端創建WebSocket對象進行訪問
通過socket就可以用一些api進行發送消息,接收消息的操作。然后把接收的數據按各自的需求展示出來就行了,前端部分就不再贅述了。
下面8088端口記得要在main方法里面設置。
if (window.WebSocket) { var host = window.location.hostname; var url = "ws://" + host + ":8088/ws"; var socket = new WebSocket(url); }else{ alert("你的瀏覽器不支持WebSocket。請不要使用低版本的IE瀏覽器。"); }