Netty實現對Websocket的支持


一、WebSocket的簡介及優勢

 

WebSocket 是一種網絡通信協議。RFC6455 定義了它的通信標准。WebSocket 是 HTML5 開始提供的一種在單個 TCP 連接上進行全雙工通訊的協議。

首先可以看下HTTP協議的有哪些不好的地方:HTTP 協議是一種無狀態的、無連接的、單向的應用層協議。它采用了請求/響應模型。通信請求只能由客戶端發起,服務端對請求做出應答處理。這種通信模型有一個弊端:HTTP 協議無法實現服務器主動向客戶端發起消息。

為了解決這些痛點,WebSocket就應運而生了。WebSocket 連接允許客戶端和服務器之間進行全雙工通信,以便任一方都可以通過建立的連接將數據推送到另一端。WebSocket 只需要建立一次連接,就可以一直保持連接狀態。

 

二、WebSocket客戶端API

 

2.1 WebSocket 構造函數

       var Socket=new WebSocket("ws://localhost:20000/web");

 

2.2 WebSocket 屬性

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

屬性 描述
Socket.readyState 只讀屬性 readyState 表示連接狀態,可以是以下值:0 - 表示連接尚未建立。1 - 表示連接已建立,可以進行通信。2 - 表示連接正在進行關閉。3 - 表示連接已經關閉或者連接不能打開。
Socket.bufferedAmount 只讀屬性 bufferedAmount 已被 send() 放入正在隊列中等待傳輸,但是還沒有發出的 UTF-8 文本字節數。

 

 

 

 

2.3 WebSocket 事件

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

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

 

 

 

 

 

 

 

 

 

2.4 WebSocket 方法

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

方法 描述
Socket.send() 使用連接發送數據
Socket.close() 關閉連接

 

 

 

 

 

 

三、WebSocket客戶端代碼

 

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Netty WebSocket</title>
</head>
<body>
<br>
<script type="text/javascript">
    var websocket;
    if(!window.WebSocket){
        window.WebSocket=window.MozWebSocket;
    }
    if(window.WebSocket){
        websocket=new WebSocket("ws://localhost:20000/web");
        websocket.onmessage=function (event) {
            console.log("websocket接受消息"+event.data);
            var text=document.getElementById('responseText');
            text.value="";
            text.value=event.data;
        }
        websocket.onopen=function (event) {
            console.log("websocket打開");
            var text=document.getElementById('responseText');
            text.value="";
            text.value="打開websocket服務正常";
        }
        websocket.onclose=function (event) {
            console.log("websocket關閉");
            var text=document.getElementById('responseText');
            text.value="";
            text.value="關閉websocket服務";
        }
        websocket.onerror=function (event) {
            console.log("websocket異常");
            var text=document.getElementById('responseText');
            text.value="";
            text.value="websocket服務異常";

        }
    }else{
        alert("你的瀏覽器不支持WebSocket");
    }

    function send(message) {
        if(websocket){
            if(websocket.readyState==WebSocket.OPEN){
                console.log("通過websocket發送消息");
                websocket.send(message);
            }
        }else{
            alert("未建立websocket連接");
        }
    }
</script>

<form onsubmit="return false;">
    <input type="text" name="message" value="Netty實踐"/>
    <br><br>
    <input type="button" value="發送消息" onclick="send (this.form.message.value)"/>
    <h3>應答消息</h3>
    <textarea id="responseText" style="width: 500px;height: 300px;"></textarea>
</form>

</body>
</html>

 

 

 

四、netty服務端代碼

 

4.1 netty啟動類

public class NettyServer {

    public static void main(String[] args) throws Exception{
        //服務器啟動
        new NettyServer().start(20000);
    }

    public void start(int port) throws Exception{
        //用於監聽連接的線程組
        EventLoopGroup bossGroup=new NioEventLoopGroup();
        //用於發送接收消息的線程組
        EventLoopGroup workGroup=new NioEventLoopGroup();

        try{
            //啟動類引導程序
            ServerBootstrap b=new ServerBootstrap();
            //綁定兩個線程組
            b.group(bossGroup,workGroup);
            //設置非阻塞,用它來建立新accept的連接,用於構造serverSocketChannel的工廠類
            b.channel(NioServerSocketChannel.class).childHandler(new ChannelInitializer<SocketChannel>() {
                @Override
                protected void initChannel(SocketChannel channel){
                    ChannelPipeline channelPipeline=channel.pipeline();
                    // HttpServerCodec:將請求和應答消息解碼為HTTP消息
                    channelPipeline.addLast(new HttpServerCodec());
                    // HttpObjectAggregator:將HTTP消息的多個部分合成一條完整的HTTP消息
                    channelPipeline.addLast(new HttpObjectAggregator(65536));
                    // ChunkedWriteHandler:向客戶端發送HTML5文件
                    channelPipeline.addLast(new ChunkedWriteHandler());
                    //在管道中添加自己實現的Handler處理類
                    channelPipeline.addLast(new WebsocketServerHandler());
                }
            });
            Channel channel=b.bind(port).sync().channel();
            System.out.println("服務器啟動端口:"+port);
            channel.closeFuture().sync();
        }finally {
            workGroup.shutdownGracefully();
            bossGroup.shutdownGracefully();
        }


    }
}

 

4.2 netty的業務處理的Handler類

public class WebsocketServerHandler extends SimpleChannelInboundHandler<Object> {

    private WebSocketServerHandshaker handshaker;

    @Override
    protected void channelRead0(ChannelHandlerContext channelHandlerContext, Object o) throws Exception {
        //傳統的http接入
        if(o instanceof FullHttpRequest){
            handleHttpRequest(channelHandlerContext,(FullHttpRequest) o);
        }
        //webSocket接入
        else if(o instanceof WebSocketFrame){
            handleWebsocketFrame(channelHandlerContext,(WebSocketFrame) o);
        }

    }

    @Override
    public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
        ctx.flush();
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        cause.printStackTrace();
        ctx.close();
    }


    private void handleHttpRequest(ChannelHandlerContext ctx, FullHttpRequest req){
        //構造握手響應返回
        WebSocketServerHandshakerFactory webSocketServerHandshakerFactory=new WebSocketServerHandshakerFactory("ws://localhost:20000/web",null,false);
        handshaker=webSocketServerHandshakerFactory.newHandshaker(req);
        handshaker.handshake(ctx.channel(),req);

    }

    private void handleWebsocketFrame(ChannelHandlerContext ctx, WebSocketFrame frame){
        //判斷是否是鏈路關閉消息
        if(frame instanceof CloseWebSocketFrame){
            handshaker.close(ctx.channel(),(CloseWebSocketFrame) frame.retain());
            return;
        }
        //判斷是否是ping消息
        if(frame instanceof PingWebSocketFrame){
            ctx.channel().write(new PongWebSocketFrame(frame.content().retain()));
            return;
        }
        //文本消息處理
        String request=((TextWebSocketFrame)frame).text();
        System.out.println("接受的信息是:"+request);
        String date=new Date().toString();
        //將接收消息寫回給客戶端
        ctx.channel().write(new TextWebSocketFrame("現在時刻:"+date+"發送了:"+request));
    }
}

 

五、運行成功截圖

 

 

參考資料:

https://www.cnblogs.com/jingmoxukong/p/7755643.html

http://www.ruanyifeng.com/blog/2017/05/websocket.html


免責聲明!

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



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