Netty中使用WebSocket實現服務端與客戶端的長連接通信發送消息


場景

Netty中實現多客戶端連接與通信-以實現聊天室群聊功能為例(附代碼下載):

https://blog.csdn.net/BADAO_LIUMANG_QIZHI/article/details/108623306

上面講了使用使用Socket搭建多客戶端的連接與通信。

那么如果在Netty中使用WebSocket進行長連接通信要怎么實現。

WebSocket

現在,很多網站為了實現推送技術,所用的技術都是 Ajax 輪詢。輪詢是在特定的的時間間隔(如每1秒),由瀏覽器對服務器發出HTTP請求,然后由服務器返回最新的數據給客戶端的瀏覽器。這種傳統的模式帶來很明顯的缺點,即瀏覽器需要不斷的向服務器發出請求,然而HTTP請求可能包含較長的頭部,其中真正有效的數據可能只是很小的一部分,顯然這樣會浪費很多的帶寬等資源。

HTML5 定義的 WebSocket 協議,能更好的節省服務器資源和帶寬,並且能夠更實時地進行通訊。

WebSocket是一種在單個TCP連接上進行全雙工通信的協議。

WebSocket使得客戶端和服務器之間的數據交換變得更加簡單,允許服務端主動向客戶端推送數據。在WebSocket API中,瀏覽器和服務器只需要完成一次握手,兩者之間就直接可以創建持久性的連接,並進行雙向數據傳輸。

瀏覽器通過 JavaScript 向服務器發出建立 WebSocket 連接的請求,連接建立以后,客戶端和服務器端就可以通過 TCP 連接直接交換數據。

當你獲取 Web Socket 連接后,你可以通過 send() 方法來向服務器發送數據,並通過 onmessage 事件來接收服務器返回的數據。

WebSocket 屬性

以下是 WebSocket 對象的屬性。假定我們使用了以上代碼創建了 Socket 對象:

 

屬性 描述
Socket.readyState

只讀屬性 readyState 表示連接狀態,可以是以下值:

  • 0 - 表示連接尚未建立。

  • 1 - 表示連接已建立,可以進行通信。

  • 2 - 表示連接正在進行關閉。

  • 3 - 表示連接已經關閉或者連接不能打開。

Socket.bufferedAmount

只讀屬性 bufferedAmount 已被 send() 放入正在隊列中等待傳輸,但是還沒有發出的 UTF-8 文本字節數。


WebSocket 事件

以下是 WebSocket 對象的相關事件。假定我們使用了以上代碼創建了 Socket 對象:

 

事件 事件處理程序 描述
open Socket.onopen 連接建立時觸發
message Socket.onmessage 客戶端接收服務端數據時觸發
error Socket.onerror 通信發生錯誤時觸發
close Socket.onclose 連接關閉時觸發

WebSocket 方法

以下是 WebSocket 對象的相關方法。假定我們使用了以上代碼創建了 Socket 對象:

 

方法 描述
Socket.send()

使用連接發送數據

Socket.close()

關閉連接

 

注:

博客:
https://blog.csdn.net/badao_liumang_qizhi
關注公眾號
霸道的程序猿
獲取編程相關電子書、教程推送與免費下載。

實現

在IDEA中搭建好Netty的項目並引入環境可以參照如下:

https://blog.csdn.net/BADAO_LIUMANG_QIZHI/article/details/108592418

在此基礎上,在src下新建包com.badao.NettyWebSocket

然后新建服務端類WebSocketServer

package com.badao.NettyWebSocket;

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;
import io.netty.handler.logging.LogLevel;
import io.netty.handler.logging.LoggingHandler;

public class WebSocketServer {
    public static void main(String[] args) throws  Exception
    {
        EventLoopGroup bossGroup = new NioEventLoopGroup();
        EventLoopGroup workerGroup = new NioEventLoopGroup();
        try{
            ServerBootstrap serverBootstrap = new ServerBootstrap();
            serverBootstrap.group(bossGroup,workerGroup).channel(NioServerSocketChannel.class)
                    .handler(new LoggingHandler(LogLevel.INFO))
                    .childHandler(new WebSocketInitializer());
            //綁定端口
            ChannelFuture channelFuture = serverBootstrap.bind(70).sync();
            channelFuture.channel().closeFuture().sync();
        }finally {
            //關閉事件組
            bossGroup.shutdownGracefully();
            workerGroup.shutdownGracefully();
        }
    }
}

服務端的搭建在上面已經講解,這里又添加了Netty自帶的日志處理器LoggingHandler

然后又添加了自定義的初始化器WebSocketInitializer

所以新建類WebSocketInitializer

package com.badao.NettyWebSocket;

import io.netty.channel.Channel;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.socket.SocketChannel;
import io.netty.handler.codec.http.HttpObjectAggregator;
import io.netty.handler.codec.http.HttpServerCodec;
import io.netty.handler.codec.http.websocketx.WebSocketServerProtocolHandler;
import io.netty.handler.stream.ChunkedWriteHandler;

public class WebSocketInitializer  extends ChannelInitializer<SocketChannel> {

    @Override
    protected void initChannel(SocketChannel ch) throws Exception {
        ChannelPipeline pipeline = ch.pipeline();

        pipeline.addLast(new HttpServerCodec());
        pipeline.addLast(new ChunkedWriteHandler());
        pipeline.addLast(new HttpObjectAggregator(8192));
        pipeline.addLast(new WebSocketServerProtocolHandler("/badao"));

        pipeline.addLast(new WebSocketHandler());
    }
}

因為Netty也是基於Http的所以這里需要添加HttpServerCodec處理器等。

要想實現WebSocket功能,主要是添加了WebSocketServerProtocolHandler這個WebSocket服務端協議處理器。注意這里的參數需要添加一個WebSocket的路徑,這里是/badao

最后添加自定義的處理器WebSocketHandler 用來做具體的處理

所以新建類WebSocketHandler

package com.badao.NettyWebSocket;

import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.handler.codec.http.websocketx.TextWebSocketFrame;

import java.time.LocalDateTime;

public class WebSocketHandler extends SimpleChannelInboundHandler<TextWebSocketFrame> {
    @Override
    protected void channelRead0(ChannelHandlerContext ctx, TextWebSocketFrame msg) throws Exception {
        System.out.println("收到消息:"+msg.text());
        ctx.channel().writeAndFlush(new TextWebSocketFrame("WebSocket服務端在"+ LocalDateTime.now()+"發送消息(公眾號:霸道的程序猿)"));
    }

    @Override
    public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
        System.out.println("handlerAdded:"+ctx.channel().id().asLongText());
    }

    @Override
    public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {
        System.out.println("handlerRemoved:"+ctx.channel().id().asLongText());
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        System.out.println("異常發生");
        ctx.close();
    }
}

 

使其繼承SimpleChannelInboundHandler注意此時的泛型類型為TextWebSocketFrame

然后重寫channelRead0方法用來對收到數據時進行處理

    @Override
    protected void channelRead0(ChannelHandlerContext ctx, TextWebSocketFrame msg) throws Exception {
        System.out.println("收到消息:"+msg.text());
        ctx.channel().writeAndFlush(new TextWebSocketFrame("WebSocket服務端在"+ LocalDateTime.now()+"發送消息(公眾號:霸道的程序猿)"));
    }

在服務端將收到的消息進行輸出並給客戶端發送數據。

然后重寫handlerAdded方法,在客戶端與服務端建立連接時進行操作

    @Override
    public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
        System.out.println("handlerAdded:"+ctx.channel().id().asLongText());
    }

這里通過通道的id方法的asLongText方法獲取連接的唯一標志。

然后在服務端輸出。

同理重寫handlerRemoved方法,在斷掉連接時將連接的唯一標志進行輸出。

    @Override
    public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {
        System.out.println("handlerRemoved:"+ctx.channel().id().asLongText());
    }

最后重寫出現異常時的處理方法,在出現異常時將連接關閉。

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        System.out.println("異常發生");
        ctx.close();
    }

至此WebSocket服務端搭建完成,然后客戶端通過JS就能實現。

在項目目錄下src下新建webapp目錄,在此目錄下新建badao.html

 

 

修改html的代碼為

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>公眾號:霸道的程序猿</title>
</head>
<body>
<script type="text/javascript">
    var socket;
    if(window.WebSocket)
    {
        socket = new WebSocket("ws://localhost:70/badao")
        socket.onmessage=function (ev) {
            var ta = document.getElementById("responseText");
            ta.value = ta.value+"\n"+ev.data;
        }

        socket.onopen = function (ev) {
            var ta = document.getElementById("responseText");
            ta.value = "連接開啟";
        }

        socket.onclose = function (ev) {
            var ta = document.getElementById("responseText");
            ta.value = ta.value+"\n連接關閉";
        }
    }
    else{
        alert("當前瀏覽器不支持WebSocket")
    }

    function send(message) {
        if(!window.WebSocket)
        {
            return;
        }else
        {
            if(socket.readyState = WebSocket.OPEN)
            {
                socket.send(message);
            }
            else
            {
                alert("連接尚未開啟");
            }
        }
    }
</script>
<form>
    <textarea name="message" style="width: 400px;height: 200px">

    </textarea>

    <input type="button" value="發送數據" onclick="send(this.form.message.value)">

    <h3>服務端輸出:</h3>

    <textarea id="responseText" style="width: 400px;height: 200px">

    </textarea>
</form>
</body>
</html>

 

在js中通過windows.WebSocket判斷是否支持WebSocket

如果支持則

socket = new WebSocket("ws://localhost:70/badao")

建立連接,url的寫法前面的ws://是固定的類似http://

后面的是跟的ip:端口號/上面配置的WebSocket路徑

然后就是以這個WebSocket對象為中心進行連接和數據的顯示。

下面的回調方法onopen會在建立連接成功后回調,onclose會在斷掉連接后回調,

onmessage會在收到服務端發送的數據時回調並通過ev.data獲取數據。

客戶端向服務端發送數據時調用的是socket的send方法,通過

if(socket.readyState = WebSocket.OPEN)

判斷連接已經成功建立。

實現長連接通信

運行WebSocketServer的main方法,然后在badao.html上右擊選擇運行

 

 

建立連接成功后會在服務端輸出連接的id,在客戶端會顯示連接開啟。

此時如果刷新瀏覽器,服務端會輸出一次斷開連接和建立連接

 

 

再將服務端停掉,客戶端會輸出連接關閉

 

 

再重新啟動服務端,在上面的輸入框輸入內容並點擊發送數據

 

 

 

服務端會收到消息並輸出,並向客戶端發送一個消息。

繼續發送也是如此

 

 

示例代碼下載

https://download.csdn.net/download/BADAO_LIUMANG_QIZHI/12853829

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM