netty踩坑初體驗

基于4.1.10.Final
目前為止踩坑最多一喘,踩netty之前嗜暴,要先踩Java NIO闷沥,因為netty還是基于Java NIO的api開發(fā)的狐赡,事件模型什么的需要有基礎疟丙,這只是一個初步的研究享郊,畢竟只是出于興趣炊琉,比較好的坑在這里又活。Java NIO 系列教程柳骄。

netty in action.png

netty的基本組件與各組件功能耐薯,netty核心就不介紹了曲初,網(wǎng)上各種大牛的源碼分析臼婆,認真看完基本就通了。本文是記錄踩坑故响,不是教學被去。思路按著這個思維導圖來走,實現(xiàn)一下簡單的功能糜值。

1.字符串傳輸寂汇。

netty是端對端的傳輸骄瓣,最簡單的可以使用嵌套字傳輸,基本功能就是hello word畔勤。假定一個場景庆揪,有一個服務端一直開著接收字符串缸榛,客戶端想發(fā)送字符串到服務端内颗,怎么做?
首先明確一點恨溜,Netty中的消息傳遞,都必須以字節(jié)的形式负懦,以ByeBuff為載體傳遞筒捺。簡單的說,就是你想直接寫個字符串過去纸厉,不行系吭,收到都是亂碼,雖然Netty定義的writer的接口參數(shù)是Object的颗品,這就是比較坑的地方了肯尺。
有了這個分析,就有思路了躯枢。
客戶端就這么發(fā):

        ByteBuf buffer = Unpooled.copiedBuffer(msg, Charset.forName("UTF-8"));
        ChannelFuture future = ctx.writeAndFlush(buffer);
        future.addListener(f ->  ctx.close());

服務器這么收:

    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        ByteBuf m = (ByteBuf) msg;
        String message = m.toString(CharsetUtil.UTF_8);
        System.out.println(message);
        ctx.close();
    }

是的可以直接強轉,原理不明锄蹂,不知道哪里進行了裝箱氓仲。---------- 遺留問題1

但是每次都要這么寫是不是有點麻煩?不是有定義好的編碼與解碼器嗎得糜,那么就可以先加一下處理敬扛,兩邊都這么加

                   new ChannelInitializer<SocketChannel>(){
                        @Override
                        protected void initChannel(SocketChannel ch) throws Exception {
                            ch.pipeline().addLast("decoder", new StringDecoder());
                            ch.pipeline().addLast("encoder", new StringEncoder());
                            ch.pipeline().addLast(new ServerHandler());
                        }
                    }

對的直接放上去,編碼與解碼不用關心順序朝抖,處理類放在最后就好了啥箭。

客戶端:

        ChannelFuture future = ctx.writeAndFlush("hello world");
        future.addListener(f ->  ctx.close());

服務器端

   
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        System.out.println(msg);
        ctx.close();
    }

對,直接就是收到一個String治宣。至此字符串就可以互相傳遞了急侥,但是還有問題砌滞,netty中在傳送字符串的長度有限制,超過1024個字節(jié)就截斷了坏怪,導致接收的信息不完整贝润,ok要這么處理一下。

new ChannelInitializer<SocketChannel>(){
     @Override
     protected void initChannel(SocketChannel ch) throws Exception {
     ch.pipeline().addLast("decoder", new LengthFieldBasedFrameDecoder(Integer.MAX_VALUE,0,4,0,4));
     ch.pipeline().addLast("encoder", new LengthFieldPrepender(4, false));
     ch.pipeline().addLast(new ServerHandler());
 }      

自定義長度陕悬, 4個字節(jié)32位题暖,足夠存了。

2.傳遞簡單對象

有幾種方法可以實現(xiàn)對象的傳遞捉超,這里用的是protocol buffers編解碼器進行對象傳遞胧卤。
首先要了解什么是protocol buffers,這東西就相當于xsd對于xml拼岳,是一個規(guī)則文件枝誊,通過.proto文件通過官方提供的工具就可以生成java類,他有兩個常用方法

public byte[] toByteArray(){};
T parseFrom(byte[]){};

他提供了序列化方法惜纸,直接把類轉化為字節(jié)數(shù)組叶撒,再把數(shù)據(jù)轉為java類,十分方便耐版。netty是天生支持這種序列化方式的
服務器端:

         @Override
         protected void initChannel(SocketChannel ch) throws Exception {
        /**
        * 采用Base 128 Varints進行編碼祠够,在消息頭上加上32個整數(shù),來標注數(shù)據(jù)的長度粪牲。
        */
        ch.pipeline().addLast("protobufVarint32FrameDecoder", new ProtobufVarint32FrameDecoder());
        ch.pipeline().addLast("protobufDecoder", new ProtobufDecoder(AddressBookProtos.AddressBook.getDefaultInstance()));

        /**
         * 對采用Base 128 Varints進行編碼的數(shù)據(jù)解碼
         */
        ch.pipeline().addLast("protobufVarint32LengthFieldPrepender", new ProtobufVarint32LengthFieldPrepender());
        ch.pipeline().addLast("protobufEncoder", new ProtobufEncoder());
        ch.pipeline().addLast(new ServerHandler());
        }

增加已經提供了的解碼古瓤、編碼器,在業(yè)務處理的handle中可以這么拿數(shù)據(jù)腺阳。

    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        AddressBookProtos.AddressBook addressBookProtos = (AddressBookProtos.AddressBook)msg;
        List<AddressBookProtos.Person> list = addressBookProtos.getPeopleList();
    }

任然是可以直接強轉成目標對象落君。然后獲取里面的成員變量。

客戶端:
在管道里加上那4個編碼亭引、解碼器绎速。然后在業(yè)務代碼中這樣定義數(shù)據(jù)并且直接塞到ctx中就可以了。其余的根本不用操心焙蚓,都封裝好了纹冤,我們只需要關心自己的業(yè)務實現(xiàn)。

    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        AddressBookProtos.AddressBook pb = AddressBookProtos.AddressBook.newBuilder()
                .addPeople(
                        AddressBookProtos.Person.newBuilder().setEmail("345@qq.com").setId(34).setName("zhangsn")
                )
                .addPeople(
                        AddressBookProtos.Person.newBuilder().setEmail("123@163.com").setId(12).setName("lisi")
                )
                .build();

        ChannelFuture future = ctx.writeAndFlush(pb);
        future.addListener(f ->  ctx.close());
    }

3.使用http協(xié)議

Netty對http協(xié)議有自己的抽象购公,把一個FullHttpRequest抽象成了HttpRequest赵哲、HttpContent、LastHttpContent君丁。生成一個http request也有點不同。例子演示了将宪,客戶端發(fā)送http請求绘闷,服務端接收并發(fā)送http響應到客戶橡庞,客戶端接收響應之后斷開連接。
服務端:

            new ChannelInitializer<SocketChannel>(){
            @Override
            protected void initChannel(SocketChannel ch) throws Exception {
               /*
               * https部分
                File certificate = new File("/Users/public.crt"); // 證書
                File privateKey = new File("/Users/private.pem"); // 私鑰
                final SslContext context = SslContextBuilder.forServer(certificate, privateKey).build();
                SSLEngine engine = context.newEngine(ch.alloc());
                ch.pipeline().addLast(new SslHandler(engine));
                */

//                            ch.pipeline().addLast("decoder", new HttpRequestDecoder());
//                            ch.pipeline().addLast("encoder", new HttpResponseEncoder());
                ch.pipeline().addLast(new HttpServerCodec()); //等于上面那兩個
                ch.pipeline().addLast(new HttpObjectAggregator(512 * 1024)); //聚合把頭部和content聚合在了一起
                ch.pipeline().addLast(new HttpServiceHandle());
            }
        }

如果不使用聚合印蔗,那么在接收的時候會多次觸發(fā)read方法扒最,第一次接收HttpRequest,之后接收HttpContent內容华嘹。使用聚合HttpServerCodec之后吧趣,接收的參數(shù)即有HttpRequest也有HttpContent。

客戶端發(fā)送與接收:

    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        URI uri = new URI("http://127.0.0.1:8889");
        String msg = "Message from client";
        DefaultFullHttpRequest request = new DefaultFullHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.POST,
                uri.toASCIIString(), Unpooled.wrappedBuffer(msg.getBytes("UTF-8")));

        // 構建http請求
        request.headers().set(HttpHeaders.Names.HOST, "127.0.0.1");
        request.headers().set(HttpHeaders.Names.CONNECTION, HttpHeaders.Values.KEEP_ALIVE);
        request.headers().set(HttpHeaders.Names.CONTENT_LENGTH, request.content().readableBytes());
        // 發(fā)送http請求
        ctx.writeAndFlush(request);
    }

    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        if (msg instanceof HttpResponse) {
            HttpResponse response = (HttpResponse) msg;
            System.out.println("CONTENT_TYPE:" + response.headers().get(HttpHeaders.Names.CONTENT_TYPE));
        }
        if(msg instanceof HttpContent) {
            HttpContent content = (HttpContent)msg;
            ByteBuf buf = content.content();
            System.out.println(buf.toString(io.netty.util.CharsetUtil.UTF_8));
            buf.release();
        }
    }

服務端接收:

    private HttpRequest request;

    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        //http 如果請求沒有聚合耙厚,則分段傳輸過來
        if (msg instanceof HttpRequest) {
            request = (HttpRequest) msg;
            String uri = request.uri();
            System.out.println("Uri:" + uri);
        }

        if (msg instanceof HttpContent) {
            HttpContent content = (HttpContent) msg;
            ByteBuf buf = content.content();
            System.out.println(buf.toString(io.netty.util.CharsetUtil.UTF_8));
            buf.release();

            String res = "response from server";
            FullHttpResponse response = new DefaultFullHttpResponse(HTTP_1_1,
                    OK, Unpooled.wrappedBuffer(res.getBytes("UTF-8")));
            response.headers().set(CONTENT_TYPE, "text/plain");
            response.headers().set(CONTENT_LENGTH, response.content().readableBytes());
            if (HttpHeaders.isKeepAlive(request)) {
                response.headers().set(CONNECTION, HttpHeaders.Values.KEEP_ALIVE);
            }
            ctx.write(response);
            ctx.flush();
        }
    }

4.心跳

這個東西應該是與HTTP長連接或者是websocket一起的强挫,這里獨立出來了。
服務端:

                    {
                        @Override
                        protected void initChannel(SocketChannel ch) throws Exception {
                            ch.pipeline().addLast(new IdleStateHandler(10, 0, 0, TimeUnit.SECONDS));
                            ch.pipeline().addLast(new WebSocketServiceHandle());
                        }
                    }

handle薛躬,如果10秒內沒有觸發(fā)讀俯渤,那么就會觸發(fā)userEventTriggered方法。

    int dealTime = 0;

    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        InetSocketAddress socketAddress = (InetSocketAddress)ctx.channel().remoteAddress();
        String ip = socketAddress.getHostName() + ":" + socketAddress.getPort();

        ByteBuf byteBuf = (ByteBuf)msg;
        String message = byteBuf.toString(CharsetUtil.UTF_8);
        System.out.println(ip + ":" + message);
    }

    @Override
    public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
        if (dealTime == 2){
            System.out.println("關嘍");
            ctx.channel().close();
        }
        dealTime++;
        String recall = "are you alive?" ;
        ByteBuf buffer = Unpooled.copiedBuffer(recall, Charset.forName("UTF-8"));
        ctx.writeAndFlush(buffer);
        super.userEventTriggered(ctx, evt);
    }

客戶端:就是一個簡單的應該型宝,連接上之后什么也不干八匠,干等10秒,等待服務端發(fā)來詢問趴酣。

     @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        ByteBuf byteBuf = (ByteBuf)msg;
        String message = byteBuf.toString(CharsetUtil.UTF_8);
        System.out.println("message from service: " + message);

        String recall = "hello service i am alive";
        ByteBuf buffer = Unpooled.copiedBuffer(recall, Charset.forName("UTF-8"));
        ctx.writeAndFlush(buffer);
    }

文件讀寫梨树、websocket、最終demo岖寞。抡四。咕咕咕

相關連接:
[netty]--最通用TCP黏包解決方案:LengthFieldBasedFrameDecoder和LengthFieldPrepender
Protocol Buffer的基本使用(六)
Protocol Buffer 語法(syntax)

?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市慎璧,隨后出現(xiàn)的幾起案子床嫌,更是在濱河造成了極大的恐慌,老刑警劉巖胸私,帶你破解...
    沈念sama閱讀 211,194評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件厌处,死亡現(xiàn)場離奇詭異,居然都是意外死亡岁疼,警方通過查閱死者的電腦和手機阔涉,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,058評論 2 385
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來捷绒,“玉大人瑰排,你說我怎么就攤上這事∨龋” “怎么了椭住?”我有些...
    開封第一講書人閱讀 156,780評論 0 346
  • 文/不壞的土叔 我叫張陵,是天一觀的道長字逗。 經常有香客問我京郑,道長宅广,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,388評論 1 283
  • 正文 為了忘掉前任些举,我火速辦了婚禮跟狱,結果婚禮上,老公的妹妹穿的比我還像新娘户魏。我一直安慰自己驶臊,他們只是感情好,可當我...
    茶點故事閱讀 65,430評論 5 384
  • 文/花漫 我一把揭開白布叼丑。 她就那樣靜靜地躺著关翎,像睡著了一般。 火紅的嫁衣襯著肌膚如雪幢码。 梳的紋絲不亂的頭發(fā)上笤休,一...
    開封第一講書人閱讀 49,764評論 1 290
  • 那天,我揣著相機與錄音症副,去河邊找鬼店雅。 笑死,一個胖子當著我的面吹牛贞铣,可吹牛的內容都是我干的闹啦。 我是一名探鬼主播,決...
    沈念sama閱讀 38,907評論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼辕坝,長吁一口氣:“原來是場噩夢啊……” “哼窍奋!你這毒婦竟也來了?” 一聲冷哼從身側響起酱畅,我...
    開封第一講書人閱讀 37,679評論 0 266
  • 序言:老撾萬榮一對情侶失蹤琳袄,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后纺酸,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體窖逗,經...
    沈念sama閱讀 44,122評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 36,459評論 2 325
  • 正文 我和宋清朗相戀三年餐蔬,在試婚紗的時候發(fā)現(xiàn)自己被綠了碎紊。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,605評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡樊诺,死狀恐怖仗考,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情词爬,我是刑警寧澤秃嗜,帶...
    沈念sama閱讀 34,270評論 4 329
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響痪寻,放射性物質發(fā)生泄漏螺句。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,867評論 3 312
  • 文/蒙蒙 一橡类、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧芽唇,春花似錦顾画、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,734評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至炮捧,卻和暖如春庶诡,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背咆课。 一陣腳步聲響...
    開封第一講書人閱讀 31,961評論 1 265
  • 我被黑心中介騙來泰國打工末誓, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人书蚪。 一個月前我還...
    沈念sama閱讀 46,297評論 2 360
  • 正文 我出身青樓喇澡,卻偏偏與公主長得像,于是被迫代替她去往敵國和親殊校。 傳聞我的和親對象是個殘疾皇子晴玖,可洞房花燭夜當晚...
    茶點故事閱讀 43,472評論 2 348

推薦閱讀更多精彩內容

  • Spring Cloud為開發(fā)人員提供了快速構建分布式系統(tǒng)中一些常見模式的工具(例如配置管理,服務發(fā)現(xiàn)为流,斷路器呕屎,智...
    卡卡羅2017閱讀 134,629評論 18 139
  • Netty的簡單介紹 Netty 是一個 NIO client-server(客戶端服務器)框架,使用 Netty...
    AI喬治閱讀 8,395評論 1 101
  • 計算機網(wǎng)絡概述 網(wǎng)絡編程的實質就是兩個(或多個)設備(例如計算機)之間的數(shù)據(jù)傳輸敬察。 按照計算機網(wǎng)絡的定義秀睛,通過一定...
    蛋炒飯_By閱讀 1,215評論 0 10
  • github鏈接:https://github.com/LantaoYu/SeqGAN論文及appendix里有很...
    yingtaomj閱讀 1,731評論 0 2
  • 妍最后一次見他是在他母親的葬禮上琅催。 妍看他悲痛的表情,心中愧疚虫给。 林南生藤抡,對不起。 林南生只是...
    柯家妍閱讀 432評論 0 0