Netty高级进阶之基于Netty的Websocket开发网页聊天室
本通過實戰(zhàn)演練,學(xué)習(xí)了如何基于Netty的websocket開發(fā)一個網(wǎng)頁聊天室。
Netty高級進(jìn)階之基于Netty的Websocket開發(fā)網(wǎng)頁聊天室
Webdocket簡介
Websockt是一種在單個TCP連接上進(jìn)行全雙工通信的協(xié)議。
Websocket使客戶端和服務(wù)端的數(shù)據(jù)交互變得簡單,允許服務(wù)器主動向客戶端推送數(shù)據(jù)。
在Websocket API中,客戶端只需要與服務(wù)器完成一次握手,兩者之間就可以創(chuàng)建持久性的連接,并進(jìn)行雙向數(shù)據(jù)傳輸。
他的應(yīng)用場景如下:
- 社交訂閱
- 協(xié)同編輯/編程
- 股票基金報價
- 體育實況更新
- 多媒體聊天
- 在線教育
Websocket和HTTP的區(qū)別
HTTP協(xié)議是應(yīng)用層的協(xié)議,是基于TCP協(xié)議的。
HTTP協(xié)議必須經(jīng)過三次握手才能發(fā)送消息。
HTTP連接分為短連接和長鏈接。短連接是每次都要經(jīng)過三次握手才能發(fā)送消息。就是說每一個request對應(yīng)一個response。長連接在一定期限內(nèi)保持TCP連接不斷開。
客戶端與服務(wù)器通信,必須由客戶端先發(fā)起,然后服務(wù)端返回結(jié)果。客戶端是主動的,服務(wù)端是被動的。
客戶端想要實時獲取服務(wù)端的消息,就要不斷發(fā)送長連接到服務(wù)端。
Websocket實現(xiàn)了多路復(fù)用,它是全雙工通信。在Websocket協(xié)議下,服務(wù)端和客戶端可以同時發(fā)送消息。
建立了Websocket連接之后,服務(wù)端可以主動給客戶端發(fā)送消息。信息中不必帶有header的部分信息,與HTTP長連接通信對比,這種方式降低了服務(wù)器的壓力,信息當(dāng)中也減少了多余的信息。
導(dǎo)入基礎(chǔ)環(huán)境
新建netty-springboot項目
[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-zt5trjYz-1651644006392)(https://cdn.jsdelivr.net/gh/terwer/upload/img/image-20220430225632849.png)]
導(dǎo)入依賴模塊
<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>導(dǎo)入靜態(tài)資源
[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(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關(guān)于Springboot整合thymeleaf的404問題,參考:
/post/404-problem-with-springboot-configuration-thymeleaf.html
代碼實現(xiàn)
服務(wù)端開發(fā)
添加Netty相關(guān)依賴
<!--引入netty依賴 --> <dependency><groupId>io.netty</groupId><artifactId>netty-all</artifactId> </dependency>Netty相關(guān)配置
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監(jiān)聽端口private int port;// webdocket訪問路徑private String path; }Netty的WebsocketServer開發(fā)
/*** Netty的Websocket服務(wù)器** @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.創(chuàng)建服務(wù)端啟動助手ServerBootstrap serverBootstrap = new ServerBootstrap();// 2.設(shè)置線程組serverBootstrap.group(bossGroup, workerGroup);// 3.設(shè)置參數(shù)serverBootstrap.channel(NioServerSocketChannel.class).handler(new LoggingHandler(LogLevel.DEBUG)).childHandler(websocketChannelInit);// 4.啟動服務(wù)端ChannelFuture channelFuture = serverBootstrap.bind(nettyConfig.getPort()).sync();System.out.println("------Netty服務(wù)端啟動成功------");channelFuture.channel().closeFuture().sync();} catch (InterruptedException e) {e.printStackTrace();bossGroup.shutdownGracefully();workerGroup.shutdownGracefully();throw new RuntimeException(e);} finally {bossGroup.shutdownGracefully();workerGroup.shutdownGracefully();}}/*** 關(guān)閉資源-容器銷毀時候關(guān)閉*/@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協(xié)議的支持pipeline.addLast(new HttpServerCodec());// 對大數(shù)據(jù)流的支持pipeline.addLast(new ChunkedWriteHandler());// post請求分為三個部分:request line/request header/message body// 對POST請求的支持,將多個信息轉(zhuǎn)化成單一的request/response對象pipeline.addLast(new HttpObjectAggregator(8000));// 對WebSocket協(xié)議的支持// 將http協(xié)議升級為ws協(xié)議pipeline.addLast(new WebSocketServerProtocolHandler(nettyConfig.getPath()));// 自定義處理handlerpipeline.addLast(websocketHandler);} }處理對象
/*** 自定義Websocket處理類* Websocket數(shù)據(jù)以幀的形式進(jìn)行處理* 需要設(shè)置通道共享** @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);// 當(dāng)前發(fā)送消息的通道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("消息發(fā)送異常。");// 移除channelList.remove(channel);} }注意:處理類需要設(shè)置成共享的
啟動類
@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開發(fā)
-
建立連接
var ws = new WebSocket("ws://localhost:8081/chat"); ws.onopen = function () {console.log("連接成功") } -
發(fā)送消息
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('');//發(fā)送消息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(); } -
關(guān)閉與錯誤處理
ws.onclose = function (){console.log("連接關(guān)閉") }ws.onerror = function (){console.log("連接異常") }
運行效果
[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-4KaNmXdr-1651644006395)(https://cdn.jsdelivr.net/gh/terwer/upload/img/image-20220502001145851.png)]
[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-oZW1k4P5-1651644006395)(…/…/…/…/…/…/…/Library/Application Support/typora-user-images/image-20220502001159603.png)]
[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-d5woRo7z-1651644006396)(https://cdn.jsdelivr.net/gh/terwer/upload/img/image-20220502001220081.png)]
總結(jié)
以上是生活随笔為你收集整理的Netty高级进阶之基于Netty的Websocket开发网页聊天室的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 压缩access数据库
- 下一篇: 传统建材采购的痛点有哪些?