1. 程式人生 > >四、Netty實現webSocket,實現伺服器與瀏覽器(HTML)線上聊天功能

四、Netty實現webSocket,實現伺服器與瀏覽器(HTML)線上聊天功能

        由於http協議有一個缺陷:通訊只能由客戶端發起。如果伺服器有連續的狀態變化,客戶端要獲知只能使用"輪詢":每隔一段時候,就發出一個詢問,瞭解伺服器有沒有新的資訊。輪詢的效率低,非常浪費資源(因為必須不停連線,或者 HTTP 連線始終開啟)。
而websocket就這樣應用而生。

        瀏覽器與伺服器之間的長連線,是websocket的支援。WebSocket是一種在單個TCP連線上進行全雙工通訊的協議。而netty他就對websocket通訊做了支援。

websocket除了上面的支援,還有一些其他的特點:
(1)建立在 TCP 協議之上。
(2)與 HTTP 協議有著良好的相容性。預設埠也是80和443,並且握手階段採用 HTTP 協議。
(3)資料格式比較輕量,效能開銷小,通訊高效。
(4)可以傳送文字,二進位制資料。
(5)客戶端可以與任意伺服器通訊。
(6)協議識別符號是ws(如果加密,則為wss),伺服器網址就是 URL。

 

下面我用netty對websocket的支援來實現一個完整了實現一個線上聊天功能:

伺服器端的程式碼實現:

package com.zhurong.netty.test5;

import com.zhurong.netty.test4.NettyServerInitializer;
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;

import java.net.InetSocketAddress;

/**
 * Description:
 * User: zhurong
 * Date: 2018-09-24  23:17
 */
public class NettyWebSocketServer {

    public static void main(String[] args) {
        //接收連線
        EventLoopGroup bossGroup = new NioEventLoopGroup();
        //連線傳送給work
        EventLoopGroup workerGroup = new NioEventLoopGroup();

        try {
            System.out.println("伺服器啟動成功!");
            ServerBootstrap serverBootstrap = new ServerBootstrap();
            serverBootstrap.group(bossGroup, workerGroup).channel(NioServerSocketChannel.class).
                    handler(new LoggingHandler(LogLevel.INFO)).
                    childHandler(new WebSocketChannelInitalizer());
            ChannelFuture channelFuture = serverBootstrap.bind(new InetSocketAddress(8000)).sync();
            channelFuture.channel().closeFuture().sync();

        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            bossGroup.shutdownGracefully();
            workerGroup.shutdownGracefully();
        }

    }

}
package com.zhurong.netty.test5;

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;

/**
 * Description:
 * User: zhurong
 * Date: 2018-09-24  11:05
 */
public class WebSocketChannelInitalizer 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("/test"));

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

 

package com.zhurong.netty.test5;

import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.handler.codec.http.websocketx.TextWebSocketFrame;
import io.netty.util.concurrent.EventExecutorGroup;

import java.util.Date;

/**
 * Description:
 * User: zhurong
 * Date: 2018-09-24  11:15
 */
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("伺服器返回訊息:" + new Date()));
    }

    @Override
    public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
        System.out.println("handlerAdded");
    }

    @Override
    public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {
        System.out.println("handlerRemoved");

    }

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

 

webapp端的程式碼:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>webSocketTest客戶端</title>
</head>
<script type="text/javascript">
    var webSocket;
    if(window.WebSocket){
        webSocket = new WebSocket("ws://localhost:8000/test");
        //客戶端收到伺服器的方法,這個方法就會被回撥
        webSocket.onmessage = function (ev) {
            var contents = document.getElementById("responseText");
            contents.value = contents.value +"\n"+ ev.data;
        }

        webSocket.onopen = function (ev) {
            var contents = document.getElementById("responseText");
            contents.value = "與伺服器端的websocket連線建立";
        }
        webSocket.onclose = function (ev) {
            var contents = document.getElementById("responseText");
            contents.value =  contents.value +"\n"+ "與伺服器端的websocket連線斷開";
        }
    }else{
        alert("該環境不支援websocket")
    }

    function sendMessage() {
        if(window.webSocket){
            if(webSocket.readyState == WebSocket.OPEN){
                var contents = document.getElementById("message").value;
                webSocket.send(contents);
            }else{
                alert("與伺服器連線尚未建立")
            }
        }
    }
</script>
<body>
    <form onsubmit="return false;">
        <h4>客戶端輸入:</h4>
        <textarea id = "message" name="message" style="width: 200px;height: 100px"></textarea>
        <br/>
        <input type="button" value="傳送到伺服器" onclick="sendMessage()">

        <h4>伺服器返回訊息:</h4>
        <textarea id = "responseText" name="message" style="width: 200px;height: 100px"></textarea>
        <br/>
        <input type="button" onclick="javascript:document.getElementById('responseText').value=''" value="clear data">
    </form>
</body>
</html>

 

伺服器輸出:

這就是一個完整的websocket功能,當然我們其實客戶端直接使用瀏覽器http就可以了,這樣更方便。