netty的学习(一): netty的官网解读

最近在做信道服务:说白了就是通信服务.这里准备做一次深入的学习

老规矩 : 先去撸一撸官网,然后再说别的https://netty.io/wiki/user-guide.html

然后看到

  • User guide for 5.x - ABANDONED VERSION - NOT SUPPORTED 
    简单来说就是5人家废弃了.所以大家消停的用4就好了,要求的jdk在1.6+

因为是翻译文档,所以吧我也不知道他会写啥,他写啥我翻译啥,凡是关键的地方我会写上我的名字yulei,所以大家可用通过搜索这个关键字提升效率

前言 解决方案: netty就是用来解决web通信和服务间通信的一个工具

Netty项目致力于提供一个异步事件驱动的网络应用程序框架和工具,以快速开发可维护的高性能和高可扩展性协议服务器和客户端。

换句话说,Netty是一个NIO客户端服务器框架,可以快速轻松地开发网络应用程序,例如协议服务器和客户端。它极大地简化和简化了网络编程,例如TCP和UDP套接字服务器的开发。

“快速简便”并不意味着最终的应用程序将遭受可维护性或性能问题的困扰。 Netty经过精心设计,结合了从许多协议(例如FTP,SMTP,HTTP以及各种基于二进制和文本的旧式协议)的实施中获得的经验。结果,Netty成功地找到了一种无需妥协即可轻松实现开发,性能,稳定性和灵活性的方法。

一些用户可能已经发现其他声称具有相同优势的网络应用程序框架,并且您可能想问一下Netty与他们有何不同。答案是它所基于的哲学。 Netty旨在从第一天开始就API和实施方面为您提供最舒适的体验。这不是有形的东西,但是您会意识到,这种哲学将使您在阅读本指南并与Netty一起玩时更加轻松。

--------------以上全是netty的google翻译结果,说实话, 我虽然写完了, 用的是springboot-netty, 我得全都整完了再决定我究竟是不是得换回来, 我得经验告诉我, 凡是能用一手的就别开二手车,出了坑各种悲剧.

Getting Started

This chapter tours around the core constructs of Netty with simple examples to let you get started quickly. You will be able to write a client and a server on top of Netty right away when you are at the end of this chapter.

If you prefer a top-down approach in learning something, you might want to start from Chapter 2, Architectural Overview and get back here.

就在我感叹netty文档就这么点的时候, 比spring人性多了, 看完这段就悲剧了, 他的意思是这个文档是个入门文档,说白是个helloworld,你要想详细了解机制, 可以啊, 从https://netty.io/3.8/guide/#architecture开始看,然后看到的就是各种图.我们先继续往下撸一下helloWord再说!!!!

Writing a Discard Server
世界上最简单的协议不是“ Hello,World!”。 而是DISCARD。 它是一种协议,它丢弃任何收到的数据而没有任何响应。

要实现DISCARD协议,您唯一需要做的就是忽略所有接收到的数据。 让我们直接从处理程序实现开始,该实现处理Netty生成的I / O事件。

package io.netty.example.discard;

import io.netty.buffer.ByteBuf;

import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;

/**
 * Handles a server-side channel.
 */
public class DiscardServerHandler extends ChannelInboundHandlerAdapter { // (1)

    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) { // (2)
        // Discard the received data silently.
        ((ByteBuf) msg).release(); // (3)
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) { // (4)
        // Close the connection when an exception is raised.
        cause.printStackTrace();
        ctx.close();
    }
}

然后...........................我擦, 说好的helloworld呢.凌乱了......好吧, 我记得好像有个包是netty-all,我试试

然后果断建了个项目yulei

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>org.example</groupId>
    <artifactId>youyou-ai-netty</artifactId>
    <version>1.0-SNAPSHOT</version>

    <dependencies>
        <!-- https://mvnrepository.com/artifact/io.netty/netty-all -->
        <dependency>
            <groupId>io.netty</groupId>
            <artifactId>netty-all</artifactId>
            <version>4.1.52.Final</version>
        </dependency>

    </dependencies>

</project>
package com.daishu.netty;

import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;

public class DiscardServerHandler  extends ChannelInboundHandlerAdapter {
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) { // (2)
        // Discard the received data silently.
        ((ByteBuf) msg).release(); // (3)
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) { // (4)
        // Close the connection when an exception is raised.
        cause.printStackTrace();
        ctx.close();
    }
}

别吐槽啊,他那明显就是源码包我就是想去看看源码

开心,没报错,我们继续!!!!!!!!!!!!!!

1.DiscardServerHandler 是ChannelInboundHandlerAdapter的实现

2.每当从客户端接收到新数据时,就使用接收到的消息调用此方法。 在此示例中,接收到的消息的类型为ByteBuf。(有点懵逼!!!!)

3.为了实现DISCARD协议,处理程序必须忽略收到的消息。 ByteBuf是一个引用计数的对象,必须通过release()方法显式释放它。 请记住,释放任何传递给处理程序的引用计数对象是处理程序的责任。 通常,channelRead()处理程序方法的实现方式如下:

@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) {
    try {
        // Do something with msg
    } finally {
        ReferenceCountUtil.release(msg);
    }
}

终于明白了!!!!开心, 这货是的意思是最终啊我们要把它释放掉, 我还是喜欢helloworld  yulei

package com.daishu.netty;

import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.util.ReferenceCountUtil;

public class DiscardServerHandler extends ChannelInboundHandlerAdapter {// (1)


    /**
     * 时间服务: 和内容服务的区别在于time 不包含任何请求就发送包含32位整数的消息,并在发送消息后关闭连接。
     * yulei重点来了,这玩意没有消息, 而且发送后连接就关闭了,前者我能明白可能为了保持连接,后者我还没想出来为个毛线非要把连接关闭了,全当他是个close的demo
     * @param ctx
     * yulei 当建立连接并准备生成流量时,将调用channelActive()方法。
     */
    @Override
    public void channelActive(final ChannelHandlerContext ctx) { // (1)
        System.out.printf("当建立连接并准备生成流量时,将调用channelActive()方法。");

        final ByteBuf time = ctx.alloc().buffer(4); // (2)
        time.writeInt((int) (System.currentTimeMillis() / 1000L + 2208988800L));
        //向请求方返回信息
        final ChannelFuture f = ctx.writeAndFlush(time); // (3)
        f.addListener(new ChannelFutureListener() {
            public void operationComplete(ChannelFuture future) {
                System.out.printf("返回time已经成功");
                assert f == future;
                ctx.close();
            }
        }); // (4)
    }

    /**
     * 内容服务
     * @param ctx
     * @param msg
     */
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) { // (2)
        // Discard the received data silently.
        try {
            // Do something with msg
            ByteBuf in = (ByteBuf) msg;
            //读取内容
            while (in.isReadable()) { // (1)
                System.out.print("接收内容"+(char) in.readByte());
                System.out.flush();
            }
            //返回内容给调用方
            System.out.print("返回内容给调用方"+msg);
            ctx.write(msg); // (1)
            ctx.flush(); // (2)
        } finally {
            ReferenceCountUtil.release(msg);
        }
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) { // (4)
        // Close the connection when an exception is raised.
        cause.printStackTrace();
        ctx.close();
    }
}
str那部分是后面的

4.当Netty因I / O错误而引发异常时,或者由于处理事件时引发异常而由处理程序实现引发异常时,将使用Throwable调用exceptionCaught()事件处理程序方法。 在大多数情况下,应该记录捕获的异常,并在此处关闭其关联的通道,尽管此方法的实现可能会有所不同,具体取决于您要处理特殊情况时要采取的措施。 例如,您可能想在关闭连接之前发送带有错误代码的响应消息。
到现在为止还挺好。 我们已经实现了DISCARD服务器的前半部分。 现在剩下的是编写main()方法,该方法使用DiscardServerHandler启动服务器。

说白了那就是个异常处理

So far so good. We have implemented the first half of the DISCARD server. What's left now is to write the main() method which starts the server with the DiscardServerHandle

然后我们要搞个启动类 , 然后我直接把解释加到代码上了哈 yulei

import io.netty.bootstrap.ServerBootstrap;

import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;

/**
 * Discards any incoming data.
 */
public class DiscardServer {

    private int port;

    public DiscardServer(int port) {
        this.port = port;
    }

    public void run() throws Exception {
        //NioEventLoopGroup是处理I / O操作的多线程事件循环。 Netty为不同类型的传输提供了各种EventLoopGroup实现。 在此示例中,我们正在实现服务器端应用程序,因此将使用两个NioEventLoopGroup。 第一个通常称为“老板”,接受传入的连接。 第二个通常称为“工人”,一旦老板接受连接并将注册的连接注册给工人,便处理已接受连接的流量。 使用多少个线程以及如何将它们映射到创建的通道取决于EventLoopGroup实现,甚至可以通过构造函数进行配置。
        //简单来说就是bossGroup是用来接受传入的连接,一旦boss接受到了链接就交给workerGroup处理.(为什么这么干其实我是知道的,装个逼!!!!,但是后续在读文档时候解释)
        EventLoopGroup bossGroup = new NioEventLoopGroup(); // (1)
        EventLoopGroup workerGroup = new NioEventLoopGroup();
        try {
            //ServerBootstrap是设置服务器的帮助程序类。 您可以直接使用Channel设置服务器。 但是,请注意,这是一个繁琐的过程,在大多数情况下您不需要这样做。
            //说白了这是设置服务器的, 建议就是你别瞎动,容易出bug
            ServerBootstrap b = new ServerBootstrap(); // (2)
            b.group(bossGroup, workerGroup)
                    //在这里,我们指定使用NioServerSocketChannel类,该类用于实例化新的Channel来接受传入的连接。
                    //这地方我的确不咋会,后面再说吧
                    .channel(NioServerSocketChannel.class) // (3)
                    // 此处指定的处理程序将始终由新接受的Channel评估。 ChannelInitializer是一个特殊的处理程序,旨在帮助用户配置新的Channel。 您很可能希望通过添加一些处理程序(例如DiscardServerHandler)来实现新的Channel的ChannelPipeline,以实现您的网络应用程序。 随着应用程序变得复杂,您可能会向管道添加更多处理程序,并最终将此匿名类提取到顶级类中。
                    //yulei 这玩意我也没太看懂,我用的是springboot-netty,easy的可怕.先放着吧,不过看到了DiscardServerHandler.管道调用了我的东西,开心
                    .childHandler(new ChannelInitializer<SocketChannel>() { // (4)
                        @Override
                        public void initChannel(SocketChannel ch) throws Exception {
                            ch.pipeline().addLast(new DiscardServerHandler());
                        }
                    })
                    //您还可以设置特定于Channel实现的参数。 我们正在编写一个TCP / IP服务器,因此我们可以设置套接字选项,例如tcpNoDelay和keepAlive。 请参考ChannelOption的apidocs和特定的ChannelConfig实现,以获取有关受支持的ChannelOptions的概述。
                    //yulei 这地方加了一句是因为我觉得这块代码挺牛逼的,我们看源码要理解设计的思想和设计模式
                    .option(ChannelOption.SO_BACKLOG, 128)          // (5)
                    //您是否注意到option()和childOption()? option()用于接受输入连接的NioServerSocketChannel。 childOption()用于父级ServerChannel接受的通道,在这种情况下为NioServerSocketChannel。
                    //yulei---这地方我是有点真的没看懂,得后续解释 option是设置boss的,child设置的Sever的父集合
                    .childOption(ChannelOption.SO_KEEPALIVE, true); // (6)

            // Bind and start to accept incoming connections.
            ChannelFuture f = b.bind(port).sync(); // (7)

            // Wait until the server socket is closed.
            // In this example, this does not happen, but you can do that to gracefully
            // shut down your server.
            f.channel().closeFuture().sync();
        } finally {
            workerGroup.shutdownGracefully();
            bossGroup.shutdownGracefully();
        }
    }

    public static void main(String[] args) throws Exception {
        int port = 8080;
        if (args.length > 0) {
            port = Integer.parseInt(args[0]);
        }

        new DiscardServer(port).run();
    }
}

后面是写他的client, 是我装逼的时候了.........

我决定用两个html页面......

想想先把他的粘上去吧

package com.daishu.netty.client;

import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;

import java.util.Date;

public class TimeClientHandler extends ChannelInboundHandlerAdapter {
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) {
        ByteBuf m = (ByteBuf) msg; // (1)
        try {
            long currentTimeMillis = (m.readUnsignedInt() - 2208988800L) * 1000L;
            System.out.println(new Date(currentTimeMillis));
            ctx.close();
        } finally {
            m.release();
        }
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
        cause.printStackTrace();
        ctx.close();
    }
}
package com.daishu.netty.client;

import io.netty.bootstrap.Bootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;

public class TimeClient {
    public static void main(String[] args) throws Exception {
        String host = "127.0.0.1";
        int port = 8080;
        EventLoopGroup workerGroup = new NioEventLoopGroup();

        try {
            Bootstrap b = new Bootstrap(); // (1)
            b.group(workerGroup); // (2)
            b.channel(NioSocketChannel.class); // (3)
            b.option(ChannelOption.SO_KEEPALIVE, true); // (4)
            b.handler(new ChannelInitializer<SocketChannel>() {
                @Override
                public void initChannel(SocketChannel ch) throws Exception {
                    ch.pipeline().addLast(new TimeClientHandler());
                }
            });

            // Start the client.
            ChannelFuture f = b.connect(host, port).sync(); // (5)

            // Wait until the connection is closed.
            f.channel().closeFuture().sync();
        } finally {
            workerGroup.shutdownGracefully();
        }
    }
}

---------------------------下面开始秀了---------------------------------------

先运行

DiscardServer

在运行

TimeClient

此时 serve输出日志

Connected to the target VM, address: '127.0.0.1:59527', transport: 'socket'
当建立连接并准备生成流量时,将调用channelActive()方法。返回time已经成功

client输出日志

Connected to the target VM, address: '127.0.0.1:59634', transport: 'socket'
Sat Oct 10 10:30:36 CST 2020
Disconnected from the target VM, address: '127.0.0.1:59634', transport: 'socket'

1.html

<!DOCTYPE html>
<html lang="en">
<body>
<div id="msg"></div>
<input type="text" id="text">
<input type="submit" value="send" οnclick="send()">
</body>
<script>
    var msg = document.getElementById("msg");
    var wsServer = "ws://127.0.0.1:8080?userId=u1";
    //var wsServer = 'ws://39.106.14.159:81'+"?userId=u1";
    var websocket = new WebSocket(wsServer);
    //监听连接打开
    websocket.onopen = function (evt) {
        msg.innerHTML = "The connection is open";
    };

    //监听服务器数据推送
    websocket.onmessage = function (evt) {
        msg.innerHTML += "<br>" + evt.data;
    };

    //监听连接关闭
    websocket.onclose = function (evt) {
        alert("连接关闭");
    };

    function send() {
        var data = document.getElementById("text").value;
        var  m=new Message('u1','u2','p2p',data);
        var s=JSON.stringify(m);
        websocket.send(s);
    }
    function Message(from, to,type, data){
        this.type=type;
        this.from=from;
        this.to=to;
        this.data=data;
    }
</script>
</html>

打开后server输出
当建立连接并准备生成流量时,将调用channelActive()方法。返回time已经成功

 

处理基于流的传输
套接字缓冲区的一个小警告
在基于流的传输中(例如TCP / IP),将接收到的数据存储到套接字接收缓冲区中。 不幸的是,基于流的传输的缓冲区不是数据包队列而是字节队列。 这意味着,即使您将两个消息作为两个独立的数据包发送,操作系统也不会将它们视为两个消息,而只是一堆字节。 因此,不能保证您所阅读的内容与远程对等方写的内容完全相同。 例如,让我们假设操作系统的TCP / IP堆栈已收到三个数据包:

发送时收到了三个数据包

由于基于流的协议具有此一般属性,因此很有可能在您的应用程序中以以下分段形式读取它们:

三个数据包拆分并合并为四个缓冲区

因此,无论是服务器端还是客户端,接收方都应将接收到的数据整理到一个或多个有意义的帧中,以使应用程序逻辑易于理解。 在上面的示例中,接收到的数据应采用以下格式:

四个缓冲区被分解为三个

=====================================================这块我是真的没看懂-------可能因为我的网页有几个图刷不出来然后继续往下看吧

第一个解决方案
现在让我们回到TIME客户端示例。 我们在这里有同样的问题。 32位整数是非常少量的数据,并且不太可能经常被分段。 但是,问题在于它可以被碎片化,并且碎片化的可能性会随着流量的增加而增加。

一种简单的解决方案是创建一个内部累积缓冲区,然后等待直到所有4个字节都被接收到内部缓冲区中为止。 以下是修改后的TimeClientHandler实现,可以解决此问题:

package com.daishu.netty.client;

import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;

import java.util.Date;

/***
 *
 */
public class TimeClientHandlerFirstSolution extends ChannelInboundHandlerAdapter {
    private ByteBuf buf;

    @Override
    public void handlerAdded(ChannelHandlerContext ctx) {
        buf = ctx.alloc().buffer(4); // (1)
    }

    @Override
    public void handlerRemoved(ChannelHandlerContext ctx) {
        //ChannelHandler具有两个生命周期侦听器方法:handlerAdded()和handlerRemoved()。 您可以执行任意(取消)初始化任务,只要它不会长时间阻塞即可。
        buf.release(); // (1)
        buf = null;
    }

    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) {
        ByteBuf m = (ByteBuf) msg;
        buf.writeBytes(m); // (2) 首先,应将所有接收到的数据累加到buf中。
        m.release();

        if (buf.readableBytes() >= 4) { // (3) 然后,处理程序必须检查buf是否有足够的数据(在此示例中为4个字节),然后继续进行实际的业务逻辑。 否则,当更多数据到达时,Netty将再次调用channelRead()方法,最终将累加所有4个字节
            long currentTimeMillis = (buf.readUnsignedInt() - 2208988800L) * 1000L;
            System.out.println(new Date(currentTimeMillis));
            ctx.close();
        }
    }

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

617/5000

第二种解决方案
尽管第一个解决方案已经解决了TIME客户端的问题,但修改后的处理程序看起来并不干净。 想象一个更复杂的协议,它由多个字段(例如可变长度字段)组成。 您的ChannelInboundHandler实现将很快变得难以维护。

您可能已经注意到,您可以在ChannelPipeline中添加多个ChannelHandler,因此,您可以将一个整体式ChannelHandler拆分为多个模块化模块,以降低应用程序的复杂性。 例如,您可以将TimeClientHandler拆分为两个处理程序:

package io.netty.example.time;

public class TimeDecoder extends ByteToMessageDecoder { // (1)
    @Override
    protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) { // (2)
        if (in.readableBytes() < 4) {
            return; // (3)
        }
        
        out.add(in.readBytes(4)); // (4)
    }
}

说实话这个地方我已经有点崩溃了, 大概明白他想干啥,又不太明白!!!!!!!!

索性我就直接跳过去!!!!!!!!!!!,等回头收拾你

Speaking in POJO instead of ByteBuf

-----------后面又说了概要和其他的

他建议吧netty的 io.netty.example package 好好看看 , 还有就是这玩意就是个helloworld,你想更深入的就得去继续深入的看

 

然后下午讨论我们自己netty的时候我想了一下拆包,粘包的问题,我这也没太涉及所以就不说了.看一下这个网址很详尽,很牛逼

https://blog.csdn.net/czk740960212/article/details/80165602

粘包问题的解决策略

由于底层的TCP无法理解上层的业务数据,所以在底层是无法保证数据包不被拆分和重组的,这个问题只能通过上层的应用协议栈设计来解决。业界的主流协议的解决方案,可以归纳如下: 
1. 消息定长,报文大小固定长度,例如每个报文的长度固定为200字节,如果不够空位补空格; 
2. 包尾添加特殊分隔符,例如每条报文结束都添加回车换行符(例如FTP协议)或者指定特殊字符作为报文分隔符,接收方通过特殊分隔符切分报文区分; 
3. 将消息分为消息头和消息体,消息头中包含表示信息的总长度(或者消息体长度)的字段; 
4. 更复杂的自定义应用层协议。

 

预知后事如何, 只能听下次分解了

 

 

 

 

 

 

 

 

 

 

 

 

 

 

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值