Netty筆記之六:Netty對websocket的支持

出現(xiàn)的背景

WebSocket是一種規(guī)范靴寂,是Html5規(guī)范的一部分磷蜀,websocket解決什么問題呢?解決http協(xié)議的一些不足百炬。我們知道褐隆,http協(xié)議是一種無狀態(tài)的,基于請求響應(yīng)模式的協(xié)議剖踊。

網(wǎng)頁聊天的程序(基于http協(xié)議的)庶弃,瀏覽器客戶端發(fā)送一個(gè)數(shù)據(jù),服務(wù)器接收到這個(gè)瀏覽器數(shù)據(jù)之后德澈,如何將數(shù)據(jù)推送給其他的瀏覽器客戶端呢歇攻?
這就涉及到服務(wù)器的推技術(shù)。早年為了實(shí)現(xiàn)這種服務(wù)器也可以像瀏覽器客戶端推送消息的長連接需求梆造,有很多方案缴守,比如說最常用的采用一種輪詢技術(shù),就是客戶端每隔一段時(shí)間镇辉,比如說2s或者3s向服務(wù)器發(fā)送請求屡穗,去請求服務(wù)器端是否還有信息沒有響應(yīng)給客戶端,有就響應(yīng)給客戶端摊聋,當(dāng)然沒有響應(yīng)就只是一種無用的請求鸡捐。

這種長輪詢技術(shù)的缺點(diǎn)有:
1)響應(yīng)數(shù)據(jù)不是實(shí)時(shí)的,在下一次輪詢請求的時(shí)候才會(huì)得到這個(gè)響應(yīng)信息麻裁,只能說是準(zhǔn)實(shí)時(shí)箍镜,而不是嚴(yán)格意義的實(shí)時(shí)。
2)大多數(shù)輪詢請求的空輪詢煎源,造成大量的資源帶寬的浪費(fèi)色迂,每次http請求攜帶了大量無用的頭信息,而服務(wù)器端其實(shí)大多數(shù)都不關(guān)注這些頭信息手销,而實(shí)際大多數(shù)情況下這些頭信息都遠(yuǎn)遠(yuǎn)大于body信息歇僧,造成了資源的消耗。

拓展
比較新的技術(shù)去做輪詢的效果是Comet锋拖。這種技術(shù)雖然可以雙向通信诈悍,但依然需要反復(fù)發(fā)出請求。而且在Comet中兽埃,普遍采用的長鏈接侥钳,也會(huì)消耗服務(wù)器資源。

WebSocket是什么柄错?

WebSocket一種在單個(gè) TCP 連接上進(jìn)行全雙工通訊的協(xié)議舷夺。WebSocket通信協(xié)議于2011年被IETF定為標(biāo)準(zhǔn)RFC 6455苦酱,并被RFC7936所補(bǔ)充規(guī)范。WebSocket API也被W3C定為標(biāo)準(zhǔn)给猾。

WebSocket 使得客戶端和服務(wù)器之間的數(shù)據(jù)交換變得更加簡單疫萤,允許服務(wù)端主動(dòng)向客戶端推送數(shù)據(jù)。在 WebSocket API 中敢伸,瀏覽器和服務(wù)器只需要完成一次握手扯饶,兩者之間就直接可以創(chuàng)建持久性的連接,并進(jìn)行雙向數(shù)據(jù)傳輸详拙。

websocket的出現(xiàn)就是解決了客戶端與服務(wù)端的這種長連接問題帝际,這種長連接是真正意義上的長連接∪恼蓿客戶端與服務(wù)器一旦連接建立雙方就是對等的實(shí)體蹲诀,不再區(qū)分嚴(yán)格意義的客戶端和服務(wù)端。長連接只有在初次建立的時(shí)候弃揽,客戶端才會(huì)向服務(wù)端發(fā)送一些請求脯爪,這些請求包括請求頭和請求體,一旦建立好連接之后矿微,客戶端和服務(wù)器只會(huì)發(fā)送數(shù)據(jù)本身而不需要再去發(fā)送請求頭信息痕慢,這樣大量減少了
網(wǎng)絡(luò)帶寬。websocket協(xié)議本身是構(gòu)建在http協(xié)議之上的升級協(xié)議涌矢,客戶端首先向服務(wù)器端去建立連接掖举,這個(gè)連接本身就是http協(xié)議只是在頭信息中包含了一些websocket協(xié)議的相關(guān)信息,一旦http連接建立之后娜庇,服務(wù)器端讀到這些websocket協(xié)議的相關(guān)信息就將此協(xié)議升級成websocket協(xié)議塔次。websocket協(xié)議也可以應(yīng)用在非瀏覽器應(yīng)用,只需要引入相關(guān)的websocket庫就可以了名秀。

HTML5定義了WebSocket協(xié)議励负,能更好的節(jié)省服務(wù)器資源和帶寬,并且能夠更實(shí)時(shí)地進(jìn)行通訊匕得。Websocket使用ws或wss的統(tǒng)一資源標(biāo)志符继榆,類似于HTTPS,其中wss表示在TLS之上的Websocket汁掠。如:

ws://example.com/wsapi
wss://secure.example.com/

優(yōu)點(diǎn)

  • 較少的控制開銷:相對與http請求的頭部信息略吨,websocket信息明顯減少。
  • 更強(qiáng)的實(shí)時(shí)性:由于協(xié)議是全雙工的考阱,所以服務(wù)器可以隨時(shí)主動(dòng)給客戶端下發(fā)數(shù)據(jù)晋南。相對于HTTP請求需要等待客戶端發(fā)起請求服務(wù)端才能響應(yīng),延遲明顯更少羔砾;即使是和Comet等類似的長輪詢比較负间,其也能在短時(shí)間內(nèi)更多次地傳遞數(shù)據(jù)。
  • 保持連接狀態(tài)姜凄。于HTTP不同的是政溃,Websocket需要先創(chuàng)建連接,這就使得其成為一種有狀態(tài)的協(xié)議态秧,之后通信時(shí)可以省略部分狀態(tài)信息董虱。而HTTP請求可能需要在每個(gè)請求都攜帶狀態(tài)信息(如身份認(rèn)證等)。
  • 更好的二進(jìn)制支持申鱼。Websocket定義了二進(jìn)制幀愤诱,相對HTTP,可以更輕松地處理二進(jìn)制內(nèi)容捐友。
  • 可以支持?jǐn)U展淫半。Websocket定義了擴(kuò)展,用戶可以擴(kuò)展協(xié)議匣砖、實(shí)現(xiàn)部分自定義的子協(xié)議科吭。如部分瀏覽器支持壓縮等。
  • 更好的壓縮效果猴鲫。相對于HTTP壓縮对人,Websocket在適當(dāng)?shù)臄U(kuò)展支持下,可以沿用之前內(nèi)容的上下文拂共,在傳遞類似的數(shù)據(jù)時(shí)牺弄,可以顯著地提高壓縮率。

netty對websocket協(xié)議的支持

demo

瀏覽器頁面向服務(wù)器發(fā)送消息宜狐,服務(wù)器將當(dāng)前消息發(fā)送時(shí)間反饋給瀏覽器頁面势告。

服務(wù)器端

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;

//websocket長連接示例
public class MyServer {
    public static void main(String[] args) throws Exception{
        EventLoopGroup bossGroup = new NioEventLoopGroup();
        EventLoopGroup wokerGroup = new NioEventLoopGroup();

        try{
            ServerBootstrap serverBootstrap = new ServerBootstrap();
            serverBootstrap.group(bossGroup,wokerGroup).channel(NioServerSocketChannel.class)
                    .handler(new LoggingHandler(LogLevel.INFO))
                    .childHandler(new WebSocketChannelInitializer());

            ChannelFuture channelFuture = serverBootstrap.bind(new InetSocketAddress(8899)).sync();
            channelFuture.channel().closeFuture().sync();
        }finally {
            bossGroup.shutdownGracefully();
            wokerGroup.shutdownGracefully();
        }

    }
}

服務(wù)器端初始化連接

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 WebSocketChannelInitializer extends ChannelInitializer<SocketChannel>{

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

        //websocket協(xié)議本身是基于http協(xié)議的,所以這邊也要使用http解編碼器
        pipeline.addLast(new HttpServerCodec());
        //以塊的方式來寫的處理器
        pipeline.addLast(new ChunkedWriteHandler());
        //netty是基于分段請求的肌厨,HttpObjectAggregator的作用是將請求分段再聚合,參數(shù)是聚合字節(jié)的最大長度
        pipeline.addLast(new HttpObjectAggregator(8192));

        //ws://server:port/context_path
        //ws://localhost:9999/ws
        //參數(shù)指的是contex_path
        pipeline.addLast(new WebSocketServerProtocolHandler("/ws"));
        //websocket定義了傳遞數(shù)據(jù)的6中frame類型
        pipeline.addLast(new TextWebSocketFrameHandler());

    }
}

WebSocketServerProtocolHandler:參數(shù)是訪問路徑培慌,這邊指定的是ws,服務(wù)客戶端訪問服務(wù)器的時(shí)候指定的url是:ws://localhost:8899/ws柑爸。
它負(fù)責(zé)websocket握手以及處理控制框架(Close吵护,Ping(心跳檢檢測request),Pong(心跳檢測響應(yīng)))表鳍。 文本和二進(jìn)制數(shù)據(jù)幀被傳遞到管道中的下一個(gè)處理程序進(jìn)行處理馅而。


WebSocket規(guī)范中定義了6種類型的楨,netty為其提供了具體的對應(yīng)的POJO實(shí)現(xiàn)譬圣。
WebSocketFrame:所有楨的父類瓮恭,所謂楨就是WebSocket服務(wù)在建立的時(shí)候,在通道中處理的數(shù)據(jù)類型厘熟。本列子中客戶端和服務(wù)器之間處理的是文本信息屯蹦。所以范型參數(shù)是TextWebSocketFrame维哈。

WebSocketFrame繼承類

自定義Handler

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

import java.time.LocalDateTime;

//處理文本協(xié)議數(shù)據(jù),處理TextWebSocketFrame類型的數(shù)據(jù)登澜,websocket專門處理文本的frame就是TextWebSocketFrame
public class TextWebSocketFrameHandler extends SimpleChannelInboundHandler<TextWebSocketFrame>{

    //讀到客戶端的內(nèi)容并且向客戶端去寫內(nèi)容
    @Override
    protected void channelRead0(ChannelHandlerContext ctx, TextWebSocketFrame msg) throws Exception {
        System.out.println("收到消息:"+msg.text());

        /**
         * writeAndFlush接收的參數(shù)類型是Object類型阔挠,但是一般我們都是要傳入管道中傳輸數(shù)據(jù)的類型,比如我們當(dāng)前的demo
         * 傳輸?shù)木褪荰extWebSocketFrame類型的數(shù)據(jù)
         */
        ctx.channel().writeAndFlush(new TextWebSocketFrame("服務(wù)時(shí)間:"+ LocalDateTime.now()));
    }

    //每個(gè)channel都有一個(gè)唯一的id值
    @Override
    public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
        //打印出channel唯一值脑蠕,asLongText方法是channel的id的全名
        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("異常發(fā)生");
        ctx.close();
    }
}

頁面

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>WebSocket客戶端</title>
</head>
<body>
<script type="text/javascript">
    var socket;

    //如果瀏覽器支持WebSocket
    if(window.WebSocket){
        //參數(shù)就是與服務(wù)器連接的地址
        socket = new WebSocket("ws://localhost:8899/ws");

        //客戶端收到服務(wù)器消息的時(shí)候就會(huì)執(zhí)行這個(gè)回調(diào)方法
        socket.onmessage = function (event) {
            var ta = document.getElementById("responseText");
            ta.value = ta.value + "\n"+event.data;
        }

        //連接建立的回調(diào)函數(shù)
        socket.onopen = function(event){
            var ta = document.getElementById("responseText");
            ta.value = "連接開啟";
        }

        //連接斷掉的回調(diào)函數(shù)
        socket.onclose = function (event) {
            var ta = document.getElementById("responseText");
            ta.value = ta.value +"\n"+"連接關(guān)閉";
        }
    }else{
        alert("瀏覽器不支持WebSocket购撼!");
    }

    //發(fā)送數(shù)據(jù)
    function send(message){
        if(!window.WebSocket){
            return;
        }

        //當(dāng)websocket狀態(tài)打開
        if(socket.readyState == WebSocket.OPEN){
            socket.send(message);
        }else{
            alert("連接沒有開啟");
        }
    }
</script>
<form onsubmit="return false">
    <textarea name = "message" style="width: 400px;height: 200px"></textarea>

    <input type ="button" value="發(fā)送數(shù)據(jù)" onclick="send(this.form.message.value);">

    <h3>服務(wù)器輸出:</h3>

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

    <input type="button" onclick="javascript:document.getElementById('responseText').value=''" value="清空數(shù)據(jù)">
</form>
</body>
</html>

啟動(dòng)服務(wù)器,然后運(yùn)行客戶端頁面谴仙,當(dāng)客戶端和服務(wù)器端連接建立的時(shí)候迂求,服務(wù)器端執(zhí)行handlerAdded回調(diào)方法,客戶端執(zhí)行onopen回調(diào)方法

服務(wù)器端控制臺:

handlerAdded:acde48fffe001122-00005c11-00000001-4ce4764fffa940fe-df037eb5

頁面:

客戶端連接建立

客戶端發(fā)送消息晃跺,服務(wù)器端進(jìn)行響應(yīng)揩局,

瀏覽器客戶端發(fā)送消息

服務(wù)端控制臺打印:

收到消息:websocket程序

客戶端也收到服務(wù)器端的響應(yīng):

瀏覽器客戶端收到服務(wù)器端響應(yīng)

打開開發(fā)者工具

頁面發(fā)送websocket請求.png

在從標(biāo)準(zhǔn)的HTTP或者HTTPS協(xié)議切換到WebSocket時(shí)哼审,將會(huì)使用一種升級握手的機(jī)制谐腰。因此,使用WebSocket的應(yīng)用程序?qū)⑹冀K以HTTP/S作為開始涩盾,然后再執(zhí)行升級十气。這個(gè)升級動(dòng)作發(fā)生的確定時(shí)刻特定與應(yīng)用程序;它可能會(huì)發(fā)生在啟動(dòng)時(shí)候春霍,也可能會(huì)發(fā)生在請求了某個(gè)特定的IURL之后砸西。

查看楨信息

參考技術(shù)
java web 服務(wù)器推送技術(shù)--comet4j
Comet:基于 HTTP 長連接的“服務(wù)器推”技術(shù)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市址儒,隨后出現(xiàn)的幾起案子芹枷,更是在濱河造成了極大的恐慌,老刑警劉巖莲趣,帶你破解...
    沈念sama閱讀 219,039評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件鸳慈,死亡現(xiàn)場離奇詭異,居然都是意外死亡喧伞,警方通過查閱死者的電腦和手機(jī)走芋,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,426評論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來潘鲫,“玉大人翁逞,你說我怎么就攤上這事「嚷兀” “怎么了挖函?”我有些...
    開封第一講書人閱讀 165,417評論 0 356
  • 文/不壞的土叔 我叫張陵,是天一觀的道長浊竟。 經(jīng)常有香客問我怨喘,道長津畸,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,868評論 1 295
  • 正文 為了忘掉前任必怜,我火速辦了婚禮洼畅,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘棚赔。我一直安慰自己,他們只是感情好徘郭,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,892評論 6 392
  • 文/花漫 我一把揭開白布靠益。 她就那樣靜靜地躺著,像睡著了一般残揉。 火紅的嫁衣襯著肌膚如雪胧后。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,692評論 1 305
  • 那天抱环,我揣著相機(jī)與錄音壳快,去河邊找鬼。 笑死镇草,一個(gè)胖子當(dāng)著我的面吹牛眶痰,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播梯啤,決...
    沈念sama閱讀 40,416評論 3 419
  • 文/蒼蘭香墨 我猛地睜開眼竖伯,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了因宇?” 一聲冷哼從身側(cè)響起七婴,我...
    開封第一講書人閱讀 39,326評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎察滑,沒想到半個(gè)月后打厘,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,782評論 1 316
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡贺辰,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,957評論 3 337
  • 正文 我和宋清朗相戀三年户盯,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片魂爪。...
    茶點(diǎn)故事閱讀 40,102評論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡先舷,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出滓侍,到底是詐尸還是另有隱情蒋川,我是刑警寧澤,帶...
    沈念sama閱讀 35,790評論 5 346
  • 正文 年R本政府宣布撩笆,位于F島的核電站捺球,受9級特大地震影響缸浦,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜氮兵,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,442評論 3 331
  • 文/蒙蒙 一裂逐、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧泣栈,春花似錦卜高、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,996評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至疼进,卻和暖如春薪缆,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背伞广。 一陣腳步聲響...
    開封第一講書人閱讀 33,113評論 1 272
  • 我被黑心中介騙來泰國打工拣帽, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人嚼锄。 一個(gè)月前我還...
    沈念sama閱讀 48,332評論 3 373
  • 正文 我出身青樓减拭,卻偏偏與公主長得像,于是被迫代替她去往敵國和親灾票。 傳聞我的和親對象是個(gè)殘疾皇子峡谊,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,044評論 2 355

推薦閱讀更多精彩內(nèi)容

  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn)刊苍,斷路器既们,智...
    卡卡羅2017閱讀 134,662評論 18 139
  • 國家電網(wǎng)公司企業(yè)標(biāo)準(zhǔn)(Q/GDW)- 面向?qū)ο蟮挠秒娦畔?shù)據(jù)交換協(xié)議 - 報(bào)批稿:20170802 前言: 排版 ...
    庭說閱讀 10,986評論 6 13
  • 1. 網(wǎng)絡(luò)基礎(chǔ)TCP/IP HTTP基于TCP/IP協(xié)議族,HTTP屬于它內(nèi)部的一個(gè)子集正什。 把互聯(lián)網(wǎng)相關(guān)聯(lián)的協(xié)議集...
    yozosann閱讀 3,444評論 0 20
  • 在上一篇《胖妞的逆襲 秒瘦10斤全靠“裝”婴氮!》中有跟大家留下一個(gè)問題斯棒,關(guān)于下面這張圖片中的胖mm通過改變哪些可以更...
    貴妃姐閱讀 481評論 0 2
  • xib文件可以針對某些視圖控制器或者視圖進(jìn)行定制,而storyboard是xib文件的整合主经,可以開發(fā)整個(gè)app的框...
    呼哮山莊閱讀 2,931評論 0 1