netty(十五)Netty提升 - 協(xié)議設(shè)計以及常見協(xié)議介紹

一莹妒、什么是協(xié)議?

在計算機網(wǎng)絡(luò)與信息通信領(lǐng)域里绰上,人們經(jīng)常提及“協(xié)議”一詞旨怠。互聯(lián)網(wǎng)中常用的具有代表性的協(xié)議有IP蜈块、TCP鉴腻、HTTP等。

簡單來說百揭,協(xié)議就是計算機與計算機之間通過網(wǎng)絡(luò)實現(xiàn)通信時事先達(dá)成的一種“約定”爽哎。這種“約定”使那些由不同廠商的設(shè)備、不同的CPU以及不同的操作系統(tǒng)組成的計算機之間器一,只要遵循相同的協(xié)議就能夠?qū)崿F(xiàn)通信课锌。

二、協(xié)議的必要性

通常祈秕,我們發(fā)送一封電子郵件渺贤、訪問某個主頁獲取信息時察覺不到協(xié)議的存在,只有在我們重新配置計算機的網(wǎng)絡(luò)連接请毛、修改網(wǎng)絡(luò)設(shè)置時才有可能涉及協(xié)議志鞍。

這就好比兩個人使用不同國家的語言說話,怎么也無法相互理解方仿。也可以比喻成沒有斷句的話固棚,有多種不同的解釋街州,雙方可能會產(chǎn)生分歧。協(xié)議可以分為很多種玻孟,每一種協(xié)議都明確地界定了它的行為規(guī)范唆缴。兩臺計算機之間必須能夠支持相同的協(xié)議,并遵循相同協(xié)議進行處理黍翎,這樣才能實現(xiàn)相互通信面徽。

TCP/IP 中消息傳輸基于流的方式,沒有邊界匣掸。

協(xié)議的目的就是劃定消息的邊界趟紊,制定通信雙方要共同遵守的通信規(guī)則。

三碰酝、常見協(xié)議示例

3.1 redis協(xié)議

比如我們要發(fā)送消息給redis霎匈,需要遵從其協(xié)議。
比如我們要發(fā)送set key value 形式的命令送爸,則我們首先要發(fā)送整個命令的長度铛嘱,然后分別發(fā)送命令每個位置的長度,如下所示:

set name Tom

則需要按照如下的協(xié)議發(fā)送:

*3 //命令長度
$3 //set長度
set //命令內(nèi)容
$4 //key的長度
name //key的內(nèi)容
$3 //value的長度
Tom //value的內(nèi)容

我們使用如下代碼連接redis并發(fā)送上面的內(nèi)容:

創(chuàng)建一個客戶端袭厂,連接本地redis:

public class RedisProtocol {
    // 回車換行
    static final byte[] LINE = {13, 10};

    public static void main(String[] args) {
        NioEventLoopGroup worker = new NioEventLoopGroup();

        try {
            ChannelFuture channelFuture = new Bootstrap().group(worker)
                    .channel(NioSocketChannel.class)
                    .handler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        protected void initChannel(SocketChannel socketChannel) {
                            socketChannel.pipeline().addLast(new ChannelInboundHandlerAdapter() {
                                //連接建立后觸發(fā)以下方法墨吓,發(fā)送內(nèi)容
                                @Override
                                public void channelActive(ChannelHandlerContext ctx) {
                                    ByteBuf buf = ctx.alloc().buffer();
                                    buf.writeBytes("*3".getBytes());
                                    buf.writeBytes(LINE);
                                    buf.writeBytes("$3".getBytes());
                                    buf.writeBytes(LINE);
                                    buf.writeBytes("set".getBytes());
                                    buf.writeBytes(LINE);
                                    buf.writeBytes("$4".getBytes());
                                    buf.writeBytes(LINE);
                                    buf.writeBytes("name".getBytes());
                                    buf.writeBytes(LINE);
                                    buf.writeBytes("$3".getBytes());
                                    buf.writeBytes(LINE);
                                    buf.writeBytes("Tom".getBytes());
                                    buf.writeBytes(LINE);
                                    ctx.writeAndFlush(buf);
                                }
                                //接收redis返回值
                                @Override
                                public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
                                    ByteBuf byteBuf = (ByteBuf) msg;
                                    System.out.println(byteBuf.toString(StandardCharsets.UTF_8));
                                }
                            });
                        }
                    }).connect("127.0.0.1", 6379);

            channelFuture.sync();
            channelFuture.channel().closeFuture().sync();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            worker.shutdownGracefully();
        }
    }
}

驗證:

127.0.0.1:0>get name
"Tom"

3.2 http協(xié)議

下面我們演示下netty當(dāng)中解析http請求的示例代碼。因為http協(xié)議我們自己實現(xiàn)太過復(fù)雜纹磺,所以我們直接使用netty提供的http編解碼器:HttpServerCodec

public final class HttpServerCodec extends CombinedChannelDuplexHandler<HttpRequestDecoder, HttpResponseEncoder> implements SourceCodec

如上圖所示帖烘,我們發(fā)現(xiàn)這個類繼承自CombinedChannelDuplexHandler,同時實現(xiàn)了編碼和解碼的功能橄杨。

下面我們提供個一個服務(wù)端代碼秘症,通過瀏覽器訪問,看看最終能收到瀏覽器請求內(nèi)容是什么樣的:

public class HttpProtocolServer {

    public static void main(String[] args) {
        NioEventLoopGroup boss = new NioEventLoopGroup(1);
        NioEventLoopGroup worker = new NioEventLoopGroup();

        try {
            ChannelFuture channelFuture = new ServerBootstrap()
                    .group(boss, worker)
                    .channel(NioServerSocketChannel.class)
                    .childHandler(new ChannelInitializer<NioSocketChannel>() {
                        @Override
                        protected void initChannel(NioSocketChannel nioSocketChannel) {
                            nioSocketChannel.pipeline().addLast(new HttpServerCodec());
                            nioSocketChannel.pipeline().addLast(new ChannelInboundHandlerAdapter() {
                                @Override
                                public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
                                    System.out.println(msg);
                                    System.out.println("****************");
                                    System.out.println(msg.getClass());
                                    super.channelRead(ctx, msg);
                                }
                            });
                        }
                    }).bind(8080);
            channelFuture.sync();
            channelFuture.channel().closeFuture().sync();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            boss.shutdownGracefully();
            worker.shutdownGracefully();
        }
    }
}

瀏覽器訪問http://localhost:8080/index.html式矫,得到如下結(jié)果:

DefaultHttpRequest(decodeResult: success, version: HTTP/1.1)
GET / HTTP/1.1
Host: localhost:8080
Connection: keep-alive
Cache-Control: max-age=0
sec-ch-ua: "Chromium";v="92", " Not A;Brand";v="99", "Google Chrome";v="92"
sec-ch-ua-mobile: ?0
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.131 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Sec-Fetch-Site: none
Sec-Fetch-Mode: navigate
Sec-Fetch-User: ?1
Sec-Fetch-Dest: document
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.9,en;q=0.8
****************
class io.netty.handler.codec.http.DefaultHttpRequest
EmptyLastHttpContent
****************
class io.netty.handler.codec.http.LastHttpContent$1

分析如上內(nèi)容乡摹,發(fā)現(xiàn)得到了很多http請求的參數(shù)內(nèi)容,以及共接收到兩個類型的類衷佃,分別是:DefaultHttpRequest 和 LastHttpContent趟卸,也就是說蹄葱,http請求會給我們兩部分的內(nèi)容氏义,分別是請求和請求體,這里無論是get還是post图云,都是如此惯悠。

所以,當(dāng)我們想要處理一個http請求的話竣况,需要對這兩個請求進行判斷去分別處理:

if (msg instanceof HttpRequest){
     // TODO do something...
}else if(msg instanceof HttpContent){
   // TODO do something...
}

也可以使用netty提供的SimpleChannelInboundHandler的針對單一類型的入站處理器去處理請求:

 nioSocketChannel.pipeline().addLast(new SimpleChannelInboundHandler<HttpRequest>() {
      @Override
       protected void channelRead0(ChannelHandlerContext channelHandlerContext, HttpRequest httpRequest) {

       }
});

處理處理請求克婶,一個正常的http請求還需要返回響應(yīng),我們用DefaultFullHttpResponse最為響應(yīng)。這個類需要需要指定請求協(xié)議的version情萤,以及請求的狀態(tài)鸭蛙。

除此之外,我們給請求增加上響應(yīng)內(nèi)容:Hello World筋岛!

完整代碼如下所示:

public class HttpProtocolServer {

    public static void main(String[] args) {
        NioEventLoopGroup boss = new NioEventLoopGroup(1);
        NioEventLoopGroup worker = new NioEventLoopGroup();

        try {
            ChannelFuture channelFuture = new ServerBootstrap()
                    .group(boss, worker)
                    .channel(NioServerSocketChannel.class)
                    .childHandler(new ChannelInitializer<NioSocketChannel>() {
                        @Override
                        protected void initChannel(NioSocketChannel nioSocketChannel) {
                            nioSocketChannel.pipeline().addLast(new HttpServerCodec());
                            nioSocketChannel.pipeline().addLast(new SimpleChannelInboundHandler<HttpRequest>() {
                                @Override
                                protected void channelRead0(ChannelHandlerContext channelHandlerContext, HttpRequest httpRequest) {
                                    System.out.println(httpRequest.uri());

                                    //創(chuàng)建響應(yīng)
                                    DefaultFullHttpResponse response =
                                            new DefaultFullHttpResponse(httpRequest.protocolVersion(), HttpResponseStatus.OK);

                                    byte[] bytes = "<h1>Hello World!<h1>".getBytes(StandardCharsets.UTF_8);
                                    //添加響應(yīng)內(nèi)容長度娶视,否則會瀏覽器會一直處于加載狀態(tài)
                                    response.headers().setInt(CONTENT_LENGTH, bytes.length);
                                    //添加響應(yīng)內(nèi)容
                                    response.content().writeBytes(bytes);

                                    //寫入響應(yīng)
                                    channelHandlerContext.channel().writeAndFlush(response);
                                }
                            });
//
//                            nioSocketChannel.pipeline().addLast(new ChannelInboundHandlerAdapter() {
//                                @Override
//                                public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
//                                    System.out.println(msg);
//                                    System.out.println("****************");
//                                    System.out.println(msg.getClass());
//
//                                    if (msg instanceof HttpRequest) {
//                                        // TODO do something...
//                                    } else if (msg instanceof HttpContent) {
//                                        // TODO do something...
//                                    }
//                                    super.channelRead(ctx, msg);
//                                }
//                            });
                        }
                    }).bind(8080);
            channelFuture.sync();
            channelFuture.channel().closeFuture().sync();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            boss.shutdownGracefully();
            worker.shutdownGracefully();
        }
    }
}

瀏覽器請求http://localhost:8080/index.html,查看結(jié)果:

/index.html
/favicon.ico
image.png
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末睁宰,一起剝皮案震驚了整個濱河市肪获,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌柒傻,老刑警劉巖孝赫,帶你破解...
    沈念sama閱讀 221,820評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異红符,居然都是意外死亡青柄,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,648評論 3 399
  • 文/潘曉璐 我一進店門预侯,熙熙樓的掌柜王于貴愁眉苦臉地迎上來刹前,“玉大人,你說我怎么就攤上這事雌桑±恚” “怎么了?”我有些...
    開封第一講書人閱讀 168,324評論 0 360
  • 文/不壞的土叔 我叫張陵校坑,是天一觀的道長拣技。 經(jīng)常有香客問我,道長耍目,這世上最難降的妖魔是什么膏斤? 我笑而不...
    開封第一講書人閱讀 59,714評論 1 297
  • 正文 為了忘掉前任,我火速辦了婚禮邪驮,結(jié)果婚禮上莫辨,老公的妹妹穿的比我還像新娘。我一直安慰自己毅访,他們只是感情好沮榜,可當(dāng)我...
    茶點故事閱讀 68,724評論 6 397
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著喻粹,像睡著了一般蟆融。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上守呜,一...
    開封第一講書人閱讀 52,328評論 1 310
  • 那天型酥,我揣著相機與錄音山憨,去河邊找鬼。 笑死弥喉,一個胖子當(dāng)著我的面吹牛郁竟,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播由境,決...
    沈念sama閱讀 40,897評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼枪孩,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了藻肄?” 一聲冷哼從身側(cè)響起蔑舞,我...
    開封第一講書人閱讀 39,804評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎嘹屯,沒想到半個月后攻询,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,345評論 1 318
  • 正文 獨居荒郊野嶺守林人離奇死亡州弟,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,431評論 3 340
  • 正文 我和宋清朗相戀三年钧栖,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片婆翔。...
    茶點故事閱讀 40,561評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡拯杠,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出啃奴,到底是詐尸還是另有隱情潭陪,我是刑警寧澤,帶...
    沈念sama閱讀 36,238評論 5 350
  • 正文 年R本政府宣布最蕾,位于F島的核電站依溯,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏瘟则。R本人自食惡果不足惜黎炉,卻給世界環(huán)境...
    茶點故事閱讀 41,928評論 3 334
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望醋拧。 院中可真熱鬧慷嗜,春花似錦、人聲如沸丹壕。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,417評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽雀费。三九已至干奢,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間盏袄,已是汗流浹背忿峻。 一陣腳步聲響...
    開封第一講書人閱讀 33,528評論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留辕羽,地道東北人逛尚。 一個月前我還...
    沈念sama閱讀 48,983評論 3 376
  • 正文 我出身青樓,卻偏偏與公主長得像刁愿,于是被迫代替她去往敵國和親绰寞。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,573評論 2 359

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