基於Netty的Websocket實現

目錄

目錄

WS協議簡介

服務器實現類

服務端

業務處理器

客戶端實現

基於HTML5實現

基於OKHttp的實現



WS協議簡介

HTTP協議是半雙工通信,同一時刻只有一個方向上的數據傳送,基於請求-響應式工作模式,只能客戶端主動向服務器發請求,服務器無法向客戶端主動發請求,而WebSocket 是 HTML5 開始提供的一種在單個 TCP 連接上進行全雙工通訊的協議。它有以下特點:

  • 單一的TCP連接,採用全雙工模式通信
  • 無頭部信息、Cookie和身份驗證
  • 服務器可雙主動發送消息給客戶端,不需要客戶端輪詢
  • 通過ping/pong 幀保持鏈接激活

 

服務器實現類

 

服務端

public class WebsocketServer {

    private int port;
    public WebsocketServer(int port){
        this.port = port;
    }
    public void run() throws Exception {
        EventLoopGroup bossGroup = new NioEventLoopGroup(1);
        EventLoopGroup workerGoup = new NioEventLoopGroup(8);
        try{
            ServerBootstrap serverBootstrap = new ServerBootstrap();
            serverBootstrap.group(bossGroup, workerGoup)
                    .channel(NioServerSocketChannel.class)
                    .handler(new LoggingHandler(LogLevel.DEBUG))
                    .childHandler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        public void initChannel(SocketChannel ch) throws Exception {
                            ChannelPipeline pipeline = ch.pipeline();
                            // websocket協議是基於http之上的升級,因此加入HttpServerCodec
                            pipeline.addLast(new HttpServerCodec());
                            // 以塊進行寫操作,因此加入ChunkedWriteHandler
                            pipeline.addLast(new ChunkedWriteHandler());
                            // http數據在傳輸過程中會分段,因此加入 HttpObjectAggregator將多個段聚合
                            pipeline.addLast(new HttpObjectAggregator(4096));
                            // 加入websocket協議處理器,將 http 協議升級爲 ws 協議 , 保持長連接
                            pipeline.addLast(new WebSocketServerProtocolHandler("/chat"));
                            // 加入業務處理器
                            pipeline.addLast(new MyTextWebSocketFrame());
                        }
                    });
            ChannelFuture channelFuture = serverBootstrap.bind(7777).sync();
            channelFuture.channel().closeFuture().sync();
        }finally {
            bossGroup.shutdownGracefully();
            workerGoup.shutdownGracefully();
        }
    }

    public static void main(String[] args) throws Exception {
        new WebsocketServer(7777).run();
    }
}

業務處理器

public class MyTextWebSocketFrame extends SimpleChannelInboundHandler<TextWebSocketFrame> {
    @Override
    public void channelRead0(ChannelHandlerContext ctx, TextWebSocketFrame msg) throws Exception {
        System.out.println("服務器收到消息 " + msg.text());
        // 回覆消息
        ctx.channel().writeAndFlush(new TextWebSocketFrame("服務器回覆消息:你好,"
                + ctx.channel().remoteAddress().toString()
                +"當前時間是" + LocalDateTime.now() ));
    }

    // 當 web 客戶端連接後, 觸發方法
    @Override
    public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
        Channel channel = ctx.channel();
        System.out.println(channel.id().asLongText() + "加入了");

    }

    // 當 web 客戶端斷開連接後, 觸發方法
    @Override
    public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {
        Channel channel = ctx.channel();
        System.out.println(channel.id().asLongText() + "離開了");
    }

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

 

 

客戶端實現

 

基於HTML5實現

客戶端使用HTML5內置的WebSocket對象進行與服務端通信

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Websocket客戶端</title>
    <script type="text/javascript">
        var websocket;
        if (window.WebSocket){
            // 創建ws連接
            websocket = new WebSocket("ws://localhost:7777/chat")
            websocket.open = function(ev){
                // 提示連接建立
                let responseContent = document.getElementById("responseContent");
                responseContent.value += ("\n連接建立。。。")
            }
            websocket.onmessage = function (ev) {
                // 回顯內容到頁面
                let responseContent = document.getElementById("responseContent");
                responseContent.value += ("\n" + ev.data)

            }
            websocket.close = function (ev) {
                // 提示連接斷開
                let responseContent = document.getElementById("responseContent");
                responseContent.value += ("\n連接斷開。。。")
            }
        }else{
            alert("當前瀏覽器不支持 websocket")
        }
        function sendMsg() {
            let sendContent = document.getElementById("sendContent");
            let content = sendContent.value
            alert(content)
            if(!window.websocket)return
            if (websocket.readyState == WebSocket.OPEN){
                // 通過websocket發送消息到服務器
                websocket.send(content)
            }else{
                alert("連接沒有建立")
            }
        }
    </script>
</head>

<body>
<form  onsubmit="return false">
    <label>消息:</label><input id="sendContent" size="80" ></input><button onclick="sendMsg()">發送</button><br/>

    <label>響應:</label><textarea id="responseContent" cols="80"  rows="10" ></textarea><br/>
</form>
</body>
</html>

基於OKHttp的實現

在安卓移動應用開發中,網絡請求框架okhttp已支持Websocket,因此我們使用okhttp創建websocket客戶端

public class WebsocketClient {
    OkHttpClient client = null;
    public WebsocketClient(){
        client = new OkHttpClient.Builder()
                .retryOnConnectionFailure(true)
                //允許失敗重試
                .readTimeout(5, TimeUnit.SECONDS)
                //設置讀取超時時間
                .writeTimeout(5, TimeUnit.SECONDS)
                //設置寫的超時時間
                .connectTimeout(5, TimeUnit.SECONDS)
                //設置連接超時時間
                .build();
    }

    public void send(){

        Request request  = new Request.Builder().url("ws://localhost:7777/chat").build();
        WebSocket webSocket = client.newWebSocket(request, new WebSocketListener() {
            @Override
            public void onOpen(WebSocket webSocket, Response response) {
                super.onOpen(webSocket, response);
                //連接成功
            }

            @Override
            public void onMessage(WebSocket webSocket, String text) {
                super.onMessage(webSocket, text);
                //接收服務器消息 text
                System.out.println("服務器響應:" + text);
            }

            @Override
            public void onMessage(WebSocket webSocket, ByteString bytes) {
                super.onMessage(webSocket, bytes);
                //如果服務器傳遞的是byte類型的
                String msg = bytes.utf8();
                System.out.println("服務器響應:" + msg);
            }

            @Override
            public void onFailure(WebSocket webSocket, Throwable t,  Response response) {
                super.onFailure(webSocket, t, response);
                //連接失敗調用 異常信息t.getMessage()
                t.printStackTrace();
            }
        });

        client.dispatcher().executorService().shutdown();//內存不足時釋放
        boolean rs = webSocket.send("發送的消息");
        if (rs){
            System.out.println("發送完畢!");
        }
    }
    public static void main(String[] args) throws Exception {
        new WebsocketClient().send();
    }

}

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章