1. 程式人生 > >Netty之WebSocket協議開發

Netty之WebSocket協議開發

開始

在本章的開頭把程式碼奉上,大家下載下來對照的學習,這些程式碼都是執行通過的。
上節我們講解了HTTP協議開發,但是,Http協議的開銷問題,導致它們不適用於低延遲的應用。為了解決這些問題,我們引入了webSocket。

HTTP協議的弊端

我們來總結一下HTTP協議的弊端:

1.HTTP協議是半雙工的協議。大家知道對講機嗎?它就是半雙工的裝置。當對方在說話時,你就不能說話了,也就是說一個時間點上,只能一個人在說話。現在已經被全雙工的電話給替代掉了,現在對講機這東西用的不多了。
2.HTTP協議冗長而繁瑣。HTTP訊息包含訊息頭,訊息體,換行符等,採用文字形式傳播,相比二進位制通訊協議,非常冗長且繁瑣。
3.針對伺服器推送的黑客攻擊。例如長時間輪詢。

為了解決以上HTTP協議效率底下的問題,WebSocket協議應運而生,它能更好的節約頻寬和伺服器資源並且實時通訊。下面就讓我們看看websocket協議的神奇之處。

WebSocket協議的特點

1.單一的TCP連線,採用全雙工通訊
2.對代理,防火牆,路由器透明。
3.無頭部資訊,cookie和身份驗證
4.無安全開銷
5.通過ping幀保持鏈路啟用
6.伺服器可以主動的對客戶端進行傳送訊息,而不需要輪詢。

總的來說我們webSocket被設計出來的目的就是為了取代輪詢這種高消耗的方式。

websocket連線過程

建立連線過程如下:

1.客戶端瀏覽器首先要向伺服器傳送一個HTTP請求,這個請求和通常的HTTP請求不同,包含了一些附加資訊,其中附加資訊“UpgradeWebSocket”表明這是一個申請協議升級的http請求。
2.伺服器解析這些附加頭資訊,然後生成應答資訊返回給客戶端,這樣客戶端和伺服器端的webSocket連線就建立起來了
3.這個連線會一直持續到客戶端或者伺服器的某一方主動關閉連線。

我們來看一下客戶端向伺服器請求的訊息:

GET /chat HTTP/1.1
Host: server.example.com
Upgrade:websocket
Connection:Upgrade
Sec-WeSocket-Key:dGhlweidxlkjsdZQ== 
Origin:http://example.com
Sec-WebSocket-Protocol:chat,superchat
Sec-webSocket-Version:13

我們來看一下伺服器返回給客戶端的應答訊息:

HTTP/1.1 101 Switching Protocols
Upgrade:websocket
Connection:Upgrade
Sec-WebSocket-accept:siefjosigdfsl=
Sec-WebSocket-Protocol:chat

請求訊息中的“Sec-WebSocket-Key”是隨機的,伺服器端會用這些資料來構造出一個SHA-1的資訊摘要,把“Sec-WebSocket-Key”加上一個魔幻字串“25EAFA5-E914-47DA-95CA-C54B0DC85B11”.使用SHA-1加密,然後進行BASE-64編碼,將結果作為Sec-WebSocket-accept頭的值,返回給客戶端。

連線示意圖如下:

websocket生命週期

握手成功後,客戶端和伺服器就可以通過”message”方式進行通訊了,一個訊息由一個或者多個幀組成。

websocket連線關閉。

為關閉websocket連線,客戶端和伺服器需要通過一個安全的方法關閉底層TCP連線及TLS會話。如果合適,丟棄任何可能接收的位元組:必要時可以通過任何可用的手段關閉連線。

編碼開始

需求:我們要編寫一個webSocket伺服器,支援WebSocket的瀏覽器通過webSocket協議傳送請求給我們編寫的webSocket伺服器,伺服器對請求訊息進行判斷,如果是合法的webSocket請求,則獲取請求訊息體,並在後面追加字串:“歡迎使用Netty WebSocket 服務,現在時刻:系統時間”。
當然客戶端也是我們編寫的,其中內嵌js指令碼去建立webSocket連線,如果握手成功列印:“開啟websocket 服務正常,瀏覽器支援Websocket!”

首先對webSocket伺服器的功能進行簡單講解。websocket服務端接收到請求訊息後,先對訊息的型別進行判斷,如圖所示:

我們就是判斷upgrade取它的值判斷是否是websocket,如果不是webSocket型別的請求訊息,則返回HTTP 400 BAD REQUEST 響應給客戶端。如果是伺服器返回訊息,雙方連線正式建立。

由於篇幅問題,只對核心程式碼講解,全部程式碼大家可以下載下來看看。

EventLoopGroup bossGroup = new NioEventLoopGroup();
    EventLoopGroup workerGroup = new NioEventLoopGroup();
    try {
        ServerBootstrap b = new ServerBootstrap();
        b.group(bossGroup, workerGroup)
        .channel(NioServerSocketChannel.class)
        .childHandler(new ChannelInitializer<SocketChannel>() {

            @Override
            protected void initChannel(SocketChannel ch) throws Exception {
                //HttpServerCodec的作用是將請求和應答訊息編碼或者解碼為HTTP訊息;
                ch.pipeline().addLast("http-codec", new HttpServerCodec());
                //使用HttpObjectAggregator會把多個訊息轉換為一個單一的FullHttpRequest或是FullHttpResponse
                ch.pipeline().addLast("http-aggregator", new HttpObjectAggregator(65536));
                //支援處理非同步傳送大資料檔案,但不佔用過多的記憶體,防止發生記憶體洩漏,這裡是向客戶端傳送html5檔案
                ch.pipeline().addLast("http-chunked", new ChunkedWriteHandler());
                //這個是我們自定義的,處理檔案伺服器邏輯。主要功能還是在這個檔案中
                ch.pipeline().addLast("http-fileServerHandler", new WebSocketServerHandler());
            }
        });
        Channel ch  = b.bind(port).sync().channel();//這裡寫你本機的IP地址
        System.out.println("web socket server started at port "+port+".");
        System.out.println("open your browser and navigate to http://localhost:"+port+"/");
        ch.closeFuture().sync();
    } catch (Exception e) {

    }finally{
        bossGroup.shutdownGracefully();
        workerGroup.shutdownGracefully();
    }

講解:

HttpServerCodec講解:HTTP協議的請求解碼器和響應編碼器即HttpServerCodec,它會將HTTP客戶端請求轉成HttpRequest物件,將HttpResponse物件編碼成HTTP響應傳送給客戶端。
HttpObjectAggregator講解:使用HttpObjectAggregator會把多個訊息轉換為一個單一的FullHttpRequest或是FullHttpResponse

下面我們來看一下核心類WebSocketServerHandler,我都做的詳細的註釋,希望大家能看懂。

/**
 * @author 作者 YYD
 * @version 建立時間:2016年8月17日 上午6:32:23
 * @function 未新增
 */
public class WebSocketServerHandler extends SimpleChannelInboundHandler<Object>{
    private static final Logger logger = Logger.getLogger(WebSocketServerHandler.class.getName());
    private WebSocketServerHandshaker handshaker;
    /**
     * 接收客戶端發過來的訊息並處理
     * FullHttpRequest :
     *  官網解釋:Combine the {@link HttpRequest} and {@link FullHttpMessage}, so the request is a <i>complete</i> HTTP
     * request.
     * 這個請求是 代表http請求完成的標記。
     */
    @Override
    protected void channelRead0(ChannelHandlerContext ctx, Object msg)
            throws Exception {
        if(msg instanceof FullHttpRequest){//接收到客戶端的握手請求,開始處理握手
            handleHttpRequest(ctx,(FullHttpRequest)msg);
        }else if(msg instanceof WebSocketFrame){//接收到客戶端發過來的訊息(只過濾文字訊息),處理後發給客戶端。
            handleWebSocketFrame(ctx, (WebSocketFrame)msg);
        }
    }
    @Override
    public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
        ctx.flush();
    }
    private void handleHttpRequest(ChannelHandlerContext ctx,FullHttpRequest req) throws Exception{
         /**
          * 如果不成功或者訊息頭不包含"Upgrade",說明不是websocket連線,報400異常。
          */
        if(!req.getDecoderResult().isSuccess()||(!"websocket".equals(req.headers().get("Upgrade")))){
            sendHttpResponse(ctx,req,new DefaultFullHttpResponse(HttpVersion.HTTP_1_1,HttpResponseStatus.BAD_REQUEST));
            return;
        }
        /**
         * WebSocket是一種全新的協議,不屬於http無狀態協議,協議名為"ws",這意味著一個websocket連線地址會是這樣的寫法: 
           ws://127.0.0.1:8080/websocket。ws不是http,所以傳統的web伺服器不一定支援,需要伺服器與瀏覽器同時支援, WebSocket才能正常執行,目前大部分瀏覽器都支援Websocket。
           WebSocketServerHandshaker 官網的解釋是:伺服器端Web套接字開啟和關閉握手基類 
           WebSocketServerHandshakerFactory 官網的解釋是:自動檢測正在使用的網路套接字協議的版本,並建立一個新的合適的 WebSocketServerHandshaker。
         */
        WebSocketServerHandshakerFactory wsFactory = new WebSocketServerHandshakerFactory("ws://localhost:8080/websocket",null,false);
        handshaker = wsFactory.newHandshaker(req);//建立一個握手協議
        if(handshaker == null){
            /**
             * Return that we need cannot not support the web socket version
             * 返回不支援websocket 版本
             */
            WebSocketServerHandshakerFactory.sendUnsupportedVersionResponse(ctx.channel());
        }else {
            handshaker.handshake(ctx.channel(), req);//開始握手
        }
    }
    /**
     * 我們判斷資料型別,只支援文字型別
     * @param ctx
     * @param frame
     */
    private void handleWebSocketFrame(ChannelHandlerContext ctx,WebSocketFrame frame) {
        if(frame instanceof CloseWebSocketFrame){//如是接收到的是關閉websocket,就關閉連線
            handshaker.close(ctx.channel(), (CloseWebSocketFrame) frame.retain());
            return;
        }
        if(frame instanceof PingWebSocketFrame){//如果資訊是2進位制資料,就反給它,
            ctx.channel().write(new PongWebSocketFrame(frame.content().retain()));
            return;
        }

        if(!(frame instanceof TextWebSocketFrame)){//哪果不是文字的資料,就報錯。
            throw new UnsupportedOperationException(String.format("%s frame types not supported",frame.getClass().getName()));
        }

        String request = ((TextWebSocketFrame)frame).text();
        if(logger.isLoggable(Level.FINE)){
            logger.fine(String.format("%s receive %s",ctx.channel(),request));
        }
        ctx.channel().write(new TextWebSocketFrame(request+",歡迎使用netty websocket 服務,現在時刻"+new Date().toString()));
    }
    private static void sendHttpResponse(ChannelHandlerContext ctx,FullHttpRequest req,FullHttpResponse res){
        if(res.getStatus().code() != 200){
            ByteBuf buf = Unpooled.copiedBuffer(res.getStatus().toString(),CharsetUtil.UTF_8);
            res.content().writeBytes(buf);
            buf.release();
            setContentLength(res,res.content().readableBytes());

        }
        ChannelFuture f = ctx.channel().writeAndFlush(res);//傳送訊息
        if(!isKeepAlive(req)||res.getStatus().code()!= 200){//如果斷開連線,或者傳送不成功,斷開連線。
            f.addListener(ChannelFutureListener.CLOSE);//關閉連線
        }
    }
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause)
            throws Exception {
            cause.printStackTrace();
            ctx.close();
    }
}

上面的程式碼,我都做的詳細的註釋,大家可以對照著看看。
在handleHttpRequest方法中我們判斷訊息頭中是否包含“Upgrade”欄位,如果不包含就返回Http 400 響應。握手請求經過簡單校驗後,我們通過構造握手工廠,建立握手出理類webSocketServerHandshaker,通過它構造握手響應訊息返回給客戶端,同時將webSocket訊息的編碼和解碼類動態新增到ChannelPipeline中,用於websocket訊息的編解碼。

下面來看看客戶端瀏覽器程式碼

<html>
<head>
<meta charset="UTF-8">
netty websocket 時間伺服器
</head>
<br>
<body>
<br>
<script type="text/javascript">
var socket;
if(!window.WebSocket){
     window.WebSocket = window.MozWebSocket;
}
if(window.WebSocket){
    var  socket = new WebSocket("ws://localhost:9090/websocket");
    socket.onmessage = function(event){
        var ta = document.getElementById('responseText');
        ta.value="";
        ta.value = event.data
    };
    socket.onopen = function(event){
        var ta = document.getElementById('responseText');
        ta.value = '';
        ta.value = "開啟websocket服務正常,瀏覽器支援websocket!";   
    };
    socket.onclose = function(event){
        var ta = document.getElementById('responseText');
        ta.value = '';
        ta.value = "websocket關閉!";
    };
}else{
    alert("抱歉,您的瀏覽器不支援WebSocket 協議!");
}
function send(message){
    if(!window.WebSocket){
        return;
    }
    if(socket.readyState == window.WebSocket.OPEN){
        socket.send(message);
    }else{
        alert("WebSocket 還沒有建立連線!")
    }
}
</script>
<form onsubmit="return false;">
<input type="text" name="message" vaule="netty最佳實踐"/>
<br><br>
<input type="button" value="傳送 WebSocket 請求訊息" onclick="send(this.form.message.value)">
<hr color="blue"/>
<h3>服務端返回的應答訊息</h3>
<textarea id="responseText" style"width:500px;height:300px;"></textarea>
</form>
</body>
</html> 

我們這裡不加講解了,大家看看就明白了。

看看客戶端的執行效果

初始狀態效果圖

傳送訊息效果圖

結尾

在本章的結尾把程式碼奉上,大家下載下來對照的學習,這些程式碼都是執行通過的。
好了就講到這裡吧,由於HttP協議本身存在的弊端,產生了websocket協議。希望對大家有所幫助。有不懂的可以聯絡我[email protected]

同時鼓勵自己,堅持就會有奇蹟,加油!

相關推薦

NettyWebSocket協議開發

開始 在本章的開頭把程式碼奉上,大家下載下來對照的學習,這些程式碼都是執行通過的。 上節我們講解了HTTP協議開發,但是,Http協議的開銷問題,導致它們不適用於低延遲的應用。為了解決這些問題,我們引入了webSocket。 HTTP協議的弊端 我們來總

Netty筆記:使用WebSocket協議開發聊天系統

       前言,之前一直圍繞著Http協議來開發專案,最近由於參與一個類似競拍專案的開發,有這樣一個場景,多個客戶端競拍一個商品,當一個客戶端加價後,其它關注這個商品的客戶端需要立即知道該商品的最新價格。這裡有個問題,Http協議是基於請求/響應的,客戶端傳送請求,然後

SpringBoot整合NettyWebsocket

前後端通過websocket通訊進行聊天~ 核心程式碼整理如下: netty元件 @Component public class NettyBooter implements ApplicationListener<ContextRefreshedEvent> {

pythonWebSocket開發

## websocket.py import socket import struct import hashlib,base64 import threading,random #執行緒,套接字,雜湊表,隨機數 #存放連結客戶fd,元組 connectionlist =

Netty自定義協議開發

自定義協議格式 %1$8s%2$4s%3$4s%4$8s%5$16s%6$32s%7$8s 類似:AAAAAAAA0001000011001001usernameusernamesession1session1session1session100000023

Swift-核心面向協議開發

Swift is a Protocol-Oriented Programming Language Swift 是一門面向協議 (POP) 開發的語言 Swift 的核心是面向協議程式設計 WWDC 對 OOP 很好的詮釋: POP 面向協議

pythonWebSocket協議

一、WebSocket理論部分 1、websocket是什麼 Websocket是html5提出的一個協議規範,參考rfc6455。 websocket約定了一個通訊的規範,通過一個握手的機制,客戶端(瀏覽器)和伺服器(webserver)之間能建立一個類似tcp的連線

不惑之年的硬體牛人轉到軟體自學netty框架(六)Netty的網路協議WebSocket和UDP

由於近期開發一箇中型的物聯專案,帶著十來個兄弟從底層硬體到無線局域通訊到通用閘道器到netty高可用框架到spring cloud的後臺開發到移動端APP開發到WEB前端的開發整體框架的搭建,雖然很辛苦,但我一直在給兄弟們說我們要三年內在物聯行業佔有一席之地,期待專案的成功。

netty同埠監聽tcp和websocket協議

前言:   軟體通訊七層結構(osi模型)中由協議套協議最終組成最高階應用層協議(http等等),下三層結構偏向與資料通訊,上三層更偏向於資料處理,中間的傳輸層則是連線上三層與下三層之間的橋樑,每一層都做不同的工作,上層協議依賴與下層協議。   七層結構的最主要功能就是幫助不同系統的主機在不同的網

Netty入門WebSocket初體驗

說一說IO通訊 BIO通訊: BIO即同步阻塞模式一請求一應答的通訊模型,該模型最大的問題就是缺乏彈性伸縮能力,當客戶端併發訪問量增加後,服務端的執行緒個數和客戶端併發訪問數呈1:1的正比關係,由於執行緒是JAVA虛擬機器非常寶貴的系統資源,當執行緒數膨脹之後,系統的效能將急劇下降,隨著併發訪問量的繼續增

SpringBoot | 第十九章:web 應用開發 WebSocket

前言 web開發也講解了三章了,這章節開始講解關於與前端通訊相關知識。實現一個線上聊天室類似的功能或者後端推送訊息到前端,在沒有WebSocket時,讀大學那夥還有接觸過DWR(Direct Web Remoting),也使用過輪詢的方式,當Servlet3.0出來後,也有

dotnet core 開發無縫相容Http和Websocket協議的介面服務

在應用介面開發中往往要針對不同協義開發相應的代理服務,但對於Websocket和http這兩種協議來說就有些不同,從實現上來看Websocket可以說是Http的升級子協議, 兩者在協議處理上基本一致,具體可以在解釋Body上有所不同。FastHttpApi在實現過程完全支援http協議升級成websocke

Netty協議開發(HTTP)

HTTP是一個屬於應用層的面向物件協議,由於其簡捷、快速的方式,適用於分散式超媒體資訊系統。 HTTP協議的URL http://host[":"port][abs_path] http表示通過HTTP協議來定位網路資源;host表示合法的Internet主機域名或者I

Springboot 整合Websocket 註解開發第一步瀏覽器和伺服器建立連線(解決了建立連線時404錯誤!!!!)

1、建立一個springboot專案 勾選web和websocket選項      建立完成後build.gradle檔案如下,主要是依賴得新增上(另外說明以下compile('org.springframework.boot:spring-boot-starter-w

【轉】Netty解決TCP粘包拆包(自定義協議)

https://www.cnblogs.com/sidesky/p/6913109.html 1、什麼是粘包/拆包        一般所謂的TCP粘包是在一次接收資料不能完全地體現一個完整的訊息資料。TCP通訊為何存在粘包呢?主要原因是TCP是以流的方

snmp 協議開發HiliSoft MIB Browser(輔助開發工具)

為了簡單起見,現在在Windows 7上建立一個SNMP服務環境,windows 7系統也自帶了該服務的支援! 控制面板中開啟新增或刪除程式! 安裝後會在服中看到SNMP服務內容: 然後我們需要安裝一個軟體,這個軟體可以檢視機器的MIB樹: HiliSo

Netty入門WebSocket(二)

什麼是WebSocket:是一種H5協議規範 解決客戶端與服務端實時通訊而產生的技術:WebSocket本質是一種基於TCP協議,先通過Http/Https發一個特殊的Http請求進行握手,握手後會建立一個用於交換資料的TCP連結,之後客戶端和服務端使用該TCP連結進行實時

JmeterRPC協議指令碼開發實現

1、首先通過本文是通過jmeter的java請求方式實現RPC協議指令碼請求,具體關於java請求如果基於jmeter實現見:jmeter之java請求;如果還想了解rpc指令碼開發詳細見:rpc協議之hprose介面測試  。 2、分別需要建立兩個類 send類、Ap

Netty解決TCP粘包拆包(自定義協議)

1、什麼是粘包/拆包        一般所謂的TCP粘包是在一次接收資料不能完全地體現一個完整的訊息資料。TCP通訊為何存在粘包呢?主要原因是TCP是以流的方式來處理資料,再加上網路上MTU的往往小於在應用處理的訊息資料,所以就會引發一次接收的資料無法滿足訊息的需要,導

Android開發技術網路篇── http協議post請求方式

Java程式碼 程式碼如下:  private Button button1,button2,button3;  private TextView textView1;  button1.setOnClickListener(new Button.OnClickListener(){