Netty高级进阶之基于Netty的Websocket开发网页聊天室
本通過實戰演練,學習了如何基于Netty的websocket開發一個網頁聊天室。
Netty高級進階之基于Netty的Websocket開發網頁聊天室
Webdocket簡介
Websockt是一種在單個TCP連接上進行全雙工通信的協議。
Websocket使客戶端和服務端的數據交互變得簡單,允許服務器主動向客戶端推送數據。
在Websocket API中,客戶端只需要與服務器完成一次握手,兩者之間就可以創建持久性的連接,并進行雙向數據傳輸。
他的應用場景如下:
- 社交訂閱
- 協同編輯/編程
- 股票基金報價
- 體育實況更新
- 多媒體聊天
- 在線教育
Websocket和HTTP的區別
HTTP協議是應用層的協議,是基于TCP協議的。
HTTP協議必須經過三次握手才能發送消息。
HTTP連接分為短連接和長鏈接。短連接是每次都要經過三次握手才能發送消息。就是說每一個request對應一個response。長連接在一定期限內保持TCP連接不斷開。
客戶端與服務器通信,必須由客戶端先發起,然后服務端返回結果。客戶端是主動的,服務端是被動的。
客戶端想要實時獲取服務端的消息,就要不斷發送長連接到服務端。
Websocket實現了多路復用,它是全雙工通信。在Websocket協議下,服務端和客戶端可以同時發送消息。
建立了Websocket連接之后,服務端可以主動給客戶端發送消息。信息中不必帶有header的部分信息,與HTTP長連接通信對比,這種方式降低了服務器的壓力,信息當中也減少了多余的信息。
導入基礎環境
新建netty-springboot項目
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-zt5trjYz-1651644006392)(https://cdn.jsdelivr.net/gh/terwer/upload/img/image-20220430225632849.png)]
導入依賴模塊
<dependencies><!-- 模板引擎 --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-thymeleaf</artifactId></dependency><!-- web模塊 --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><optional>true</optional></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope></dependency> </dependencies>導入靜態資源
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-uiAqciq1-1651644006394)(https://cdn.jsdelivr.net/gh/terwer/upload/img/image-20220430230249496.png)]
配置yaml
server:port: 8080 resources:static-locations:- classpath:/static/ spring:thymeleaf:cache: falsechecktemplatelocation: trueenabled: trueencoding: UTF-8mode: HTMLprefix: classpath:/templates/suffix: .html關于Springboot整合thymeleaf的404問題,參考:
/post/404-problem-with-springboot-configuration-thymeleaf.html
代碼實現
服務端開發
添加Netty相關依賴
<!--引入netty依賴 --> <dependency><groupId>io.netty</groupId><artifactId>netty-all</artifactId> </dependency>Netty相關配置
netty:port: 8081ip: 127.0.0.1path: /chatNetty配置類
/*** Netty配置類** @name: NettyConfig* @author: terwer* @date: 2022-05-01 00:04**/ @Component @Data @ConfigurationProperties(prefix = "netty") public class NettyConfig {// netty監聽端口private int port;// webdocket訪問路徑private String path; }Netty的WebsocketServer開發
/*** Netty的Websocket服務器** @name: NettyWebsocketServer* @author: terwer* @date: 2022-05-01 00:11**/ @Component public class NettyWebsocketServer implements Runnable {@Autowiredprivate NettyConfig nettyConfig;@Autowiredprivate WebsocketChannelInit websocketChannelInit;private NioEventLoopGroup bossGroup = new NioEventLoopGroup(1);private NioEventLoopGroup workerGroup = new NioEventLoopGroup();@Overridepublic void run() {try {// 1.創建服務端啟動助手ServerBootstrap serverBootstrap = new ServerBootstrap();// 2.設置線程組serverBootstrap.group(bossGroup, workerGroup);// 3.設置參數serverBootstrap.channel(NioServerSocketChannel.class).handler(new LoggingHandler(LogLevel.DEBUG)).childHandler(websocketChannelInit);// 4.啟動服務端ChannelFuture channelFuture = serverBootstrap.bind(nettyConfig.getPort()).sync();System.out.println("------Netty服務端啟動成功------");channelFuture.channel().closeFuture().sync();} catch (InterruptedException e) {e.printStackTrace();bossGroup.shutdownGracefully();workerGroup.shutdownGracefully();throw new RuntimeException(e);} finally {bossGroup.shutdownGracefully();workerGroup.shutdownGracefully();}}/*** 關閉資源-容器銷毀時候關閉*/@PreDestroypublic void close() {bossGroup.shutdownGracefully();workerGroup.shutdownGracefully();} }通道初始化對象
/*** 通道初始化對象** @name: WebsocketChannelInit* @author: terwer* @date: 2022-05-01 23:11**/ @Component public class WebsocketChannelInit extends ChannelInitializer {@Autowiredprivate NettyConfig nettyConfig;@Autowiredprivate WebsocketHandler websocketHandler;@Overrideprotected void initChannel(Channel channel) throws Exception {ChannelPipeline pipeline = channel.pipeline();// 對HTTP協議的支持pipeline.addLast(new HttpServerCodec());// 對大數據流的支持pipeline.addLast(new ChunkedWriteHandler());// post請求分為三個部分:request line/request header/message body// 對POST請求的支持,將多個信息轉化成單一的request/response對象pipeline.addLast(new HttpObjectAggregator(8000));// 對WebSocket協議的支持// 將http協議升級為ws協議pipeline.addLast(new WebSocketServerProtocolHandler(nettyConfig.getPath()));// 自定義處理handlerpipeline.addLast(websocketHandler);} }處理對象
/*** 自定義Websocket處理類* Websocket數據以幀的形式進行處理* 需要設置通道共享** @name: WebsocketHandler* @author: terwer* @date: 2022-05-01 23:21**/ @Component @ChannelHandler.Sharable public class WebsocketHandler extends SimpleChannelInboundHandler<TextWebSocketFrame> {public static List<Channel> channelList = new ArrayList<>();/*** 通道就緒事件** @param ctx* @throws Exception*/@Overridepublic void channelActive(ChannelHandlerContext ctx) throws Exception {Channel channel = ctx.channel();// 有客戶端連接時,將通道放入集合channelList.add(channel);System.out.println("有新的鏈接");}/*** 通道未就緒** @param ctx* @throws Exception*/@Overridepublic void channelInactive(ChannelHandlerContext ctx) throws Exception {// channel下線Channel channel = ctx.channel();// 客戶端連接端口,移除連接channelList.remove(channel);System.out.println("連接斷開");}/*** 通道讀取事件** @param ctx* @param textWebSocketFrame* @throws Exception*/@Overrideprotected void channelRead0(ChannelHandlerContext ctx, TextWebSocketFrame textWebSocketFrame) throws Exception {String msg = textWebSocketFrame.text();System.out.println("接收到消息:" + msg);// 當前發送消息的通道Channel channel = ctx.channel();for (Channel channel1 : channelList) {// 排除自身通道if (channel != channel1) {channel1.writeAndFlush(new TextWebSocketFrame(msg));}}}/*** 異常處理事件** @param ctx* @param cause* @throws Exception*/@Overridepublic void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {cause.printStackTrace();Channel channel = ctx.channel();System.out.println("消息發送異常。");// 移除channelList.remove(channel);} }注意:處理類需要設置成共享的
啟動類
@SpringBootApplication public class NettySpringbootApplication implements CommandLineRunner {@Autowiredprivate NettyWebsocketServer nettyWebsocketServer;public static void main(String[] args) {SpringApplication.run(NettySpringbootApplication.class, args);}@Overridepublic void run(String... args) throws Exception {new Thread(nettyWebsocketServer).start();} }前端js開發
-
建立連接
var ws = new WebSocket("ws://localhost:8081/chat"); ws.onopen = function () {console.log("連接成功") } -
發送消息
function sendMsg() {var message = $("#my_test").val();$("#msg_list").append(`<li class="active"}><div class="main self"><div class="text">` + message + `</div></div></li>`);$("#my_test").val('');//發送消息message = username + ":" + message;ws.send(message);// 置底setBottom(); } -
接收消息
ws.onmessage = function (evt) {showMessage(evt.data); }function showMessage(message) {// 張三:你好var str = message.split(":");$("#msg_list").append(`<li class="active"}><div class="main"><img class="avatar" width="30" height="30" src="/img/user.png"><div><div class="user_name">${str[0]}</div><div class="text">${str[1]}</div></div> </div></li>`);// 置底setBottom(); } -
關閉與錯誤處理
ws.onclose = function (){console.log("連接關閉") }ws.onerror = function (){console.log("連接異常") }
運行效果
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-4KaNmXdr-1651644006395)(https://cdn.jsdelivr.net/gh/terwer/upload/img/image-20220502001145851.png)]
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-oZW1k4P5-1651644006395)(…/…/…/…/…/…/…/Library/Application Support/typora-user-images/image-20220502001159603.png)]
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-d5woRo7z-1651644006396)(https://cdn.jsdelivr.net/gh/terwer/upload/img/image-20220502001220081.png)]
總結
以上是生活随笔為你收集整理的Netty高级进阶之基于Netty的Websocket开发网页聊天室的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 压缩access数据库
- 下一篇: php 将中文字符转英文字母_php中怎