关于netty:Netty开发及粘包解决

<article class=“article fmt article-content”><h2>1. Netty介绍</h2><p>Netty是一款开源的Java网络编程框架,广泛应用于很多高流量的服务器端应用程序:</p><ul><li>异步和事件驱动:Netty基于NIO(非阻塞I/O)构建,操作都是异步回调来触发事件,如连贯建设、数据达到等。</li><li>高性能:Netty的一大长处就是高性能。它的设计可能让你最大限度地利用古代的多核硬件。</li><li>灵便的协定反对:Netty反对各种协定,包含TCP、UDP、HTTP/HTTPS、Unix Socket、WebSockets等。</li><li>零拷贝:Netty反对“零拷贝”,这能够缩小不必要的零碎调用,显著进步数据处理性能。</li></ul><p></p><p>Netty 目前最新版本是 4.1.95Final </p><p>很久之前 Netty就公布了 5 的测试版本,市场上都有很多介绍 Netty5 的书在卖了,但惋惜问题太多,最终废除了,目前仍然只保护 4 的版本。</p><h3>1.1. 组件</h3><h4>1.1.1. EventLoopGroup</h4><p>EventLoopGroup 是一个线程池,用于治理和调度 EventLoop 对象。在 Netty 中,每个 EventLoopGroup 有一个或多个 EventLoop,用于解决连贯申请和 I/O 操作,而每个EventLoop是单线程的。</p><p>所以Netty能够通过EventLoopGroup的结构调参,来实现不同的Reactor模型:</p><p>(1)既可也是单Reactor单线程模型:</p><pre><code>EventLoopGroup group = new NioEventLoopGroup(1);Bootstrap bootstrap = new Bootstrap();bootstrap.group(group)</code></pre><p>(2)也能够是 主从Reactor多线程模型:</p><pre><code>EventLoopGroup bossGroup = new NioEventLoopGroup();EventLoopGroup workerGroup = new NioEventLoopGroup(n);ServerBootstrap bootstrap = new ServerBootstrap();bootstrap.group(bossGroup, workerGroup)</code></pre><blockquote><strong>主从分工</strong></blockquote><ul><li><strong>BossEventLoopGroup</strong>:负责接管客户端的连贯申请。并将连贯申请调配给workerEventLoopGroup中的某个EventLoop进行解决。BossGroup中通常只需一个EventLoop;</li><li><strong>WorkerEventLoopGroup</strong>:负责解决服务器端的连贯和数据读写操作。每个EventLoop都绑定在一个具体的线程上,在运行过程中只解决该线程所监听的IO事件。 workerGroup通常须要多个EventLoop。</li></ul><h4>1.1.2. EventLoop</h4><p>EventLoop 则是事件循环的外围,负责监听和解决 Channel 中的事件和 I/O 操作。在 EventLoopGroup 中,每个 EventLoop 都是独立的,能够并发地解决多个 Channel 上的事件和 I/O 操作。</p><h4>1.1.3. Channel 和 ByteBuf</h4><blockquote><strong>定义</strong></blockquote><ul><li><strong>Channel</strong>:代表了一个网络通道,能够用于进行网络通信。通过应用 Channel,咱们能够连贯到近程服务器、发送和接收数据等。</li><li><strong>ByteBuf</strong>:则是用于治理和操作数据的缓冲区,通过应用 ByteBuf,咱们能够进行数据的读、写、复制、切片、合并等操作。</li></ul><blockquote><strong>搭配应用</strong></blockquote><p>在 Netty 中,Channel 和 ByteBuf 是紧密结合的,通常一次数据传输会波及到两个 Channel 和两个 ByteBuf 对象,别离代表了发送端和接收端的数据缓冲区。以下是 Channel 和 ByteBuf 的搭配应用流程:</p><ol><li><strong>创立 Channel</strong>:首先,咱们须要创立一个 Channel 对象,用于示意一个网络通道。能够通过 Bootstrap 或 ServerBootstrap 类来创立 Channel 对象,并配置其参数和属性。</li><li><strong>写入数据</strong>:当须要向近程服务器发送数据时,咱们须要先将数据写入到 ByteBuf 对象中,而后将 ByteBuf 对象写入到 Channel 对象中。在写入数据时,能够通过 write() 或 writeAndFlush() 办法来实现。</li><li><strong>读取数据</strong>:当近程服务器发送数据时,咱们须要通过 Channel 对象来读取数据。读取数据时,Channel 对象会将数据存储到 ByteBuf 对象中,咱们能够通过 read() 办法来获取数据或数据大小。在读取数据之后,咱们须要及时开释 ByteBuf 对象,以便防止内存透露和内存溢出等问题。</li><li><strong>开释资源</strong>:当数据传输实现后,咱们须要开释 Channel 和 ByteBuf 对象的资源。在 Netty 中,Channel 和 ByteBuf 对象都须要显式地开释资源,以防止内存透露和内存溢出等问题。能够通过 release() 办法来开释 ByteBuf 对象,通过 close() 办法来开释 Channel 对象。</li></ol><h4>1.1.4. ChannelPipeline 和 Channel</h4><p>定义</p><ul><li><strong>Channel</strong>:对象示意一个通信通道,能够进行数据的读写和事件的触发等操作。</li><li><strong>ChannelPipeline</strong>:则是一个事件处理器的链表,用于解决 Channel 中的事件和数据。</li></ul><p>每个 Channel 都有一个关联的 ChannelPipeline 对象,当有事件产生时,Netty 会将事件从 Channel 中传递到 ChannelPipeline 中,而后依照程序顺次触发各个事件处理器 ChannelHandler 的逻辑。当事件处理完毕后,Netty 会将处理结果返回到 Channel 中,以便进行数据的读写等操作。</p><p>在 ChannelPipeline 中,能够增加多个事件处理器,用于解决不同类型的事件和数据。例如,能够增加一个音讯解码器、一个音讯编码器、一个业务逻辑处理器等。每个事件处理器都能够进行特定的逻辑解决,并将处理结果传递给下一个事件处理器。</p><p></p><h3>1.2. 网络协议</h3><p>Netty是一个十分弱小和灵便的网络编程框架,它反对多种通信协议。以下是一些Netty反对的通信协议:</p><ul><li><strong>TCP/IP 和 UDP/IP</strong>:Netty 提供了底层的网络通信反对,能够构建基于TCP/IP或UDP/IP的利用。</li><li><strong>HTTP/HTTPS and HTTP/2</strong>:Netty 提供了HTTP、HTTPS以及HTTP/2的高级反对。</li><li><strong>WebSocket</strong>:Netty 反对 WebSocket,容许 Web 浏览器和服务器之间进行全双工通信。</li><li><strong>Google Protobuf</strong>:Netty 为 Google 的 Protobuf 序列化库提供了反对。</li><li><strong>SSL/TLS</strong>:通过JDK的Secure Socket Extension (JSSE),Netty 反对 SSL/TLS 实现平安通信。</li><li><strong>Unix Domain Socket</strong>:从 Netty 4.1版本开始,Netty也开始反对 Unix Domain Socket。</li></ul><p>因为 Netty 反对的网络协议丰盛,所以当有非Http协定网络通信的需要时,大家第一工夫会想到 Netty。</p><h2>2. 代码示例</h2><h3>2.1. 基于tcp协定</h3><h4>2.1.1.服务端</h4><blockquote><strong>pom</strong></blockquote><pre><code class=“xml”> <dependency> <groupId>io.netty</groupId> <artifactId>netty-all</artifactId> <version>4.1.63.Final</version> </dependency></code></pre><blockquote><strong>服务端</strong></blockquote><pre><code class=“java”>@Componentpublic class NettyServer { // 创立两个线程组,别离用于接管客户端连贯和解决网络IO操作 private final EventLoopGroup bossGroup = new NioEventLoopGroup(); private final EventLoopGroup workerGroup = new NioEventLoopGroup(); @PostConstruct public void start() throws InterruptedException { ServerBootstrap b = new ServerBootstrap(); b.group(bossGroup, workerGroup) // 指定应用 NioServerSocketChannel 作为通道实现 .channel(NioServerSocketChannel.class) // 定义 ChannelPipeline(多个ChannelHandler组合) .childHandler(new ChannelInitializer<SocketChannel>() { @Override public void initChannel(SocketChannel ch) throws Exception { ch.pipeline().addLast(new StringDecoder(CharsetUtil.UTF_8)); ch.pipeline().addLast(new StringEncoder(CharsetUtil.UTF_8)); ch.pipeline().addLast(new ServerHandler()); } }); // 绑定端口,开始接管进来的连贯 ChannelFuture f = b.bind(8080).sync(); if (f.isSuccess()) { System.out.println(“启动Netty服务胜利,端口号:” + 8080); } } @PreDestroy public void shutdown() { bossGroup.shutdownGracefully(); workerGroup.shutdownGracefully(); }}</code></pre><blockquote><strong>ChannelHandler 音讯解决</strong></blockquote><pre><code class=“java”>public class ServerHandler extends SimpleChannelInboundHandler<String> { @Override protected void channelRead0(ChannelHandlerContext ctx, String msg) throws Exception { System.out.println(“Received message from client: " + msg); // 回复音讯给客户端 //ctx.writeAndFlush(“Received your message: " + msg); } @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) { cause.printStackTrace(); ctx.close(); }}</code></pre><h4>2.1.2.客户端</h4><blockquote><strong>客户端</strong></blockquote><pre><code class=“java”>@DependsOn({“nettyServer”})@Componentpublic class NettyClient { private EventLoopGroup group; private Channel channel; @PostConstruct public void start() throws InterruptedException { group = new NioEventLoopGroup(); Bootstrap bootstrap = new Bootstrap(); bootstrap.group(group) .channel(NioSocketChannel.class) .handler(new ChannelInitializer<SocketChannel>() { @Override protected void initChannel(SocketChannel socketChannel) { ch.pipeline().addLast(new StringDecoder(CharsetUtil.UTF_8)); ch.pipeline().addLast(new StringEncoder(CharsetUtil.UTF_8)); socketChannel.pipeline().addLast(new ClientHandler()); } }); ChannelFuture future = bootstrap.connect(“127.0.0.1”, 8080).sync(); if (future.isSuccess()) { System.out.println(“连贯服务器胜利”); } channel = future.channel(); } @PreDestroy public void destroy() { if (group != null) { group.shutdownGracefully(); } }</code></pre><blockquote><strong>ChannelHandler 音讯解决</strong></blockquote><pre><code class=“java”>public class ClientHandler extends SimpleChannelInboundHandler<String> { @Override protected void channelRead0(ChannelHandlerContext ctx, String msg) { System.out.println(“Server response:” + msg); } @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) { cause.printStackTrace(); ctx.close(); }}</code></pre><h3>2.2. 基于Unix Socket协定</h3><p>其余的不变,这里只关注客户服务端代码。</p><h4>2.2.1. 代码</h4><blockquote><strong>服务端</strong></blockquote><pre><code class=“java”>private final EventLoopGroup bossGroup = new KQueueEventLoopGroup(); private final EventLoopGroup workerGroup = new KQueueEventLoopGroup(); @PostConstruct public void start() throws InterruptedException { ServerBootstrap b = new ServerBootstrap(); b.group(bossGroup, workerGroup) .channel(KQueueServerDomainSocketChannel.class) .childHandler(new ChannelInitializer<KQueueDomainSocketChannel>() { @Override public void initChannel(KQueueDomainSocketChannel ch) throws Exception { ch.pipeline().addLast(new StringDecoder(CharsetUtil.UTF_8)); ch.pipeline().addLast(new StringEncoder(CharsetUtil.UTF_8)); ch.pipeline().addLast(new ServerHandler()); } }); ChannelFuture f = b.bind(new DomainSocketAddress("/tmp/test.sock”)).sync(); if (f.isSuccess()) { System.out.println(“启动Netty服务胜利,文件:” + “/tmp/test.sock”); } }</code></pre><blockquote><strong>客户端</strong></blockquote><pre><code class=“java”>public void start() throws InterruptedException { group = new KQueueEventLoopGroup(); Bootstrap bootstrap = new Bootstrap(); bootstrap.group(group) .channel(KQueueDomainSocketChannel.class) .handler(new ChannelInitializer<KQueueDomainSocketChannel>() { @Override protected void initChannel(KQueueDomainSocketChannel socketChannel) { socketChannel.pipeline().addLast(new StringDecoder(CharsetUtil.UTF_8)); socketChannel.pipeline().addLast(new StringEncoder(CharsetUtil.UTF_8)); socketChannel.pipeline().addLast(new ClientHandler()); } }); ChannelFuture future = bootstrap.connect(new DomainSocketAddress("/tmp/test.sock”)).sync(); if (future.isSuccess()) { System.out.println(“连贯服务器胜利”); } channel = future.channel(); }</code></pre><h4>2.2.2. 剖析</h4><blockquote><strong>Unix Socket协定</strong></blockquote><p>Unix Domain Socket(简称UDS)是一个用于实现本地过程间通信的协定。与应用网络套接字(socket)进行通信不同,UDS仅用于同一台机器上的相邻过程之间的通信。</p><p>在Unix/Linux零碎中,UDS通常被用于代替TCP/IP套接字来进步性能和安全性。不过它们能够通过文件系统门路来建设连贯,不能跨机器通信。</p><blockquote><strong>Netty中协定切换</strong></blockquote><p>通过比照上述代码,能够看出netty中切换协定是比较简单的,换成对应的 Channel 实现类,以及连贯形式就能够了。</p><p>因为是mac中运行,示例代码中用KQueueDomainSocketChannel代替DomainSocketChannel</p><h3>2.3. 测试</h3><blockquote><strong>Controller发消息</strong></blockquote><pre><code class=“java”>@RestControllerpublic class MsgController { @Autowired private NettyClient nettyClient; @PostMapping("/send") public ResponseEntity<Void> sendMsg(@RequestBody String msg) { System.out.println(msg.getBytes(StandardCharsets.UTF_8).length); try { for (int i = 0; i < 1000; i++) { nettyClient.send(msg); } return new ResponseEntity<>(HttpStatus.OK); } catch (Exception e) { return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR); } }}</code></pre><blockquote><strong>测试后果</strong></blockquote><p>后面曾经基于 TCP协定写好了netty的客户端、服务端,<br/>当初写接口,能够通过客户端给服务端发消息,不过单次调用会一次性发1000遍。</p><p>接口调用传入:hello</p><p>预期后果:</p><pre><code>Received message from client: helloReceived message from client: hello… … // 同样输入1000遍</code></pre><p>理论后果:</p><pre><code>Received message from client: helloReceived message from client: hellohelloReceived message from client: helloheReceived message from client: llohellohellohellohelloReceived message from client: hellohellohello… … // 无规则</code></pre><p>呈现问题的起因就是下一章要将的粘包、拆包问题。</p><h2>3. 粘包、拆包问题</h2><h3>3.1. 问题剖析</h3><h4>3.1.1. tcp协定呈现问题的起因</h4><p>粘包/拆包问题是由TCP协定自身造成的,和Netty自身无关,任何基于TCP协定实现数据传输的技术都会面临这个问题,起因如下:</p><ul><li><strong>应用程序写入数据的字节大小大于套接字发送缓冲区的大小</strong>:这种状况下,会产生拆包景象,发送方的TCP协定栈会把一次应用程序的发送操作分成多个数据段进行发送。</li><li><strong>进行了屡次写入操作,但数据没有被及时发送进来</strong>:这可能是因为TCP的Nagle算法造成的。</li><li><strong>应用程序读取操作不及时</strong>:如果接管方面的应用层没有及时读取接收缓冲区的数据,造成沉积,从而造成一个大的数据块。如果此时应用层进行数据读取,就容易读取到多个TCP数据段的数据,造成了粘包景象。</li><li><strong>网络环境等硬件问题</strong>:如网络提早、抖动等,也可能导致多个小的数据包合并为一个大包进行传送,从而导致粘包。</li></ul><p></p><p>解决粘包和拆包问题的根本策略就是在应用层引入数据边界。常见的办法有:<strong>固定长度、分隔符、在包头中退出长度字段</strong>等。</p><h4>3.1.2. 其余协定为什么没问题</h4><blockquote><strong>HTTP 协定</strong></blockquote><p>HTTP 协定 基于 TCP 协定构建,而 TCP 是一种面向流的协定,所以实践上可能会有粘包问题。然而在理论利用中,HTTP 协定曾经做了明确的分包解决,因而通常不须要开发者去解决粘包问题,HTTP 应用了一些特定的形式来定义数据包的边界:</p><p>对于 HTTP/1.0 和 HTTP/1.1,一次残缺的 HTTP 交互由一个申请和一个响应组成,它们都是绝对独立的。申请和响应都有明确的开始行(申请行或状态行)和完结标记(如 Content-Length 头或 chunked 编码表示的音讯体长度)。这样能够很分明地晓得报文的开始和完结,防止了粘包问题。<br/>对于 HTTP/2,它引入了二进制帧的概念。每个帧有明确的长度和类型,这也使得在接收端能够精确地解析出各个帧,防止粘包问题。</p><blockquote><strong>UDP 协定</strong></blockquote><p>UDP 协定 是一种无连贯的、不牢靠的协定,它并没有像TCP协定那样提供流量管制和拥塞管制等性能,因而在传输过程中可能会呈现丢包或乱序等问题。因为UDP协定采纳数据报形式进行传输,每个UDP数据报都有独立的头部标识,因而不会呈现粘包问题。</p><blockquote><strong>WebSocket 协定</strong></blockquote><p>WebSocket 协定 建设连贯后,客户端和服务器之间会放弃长时间的连贯状态,能够随时发送和接收数据。当服务器发送数据时,会将数据封装到一个残缺的WebSocket帧中,并通过TCP协定进行传输。而客户端收到数据后,会从WebSocket帧中解析出数据,并进行相应解决。这样就防止了TCP协定中的“粘包”和“拆包”问题。</p><h4>3.1.3. Unix Socket 为什么也有问题</h4><p>Unix Socket(也被称为 Unix Domain Socket,UDS)次要反对以下两种类型的通信协议:</p><ul><li>流式协定 (SOCK_STREAM): 相似于 TCP,在发送和接收数据时提供了字节流服务。数据在两个方向上都是有序的,并且不会反复或者失落。这种模式下,一端发送的数据程序和另一端接管的数据程序是雷同的。</li><li>数据报协定 (SOCK_DGRAM): 这种类型的 socket 提供了一种无需连贯的、固定大小的音讯服务,相似于 UDP。每次读操作都返回最多一条残缺的音讯;如果音讯超出缓冲区的大小,那么该音讯可能会被截断。</li></ul><p>Unix Socket 的这两种模式在行为上与 TCP 和 UDP 很类似。因而在基于 SOCK_STREAM 协定应用 Netty 开发服务端和客户端时,可能会呈现相似粘包的问题。</p><p>后面有现成的基于Unix Stream协定实现的代码,咱们同样调用接口试一下,发现 Unix Socket 同样会产生粘包问题</p><h4>3.1.4. 解决思路</h4><p>联合HTTP、UDP、WebSocket 解决粘包/拆包问题的思路,同样也能够推导解决TCP问题的思路:在发送数据时,应该设计一种协定来确定音讯的边界,比方:增加非凡的分隔符,或者在每个音讯的头部蕴含音讯的长度等。</p><p>基于这个思路,Netty 框架提供了 LineBasedFrameDecoder、DelimiterBasedFrameDecoder和 LengthFieldBasedFrameDecoder等解决方案,上面一一介绍。</p><h3>3.2. 解决方案</h3><h4>3.2.1. LineBasedFrameDecoder</h4><blockquote><strong>解决形式</strong></blockquote><p>应用行结束符作为数据包的分隔符。每条音讯前面都有一个行结束符(例如 \n 或 \r\n),它会始终读取字节直到遇到这个结束符,而后把之前读取到的字节组装成一条音讯。</p><p>如果没有找到行结束符,那么就认为以后还没有读取到残缺的数据包,须要将曾经读取到的字节保存起来,期待下次读取。</p><blockquote><strong>代码-客户端批改</strong></blockquote><p>发送音讯的办法中,每条音讯结尾都加上行结束符后缀:</p><pre><code class=“java”>public void send(String msg) { if (channel != null) { channel.writeAndFlush(msg + “\n”); } else { System.out.println(“message sending failed, connection not established”); } }</code></pre><blockquote><strong>代码-服务端批改</strong></blockquote><p>在 ChannelPipeline 中加上下列解码的 ChannelHandler:</p><pre><code class=“java”>ch.pipeline().addLast(new LineBasedFrameDecoder(1024));</code></pre><blockquote><strong>局限性</strong></blockquote><ul><li><strong>固定的分隔符</strong>:次要是通过 \n 或 \r\n 来标识一个残缺的音讯。这意味着如果你的协定中没有应用这两个字符作为完结标记,或者这两个字符在音讯体中有非凡含意,则不能正确工作。</li><li><li><strong>只反对文本数据</strong>: 次要设计为解决文本协定。对于二进制数据,尤其是蕴含 <code>\n</code> 或 <code>\r\n</code> 的二进制数据,可能会呈现误切割的状况。</li><li><li><strong>无奈解决大数据包</strong>: 如果一个十分大的数据块在没有任何分隔符的状况下被发送,会耗费大量内存来存储这些数据,直到找到一个分隔符。这可能会导致内存溢出问题。所以构造方法中要设置 maxLength 参数(如示例中的 1024)。</li></li></li></ul><h4>3.2.2. DelimiterBasedFrameDecoder</h4><blockquote><strong>解决形式</strong></blockquote><p>和LineBasedFrameDecoder相似,当接管到数据时,会查看是否存在分隔符。如果存在,它就认为曾经读取到了一个残缺的音讯,并将这个消息传递给下一个ChannelHandler进行解决。如果不存在,它将持续期待,直到读取到分隔符。</p><p>区别在于,前者的分隔符固定,而它的分隔符能够自定义。</p><blockquote><strong>代码-客户端批改</strong></blockquote><p>发送音讯的办法中,每条音讯结尾都加上行结束符后缀:</p><pre><code class=“java”>public void send(String msg) { if (channel != null) { channel.writeAndFlush(msg + “$”); } else { System.out.println(“message sending failed, connection not established”); } }</code></pre><blockquote><strong>代码-服务端批改</strong></blockquote><p>在 ChannelPipeline 中加上下列解码的 ChannelHandler:</p><pre><code class=“java”>ByteBuf delimiter = Unpooled.copiedBuffer("$".getBytes());ch.pipeline().addLast(new DelimiterBasedFrameDecoder(1024, delimiter));</code></pre><blockquote><strong>局限性</strong></blockquote><ul><li><strong>依赖于特定的分隔符</strong>:须要依赖特定的分隔符来断定一个音讯的完结,然而在某些状况下,这样的分隔符可能并不存在,或者不能很好地被利用在该协定上。同样可能呈现误判。</li><li><strong>不适宜二进制协定</strong>: 因为DelimiterBasedFrameDecoder次要是针对文本协定设计的,所以在解决一些二进制协定时可能会遇到困难。</li><li><strong>内存问题</strong>: 如果一个十分大的数据块在没有任何分隔符的状况下被发送,DelimiterBasedFrameDecoder可能会耗费过多的内存来存储这些数据,直到找到一个分隔符。这可能会导致内存溢出问题。所以也须要设置 maxFrameLength(如示例中的 1024)。</li></ul><h4>3.2.3. FixedLengthFrameDecoder</h4><blockquote><strong>解决形式</strong></blockquote><p>工作原理次要是每次从 ByteBuf 中读取固定长度的字节,而后结构成一个独立的 frame 对象,传递给下一个 handler 解决。</p><p>这样能够确保不会因为 TCP 粘包导致多个音讯被当作一个音讯解决,也不会因为 TCP 拆包导致一个音讯被当作多个音讯解决。</p><blockquote><strong>代码-服务端批改</strong></blockquote><p>在 ChannelPipeline 中加上下列解码的 ChannelHandler:</p><pre><code class=“java”>ch.pipeline().addLast(new FixedLengthFrameDecoder(5));</code></pre><p>因为传输的参数“hello”是5个字节,这类就固定为5.</p><blockquote><strong>局限性</strong></blockquote><ul><li><strong>固定长度限度</strong>: FixedLengthFrameDecoder 只能解决固定长度的音讯,如果理论利用中的音讯长度不固定,那么就无奈应用 FixedLengthFrameDecoder 进行解码。相应地,如果音讯长度小于固定长度,那么必须填充到固定长度,这就可能会节约带宽。</li><li><strong>无内置校验</strong>: FixedLengthFrameDecoder 仅仅是依照固定长度切分音讯,它并不关怀音讯的完整性和正确性。如果你想对音讯进行校验,须要本人实现。</li></ul><h4>3.2.4. LengthFieldBasedFrameDecoder</h4><blockquote><strong>解决形式</strong></blockquote><ul><li><strong>长度字段标识</strong>: LengthFieldBasedFrameDecoder 解决粘包问题的形式次要是通过在数据包中增加一个示意后续数据长度的字段,这个字段的地位和长度能够由开发者自定义,解码器会依据这个长度字段得悉具体的音讯体长度,而后进行正确的截取。</li><li><strong>校验读取</strong>: 当接管到新的数据包时,解码器首先找到长度字段,读取出音讯体的长度,而后期待足够长度的数据达到后,再从 ByteBuf 中读取,造成一个残缺的音讯帧。</li><li><strong>打消半包读取</strong>: 通过以上形式,LengthFieldBasedFrameDecoder 能够确保每次都能从 ByteBuf 中读取到残缺的音讯帧,不会呈现只读取到半个音讯帧的状况。</li></ul><p>在网络通信中,发送和接收数据须要遵循同一种协定。LengthFieldBasedFrameDecoder 是一个基于长度字段的解码器,而 LengthFieldPrepender 则是一个对应的编码器,它会在音讯体后面加上一个长度字段。</p><p>它们个别会配套应用,这样发送端发送的数据和接收端接管的数据结构就会保持一致,从而可能正确地进行解码。</p><blockquote><strong>代码-客户端批改</strong></blockquote><p>增加ChannelHandler实现,通过LengthFieldPrepender这个编码器,在发送的音讯前增加长度字段(这里的 4 是指长度字段自身占用的字节数量):</p><pre><code class=“java”>socketChannel.pipeline().addLast(new LengthFieldPrepender(4));</code></pre><blockquote><strong>代码-服务端批改</strong></blockquote><p>在 ChannelPipeline 中加上下列解码的 ChannelHandler:</p><pre><code class=“java”>ch.pipeline().addLast(new LengthFieldBasedFrameDecoder(1024, 0, 4, 0, 4));</code></pre><h2>4. Netty个性优化</h2><h3>4.1. 内存池 PooledByteBufAllocator</h3><p>内存池是一种用于治理和复用内存块的技术。能够防止频繁地调配和开释内存,从而缩小零碎开销和内存碎片问题,进步零碎的效率和性能。</p><p>PooledByteBufAllocator(分配器 [ælketr]) 是 Netty 提供的一个基于内存池的 ByteBuf 分配器。与间接创立新的 ByteBuf 实例相比,PooledByteBufAllocator 提供了重用内存的能力,这能够显著缩小内存调配和垃圾收集的开销,进步性能:</p><ul><li><strong>内存分区</strong>:PooledByteBufAllocator 将内存划分为多个 Arena,每个 Arena 进一步划分为多个 Chunk 和 Page。通过这种形式,PooledByteBufAllocator 可能满足不同大小的内存需要,并且可能疾速找到适合的内存块进行调配。</li><li><strong>对象复用</strong>:当 ByteBuf 的援用计数为 0 时,它的内存会被返回到原来的 Arena 并能够被重用。这防止了频繁创立和销毁对象,升高了零碎开销。</li><li><strong>线程本地缓存</strong>:PooledByteBufAllocator 应用了线程本地缓存技术(Thread Local Cache),每个线程都有本人的一份缓存池,这能够缩小线程间的竞争,进一步提高性能。</li><li><strong>内存调配策略</strong>:对于小于 Page 大小的内存调配申请,PooledByteBufAllocator 应用 jemalloc 策略进行内存调配。这是一种高效的内存调配策略,可能缩小内存碎片,进步内存使用率。</li></ul><p>通过这些形式,PooledByteBufAllocator 能够无效地复用内存,进步了内存应用的效率和性能。</p><blockquote><strong>PooledByteBufAllocator 创立 ByteBuf 过程</strong></blockquote><pre><code class=“java”>PooledByteBufAllocator allocator = new PooledByteBufAllocator();// 别离调配堆内存、堆外内存,内存大小也能够指定,如: allocator.heapBuffer(1024);ByteBuf heapBuffer = allocator.heapBuffer();ByteBuf directBuffer = allocator.directBuffer(); // 失常将写入数据或读取heapBuffer.writeBytes(data);byte b = heapBuffer.readByte();// 记得不必时开释内存,堆外内存不受垃圾回收,不开释会有内存泄露heapBuffer.release();directBuffer.release();</code></pre><p>不过理论我的项目中,很少有见过通过创立 PooledByteBufAllocator,再创立 ByteBuf 的。</p><p>根本都是由 Unpooled 工具类 创立 ByteBuf。</p><blockquote><strong>创立:堆内内存 OR 堆外内存?</strong></blockquote><p>(1)堆内内存:</p><p>如果你须要解决的数据比拟小(比方几 KB 或几百 KB),而且须要进行频繁的读写操作,那么倡议应用堆内内存。</p><p>(2)堆外内存:</p><p>如果你须要解决的数据比拟大(比方几 MB 或几十 MB),而且须要进行频繁的 IO 操作,那么倡议应用堆外内存。堆外内存是由操作系统治理的,数据存储在操作系统的内存中,能够间接进行 IO 操作。此外,在应用堆外内存时,能够防止 Java 堆和操作系统之间的数据拷贝,缩小了零碎的开销和提早。</p><p>须要留神的是,堆外内存的申请和开释须要调用 JNI 接口,因而申请和开释堆外内存的开销会比拟高。因而一般来说:</p><p>对于小规模的数据处理利用,倡议应用堆内内存;对于大规模的数据处理利用,倡议应用堆外内存</p><h3>4.2. 内存池 Unpooled</h3><p>Unpooled 是 Netty 中一个工具类,用于创立不同类型的 ByteBuf 对象,而且同样是应用PooledByteBufAllocator 类来调配和治理内存。</p><p>只不过它提供了一些静态方法,能够很不便地创立 HeapBuf、DirectBuf、CompositeBuf 等类型的 ByteBuf 对象。常见办法:</p><ul><li>buffer():创立一个 HeapBuf 对象,应用 JVM 堆内存来存储数据。</li><li>directBuffer():创立一个 DirectBuf 对象,应用间接内存来存储数据。</li><li>wrappedBuffer():创立一个 CompositeBuf 对象,能够将多个 ByteBuf 对象合并成一个虚构的 ByteBuf 对象。</li><li>copiedBuffer():创立一个 HeapBuf 对象,并将字节数组的内容复制到 HeapBuf 中。</li><li>unsafeBuffer():创立一个不平安的 ByteBuf 对象,用于一些非凡的场景,例如 JNI 调用等。</li></ul><p>不过同样要记得在应用结束后,应该及时调用 release() 办法来开释 ByteBuf 对象的资源哦。</p><p>回顾一下:思考到Netty中 ByteBuf 等罕用类,为防止频繁地调配和开释内存,通过内存池实现内存复用。但 ByteBuf 也是类,频繁地创立、销毁对象同样有大量的性能开销,怎么优化?</p><p>那么接下来咱们看一下 对象池。</p><h3>4.3. 对象池 Recycler</h3><p>Recycler (回收器,[risakl] )是 Netty是一个对象池,次要用于重用对象,防止频繁创立和销毁带来的性能开销。被宽泛地利用于各种场景中,例如 ByteBuf 对象池、EventExecutor 对象池、ChannelHandlerContext 对象池等等。咱们还是来看看 ByteBuf。</p><p><strong>ByteBuf</strong> 中蕴含一个 Recycler.Handle 对象,用于治理 ByteBuf 对象池的创立和销毁。当须要创立一个新的 ByteBuf 对象时,无论通过后面介绍的PooledByteBufAllocator、Unpooled,都是通过 ByteBufAllocator 接口提供的 directBuffer() 或 heapBuffer() 等办法来创立。</p><p>这些办法就是基于Recycler,会主动从线程本地的对象池中获取一个 ByteBuf 对象,如果对象池为空,则会创立一个新对象,并将其退出对象池中。当不再须要这个对象时,能够通过调用 release() 办法将其回收到对象池中,期待下次应用。</p><p><strong>ChannelHandlerContext</strong> 对象池也相似,在 Netty 中,能够通过 ChannelHandlerContext 的 newContext() 办法来获取一个新的 ChannelHandlerContext 对象,这个办法会从 Recycler 对象池中获取一个 ChannelHandlerContext 对象并进行初始化,如果没有可用的对象,则会创立一个新对象。在应用完后,通过调用 ChannelHandlerContext 的 recycle() 办法将其回收到对象池中,期待下次应用。</p><p>当然 Recycler 是 Netty 中实现对象池的机制,并不局限于只有 Netty 的这些组件类能够用,任何咱们自定义的类都能够。上面看一个例子。</p><blockquote><strong>示例(任何对象)</strong></blockquote><pre><code class=“java”>public class UserCache { private static final Recycler<User> userRecycler = new Recycler<User>() { @Override protected User newObject(Handle<User> handle) { return new User(handle); } }; static final class User { private String name; private Recycler.Handle<User> handle; public void setName(String name) { this.name = name; } public String getName() { return name; } public User(Recycler.Handle<User> handle) { this.handle = handle; } public void recycle() { handle.recycle(this); } } public static void main(String[] args) { User user1 = userRecycler.get(); user1.setName(“hello”); user1.recycle(); User user2 = userRecycler.get(); System.out.println(user1 == user2); }}</code></pre><p>右边的例子中,咱们定义了一个User类,main办法中,user1.recycle(),user1回收了之后,而后 user2 再获取。</p><ul><li>(1)user2获取的仍然是同一个对象,所以打印出的后果是:hello 和 true。</li><li>(2)如果咱们正文掉 user1.cecycle(),user2 会获取不到对象,打印的后果就是:null 和 false。</li></ul><blockquote><strong>线程平安</strong></blockquote><p>另外,Recycler 应用线程本地变量(FastThreadLocal)来存储对象,每个线程都有一个独立的对象池。这个机制能够保障对象的安全性和线程相互独立,防止了线程平安问题和竞争条件的呈现。</p><p>那么这个 FastThreadLocal 是啥?和常见的 ThreadLocal 有啥关系呢?</p><h3>4.4. 本地线程优化 FastThreadLocal</h3><p>FastThreadLocal(更快的ThreadLocal) 是 Netty 本人研发的一个工具类,用于替换 Java 原生的 ThreadLocal。次要有以下几个起因:</p><ul><li><strong>性能</strong>:与 ThreadLocal 相比,FastThreadLocal 在存取线程局部变量时有更快的速度。在 ThreadLocal 中,每次获取变量都须要通过哈希映射进行查找,当线程局部变量很多时,这会成为一个性能瓶颈。而 FastThreadLocal 则将所有线程的局部变量存储在一个数组中,通过索引疾速定位,进步了存取速度。</li><li><strong>防止内存透露</strong>:ThreadLocal 在应用不过后,很容易造成内存透露,须要咱们在应用后再手动调用reomve()办法。而 FastThreadLocal 能无效防止这个问题。它会在每个线程完结时主动清理线程局部变量,而不是依赖于 JVM 的垃圾回收。</li><li><strong>更好的整合</strong>:Netty 中很多中央应用了线程局部变量,例如 ByteBuf 的内存池、Recycler 对象池等。有了本人的 FastThreadLocal,Netty 能够更好地管制和优化这些性能,进步整体性能。</li></ul><blockquote><strong>代码示例</strong></blockquote><pre><code class=“java”>public class FastThreadLocalDemo { private static final FastThreadLocal<Integer> THREAD_LOCAL = new FastThreadLocal<Integer>() { @Override protected Integer initialValue() throws Exception { return 1; } }; public static void main(String[] args) { new FastThreadLocalThread(() -> { for (int i = 0; i < 10; i++) { System.out.println(Thread.currentThread().getName() + " –> " + THREAD_LOCAL.get()); THREAD_LOCAL.set(THREAD_LOCAL.get() + 1); } }, “FastThreadLocalThread-1”).start(); }}</code></pre><blockquote><strong>注意事项</strong></blockquote><p>FastThreadLocal 的应用形式和 ThreadLocal差异不大,然而有几点须要留神:</p><ul><li>应用 FastThreadLocal 的线程最好是 FastThreadLocalThread 类型或者其子类。FastThreadLocal 会在这些线程中有更好的性能。如果应用的是Thread或其余实现的话,FastThreadLocal 依然能够工作,但性能会降级。</li><li>相比于 ThreadLocal,FastThreadLocal 的劣势在于当一个线程有多个线程本地变量时,它能够通过缩小哈希抵触和查找来进步性能。然而如果一个线程只有一个或者很少的线程本地变量,那么 ThreadLocal 可能会有更好的性能。</li><li>当你不再须要应用 FastThreadLocal 中的对象时,还是应该调用 remove() 来防止内存透露。</li></ul><p>虽说在应用了 FastThreadLocalThread 实例的状况下,在线程完结时,FastThreadLocal 会主动清理所有线程局部变量。但显式地调用 remove() 办法依然是一个好的实际。特地是在长生命周期的线程或者应用了线程池的状况下,显式地清理线程局部变量能够帮忙防止潜在的内存透露问题。</p></article> ...

February 15, 2024 · 6 min · jiezi

关于netty:NettyA-Netty-ByteBuf-Memory-Leak-Story-and-the-Lessons-Learned

netty引言如题目所言,讲述了一个难以排查的 Netty ByteBuf 内存透露问题的排查和优化实战。这种经验之谈十分有学习和参考价值。 原文A Netty ByteBuf Memory Leak Story and the Lessons Learned | Logz.io By: Asaf MesikaJust a while ago, I was chasing a memory leak we had at Logz.io while I was refactoring our log receiver. We were using Netty, and after a major refactoring, we noticed that there was a gradual decrease of free memory to the machine. 就在不久前,我在Logz.io重构咱们的日志收集器时发现了一个内存泄露问题。咱们过后应用的是Netty,在重构之后,咱们发现机器的可用内存在逐步缩小。 Our first action was to try to run garbage collection to see if this was an on-heap or off-heap (utilizing ByteBuf) memory issue. We quickly found that it was an off-heap issue and started to read through the code to see where we forgot to call the release() method on the ByteBuf type. We could not find anything obvious — but that is usually the case when it comes to memory leaks. ...

July 11, 2023 · 4 min · jiezi

关于netty:跟闪电侠学Netty阅读笔记-聊天系统实现

引言本局部整合聊天零碎无关的章节,内容次要是介绍要害性能的实现逻辑和局部代码实现,倡议读者先看看作者的博客我的项目,切换到不同的分支看看各个细节性能如何实现。这里仅仅记录一些集体学习过程的重点局部。 思维导图https://www.mubu.com/doc/1dunN_7Luzl 我的项目代码作者的仓库代码地址:https://github.com/lightningMan/flash-netty5 通信协议设计和自定义编解码实现什么是通信协议?基于TCP通信均为二进制协定,底层都是通过字节进行传输的。在通信协议当中规定数据传输的每一个字节含意。 通信过程客户端转换数据为二进制。网络传输给服务端。服务端依据协定规定读取二进制数据。服务端解决数据返回响应后果给客户端。聊天零碎的通信协议数据对象设计在聊天零碎当中通信协议的设计如下。 4字节魔数比方Java的字节码CafeBabe,用于疾速辨认是否自定义协定,也能够不便疾速提取数据。 public static final int MAGIC_NUMBER = 0x12345678;1 字节版本号相似TCP的IPV4还是IPV6。 /** * 协定版本 */ @JSONField(deserialize = false, serialize = false) private Byte version = 1;1 字节序列化算法应用1个字节来标识算法。 /** * 序列化算法定义 */ public interface SerializerAlgorithm { /** * json 序列化 */ byte JSON = 1; }1 字节指令一个字节最多示意256种指令。留神在设计上指令和版本号进行绑定关联,实现不同版本之间的指令兼容,进步程序的健壮性。 @Data public abstract class Packet { /** * 协定版本 */ @JSONField(deserialize = false, serialize = false) private Byte version = 1; @JSONField(serialize = false) public abstract Byte getCommand(); }4字节数据长度数据长度是必要的,次要用于字节流这种连续不断的数据模式进行切割。 ...

July 11, 2023 · 8 min · jiezi

关于netty:跟闪电侠学Netty阅读笔记-ChannelHandler-生命周期

引言本文次要介绍ChannelHandler当中的ChannelInboundHandler。 思维导图https://www.mubu.com/doc/1lK922R14Bl LifeCycleTestHandler 案例首先来看一下案例,LifeCycleTestHandlerTest 利用适配器 ChannelInboundHandlerAdapter 重写,重写相干办法。 public void handlerAdded(ChannelHandlerContext ctx) throws Exceptionpublic void channelRegistered(ChannelHandlerContext ctx) throws Exceptionpublic void channelActive(ChannelHandlerContext ctx) throws Exceptionpublic void channelRead(ChannelHandlerContext ctx, Object msg) throws Exceptionpublic void channelReadComplete(ChannelHandlerContext ctx) throws Exceptionpublic void channelInactive(ChannelHandlerContext ctx) throws Exceptionpublic void channelUnregistered(ChannelHandlerContext ctx) throws Exceptionpublic void handlerRemoved(ChannelHandlerContext ctx) throws Exception/** * 测试netty channelHandler 的生命周期 * * 对应的是 ChannelInboundHandlerAdapter */@Slf4jpublic class LifeCycleTestHandlerTest extends ChannelInboundHandlerAdapter { public static void main(String[] args) { ServerBootstrap serverBootstrap = new ServerBootstrap(); NioEventLoopGroup boss = new NioEventLoopGroup(); NioEventLoopGroup worker = new NioEventLoopGroup(); serverBootstrap .group(boss, worker) .channel(NioServerSocketChannel.class) .childHandler(new ChannelInitializer<NioSocketChannel>() { @Override protected void initChannel(NioSocketChannel ch) throws Exception { ch.pipeline().addLast(new StringDecoder()); ch.pipeline().addLast(new LifeCycleTestHandlerTest()); } }) .bind(8001); } @Override public void handlerAdded(ChannelHandlerContext ctx) throws Exception { log.info("逻辑处理器被增加 handlerAdded() "); super.handlerAdded(ctx); } @Override public void channelRegistered(ChannelHandlerContext ctx) throws Exception { log.info("channel 绑定到线程 (NioEventLoop):channelRegistered() "); super.channelRegistered(ctx); } @Override public void channelActive(ChannelHandlerContext ctx) throws Exception { log.info("channel 准备就绪 channelActive()"); super.channelActive(ctx); } @Override public void channelReadComplete(ChannelHandlerContext ctx) throws Exception { log.info("channel 某次数据读写实现 channelReadComplete() "); super.channelReadComplete(ctx); } @Override public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { log.info("channel 有数据可读 channelRead() "); super.channelRead(ctx, msg); } @Override public void channelInactive(ChannelHandlerContext ctx) throws Exception { log.info("channel 被敞开 channelInactive()"); super.channelInactive(ctx); } @Override public void channelUnregistered(ChannelHandlerContext ctx) throws Exception { log.info("channel勾销线程 (NioEventLoop) 的绑定:channelUnregistered() "); super.channelUnregistered(ctx); } @Override public void handlerRemoved(ChannelHandlerContext ctx) throws Exception { log.info("逻辑处理器被删除 handlerRemoved() "); super.handlerRemoved(ctx); }}如果启动Client客户端并且进行连贯,ChannelHanlder 的回调办法执行程序如下: ...

July 9, 2023 · 2 min · jiezi

关于netty:javanettySelector

背景:java网络编程框架底层的多路复用的 面向对象设计NioEventLoopGroup:下层是bootstrap起动器,上层是selector。 从学习过程中的案例能够看出,EventLoopGroup里有多个线程, 这些线程从治理连贯通道(channel),解决channel上的读写事件,此时就呈现了selector和selectionKeyselector:干活的,从注册的socket+事件中,我的了解:当有socket事件产生时,这个事件会被写入到socket事件缓冲区,selector会循环本人治理的socket事件列表,看哪个socket事件缓冲去有数据,则把数据去进去解决。selectionKey:封装数据用的,蕴含socket+事件+channel

July 2, 2023 · 1 min · jiezi

关于netty:跟闪电侠学Netty阅读笔记-Netty入门程序解析

引言上一节[[《跟闪电侠学Netty》浏览笔记 - 开篇入门Netty]] 中介绍了Netty的入门程序,本节如题目所言将会一步步剖析入门程序的代码含意。 思维导图 服务端最简化代码 public static void main(String[] args) { ServerBootstrap serverBootstrap = new ServerBootstrap(); NioEventLoopGroup boos = new NioEventLoopGroup(); NioEventLoopGroup worker = new NioEventLoopGroup(); serverBootstrap .group(boos, worker) .channel(NioServerSocketChannel.class) .childHandler(new ChannelInitializer<NioSocketChannel>() { protected void initChannel(NioSocketChannel ch) { ch.pipeline().addLast(new StringDecoder()); ch.pipeline().addLast(new SimpleChannelInboundHandler<String>() { @Override protected void channelRead0(ChannelHandlerContext ctx, String msg) { System.out.println(msg); } }); } }) .bind(8000); }两个NioEventLoopGroup服务端一上来先构建两个对象NioEventLoopGroup,这两个对象将间接决定Netty启动之后的工作模式,在这个案例中boos和JDK的NIO编程一样负责进行新连贯的“轮询”,他会定期检查客户端是否曾经筹备好能够接入。worker则负责解决boss获取到的连贯,当查看连贯有数据能够读写的时候就进行数据处理。 NioEventLoopGroup boos = new NioEventLoopGroup();NioEventLoopGroup worker = new NioEventLoopGroup();那么应该如何了解?其实这两个Group对象简略的看成是线程池即可,和JDBC的线程池没什么区别。通过浏览源码能够晓得,bossGroup只用了一个线程来解决近程客户端的连贯,workerGroup 领有的线程数默认为2倍的cpu外围数。 ...

June 25, 2023 · 3 min · jiezi

关于netty:NettyNacosDisruptor自研企业级API网关

download:Netty+Nacos+Disruptor自研企业级API网关OpenCV三大经典我的项目:理解计算机视觉的经典案例OpenCV(Open Source Computer Vision Library)是一个宽泛应用的计算机视觉和机器学习库,具备优良的跨平台性能。它能够用于许多应用程序和我的项目,包含图像处理、视频剖析、实时跟踪等。以下是OpenCV三个最受欢迎的经典我的项目: 基于OpenCV的人脸识别人脸识别是一种智能安全措施,现已被广泛应用于各个领域,如银行、机场和公共交通等。基于OpenCV的人脸识别我的项目旨在为用户提供牢靠且精确的身份认证解决方案。这个我的项目利用了OpenCV的图像处理技术和人脸检测算法,能够通过比对存储在零碎中的人脸图像来验证身份。基于OpenCV的对象跟踪基于OpenCV的对象跟踪是一种实时跟踪应用程序,能够帮忙用户追踪并定位静止中的对象。该我的项目应用OpenCV的指标检测和跟踪算法,并联合了计算机视觉相干的工具和技术,以实现疾速、精确的对象跟踪。这个我的项目实用于视频监控和主动驾驶等畛域。基于OpenCV的手势辨认基于OpenCV的手势辨认是一个能够将手部动作辨认为特定命令的应用程序。目前,它曾经被广泛应用于虚拟现实、智能家居和健身应用程序等畛域。该我的项目采纳了OpenCV的图像处理技术和机器学习算法,并联合了深度学习模型,能够高效地辨认各种手势并将其与相应的操作分割起来。论断OpenCV已成为计算机视觉畛域的重要工具和资源。三个最受欢迎的OpenCV我的项目:基于OpenCV的人脸识别、基于OpenCV的对象跟踪和基于OpenCV的手势辨认,展现了计算机视觉的高级利用。它们不仅提供了解决方案,而且还展现了如何应用OpenCV的弱小性能来实现更多翻新的计算机视觉利用。

June 11, 2023 · 1 min · jiezi

关于netty:NettyNIO三剖析-Selector

前言本篇博文是《从0到1学习 Netty》中 NIO 系列的第三篇博文,次要内容是介绍通过应用 Selector,一个独自的线程能够无效地监督多个通道,从而进步应用程序的解决效率,往期系列文章请拜访博主的 Netty 专栏,博文中的所有代码全副收集在博主的 GitHub 仓库中; 介绍在 Java 中,Selector 是 NIO(New Input/Output)库中的一种对象,用于监控多个通道的状态,例如文件 I/O 或者网络 I/O。 Selector 的工作原理是应用 select() 办法轮询已注册的通道,获取它们的就绪状态,并返回一个已筹备好进行 I/O 操作的通道汇合。通过应用此机制,能够监督几个通道的状态,并且只有当至多一个通道处于就绪状态时才会执行 I/O 操作,从根本上防止了 CPU 的节约。 总之,Selector 是一种弱小的工具,可实现高效的 I/O 操作和网络编程,因为它可能轻松地监督多个通道的状态并在须要时对它们进行操作。 应用1、创立 selector,治理多个 channel; Selector selector = Selector.open();2、注册 selector 和 channel 的分割; SelectionKey sscKey = ssc.register(selector, 0, null); sscKey.interestOps(SelectionKey.OP_ACCEPT);SelectionKey 示意一个通道(Channel)与一个选择器(Selector)之间的注册关系。每个通道在与选择器进行注册时都会创立一个对应的 SelectionKey 对象,这个对象蕴含了对于通道和选择器的一些元数据信息。 这里 SelectionKey 调用 register() 办法指定感兴趣的事件类型,绑定的事件类型有以下几种: connect - 客户端连贯胜利时触发;accept - 服务器端胜利承受连贯时触发;read - 数据可读入时触发,有因为接管能力弱,数据暂不能读入的状况;write - 数据可写出时触发,有因为发送能力弱,数据暂不能写出的状况;3、通过 selector 监听事件,并取得就绪的通道个数,若没有通道就绪,线程会被阻塞: ...

June 8, 2023 · 4 min · jiezi

关于netty:NettyNIO二阻塞模式与非阻塞模式

前言本篇博文是《从0到1学习 Netty》中 NIO 系列的第二篇博文,次要内容是通过 NIO 来了解阻塞模式与非阻塞模式,往期系列文章请拜访博主的 Netty 专栏,博文中的所有代码全副收集在博主的 GitHub 仓库中; 介绍阻塞模式在 Java NIO 中,阻塞模式是一种传统的 I/O 解决形式,当咱们试图从通道进行读取或向通道写入数据时,这种模式会使线程阻塞直到操作实现。具体来说,在阻塞模式下,当没有可用的数据时,读操作将被阻塞,直到有数据可读;同样地,当通道已满时,写操作将被阻塞,直到有空间可用于写入。 在网络编程中,应用阻塞模式能够很不便地实现简略、易于了解的代码逻辑。然而,它也有一些毛病。首先,当存在大量连贯时,因为每个连贯都须要一个线程来解决 I/O,因而阻塞模式可能导致系统资源耗尽。其次,在高负载条件下,I/O 操作可能会变得十分迟缓,因为线程必须期待操作实现。因而,对于高并发应用程序,通常应用非阻塞和异步 I/O 模式来进步性能。 非阻塞模式在 Java NIO 中,非阻塞模式是一种十分重要的概念。在传统的阻塞模式中,当一个线程调用输出或输入操作时,它会始终期待,直到操作实现为止。这意味着,如果有多个客户端申请连贯或发送数据,服务器将不得不创立多个线程来解决每个申请,从而可能导致系统资源耗尽。 非阻塞 I/O(NIO)解决了这个问题,因为它容许应用程序异步地解决多个通道。在非阻塞模式下,当一个线程向通道发出请求并没有立刻失去响应时,该线程能够持续解决其余工作。只有当数据筹备好读取或写入时,线程才会返回通道。这样就能够应用单个线程解决多个连贯和申请。 接下来联合代码进行深刻了解; Block Server创立一个 ByteBuffer 用来接收数据; ByteBuffer buffer = ByteBuffer.allocate(16);创立服务器; ServerSocketChannel ssc = ServerSocketChannel.open();绑定监听端口; ssc.bind(new InetSocketAddress(7999));创立连贯汇合; ArrayList<SocketChannel> channels = new ArrayList<>();accept() 建设与客户端连贯,SocketChannel() 用来与客户端之间通信; SocketChannel sc = ssc.accept();接管客户端发送的数据; channel.read(buffer);残缺代码如下: import lombok.extern.slf4j.Slf4j;import java.io.IOException;import java.net.InetSocketAddress;import java.net.Socket;import java.nio.ByteBuffer;import java.nio.channels.ServerSocketChannel;import java.nio.channels.SocketChannel;import java.util.ArrayList;import static com.sidiot.netty.c1.ByteBufferUtil.debugRead;@Slf4jpublic class Server { public static void main(String[] args) throws IOException { // 应用 nio 来了解阻塞模式,单线程 // 1. ByteBuffer ByteBuffer buffer = ByteBuffer.allocate(16); // 2. 创立服务器 ServerSocketChannel ssc = ServerSocketChannel.open(); // 3. 绑定监听端口 ssc.bind(new InetSocketAddress(7999)); // 4. 创立连贯汇合 ArrayList<SocketChannel> channels = new ArrayList<>(); while (true) { // 5. accept 建设与客户端连贯,SocketChannel 用来与客户端之间通信 log.debug("connecting..."); SocketChannel sc = ssc.accept(); log.debug("connected... {}", sc); channels.add(sc); for (SocketChannel channel : channels) { // 6. 接管客户端发送的数据 log.debug("before read... {}", channel); channel.read(buffer); buffer.flip(); debugRead(buffer); buffer.clear(); log.debug("after read... {}", channel); } } }}Non Block Server与 Block Server 的代码相相似,然而将 ServerSocketChannel 和 SocketChannel 都设置成了非阻塞模式: ...

June 8, 2023 · 3 min · jiezi

关于netty:使用NettyNacosDisruptor构建高性能分布式系统

应用Netty+Nacos+Disruptor构建高性能分布式系统在分布式系统开发中,须要应用牢靠的网络通信框架、服务发现和注册核心、以及高效的异步事件处理机制。本文将介绍如何应用Netty、Nacos和Disruptor来构建一个高性能的分布式系统。 NettyNetty是一个基于Java NIO的高性能网络应用程序框架,它提供了异步事件驱动的网络编程模型和灵便的TCP/UDP套接字服务器。Netty的长处包含高性能、易于应用、扩展性强等等。在分布式系统中,Netty能够帮忙咱们构建高性能的网络通信模块。 NacosNacos是阿里巴巴开源的服务注册和配置核心,它反对多种协定(HTTP、DNS和gRPC)和多种数据格式(JSON/YAML)的服务注册和发现。Nacos能够为分布式系统提供服务注册和发现性能,并且还具备动静配置管理、服务健康检查、流量治理等性能。 DisruptorDisruptor是一个高性能的无锁、无GC的并发框架,它采纳了环形缓冲区的设计模式来实现疾速的消息传递。Disruptor的长处包含高吞吐量、低提早、无锁设计等等。在分布式系统中,Disruptor能够帮忙咱们实现高效的异步事件处理。 架构设计基于Netty、Nacos和Disruptor,咱们能够设计一个高性能的分布式系统架构,包含以下模块: 网络通信模块:应用Netty来实现TCP/UDP套接字服务器,解决网络通信申请。服务发现和注册模块:应用Nacos来实现服务注册和发现性能,为客户端提供牢靠的服务调用地址。异步事件处理模块:应用Disruptor来实现疾速的消息传递,解决异步事件。实战演练接下来,咱们将通过一个简略的代码示例来演示如何应用Netty、Nacos和Disruptor构建高性能的分布式系统。 咱们将实现一个简略的聊天室应用程序,该程序包含以下模块: 客户端:向聊天室发送音讯;服务端:接管客户端的音讯并播送给所有在线用户。论断本文介绍了如何应用Netty、Nacos和Disruptor来构建高性能的分布式系统。这些工具能够帮忙咱们实现高效的网络通信、服务发现和注册、以及异步事件处理。通过学习本文提供的实例,置信读者能够更好地把握这些技术,并将它们利用到本人的我的项目中。

May 26, 2023 · 1 min · jiezi

关于netty:一分钟学会三分钟上手五分钟应用快速上手开源责任链框架详解-京东云技术团队

作者:京东物流 覃玉杰 1. pie 简介责任链模式是开发过程中罕用的一种设计模式,在SpringMVC、Netty等许多框架中均有实现。咱们日常的开发中如果要应用责任链模式,通常须要本人来实现,但本人长期实现的责任链既不通用,也很容易产生框架与业务代码耦合不清的问题,减少Code Review 的老本。 Netty的代码向来以优雅著称,早年我在浏览Netty的源码时,萌发出将其责任链的实现利用到业务开发中的想法,之后花了点工夫将Netty中责任链的实现代码抽取进去,造成了本我的项目,也就是pie。pie的外围代码均来自Netty,绝大部分的 API 与 Netty 是统一的。 pie 是一个可疾速上手的责任链框架,开发者只须要专一业务,开发相应的业务Handler,即可实现业务的责任链落地。 一分钟学会、三分钟上手、五分钟利用,欢送 star。 pie 源码地址:https://github.com/feiniaojin/pie.git pie 案例工程源码地址:https://github.com/feiniaojin/pie-example.git 2. 疾速入门2.1 引入 maven 依赖pie 目前已打包公布到 maven 地方仓库,开发者能够间接通过 maven 坐标将其引入到我的项目中。 <dependency> <groupId>com.feiniaojin.ddd.ecosystem</groupId> <artifactId>pie</artifactId> <version>1.0</version></dependency>目前最新的版本是 1.0 2.2 实现出参工厂出参也就是执行后果,个别的执行过程都要求有执行后果返回。实现 OutboundFactory 接口,用于产生接口默认返回值。 例如: public class OutFactoryImpl implements OutboundFactory { @Override public Object newInstance() { Result result = new Result(); result.setCode(0); result.setMsg("ok"); return result; }}2.3 实现 handler 接口实现业务逻辑在 pie 案例工程( https://github.com/feiniaojin/pie-example.git )的 Example1 中,为了展现 pie 的应用办法,实现了一个虚构的业务逻辑:CMS类我的项目修改文章题目、注释,大家不要关注批改操作放到两个 handler 中是否正当,仅作为解说案例。 ...

May 6, 2023 · 3 min · jiezi

关于netty:Netty服务端开发及性能优化-京东云技术团队

作者:京东物流 王奕龙 Netty是一个异步基于事件驱动的高性能网络通信框架,能够看做是对NIO和BIO的封装,并提供了简略易用的API、Handler和工具类等,用以疾速开发高性能、高可靠性的网络服务端和客户端程序。 1. 创立服务端服务端启动须要创立 ServerBootstrap 对象,并实现初始化线程模型,配置IO模型和增加业务解决逻辑(Handler) 。在增加业务解决逻辑时,调用的是 childHandler() 办法增加了一个ChannelInitializer,代码示例如下 // 负责服务端的启动ServerBootstrap serverBootstrap = new ServerBootstrap();// 以下两个对象能够看做是两个线程组// boss线程组负责监听端口,承受新的连贯NioEventLoopGroup boss = new NioEventLoopGroup();// worker线程组负责读取数据NioEventLoopGroup worker = new NioEventLoopGroup();// 配置线程组并指定NIO模型serverBootstrap.group(boss, worker).channel(NioServerSocketChannel.class) // 定义后续每个 新连贯 的读写业务逻辑 .childHandler(new ChannelInitializer<NioSocketChannel>() { @Override protected void initChannel(NioSocketChannel nioSocketChannel) throws Exception { nioSocketChannel.pipeline() // 增加业务解决逻辑 .addLast(new SimpleChannelInboundHandler<String>() { @Override protected void channelRead0(ChannelHandlerContext channelHandlerContext, String msg) throws Exception { System.out.println(msg); } }); } });// 绑定端口号serverBootstrap.bind(2002);通过调用 .channel(NioServerSocketChannel.class) 办法指定 Channel 类型为NIO类型,如果要指定为BIO类型,参数改成 OioServerSocketChannel.class 即可。 其中 nioSocketChannel.pipeline() 用来获取 PipeLine 对象,调用办法 addLast() 增加必要的业务解决逻辑,这里采纳的是责任链模式,会将每个Handler作为一个节点进行解决。 1.1 创立客户端客户端与服务端启动相似,不同的是,客户端须要创立 Bootstrap 对象来启动,并指定一个客户端线程组,雷同的是都须要实现初始化线程模型,配置IO模型和增加业务解决逻辑(Handler) , 代码示例如下 // 负责客户端的启动Bootstrap bootstrap = new Bootstrap();// 客户端的线程模型NioEventLoopGroup group = new NioEventLoopGroup();// 指定线程组和NIO模型bootstrap.group(group).channel(NioSocketChannel.class) // handler() 办法封装业务解决逻辑 .handler(new ChannelInitializer<Channel>() { @Override protected void initChannel(Channel channel) throws Exception { channel.pipeline() // 增加业务解决逻辑 .addLast(new SimpleChannelInboundHandler<String>() { @Override protected void channelRead0(ChannelHandlerContext channelHandlerContext, String msg) throws Exception { System.out.println(msg); } }); } });// 连贯服务端IP和端口bootstrap.connect("127.0.0.1", 2002);(留神:下文中内容均以服务端代码示例为准) ...

May 4, 2023 · 6 min · jiezi

关于netty:长连接Netty服务内存泄漏看我如何一步步捉虫解决-京东云技术团队

作者:京东科技 王长春 背景事件要回顾到双11.11备战前夕,在那个风雨交加的夜晚,一个短促的咚咚报警,惊破了电闪雷鸣的黑夜,将沉迷在梦香,酣睡的我惊醒。 一看手机咚咚报警,不好!有小事产生了!电话马上打给老板: 老板说: 长连贯吗? 我说:是的! 老板说:该来的还是要来的,最终还是来了,快,连忙先把服务重启下! 我说:曾经重启了! 老板说: 这问题必须给我解决了! 我说:必须的!线上利用长连贯Netty服务呈现内存透露了!真让人头大 在这风雨交加的夜晚,此时,面对毫无脉络的问题,以及迫切想攻克问题的心,曾经让我兴奋不已,手一把揉揉刚还迷糊的眼,今晚又注定是一个不眠之夜! 利用介绍说起领取业务的长连贯服务,真是说来话长,咱们这就长话短说: 随着业务及零碎架构的复杂化,一些场景,用户操作无奈同步失去后果。个别采纳的短连贯轮训的策略,客户端须要不停的发动申请,时效性较差还节约服务器资源。 短轮训痛点: 时效性差消耗服务器性能建设、敞开链接频繁相比于短连贯轮训策略,长连贯服务可做到实时推送数据,并且在一个链接放弃期间可进行屡次数据推送。服务利用常见场景:PC端扫码领取,用户关上扫码领取页面,手机扫码实现领取,页面实时展现领取胜利信息,提供良好的用户体验。 长连服务劣势: 时效性高晋升用户体验缩小链接建设次数一次链接屡次推送数据进步零碎吞吐量 这个长连贯服务应用Netty框架,Netty的高性能为这个利用带来了无上的荣光,承接了泛滥长连贯应用场景的业务: PC收银台微信领取声波红包POS线下扫码领取问题景象回到线上问题,呈现内存透露的是长连贯前置服务,察看线上服务,这个利用的内存透露的景象总随同着内存的增长,这个增长真是十分的迟缓,迟缓,迟缓,2、3个月内从30%缓缓增长到70%,极难发现: 每次产生内存透露,内存快耗尽时,总得重启下,虽说重启是最快解决的办法,然而程序员是天生懈怠的,要数着日子来重启,那相对不是一个优良程序员的行为!问题必须彻底解决! 问题排查与复现排查遇到问题,毫无脉络,首先还是须要去案发第一现场,排查“死者(利用实例)”死亡现场,通过在产生FullGC的工夫点,通过Digger查问ERROR日志,没想到还真找到破案的第一线索: io.netty.util.ResourceLeakDetector [176] - LEAK: ByteBuf.release() was not called before it's garbage-collected. Enable advanced leak reporting to find out where the leak occurred. To enable advanced leak reporting, specify the JVM option '-Dio.netty.leakDetection.level=advanced' or call ResourceLeakDetector.setLevel() See http://netty.io/wiki/reference-counted-objects.html for more information.线上日志居然有一个显著的"LEAK"透露字样,作为技术人的敏锐的技术嗅觉,和找Bug的直觉,能够确认,这就是事变案发第一现场。 咱们凭借下大学四六级英文程度的,持续翻译下线索,原来是这呐! ByteBuf.release() 在垃圾回收之前没有被调用。启用高级透露报告以找出透露产生的地位。要启用高级透露报告,请指定 JVM 选项“-Dio.netty.leakDetectionLevel=advanced”或调用 ResourceLeakDetector.setLevel()啊哈!这信息不就是说了嘛!ByteBuf.release()在垃圾回收前没有调用,有ByteBuf对象没有被开释,ByteBuf可是调配在间接内存的,没有被开释,那就意味着堆外内存透露,所以内存始终是十分迟缓的增长,GC都不可能进行开释。 ...

April 23, 2023 · 2 min · jiezi

关于netty:跟闪电侠学Netty阅读笔记-开篇入门Netty

引言《跟闪电侠学Netty》 并不是集体接触的第一本Netty书籍,但集体更举荐读者把它作为作为第一本Netty入门的书籍。 和 《Netty In Action》 不同,这本书间接从Netty入门程序代码开始引入Netty框架,前半部分教你如何用Netty搭建繁难的通信零碎,整体难度比拟低,后半局部间接从服务端源码、客户端源码、ChannelPipeline开始介绍,和前半部分割裂较为重大。 相较于入门的程序,源码剖析毫无疑问是比拟有干货的局部,然而和后面入门程序相比有点学完了99乘法表就让你去做微积分的卷子一样,如果Netty应用陌生源码局部解说必定是非常难懂的,所以更倡议只看前半截。 集体比拟举荐这本书吃透Netty编写的简略通信“我的项目”之后,间接去看《Netty In Action》做一个更为零碎的深刻和根底坚固。等《Netty In Action》看明确之后,再回过头来看《跟闪电侠学Netty》的源码剖析局部。 抛开源码剖析局部,这本书是“我奶奶都能学会”的优良入门书籍,用代码实战加解说形式学起来轻松印象粗浅。 开篇入门局部先不引入我的项目,这里先对于过来JDK的网络IO模型作为引子介绍为什么咱们须要用Netty,学习Netty带来的益处等。 思维导图 Netty 依赖版本(4.1.6.Final)本书应用的Netty版本为 4.1.6,为了防止前面浏览源码的时候产生误解,倡议以此版本为基准。 <dependency> <groupId>io.netty</groupId> <artifactId>netty-all</artifactId> <version>4.1.6.Final</version> </dependency>JDK 原生编程模型到目前为止,JDK一共实现了三种网络IO编程模型:BIO、NIO和AIO。遗憾的是这三种模型不仅产生的距离时间跨度大,并且由三组齐全不同编程格调的开发人员设计API,不同编程模型和设计思路之间的切换十分复杂,开发者的学习老本也比拟大。 针对这些问题,咱们间接理解Netty如何对立这些模型以及如何升高并发编程的开发难度,咱们先对过来的JDK网络IO编程模型做一个理解。 洗衣机洗衣服例子理清了解阻塞非阻塞,同步异步概念在理解JDK的网络IO模型之前,比方得先理解绕不过的阻塞非阻塞,同步异步的概念。 同步和异步指的是工作之间是否须要期待其它工作实现或者期待某个事件的产生。如果一个工作必须期待另一个工作实现能力继续执行,那么这两个工作就是同步的;如果一个工作能够间接继续执行而无需期待另一个工作的实现,那么这两个工作就是异步的。 阻塞和非阻塞指的是工作在期待后果时是否会始终占用CPU资源。如果一个工作在期待后果时会始终占用CPU资源,那么这个工作就是阻塞的;如果一个工作在期待后果时不会占用CPU资源,那么这个工作就是非阻塞的。 这里给一个生存中洗衣服的例子帮忙齐全没有理解过这些概念的读者加深印象,这个例子来源于某个网课,集体感觉非常贴切和易懂就拿过去用了。 同步阻塞了解: 洗衣服丢到洗衣机,全程看着洗衣机洗完,洗好之后晾衣服。 类比 : 申请接口期待接口返回后果,两头不能做其余事件。拿到后果解决数据剖析:同步:全程看着洗衣机洗完。阻塞:期待洗衣机洗好衣服之后跑过来晾衣服。 同步非阻塞了解: 把衣服丢到洗衣机洗,而后回客厅做其余事件,定时看看洗衣机是不是洗完了,洗好后再去晾衣服。(期待期间你能够做其余事件,比方用电脑刷剧看视频)。 这种模式相似日常生活洗衣机洗衣服。类比: 申请接口。期待期间切换到其余工作,然而须要定期察看接口是否有回送数据。拿到后果解决数据。剖析: 和阻塞形式的最大区别是不须要始终盯着洗衣机,期间能够抽空干其余的事件。 同步:期待洗衣机洗完这个事件没有实质变动,洗好衣服之后还是要跑过来晾衣服。非阻塞:拿到衣服之前能够干别的事件,只不过须要每次隔一段时间查看能不能拿到洗好的衣服。 异步阻塞了解: 把衣服丢到洗衣机洗,而后看着洗衣机洗完,洗好后再去晾衣服(没这个状况,简直没这个说法,能够疏忽)。 类比: 申请接口,不须要关怀后果。客户端能够抽空干其余事件,然而非得期待接口返回后果拿到服务端的处理结果剖析: 难以描述,简直不存在这种说法。 异步非阻塞了解: 把衣服丢到洗衣机洗,而后回客厅做其余事件,洗衣机洗好后会主动去晾衣服,晾实现后放个音乐通知你洗好衣服并晾好了。 类比 : 申请接口,此时客户端能够继续执行代码。服务端筹备并且解决数据,在解决实现之后在适合的工夫告诉客户端客户端收到服务端解决实现的后果。剖析:异步:洗衣机本人不仅把衣服洗好了还帮咱们把衣服晾好了。非阻塞:拿到“衣服”后果之前能够干别的事件。 留神异步非阻塞状况下,“咱们”看待洗衣服这件事件的“态度”齐全变了。BIO 编程模型BIO叫做阻塞IO模型,在阻塞IO模型中两个工作之间须要期待响应后果,利用过程须要期待内核把整个数据筹备好之后能力开始进行解决。 BIO是入门网络编程的第一个程序,从JDK1.0开始便存在了,存在于java.net包当中。上面的程序也是入门Tomcat源码的根底程序。 Java实现代码在BIO的实现代码中,服务端通过accept始终阻塞期待直到有客户端连贯。首先是服务端代码。 public static void main(String[] args) throws IOException { ServerSocket serverSocket = new ServerSocket(8000); // 承受连贯 new Thread(() -> { while (true) { // 1. 阻塞获取连贯 try { Socket socket = serverSocket.accept(); // 2. 为每一个新连贯应用一个新线程 new Thread(() -> { try { int len; byte[] data = new byte[1024]; InputStream inputStream = socket.getInputStream(); // 字节流读取数据 while ((-1 != (len = inputStream.read()))) { System.err.println(new String(data, 0, len)); } } catch (IOException ioException) { ioException.printStackTrace(); } }).start(); } catch (IOException e) { e.printStackTrace(); } } }).start(); }较为外围的局部是serverSocket.accept()这一串代码,会导致服务端阻塞期待客户端的连贯申请,即便没有连贯也会始终阻塞。 ...

April 13, 2023 · 6 min · jiezi

关于netty:NettySpringBoot-开发即时通讯系统无秘朝如青丝暮

Netty+SpringBoot 开发即时通讯零碎 download:https://www.666xit.com/3872/ 在前端开发中,Vue3、Pinia、Vite和TS成为了最罕用的开发工具。在这篇文章中,咱们将具体介绍如何应用Vue3、Pinia、Vite和TS进行前端开发。文章内容将分为以下几个方面: 什么是Vue3、Pinia、Vite和TS如何搭建Vue3、Pinia、Vite和TS我的项目如何在Vue3我的项目中应用Pinia如何在Vue3我的项目中应用Vite如何在Vue3我的项目中应用TS什么是Vue3、Pinia、Vite和TSVue3是一款风行的JavaScript框架,用于构建Web界面。Vue3相比Vue2,领有更好的性能和更多的个性,如Composition API和Teleport。Pinia是一个基于Vue3的状态治理库,它应用了Reactive API和Composition API,能够轻松管理应用程序的状态。Vite是一个疾速的构建工具,能够疾速构建现代化的Web应用程序。它应用了ES modules和古代的浏览器原生个性,能够实现疾速的开发和构建。TS是TypeScript的缩写,是一种类型平安的JavaScript超集,它增加了动态类型检查和其余一些个性,使代码更加牢靠和易于保护。 2:如何搭建Vue3、Pinia、Vite和TS我的项目首先,咱们须要装置Node.js和npm。而后,在终端中执行以下命令: npm init vite@latest my-vue-app --template vue-tscd my-vue-appnpm install这个命令将应用Vite创立一个Vue3+TS我的项目,并装置所有必要的依赖项。咱们当初能够应用以下命令启动开发服务器:npm run dev这将启动一个本地开发服务器,咱们能够在其中进行开发。接下来,咱们将介绍如何在Vue3我的项目中应用Pinia、Vite和TS。 3:如何在Vue3我的项目中应用Pinia要在Vue3我的项目中应用Pinia,咱们须要首先装置Pinia。咱们能够应用以下命令装置Pinia:npm install pinia而后,在咱们的Vue组件中应用Pinia。首先,咱们须要创立一个Pinia实例,而后将其传递给Vue应用程序: import { createApp } from 'vue'import { createPinia } from 'pinia'import App from './App.vue'const pinia = createPinia()const app = createApp(App)app.use(pinia)app.mount('#app')当初,咱们能够在组件中应用Pinia: import { defineComponent } from 'vue'import { useStore } from 'pinia'export default defineComponent({ setup() { const store = useStore() const count = store.count return { count

April 5, 2023 · 1 min · jiezi

关于netty:简说Netty

1、Netty整体架构

March 29, 2023 · 1 min · jiezi

关于netty:一文搞懂Reactor模型与实现

一文搞懂Reactor模型与实现

March 15, 2023 · 1 min · jiezi

关于netty:Java-Netty框架自建DNS代理服务器教程

前言DNS协定作为着互联网客户端-服务器通信模式得第一关,在当下每天都有成千上亿上网记录产生得当今社会,其重要性天然不可言喻。在国内比拟有名得DNS服务器有电信得114.114.114.114、阿里云得223.5.5.5,DNSPod得119.29.29.29,配置一个好的DNS服务器能够缩短申请响应工夫、升高DNS劫持概率,晋升上网体验。下面这些都是互联网专用DNS服务器,本文博主教大家应用 Java Netty 自建DNS代理服务器,目前网上对于应用Netty自建DNS服务器得教程参差不齐,大多没有代理步骤,达不到博主想要得代理成果,因此创立此文。 一、自建DNS代理服务器有哪些劣势域名管制:对于特定域名能够自在管制拜访权限(屏蔽对特定网站拜访)域名记录:记录局域网内各个主机得域名拜访(记录员工上网记录)配置内网域名:通过自建DNS服务器能够配置内网域名,节约老本DNS负载平衡:通过自建DNS服务器能够轻松实现对于拜访域名得负载平衡配置...二、自建DNS代理服务器代码增加域名黑名单文件,resources 文件夹下增加 black_list.txt 文件google.com.facebook.com.初始化 BLACK_LIST_DOMAIN private static final List<String> BLACK_LIST_DOMAIN = new ArrayList<>(); static { String s; try (InputStream is = DnsServer.class.getClassLoader().getResourceAsStream("black_list.txt"); BufferedReader br = new BufferedReader(new InputStreamReader(is))) { while (StrUtil.isNotBlank(s = br.readLine())) { BLACK_LIST_DOMAIN.add(s); } } catch (Exception e) { log.error(e.getMessage(), e); } }应用 UDP 协定绑定本机53端口,并初始化 ProxyUdp DNS申请代理对象@Slf4jpublic final class DnsServer { private static final List<String> BLACK_LIST_DOMAIN = new ArrayList<>(); static { ... } public static void main(String[] args) throws Exception { ProxyUdp proxyUdp = new ProxyUdp(); proxyUdp.init(); final int[] num = {0}; final NioEventLoopGroup group = new NioEventLoopGroup(); Bootstrap bootstrap = new Bootstrap(); bootstrap.group(group).channel(NioDatagramChannel.class) .handler(new ChannelInitializer<NioDatagramChannel>() { @Override protected void initChannel(NioDatagramChannel nioDatagramChannel) { nioDatagramChannel.pipeline().addLast(...); } }).option(ChannelOption.SO_BROADCAST, true); int port = 53; ChannelFuture future = bootstrap.bind(port).addListener(future1 -> { log.info("server listening port:{}", port); }); future.channel().closeFuture().addListener(future1 -> { if (future.isSuccess()) { log.info(future.channel().toString()); } }); }}给 nioDatagramChannel.pipeline() 增加 ChannelHandlernioDatagramChannel.pipeline().addLast(new DatagramDnsQueryDecoder()); nioDatagramChannel.pipeline().addLast(new SimpleChannelInboundHandler<DatagramDnsQuery>() { @Override protected void channelRead0(ChannelHandlerContext ctx, DatagramDnsQuery msg) { try { DefaultDnsQuestion dnsQuestion = msg.recordAt(DnsSection.QUESTION); String name = dnsQuestion.name(); log.info(name + ++num[0]); Channel channel = ctx.channel(); int id = msg.id(); channel.attr(AttributeKey.<DatagramDnsQuery>valueOf(String.valueOf(id))).set(msg); if (BLACK_LIST_DOMAIN.contains(name)) { DnsQuestion question = msg.recordAt(DnsSection.QUESTION); DatagramDnsResponse dnsResponse = getDatagramDnsResponse(msg, id, question); channel.writeAndFlush(dnsResponse); return; } proxyUdp.send(name, msg.id(), channel); } catch (Exception e) { log.error(e.getMessage(), e); } } private DatagramDnsResponse getDatagramDnsResponse(DatagramDnsQuery msg, int id, DnsQuestion question) { DatagramDnsResponse dnsResponse = new DatagramDnsResponse(msg.recipient(), msg.sender(), id); dnsResponse.addRecord(DnsSection.QUESTION, question); DefaultDnsRawRecord queryAnswer = new DefaultDnsRawRecord( question.name(), DnsRecordType.A, 600, Unpooled.wrappedBuffer(new byte[]{(byte) 192, (byte) 168, 1, 1})); dnsResponse.addRecord(DnsSection.ANSWER, queryAnswer); return dnsResponse; } @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable e) { log.error(e.getMessage(), e); } }); nioDatagramChannel.pipeline().addLast(new DatagramDnsResponseEncoder());在 new SimpleChannelInboundHandler<DatagramDnsQuery>() 中 解析客户端DNS查问报文, 获取拜访域名信息,如果拜访域名在黑名单中,则通过 getDatagramDnsResponse() 间接返回 192.168.1.1 的DNS响应报文,反之则通过 proxyUdp 对象转发DNS查问。 ...

January 9, 2023 · 5 min · jiezi

关于netty:折腾了我一周原来Netty网络编程就是这么个破玩意儿

1、阻塞阻塞模式下,相干办法都会导致线程暂停 ServerSocketChannel.accept 会在没有连贯建设时让线程暂停SocketChannel.read 会在通道中没有数据可读时让线程暂停阻塞的体现其实就是线程暂停了,暂停期间不会占用 cpu,但线程相当于闲置单线程下,阻塞办法之间相互影响,简直不能失常工作,须要多线程反对但多线程下,有新的问题,体现在以下方面 32 位 jvm 一个线程 320k,64 位 jvm 一个线程 1024k,如果连接数过多,必然导致 OOM,并且线程太多,反而会因为频繁上下文切换导致性能升高能够采纳线程池技术来缩小线程数和线程上下文切换,但治标不治本,如果有很多连贯建设,但长时间 inactive,会阻塞线程池中所有线程,因而不适宜长连贯,只适宜短连贯服务端代码 public class Server { public static void main(String[] args) { // 创立缓冲区 ByteBuffer buffer = ByteBuffer.allocate(16); // 取得服务器通道 try(ServerSocketChannel server = ServerSocketChannel.open()) { // 为服务器通道绑定端口 server.bind(new InetSocketAddress(8080)); // 用户寄存连贯的汇合 ArrayList<SocketChannel> channels = new ArrayList<>(); // 循环接管连贯 while (true) { System.out.println("before connecting..."); // 没有连贯时,会阻塞线程 SocketChannel socketChannel = server.accept(); System.out.println("after connecting..."); channels.add(socketChannel); // 循环遍历汇合中的连贯 for(SocketChannel channel : channels) { System.out.println("before reading"); // 解决通道中的数据 // 当通道中没有数据可读时,会阻塞线程 channel.read(buffer); buffer.flip(); ByteBufferUtil.debugRead(buffer); buffer.clear(); System.out.println("after reading"); } } } catch (IOException e) { e.printStackTrace(); } }}客户端代码 ...

December 29, 2022 · 8 min · jiezi

关于netty:进腾讯了全靠着这两份近千页的RedisNetty技术笔记

Redis和Netty是Java程序员涨薪路线上的绊脚石,但当咱们能够跨过它的时候,这块绊脚石就成为咱们涨薪的垫脚石。如果你不想被绊倒,无妨来看上面这两份笔记学习。RedisRedis曾经是IT企业技术栈中重要的一环,与其相干的从业者数量也逐年增多,对大多数人来说Redis堪称既相熟又神秘,只有有余4MB的源码却实现了一个功能丰富且强壮的数据库。Redis以其高速、轻量和丰盛的数据结构与性能被越来越多的工程师所钟爱。然而,用Redis的人很多,真正懂Redis的人很少。 本书正是写给那些应用了Redis 并心愿可能进一步深刻了解Redis的读者。作者及其团队通过对Redis各局部源码的剖析,庖丁解牛,深入浅出,率领读者一步步摸索Redis的方方面面,让读者从原理层面真正懂得Redis。本书的出版对于想深刻理解Redis的从业者来说是一个好消息。本书从源码层面对Redis进行深刻分析,尤其是数据结构局部,其学习意义不限于Redis,强烈推荐浏览。 优质的菜品须要有技能精湛的厨师来烹饪,本书就像以优质菜品做成的“大菜”。整本书没有太多啰嗦的语言,间接抽丝剥茧: 从根本的数据结构类型 到Redis 外部每个操作命令的底层代码运行逻辑和构造, 始终到整个Redis长久化技术、主从技术、分布式集群技术,等,都有深刻源码级别的解说, 让你领略从数据结构到整个高性能服务的全副设计之美。这份Redis源码笔记共有433页,须要完整版的小伙伴,能够【点击此处】来获取! Netty须要指出的是,网络通信框架的优良不仅仅体现在性能和效率上,更重要的体现是,是否可能屏蔽底层复杂度,编程模型是否简略易懂,是否实用更多的利用场景,以及开发社区是否沉闷。Netty 的胜利正是很好地满足了上述的这几点。作为互联网从业人员,相熟基于 Netty 网络编程乃至深刻了解 Netty 的设计和实现,对于无论是自研零碎,还是学习开源产品,都有很大的帮忙。 网络上介绍、剖析 Netty 的中文文章不少,其中可能做到成体系介绍,深入浅出,原理利用并重的寥寥。通过对这本书的学习,读者能够疾速把握基于 Netty 的编程,以及框架背地的设计哲学。对投身互联网零碎开发的工程师疾速把握 Netty 会有很大的帮忙。 第一局部是对框架的具体介绍,涵盖了它的设计、组件以及编程接口。 数据转换是网络编程中最常见的操作之一。第二局部介绍了 Netty 提供的用于简化这一工作的丰盛的工具集。 第三局部具体论述了几种本书后面简要介绍过的网络协议。咱们将会再次看到 Netty 是如何使你能在本人的应用程序中轻松采纳简单的 API,而又不用关怀其外部复杂性的。 第四局部介绍了由应用 Netty 实现了工作关键性零碎的出名公司提交的 5 份案例钻研。这些案例不仅阐明了咱们在整本书中所探讨过的框架各个组件在事实世界中的利用,而且还演示了Netty 的设计以及架构准则,在构建高度可伸缩和可扩大的应用程序方面的利用。 该附录的次要目标是提供一个对于 Apache Maven 的根本介绍,以便读者能够编译和运行本书的示例代码清单,并在开始应用 Netty 时扩大它们来创立本人的我的项目。 这份Netty实战文档共有272页,须要完整版的小伙伴,能够【点击此处】即可获取!

November 25, 2022 · 1 min · jiezi

关于netty:netty系列之在netty中使用proxy-protocol

简介咱们晓得proxy protocol是haproxy提出的一个代理协定,通过这个协定,所有实现这个协定的proxy或者LBS,都能够附带实在客户端的IP地址和端口号,这使得proxy protocol在理论利用中十分有用。 这么优良的协定,没有理由netty不反对。本文将谈判一下netty中对proxy protoco代理协定的反对。 netty对proxy protocol协定的反对proxy protocol协定其实很简略,就是在申请后面带了proxy header信息。 在netty中这个header信息叫做HAProxyMessage: public final class HAProxyMessage extends AbstractReferenceCounted {HAProxyMessage是一个ReferenceCounted,这一点和ByteBuf很相似,阐明HAProxyMessage保留着和ByteBuf很相似的个性。 依据proxy protocol协定,该协定能够分为两个版本,别离是v1和v2,其中v1版本是文本协定,而v2版本反对二进制的格局。 显然从代码编写和调试的角度来看v1更加敌对,然而从程序的角度来看,v2可能性能更高。 HAProxyMessage中有个专门的HAProxyProtocolVersion类,来示意proxy protocol的版本信息: public enum HAProxyProtocolVersion { V1(VERSION_ONE_BYTE), V2(VERSION_TWO_BYTE);HAProxyProtocolVersion是一个枚举类,在它外面定义了和proxy协定绝对应的两个版本号。 在版本号之后是command,在netty中用HAProxyCommand来示意: public enum HAProxyCommand { LOCAL(HAProxyConstants.COMMAND_LOCAL_BYTE), PROXY(HAProxyConstants.COMMAND_PROXY_BYTE);HAProxyCommand也是一个枚举类,外面定义了两个command的值,别离是local和proxy。 其中local示意该申请是代理服务器被动发动的,而不是客户端发动的,比方监控检测等申请。 proxy示意该申请是一个代理申请。 接下来是AddressFamily和TransportProtocol,这两个字段用同一个byte来示意,所以这两个类都是HAProxyProxiedProtocol的外部类。 先看下AddressFamily的定义: public enum AddressFamily { AF_UNSPEC(AF_UNSPEC_BYTE), AF_IPv4(AF_IPV4_BYTE), AF_IPv6(AF_IPV6_BYTE), AF_UNIX(AF_UNIX_BYTE);AddressFamily中定义了4个address family类型,别离是unspec,ipv4,ipv6和unix。别离对应未知family,ipv4,ipv6和unix domain socket。 再看下TransportProtocol的定义: public enum TransportProtocol { UNSPEC(TRANSPORT_UNSPEC_BYTE), STREAM(TRANSPORT_STREAM_BYTE), DGRAM(TRANSPORT_DGRAM_BYTE);TransportProtocol有3个值,别离是unspec,stream和dgram。别离对应未知协定,http/https协定,udp/tcp协定。 因为AddressFamily和TransportProtocol实际上是同一个byte,所以通过组合之后能够失去上面的几个枚举值: UNKNOWN(TPAF_UNKNOWN_BYTE, AddressFamily.AF_UNSPEC, TransportProtocol.UNSPEC), TCP4(TPAF_TCP4_BYTE, AddressFamily.AF_IPv4, TransportProtocol.STREAM), TCP6(TPAF_TCP6_BYTE, AddressFamily.AF_IPv6, TransportProtocol.STREAM), UDP4(TPAF_UDP4_BYTE, AddressFamily.AF_IPv4, TransportProtocol.DGRAM), UDP6(TPAF_UDP6_BYTE, AddressFamily.AF_IPv6, TransportProtocol.DGRAM), UNIX_STREAM(TPAF_UNIX_STREAM_BYTE, AddressFamily.AF_UNIX, TransportProtocol.STREAM), UNIX_DGRAM(TPAF_UNIX_DGRAM_BYTE, AddressFamily.AF_UNIX, TransportProtocol.DGRAM);以上的枚举值也是HAProxyProxiedProtocol中定义的值。 ...

November 21, 2022 · 3 min · jiezi

关于netty:netty系列之-在netty中使用-tls-协议请求-DNS-服务器

简介在后面的文章中咱们讲过了如何在netty中结构客户端别离应用tcp和udp协定向DNS服务器申请音讯。在申请的过程中并没有进行音讯的加密,所以这种申请是不平安的。 那么有同学会问了,就是申请解析一个域名的IP地址而已,还须要平安通信吗? 事实上,不加密的DNS查问音讯是很危险的,如果你在拜访一个重要的网站时候,DNS查问音讯被监听或者篡改,有可能你收到的查问返回IP地址并不是实在的地址,而是被篡改之后的地址,从而关上了钓鱼网站或者其余歹意的网站,从而造成了不必要的损失。 所以DNS查问也是须要保障平安的。 侥幸的是在DNS的传输协定中特意指定了一种加密的传输协定叫做DNS-over-TLS,简称("DoT")。 那么在netty中能够应用DoT来进行DNS服务查问吗?一起来看看吧。 反对DoT的DNS服务器因为DNS中有很多传输协定标准,但并不是每个DNS服务器都反对所有的标准,所以咱们在应用DoT之前须要找到一个可能反对DoT协定的DNS服务器。 这里我还是抉择应用阿里DNS服务器: 223.5.5.5之前应用TCP和UDP协定的时候查问的DNS端口是53,如果换成了DoT,那么端口就须要变成853。 搭建反对DoT的netty客户端DoT的底层还是TCP协定,也就是说TLS over TCP,所以咱们须要应用NioEventLoopGroup和NioSocketChannel来搭建netty客户端,如下所示: EventLoopGroup group = new NioEventLoopGroup(); Bootstrap b = new Bootstrap(); b.group(group) .channel(NioSocketChannel.class) .handler(new DotChannelInitializer(sslContext, dnsServer, dnsPort)); final Channel ch = b.connect(dnsServer, dnsPort).sync().channel();这里抉择的是NioEventLoopGroup和NioSocketChannel。而后向Bootstrap中传入自定义的DotChannelInitializer即可。 DotChannelInitializer中蕴含了自定义的handler和netty自带的handler。 咱们来看下DotChannelInitializer的定义和他的构造函数: class DotChannelInitializer extends ChannelInitializer<SocketChannel> { public DotChannelInitializer(SslContext sslContext, String dnsServer, int dnsPort) { this.sslContext = sslContext; this.dnsServer = dnsServer; this.dnsPort = dnsPort; }DotChannelInitializer须要三个参数别离是sslContext,dnsServer和dnsPort。 这三个参数都是在sslContext中应用的: protected void initChannel(SocketChannel ch) { ChannelPipeline p = ch.pipeline(); p.addLast(sslContext.newHandler(ch.alloc(), dnsServer, dnsPort)) .addLast(new TcpDnsQueryEncoder()) .addLast(new TcpDnsResponseDecoder()) .addLast(new DotChannelInboundHandler()); }SslContext次要用来进行TLS配置,上面是SslContext的定义: ...

November 4, 2022 · 2 min · jiezi

关于netty:Java-技术栈中间件优雅停机方案设计与实现全景图

本系列 Netty 源码解析文章基于 4.1.56.Final 版本本文概要在上篇文章 我为 Netty 奉献源码 | 且看 Netty 如何应答 TCP 连贯的失常敞开,异样敞开,半敞开场景 中笔者为大家具体介绍了 Netty 在解决连贯敞开时的残缺过程,并具体介绍了 Netty 如何应答 TCP 连贯在敞开时会遇到的各种场景。 在连贯敞开之后,接下来就轮到 Netty 的谢幕时刻了,本文笔者会为大家详尽 Java 技术栈中间件中对于优雅停机计划的具体设计和实现。 笔者会从日常开发工作中常见的版本公布,服务高低线的场景聊起,引出服务优雅启停的需要,并从这个需要登程,一步一步带大家探索各个中间件里的优雅停机的相干设计。 相熟笔者文风的读者敌人应该晓得,笔者必定不会只是简略的介绍,要么不讲,要讲就要把整个技术体系的前世今生给大家讲清楚,讲明确。 基于这目标,笔者会先从反对优雅停机的底层技术基石--内核中的信号量开始聊起。 从内核层咱们接着会聊到 JVM 层,在 JVM 层一探优雅停机底层的技术玄机。 随后咱们会从 JVM 层一路奔袭到 Spring 而后到 Dubbo。在这个过程中,笔者还会带大家一起 Shooting Dubbo 在优雅停机下的一个 Bug,并为大家具体介绍修复过程。 最初由 Dubbo 层的优雅停机,引出咱们的配角--Netty 优雅停机的设计与实现: 上面咱们来正式开始本文的内容~~ 1. Java 过程的优雅启停在咱们的日常开发工作中,业务需要的迭代和优化随同围绕着咱们整个开发周期,当咱们加班加点实现了业务需要的开发,而后又历经各种艰难险阻通过了测试的验证,最初通过和产品经理的各种纠缠相爱相杀之后,终于到了最最激动人心的时刻程序要部署上线了。 那么在程序部署上线的过程中势必会波及到线上服务的敞开和重启,对于对线上服务的启停这外面有很多的考究,万万不能简略粗犷的进行敞开和重启,因为此时线上服务可能承载着生产的流量,可能正在进行重要的业务解决流程。 比方:用户正在购买商品,钱曾经付了,恰好这时赶上程序上线,如果咱们这时简略粗犷的对服务进行敞开,重启,可能就会导致用户付了钱,然而订单未创立或者商品未呈现在用户的购物清单中,给用户造成了本质的损失,这是十分重大的结果。 为了保障能在程序上线的过程中做到业务无损,所以线上服务的优雅敞开和优雅启动显得就十分十分重要了。 1.1 优雅启动在 Java 程序的运行过程中,程序的运行速度个别会随着程序的运行缓缓的进步,所以从线上体现上来看 Java 程序在运行一段时间后往往会比程序刚启动的时候会快很多。 这是因为 Java 程序在运行过程中,JVM 会一直收集到程序运行时的动态数据,这样能够将高频执行代码通过即时编译成机器码,随后程序运行就间接执行机器码,运行速度齐全不输 C 或者 C++ 程序。 ...

August 8, 2022 · 25 min · jiezi

关于netty:一文聊透-Netty-IO-事件的编排利器-pipeline-详解所有-IO-事件的触发时机以及传播路径

本系列Netty源码解析文章基于 4.1.56.Final版本1. 前文回顾在前边的系列文章中,笔者为大家具体分析了 Reactor 模型在 netty 中的创立,启动,运行,接管连贯,接收数据,发送数据的残缺流程,在具体分析整个 Reactor 模型如何在 netty 中实现的过程里,咱们或多或少的见到了 pipeline 的身影。 比方在 Reactor 启动的过程中首先须要创立 NioServerSocketChannel ,在创立的过程中会为 NioServerSocketChannel 创立调配一个 pipeline ,用于对 OP_ACCEPT 事件的编排。 当 NioServerSocketChannel 向 main reactor 注册胜利后,会在 pipeline 中触发 ChannelRegistered 事件的流传。 当 NioServerSocketChannel 绑定端口胜利后,会在 pipeline 中触发 ChannelActive 事件的流传。 又比方在 Reactor 接管连贯的过程中,当客户端发动一个连贯并实现三次握手之后,连贯对应的 Socket 会寄存在内核中的全连贯队列中,随后 JDK Selector 会告诉 main reactor 此时 NioServerSocketChannel 上有 OP_ACCEPT 事件沉闷,最初 main reactor 开始执行 NioServerSocketChannel 的底层操作类 NioMessageUnsafe#read 办法在 NioServerSocketChannel 中的 pipeline 中流传 ChannelRead 事件。 ...

August 4, 2022 · 36 min · jiezi

关于netty:Netty-如何高效接收网络数据一文聊透-ByteBuffer-动态自适应扩缩容机制

本系列Netty源码解析文章基于 4.1.56.Final版本 前文回顾在前边的系列文章中,咱们从内核如何收发网络数据开始以一个C10K的问题作为主线具体从内核角度论述了网络IO模型的演变,最终在此基础上引出了Netty的网络IO模型如下图所示: 具体内容可回看《从内核角度看IO模型的演变》后续咱们又围绕着Netty的主从Reactor网络IO线程模型,在《Reactor模型在Netty中的实现》一文中具体论述了Netty的主从Reactor模型的创立,以及介绍了Reactor模型的要害组件。搭建了Netty的外围骨架如下图所示: 在外围骨架搭建结束之后,咱们随后又在《具体图解Reactor启动全流程》一文中论述了Reactor启动的全流程,一个十分重要的外围组件NioServerSocketChannel开始在这里首次亮相,承当着一个网络框架最重要的工作--高效接管网络连接。咱们介绍了NioServerSocketChannel的创立,初始化,向Main Reactor注册并监听OP_ACCEPT事件的整个流程。在此基础上,Netty得以整装待发,严阵以待开始迎接海量的客户端连贯。 随后紧接着咱们在《Netty如何高效接管网络连接》一文中具体介绍了Netty高效接管客户端网络连接的全流程,在这里Netty的外围重要组件NioServerSocketChannel开始正是退场,在NioServerSocketChannel中咱们创立了客户端连贯NioSocketChannel,并具体介绍了NioSocketChannel的初始化过程,随后通过在NioServerSocketChannel的pipeline中触发ChannelRead事件,并最终在ServerBootstrapAcceptor中将客户端连贯NioSocketChannel注册到Sub Reactor中开始监听客户端连贯上的OP_READ事件,筹备接管客户端发送的网络数据也就是本文的主题内容。 自此Netty的外围组件全副就绪并启动结束,开始腾飞~~~ 之前文章中的配角是Netty中主Reactor组中的Main Reactor以及注册在Main Reactor上边的NioServerSocketChannel,那么从本文开始,咱们文章中的配角就切换为Sub Reactor以及注册在SubReactor上的NioSocketChannel了。 上面就让咱们正式进入明天的主题,看一下Netty是如何解决OP_READ事件以及如何高效接管网络数据的。 1. Sub Reactor解决OP_READ事件流程总览 客户端发动零碎IO调用向服务端发送数据之后,当网络数据达到服务端的网卡并通过内核协定栈的解决,最终数据达到Socket的接收缓冲区之后,Sub Reactor轮询到NioSocketChannel上的OP_READ事件就绪,随后Sub Reactor线程就会从JDK Selector上的阻塞轮询APIselector.select(timeoutMillis)调用中返回。转而去解决NioSocketChannel上的OP_READ事件。 留神这里的Reactor为负责解决客户端连贯的Sub Reactor。连贯的类型为NioSocketChannel,解决的事件为OP_READ事件。在之前的文章中笔者曾经屡次强调过了,Reactor在解决Channel上的IO事件入口函数为NioEventLoop#processSelectedKey 。 public final class NioEventLoop extends SingleThreadEventLoop { private void processSelectedKey(SelectionKey k, AbstractNioChannel ch) { final AbstractNioChannel.NioUnsafe unsafe = ch.unsafe(); ..............省略................. try { int readyOps = k.readyOps(); if ((readyOps & SelectionKey.OP_CONNECT) != 0) { ..............解决OP_CONNECT事件................. } if ((readyOps & SelectionKey.OP_WRITE) != 0) { ..............解决OP_WRITE事件................. } if ((readyOps & (SelectionKey.OP_READ | SelectionKey.OP_ACCEPT)) != 0 || readyOps == 0) { //本文重点解决OP_ACCEPT事件 unsafe.read(); } } catch (CancelledKeyException ignored) { unsafe.close(unsafe.voidPromise()); } }}这里须要重点强调的是,以后的执行线程当初曾经变成了Sub Reactor,而Sub Reactor上注册的正是netty客户端NioSocketChannel负责解决连贯上的读写事件。 ...

July 27, 2022 · 7 min · jiezi

关于netty:Netty网络编程Netty应用与核心模块组件

1.用Netty实现群聊零碎2.用Netty实现心跳检测3.用Netty编程实现客户端与服务器端的长链接4.Netty外围模块组件剖析咱们先通过代码示例,感受一下Netty的应用,而后再分析这些工具类每一个的作用。 1.用Netty实现群聊零碎 咱们用Netty实现一个群聊零碎,实现客户端和服务器端之间的简略通信。 性能点如下:1)服务器端:能够检测用户上线,离线,并实现音讯转发2)客户端:通过channel能够把音讯发送给所有其它用户,同时也能接管其它用户发送的信息。 server端:client端:server: package com.example.demo.netty.nettyDemo.chat;import io.netty.bootstrap.ServerBootstrap;import io.netty.channel.*;import io.netty.channel.nio.NioEventLoopGroup;import io.netty.channel.socket.SocketChannel;import io.netty.channel.socket.nio.NioServerSocketChannel;import io.netty.handler.codec.string.StringDecoder;import io.netty.handler.codec.string.StringEncoder;import lombok.extern.slf4j.Slf4j;/** * @author sulingfeng * @title: GroupChatServer * @projectName netty-learn * @description: TODO * @date 2022/7/12 17:12 */@Slf4jpublic class GroupChatServer { private int port; public GroupChatServer(int port) { this.port = port; } public void run() throws Exception{ //两个工作线程组 EventLoopGroup bossGroup = new NioEventLoopGroup(); EventLoopGroup workerGroup = new NioEventLoopGroup(); try{ ServerBootstrap bootstrap = new ServerBootstrap(); bootstrap.group(bossGroup, workerGroup) .channel(NioServerSocketChannel.class)//应用NioSocketChannel作为通道的实现 .option(ChannelOption.SO_BACKLOG, 128)//设置线程队列的连贯数量下限 .childOption(ChannelOption.SO_KEEPALIVE, true)//放弃流动连贯状态 .childHandler(new ChannelInitializer<SocketChannel>() { @Override protected void initChannel(SocketChannel ch) throws Exception { //获取到pipeline ChannelPipeline pipeline = ch.pipeline(); //向pipeline退出解码器 pipeline.addLast("decoder", new StringDecoder()); //向pipeline退出编码器 pipeline.addLast("encoder", new StringEncoder()); //退出本人的业务解决handler pipeline.addLast(new GroupChatServerHandler()); } }); log.info("netty 服务器启动!"); ChannelFuture channelFuture = bootstrap.bind(port).sync(); channelFuture.channel().closeFuture().sync(); }finally { bossGroup.shutdownGracefully(); workerGroup.shutdownGracefully(); } } public static void main(String[] args) throws Exception{ //启动 GroupChatServer groupChatServer = new GroupChatServer(10087); groupChatServer.run(); }}serverHandler: ...

July 26, 2022 · 5 min · jiezi

关于netty:抓到-Netty-一个-Bug-顺带来透彻地聊一下-Netty-是如何高效接收网络连接的

欢送关注公众号:bin的技术小屋,浏览公众号原文 本系列Netty源码解析文章基于 4.1.56.Final版本对于一个高性能网络通讯框架来说,最最重要也是最外围的工作就是如何高效的接管客户端连贯,这就好比咱们开了一个饭店,那么迎接客人就是饭店最重要的工作,咱们要先把客人迎接进来,不能让客人一看人多就走掉,只有客人进来了,哪怕菜做的慢一点也没关系。 本文笔者就来为大家介绍下netty这块最外围的内容,看看netty是如何高效的接管客户端连贯的。 下图为笔者在一个月黑风高天空显得那么高深边远的夜晚,闲来无事,于是捧起Netty对于如何接管连贯这部分源码细细品读的时候,意外的发现了一个影响Netty接管连贯吞吐的一个Bug。 于是笔者就在Github提了一个Issue#11708,论述了下这个Bug产生的起因以及导致的后果并和Netty的作者一起探讨了下修复措施。如上图所示。 Issue#11708:https://github.com/netty/nett...这里先不具体解释这个Issue,也不倡议大家当初就关上这个Issue查看,笔者会在本文的介绍中随着源码深刻的解读缓缓的为大家一层一层地拨开迷雾。 之所以在文章的结尾把这个拎进去,笔者是想让大家带着狐疑,扫视,观赏,崇拜,敬畏的态度来一起品读世界顶级程序员编写的代码。由衷的感激他们在这一畛域做出的奉献。 好了,问题抛出来后,咱们就带着这个疑难来开始本文的内容吧~~~ 前文回顾依照老规矩,再开始本文的内容之前,咱们先来回顾下前边几篇文章的概要内容帮忙大家梳理一个框架全貌进去。 笔者这里再次想和读者敌人们强调的是本文能够独立观看,并不依赖前边系列文章的内容,只是大家如果对相干细节局部感兴趣的话,能够在浏览完本文之后在去回看相干文章。在前边的系列文章中,笔者为大家介绍了驱动Netty整个框架运行的外围引擎Reactor的创立,启动,运行的全流程。从当初开始Netty的整个外围框架就开始运转起来开始工作了,本文要介绍的次要内容就是Netty在启动之后要做的第一件事件:监听端口地址,高效接管客户端连贯。 在《聊聊Netty那些事儿之从内核角度看IO模型》一文中,咱们是从整个网络框架的基石IO模型的角度整体论述了下Netty的IO线程模型。 而Netty中的Reactor正是IO线程在Netty中的模型定义。Reactor在Netty中是以Group的模式呈现的,分为: 主Reactor线程组也就是咱们在启动代码中配置的EventLoopGroup bossGroup,main reactor group中的reactor次要负责监听客户端连贯事件,高效的解决客户端连贯。也是本文咱们要介绍的重点。从Reactor线程组也就是咱们在启动代码中配置的EventLoopGroup workerGroup,sub reactor group中的reactor次要负责解决客户端连贯上的IO事件,以及异步工作的执行。最初咱们得出Netty的整个IO模型如下: 本文咱们探讨的重点就是MainReactorGroup的外围工作上图中所示的步骤1,步骤2,步骤3。 在从整体上介绍完Netty的IO模型之后,咱们又在《Reactor在Netty中的实现(创立篇)》中残缺的介绍了Netty框架的骨架主从Reactor组的搭建过程,论述了Reactor是如何被创立进去的,并介绍了它的外围组件如下图所示: thread即为Reactor中的IO线程,次要负责监听IO事件,解决IO工作,执行异步工作。selector则是JDK NIO对操作系统底层IO多路复用技术实现的封装。用于监听IO就绪事件。taskQueue用于保留Reactor须要执行的异步工作,这些异步工作能够由用户在业务线程中向Reactor提交,也能够是Netty框架提交的一些本身外围的工作。scheduledTaskQueue则是保留Reactor中执行的定时工作。代替了原有的工夫轮来执行延时工作。tailQueue保留了在Reactor须要执行的一些尾部收尾工作,在一般工作执行完后 Reactor线程会执行尾部工作,比方对Netty 的运行状态做一些统计数据,例如工作循环的耗时、占用物理内存的大小等等在骨架搭建结束之后,咱们随后又在在《具体图解Netty Reactor启动全流程》》一文中介绍了本文的配角服务端NioServerSocketChannel的创立,初始化,绑定端口地址,向main reactor注册监听OP_ACCEPT事件的残缺过程。 main reactor如何解决OP_ACCEPT事件将会是本文的次要内容。 自此Netty框架的main reactor group曾经启动结束,开始筹备监听OP_accept事件,当客户端连贯上来之后,OP_ACCEPT事件沉闷,main reactor开始解决OP_ACCEPT事件接管客户端连贯了。 而netty中的IO事件分为:OP_ACCEPT事件,OP_READ事件,OP_WRITE事件和OP_CONNECT事件,netty对于IO事件的监听和解决对立封装在Reactor模型中,这四个IO事件的处理过程也是咱们后续文章中要独自拿进去介绍的,本文咱们聚焦OP_ACCEPT事件的解决。 而为了让大家可能对IO事件的解决有一个完整性的意识,笔者写了《一文聊透Netty外围引擎Reactor的运行架构》这篇文章,在文章中具体介绍了Reactor线程的整体运行框架。 Reactor线程会在一个死循环中996不停的运行,在循环中会一直的轮询监听Selector上的IO事件,当IO事件沉闷后,Reactor从Selector上被唤醒转去执行IO就绪事件的解决,在这个过程中咱们引出了上述四种IO事件的解决入口函数。 private void processSelectedKey(SelectionKey k, AbstractNioChannel ch) { //获取Channel的底层操作类Unsafe final AbstractNioChannel.NioUnsafe unsafe = ch.unsafe(); if (!k.isValid()) { ......如果SelectionKey曾经生效则敞开对应的Channel...... } try { //获取IO就绪事件 int readyOps = k.readyOps(); //解决Connect事件 if ((readyOps & SelectionKey.OP_CONNECT) != 0) { int ops = k.interestOps(); //移除对Connect事件的监听,否则Selector会始终告诉 ops &= ~SelectionKey.OP_CONNECT; k.interestOps(ops); //触发channelActive事件处理Connect事件 unsafe.finishConnect(); } //解决Write事件 if ((readyOps & SelectionKey.OP_WRITE) != 0) { ch.unsafe().forceFlush(); } //解决Read事件或者Accept事件 if ((readyOps & (SelectionKey.OP_READ | SelectionKey.OP_ACCEPT)) != 0 || readyOps == 0) { unsafe.read(); } } catch (CancelledKeyException ignored) { unsafe.close(unsafe.voidPromise()); } }本文笔者将会为大家重点介绍OP_ACCEPT事件的解决入口函数unsafe.read()的整个源码实现。 ...

July 26, 2022 · 10 min · jiezi

关于netty:Netty网络编程Reactor模式高性能架构设计原理

1.传统I/O模型2.Reactor模式3.单Reactor单线程4.单Reactor多线程5.主从Reactor多线程6.Reactor模式特点总结不同的线程模式对于性能有微小的影响,为了后续更好地发展Netty的线程模式,咱们先来看下各种线程模式的原理和区别,最初进行比照。 1.传统I/O模型 1.1)工作原理图: 1.2)模型特点:传统I/O模型也就是BIO模型,这在咱们后面曾经介绍过。Netty网络编程——Netty的根本介绍与BIO,它的毛病有: 1)每个申请都要创立线程,浪费资源。 2)连贯建设后,如果服务端的数据还没有筹备好,则服务端线程不会去解决别的申请,会阻塞,造成资源节约。 2.Reactor模式针对传统I/O模型的毛病,咱们有如下的解决方案: 1)基于多路复用,一个线程解决多个客户端连贯。 2)业务逻辑解决线程基于线程池,一个线程能够解决多个连贯的业务。 Reactor模式原理图: 咱们从图中能够得出, 1)服务器端程序处理传进来的多个申请,并将他们分派给对应的解决线程,咱们称之为Reactor(反应堆)模式。2)Reactor采纳IO多路复用,一个Handler解决多个客户端连贯,解决线程结束后,会通过Handler返回对应的客户端。 咱们能够得出Reactor里有两个角色:ServiceHandler:负责监听和散发事件,分发给适当的线程来对业务逻辑进行解决。 EventHandler:解决申请的理论线程。 Reactor模式还分为三种:单Reactor单线程,单Reactor多线程,主从Reactor多线程,咱们来顺次进行解说。 3.单Reactor单线程原理图: 解决逻辑:1)当有client申请的时候,咱们通过select进行I/O多路复用解决申请。2)Reactor通过select监听客户端事件,通过dispatch来转发申请。3)如果是建设连贯的申请,咱们应用Acceptor通过accept解决申请。4)如果是不是建设连贯申请,Reactor则会调用对应的Handler解决。 总结:服务器端用一个线程实现所有客户端的IO操作,模型简略易懂。 优缺点剖析:长处:1)简略,没有线程竞争,全副都由一个线程实现。 毛病:1)性能不佳,一个线程要解决这么多申请,当Handler正在进行I/O密集型的操作时,整个Reactor无奈解决其它事件。2)高度耦合性,导致线程出现异常的时候,会导致整个模块不可用。 4.单Reactor多线程 原理图: 解决逻辑: 1)通过Reactor中的select解决所有连贯申请。2)通过Reactor中的dispatch来散发申请。3)通过Acceptor来解决连贯。4)通过不同的handler来解决不同的解决申请,然而不做具体解决,而是通过send发送给线程池。5)worker线程业务处理完毕后,将后果返回handler。 优缺点剖析: 长处:相比单线程模式,更能施展cpu的能力。 毛病:建设连贯的线程还是只有一个,在连接数多的状况下,容易呈现瓶颈。 5.主从Reactor多线程 针对单Reactor中的连接数过大造成的瓶颈,能够让Reactor在多线程中进行运行。 原理图: 解决逻辑:1)MainReactor主线程通过select解决多个连贯申请,以及解决建设连贯的逻辑。2)MainReactor承受到处理事件后,把事件分发给Reactor子线程。3)SubReactor将连贯退出到连贯队列进行监听,并依据事件创立不同的handler4)handler将对应的事件分发给worker线程进行解决。5)返回后果6)Reactor主线程能够对应多个Reactor子线程。 优缺点剖析: 长处:1)父子线程职责明确,父线程只须要建设连贯,子线程解决业务逻辑2)父子线程只须要进行转发即可,交互简略。 毛病:1)编程复杂度高。 6.Reactor模式特点总结 1)响应快,不用为某个长时间解决的业务进行阻塞期待。2)分工明确,各自的线程解决各自的逻辑,繁多职责。3)扩展性好,主须要减少不同的Handler就能够解决不同的工作,还不必批改原来的代码。4)复用性好,Reactor与具体的逻辑无关,具备很高的复用性。5)防止简单的多线程的切换开销。

July 22, 2022 · 1 min · jiezi

关于netty:快来体验快速通道netty中epoll传输协议详解

简介在后面的章节中,咱们解说了kqueue的应用和原理,接下来咱们再看一下epoll的应用。两者都是更加高级的IO形式,都须要借助native的办法实现,不同的是Kqueue用在mac零碎中,而epoll用在liunx零碎中。 epoll的具体应用epoll的应用也很简略,咱们还是以罕用的聊天室为例来解说epoll的应用。 对于server端来说须要创立bossGroup和workerGroup,在NIO中这两个group是NIOEventLoopGroup,在epoll中则须要应用EpollEventLoopGroup: EventLoopGroup bossGroup = new EpollEventLoopGroup(1); EventLoopGroup workerGroup = new EpollEventLoopGroup();接着须要将bossGroup和workerGroup传入到ServerBootstrap中: ServerBootstrap b = new ServerBootstrap(); b.group(bossGroup, workerGroup) .channel(EpollServerSocketChannel.class) .handler(new LoggingHandler(LogLevel.INFO)) .childHandler(new NativeChatServerInitializer());留神,这里传入的channel是EpollServerSocketChannel,专门用来解决epoll的申请。其余的局部和一般的NIO服务是一样的。 接下来看下epoll的客户端,对于客户端来说须要创立一个EventLoopGroup,这里应用的是EpollEventLoopGroup: EventLoopGroup group = new EpollEventLoopGroup();而后将这个group传入Bootstrap中去: Bootstrap b = new Bootstrap(); b.group(group) .channel(EpollSocketChannel.class) .handler(new NativeChatClientInitializer());这里应用的channel是EpollSocketChannel,是和EpollServerSocketChannel对应的客户端的channel。 EpollEventLoopGroup先看下EpollEventLoopGroup的定义: public final class EpollEventLoopGroup extends MultithreadEventLoopGroup 和KqueueEventLoopGroup一样,EpollEventLoopGroup也是继承自MultithreadEventLoopGroup,示意它能够开启多个线程。 在应用EpollEventLoopGroup之前,须要确保epoll相干的JNI接口都曾经筹备结束: Epoll.ensureAvailability();newChild办法用来生成EpollEventLoopGroup的子EventLoop: protected EventLoop newChild(Executor executor, Object... args) throws Exception { Integer maxEvents = (Integer) args[0]; SelectStrategyFactory selectStrategyFactory = (SelectStrategyFactory) args[1]; RejectedExecutionHandler rejectedExecutionHandler = (RejectedExecutionHandler) args[2]; EventLoopTaskQueueFactory taskQueueFactory = null; EventLoopTaskQueueFactory tailTaskQueueFactory = null; int argsLength = args.length; if (argsLength > 3) { taskQueueFactory = (EventLoopTaskQueueFactory) args[3]; } if (argsLength > 4) { tailTaskQueueFactory = (EventLoopTaskQueueFactory) args[4]; } return new EpollEventLoop(this, executor, maxEvents, selectStrategyFactory.newSelectStrategy(), rejectedExecutionHandler, taskQueueFactory, tailTaskQueueFactory); }从办法中能够看到,newChild承受一个executor和多个额定的参数,这些参数别离是SelectStrategyFactory,RejectedExecutionHandler,taskQueueFactory和tailTaskQueueFactory,最终将这些参数传入EpollEventLoop中,返回一个新的EpollEventLoop对象。 ...

July 14, 2022 · 2 min · jiezi

关于netty:nettysocketio

netty-socketio 学习资源初级教程,也是源码git我的项目举荐的demo因为没有guide,所以只能多看我的项目,一下是在网上参考的一些我的项目,给了我很大的帮忙: Spring Boot实战之netty-socketio实现简略聊天室(给指定用户推送音讯)git 上的一个用了分布式和分布式锁的 NettySocketServer 该类MySocketIOServer是比拟重要的一个类Spring Boot实战之netty-socketio实现简略聊天室根本应用这里总结一下NettySocketIO的根本应用,只是说一下大略流程,具体细节实现看代码: 配置com.corundumstudio.socketio.Configuration 并实例化 SocketIOServer server = new SocketIOServer(config)。通过com.corundumstudio.socketio.annotation.OnConnect 等注解 来实现监听socket连贯、断开、接管音讯等。而后通过依赖注入 new SpringAnnotationScanner(SocketIOServer socketIOServer) ,通过该类来扫描@OnConnect, OnEvent等注解,注册正真的监听器。当然了,也能够通过 Configuration.setXxxListener 和 SocketIOServer.addEventListener 来注册监听器,算是2步骤的扩大。最初客户端就能够通过 io.connect('http://localhost:9090?token='+userName); 来连贯到socket,而后就能够通信了。因为NettySocketIO是通过集成socket.io这个库来实现socket的,所以客户端语言不限,然而必须也要应用socketio的库,socketio官网NettySocketIO是一个开源框架,非要说什么官网的话 怕就是下面说到的他的git地址了。而且本人去找NettySocketIO的应用DEMO也是少的可怜,文档更是没有,所以只能本人去钻研了。博主通过钻研NettySocketIO源码,总结以下: Namespace : Namespace implements SocketIONamespace。而且也是SocketIONamespace的惟一实现,SocketIOServer中申明了它的实例 mainNamespace,外面保留了 所有的监听器、所有的客户端、客户端与Room的对应关系等数据,能够说他保留着SocketServer的用户所有信息。Namespace都会有一个名字,新创建一个Server的时候,都会默认创立一个名字为""的Namespace。NamespaceHub : SocketIOServer中有它的一个实例,叫namespaceHub。保留了该server的所有Namespace(尽管当初还不晓得第二个create第二个Namespace用来干什么)和 该Server的配置room : 房间。NettySocketIO曾经提供了房间的性能。能够通过String 类型的房间名来给所有在该房间里的所有用户发送音讯。能够通过 socketIOClient.joinRoom("room1");来退出房间,也能够通过SocketIOServer.getNamespace(xx).joinRoom()办法退出房间。房间信息,次要就保留在Namespace的roomClients和clientRooms中。默认的所有用户都会进入 Namespace.DEFAULT_NAME(""),能够了解为是大厅房间。BroadcastOperations : BroadcastOperations 就是一个播送器,外面保留了SocketIoClient的列表和StoreFactory(临时认为是一个公布音讯的类),所以如果想给某一类用户发消息,就能够把这类用户放入到这个类的实例中,而后一起发送。给某个房间的所有用户发信息server.getRoomOperations("roomName").sendEvent() 参考https://www.xncoding.com/2017...https://blog.csdn.net/u014635...https://github.com/root-wyj/s...https://github.com/wangnamu/N...https://github.com/dolyw/Proj...版本问题:https://blog.csdn.net/qq_4173...https://github.com/Heit/netty...https://github.com/mrniko/net...https://github.com/hiwepy/soc...https://developer.aliyun.com/... 官网https://socket.io/https://github.com/mrniko/net...https://github.com/socketio/e...https://github.com/trinopoty/...

June 24, 2022 · 1 min · jiezi

关于netty:netty系列之netty中的核心编码器base64

简介咱们晓得数据在netty中传输是以ByteBuf的模式进行的,能够说ByteBuf是netty的数据传输根底。然而对于古代的应用程序来说,通常咱们须要用到其余的数据结构或者类型。 为了不便咱们在程序中的编写,一种形式就是在将数据传入到netty中的时候由程序员本身将数据格式进行转换,而后再调用netty的零碎办法。另外一种形式就是定义一些codec,由netty的外在编码机制将程序中用到的数据格式和ByteBuf进行主动转换。 很显著,应用codec的形式更加简捷,也更加合乎程序的开发规定。 为了不便程序的开发,netty自身在外部定义了一些外围的codec插件,咱们在须要的时候间接选用即可。 本文将会解说netty外部实现codec的形式和一个最外围的编码器base64。 netty codec的实现逻辑所有的netty codec的目标就是在数据传输过程中进行数据类型的转换,换句话说就是对数据进行解决。咱们晓得netty中有两个对数据进行handler的类,别离是ChannelInboundHandlerAdapter和ChannelOutboundHandlerAdapter,他们别离对应channel中的inbound音讯和outbound音讯进行解决。 所以很天然的,咱们的codec逻辑只须要在这两个中央增加即可。 netty为咱们提供了两个HandlerAdapter类的继承类,别离是MessageToMessageDecoder和MessageToMessageEncoder: public abstract class MessageToMessageEncoder<I> extends ChannelOutboundHandlerAdapter public abstract class MessageToMessageDecoder<I> extends ChannelInboundHandlerAdapter从名字就可以看进去这两个类别离应用来编码和解码用的,所以咱们的codec只须要别离实现这两个类即可。 以下是一个StringToIntegerDecoder和IntegerToStringEncoder的例子: public class StringToIntegerDecoder extends MessageToMessageDecoder<String> { @Override public void decode(ChannelHandlerContext ctx, String message, List<Object> out) throws Exception { out.add(message.length()); } } public class IntegerToStringEncoder extends MessageToMessageEncoder<Integer> { @Override public void encode(ChannelHandlerContext ctx, Integer message, List<Object> out) throws Exception { out.add(message.toString()); } }最简略的实现就是别离重构这两个类的decode和encode办法。 netty中Base64的实现咱们晓得JDK中曾经有了Base64实现的工具类叫做java.util.Base64。然而在netty中又应用了一个新的实现类同样叫做Base64,它的全称是io.netty.handler.codec.base64.Base64。 这个Base64类中用到了一个Base64Dialect类,也就是netty中Base64反对的Base64编码方式。Base64Dialect中提供了上面的几种类型: ...

April 22, 2022 · 1 min · jiezi

关于netty:netty项目注册到eureka

我的项目中须要实现即时通信IM沟通,因为我的项目是微服务架构,并且每个子系统都是集群部署模式。须要解决前端的websocket申请通过springcloud-gateway网关平衡散发到集群中的netty服务中,实现零碎的高可用。 netty服务注册到eureka中 import com.netflix.appinfo.*;import com.netflix.discovery.DiscoveryClient;import com.xgjk.xgware.im.websocket.NettyProperties;import org.springframework.beans.factory.SmartInitializingSingleton;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.cloud.commons.util.InetUtils;import org.springframework.cloud.netflix.eureka.EurekaClientConfigBean;import org.springframework.stereotype.Component;import java.util.Map;@Componentpublic class NettyDiscoveryClient implements SmartInitializingSingleton { @Autowired private EurekaInstanceConfig config; @Autowired private NettyProperties nettyProperties; @Autowired private InetUtils inetUtils; @Autowired private EurekaClientConfigBean eurekaClientConfigBean; /** * 在spring容器治理的所有单例对象(非懒加载对象)初始化实现之后调用的回调接口 */ @Override public void afterSingletonsInstantiated() { /*EurekaClientConfigBean eurekaClientConfigBean = new EurekaClientConfigBean(); eurekaClientConfigBean.setServiceUrl(new HashMap<String, String>() {{ put("defaultZone", defaultZone); }});*/ String host = inetUtils.findFirstNonLoopbackHostInfo().getIpAddress(); InstanceInfo instanceInfo = createInstance(config); ApplicationInfoManager applicationInfoManager = new ApplicationInfoManager(new MyDataCenterInstanceConfig() { @Override public String getHostName(boolean refresh) { return host; } }, instanceInfo); //创立一个客户端实例 DiscoveryClient discoveryClient = new DiscoveryClient(applicationInfoManager, eurekaClientConfigBean); } private InstanceInfo createInstance(EurekaInstanceConfig config) { LeaseInfo.Builder leaseInfoBuilder = LeaseInfo.Builder.newBuilder() .setRenewalIntervalInSecs(config.getLeaseRenewalIntervalInSeconds()) .setDurationInSecs(config.getLeaseExpirationDurationInSeconds()); // Builder the instance information to be registered with eureka InstanceInfo.Builder builder = InstanceInfo.Builder.newBuilder(); String namespace = config.getNamespace(); if (!namespace.endsWith(".")) { namespace = namespace + "."; } String host = inetUtils.findFirstNonLoopbackHostInfo().getIpAddress(); builder.setNamespace(namespace).setAppName(nettyProperties.getName()) .setInstanceId(String.join(":", host, String.valueOf(nettyProperties.getPort()))) .setAppGroupName(config.getAppGroupName()) .setDataCenterInfo(config.getDataCenterInfo()) .setIPAddr(host).setHostName(host) .setPort(nettyProperties.getPort()) .enablePort(InstanceInfo.PortType.UNSECURE, config.isNonSecurePortEnabled()) .setSecurePort(config.getSecurePort()) .enablePort(InstanceInfo.PortType.SECURE, config.getSecurePortEnabled()) .setVIPAddress(nettyProperties.getName()) .setSecureVIPAddress(nettyProperties.getName()) .setHomePageUrl("/", null) .setStatusPageUrl(config.getStatusPageUrlPath(), config.getStatusPageUrl()) .setHealthCheckUrls(config.getHealthCheckUrlPath(), config.getHealthCheckUrl(), config.getSecureHealthCheckUrl()) .setASGName(config.getASGName()); builder.setStatus(InstanceInfo.InstanceStatus.UP); // Add any user-specific metadata information for (Map.Entry<String, String> mapEntry : config.getMetadataMap().entrySet()) { String key = mapEntry.getKey(); String value = mapEntry.getValue(); // only add the metadata if the value is present if (value != null && !value.isEmpty()) { builder.add(key, value); } } InstanceInfo instanceInfo = builder.build(); instanceInfo.setLeaseInfo(leaseInfoBuilder.build()); return instanceInfo; }}import lombok.Data;import org.springframework.boot.context.properties.ConfigurationProperties;@ConfigurationProperties(prefix = "netty")@Datapublic class NettyProperties { /** * socket端口 */ private int port; private String name; private String path = "/webSocket";}配置文件 ...

March 22, 2022 · 2 min · jiezi

关于netty:全链路异步Rest客户端-ESA-RestClient

ESA Stack(Elastic Service Architecture) 是OPPO云计算中心孵化的技术品牌,致力于微服务相干技术栈,帮忙用户疾速构建高性能,高可用的云原生微服务。产品蕴含高性能Web服务框架、RPC框架、服务治理框架、注册核心、配置核心、调用链追踪零碎,Service Mesh、Serverless等各类产品及钻研方向。 以后局部产品曾经对外开源: 开源主站:https://www.esastack.io Github: https://github.com/esastack RestClient 我的项目地址:https://github.com/esastack/e... RestClient 文档地址:https://www.esastack.io/esa-r... ESA RestClientESA RestClient 是一个基于 Netty 的全链路异步事件驱动的高性能轻量级的HTTP客户端。 以下简称RestClient1、Quick StartStep1:增加依赖<dependency> <groupId>io.esastack</groupId> <artifactId>restclient</artifactId> <version>1.0.0</version></dependency>Step2: 构建RestClient并发送申请解决响应final RestClient client = RestClient.ofDefault(); //疾速创立RestClient,各项配置均为默认配置。 //如果用户想自定义一些配置,则能够应用RestClient.create()来进行自定义配置。client.post("http://127.0.0.1:8081/") .entity("Hello Server") //设置申请体 .execute() //执行申请逻辑 .thenAccept((response)-> { //异步解决响应 try { System.out.println(response.bodyToEntity(String.class)); //调用response.bodyToEntity(Class TargetClass)来 Decode 响应, //TargetClass为冀望的响应类型 } catch (Exception e) { e.printStackTrace(); } });2、性能个性Http1/H2/H2cUpgrade/HttpsEncode 与 EncodeAdviceDecode 与 DecodeAdviceRestInterceptor大文件发送申请级别读超时申请级别重试申请级别重定向100-expect-continueMultipartMetricsmore …2.1 Encode 与 EncodeAdvice2.1.1 EncodeRestClient会主动依据用户的 Headers 与 Entity 等抉择适合的Encoder进行Encode。其内置了上面这些Encoder: ...

March 21, 2022 · 4 min · jiezi

关于netty:netty系列之channelHandlerContext详解

简介咱们晓得ChannelHandler有两个十分重要的子接口,别离是ChannelOutboundHandler和ChannelInboundHandler,基本上这两个handler接口定义了所有channel inbound和outbound的解决逻辑。 不论是ChannelHandler还是ChannelOutboundHandler和ChannelInboundHandler,简直他们中所有的办法都带有一个ChannelHandlerContext参数,那么这个ChannelHandlerContext到底是做什么用的呢?它和handler、channel有什么关系呢? ChannelHandlerContext和它的利用相熟netty的敌人应该都接触过ChannelHandlerContext,如果没有的话,这里有一个简略的handler的例子: public class ChatServerHandler extends SimpleChannelInboundHandler<String> { @Override public void channelActive(ChannelHandlerContext ctx) throws Exception { log.info("accepted channel: {}", ctx.channel()); log.info("accepted channel parent: {}", ctx.channel().parent()); // channel沉闷 ctx.write("Channel Active状态!\r\n"); ctx.flush(); }}这里的handler继承了SimpleChannelInboundHandler,只须要实现对应的办法即可。这里实现的是channelActive办法,在channelActive办法中,传入了一个ChannelHandlerContext参数,咱们能够通过应用ChannelHandlerContext来调用它的一些办法。 先来看一下ChannelHandlerContext的定义: public interface ChannelHandlerContext extends AttributeMap, ChannelInboundInvoker, ChannelOutboundInvoker {首先ChannelHandlerContext是一个AttributeMap,能够用来存储多个数据。 而后ChannelHandlerContext继承了ChannelInboundInvoker和ChannelOutboundInvoker,能够触发inbound和outbound的一些办法。 除了继承来的一些办法之外,ChannelHandlerContext还能够作为channel,handler和pipline的沟通桥梁,因为能够从ChannelHandlerContext中获取到对应的channel,handler和pipline: Channel channel();ChannelHandler handler();ChannelPipeline pipeline();还要留神的是ChannelHandlerContext还返回一个EventExecutor,用来执行特定的工作: EventExecutor executor();接下来,咱们具体看一下ChannelHandlerContext的实现。 AbstractChannelHandlerContextAbstractChannelHandlerContext是ChannelHandlerContext的一个十分重要的实现,尽管AbstractChannelHandlerContext是一个抽象类,然而它基本上实现了ChannelHandlerContext的所有性能。 首先看一下AbstractChannelHandlerContext的定义: abstract class AbstractChannelHandlerContext implements ChannelHandlerContext, ResourceLeakHintAbstractChannelHandlerContext是ChannelHandlerContext的一个具体实现。 通常来说一个handler对应一个ChannelHandlerContext,然而在一个程序中可能会有多于一个handler,那么如何在一个handler中获取其余的handler呢? 在AbstractChannelHandlerContext中有两个同样是AbstractChannelHandlerContext类型的next和prev,从而使得多个AbstractChannelHandlerContext能够构建一个双向链表。从而能够在一个ChannelHandlerContext中,获取其余的ChannelHandlerContext,从而取得handler解决链。 volatile AbstractChannelHandlerContext next; volatile AbstractChannelHandlerContext prev;AbstractChannelHandlerContext中的pipeline和executor都是通过构造函数传入的: AbstractChannelHandlerContext(DefaultChannelPipeline pipeline, EventExecutor executor, String name, Class<? extends ChannelHandler> handlerClass) { this.name = ObjectUtil.checkNotNull(name, "name"); this.pipeline = pipeline; this.executor = executor; this.executionMask = mask(handlerClass); // Its ordered if its driven by the EventLoop or the given Executor is an instanceof OrderedEventExecutor. ordered = executor == null || executor instanceof OrderedEventExecutor; }可能有敌人会有疑难了,ChannelHandlerContext中的channel和handler是如何失去的呢? ...

March 2, 2022 · 2 min · jiezi

关于netty:netty自定义解码器

/** * 一、netty自身提供了四种不同的解码器供用户应用,但并不能齐全满足所有需要,这时就须要自定义解码器用于netty加载失去咱们想要的数据。 * demo是将tcp的流信息进行截取,ConstantValue.START_DATA = 0x40,ConstantValue.END_DATA = 0x23; * 截取为例如:十六进制 40 40 。。。。23 23 的数据包,用于理论利用或存储。 * 代码如下: */protected void decode(ChannelHandlerContext ctx, ByteBuf buffer,Listout) throws Exception {int beginReader = 0; while(buffer.isReadable()) { beginReader = buffer.readerIndex();buffer.markReaderIndex();buffer.resetReaderIndex();buffer.readByte();if(beginReader >= 0){if (((buffer.getByte(0) == ConstantValue.START_DATA))) {if(beginReader >= 1) {if (!((buffer.getByte(1) == ConstantValue.START_DATA))) {//如果第二位不是40,抛弃buffer.discardReadBytes();continue;}} } else {buffer.discardReadBytes();continue;}}if(beginReader > 2){if (!((buffer.getByte(beginReader) == ConstantValue.END_DATA) && (buffer.getByte(beginReader -1) == ConstantValue.END_DATA))) {continue;} else {int length = buffer.readerIndex();byte[] data = new byte[length]; ...

February 28, 2022 · 1 min · jiezi

关于netty:深入学习Netty一NIO基础篇

NIO 根底什么是 NIOJava NIO 全称 Java non-blocking IO,指的是 JDK 提供的新 API。从 JDK 1.4 开始,Java 提供了一系列改良的输出/输入的新个性,被统称为 NIO,即 New IO,是同步非阻塞的。NIO 相干类都放在 java.nio 包下,并对原 java.io 包中很多类进行了改写。NIO 有三大外围局部:Channel(管道)、Buffer(缓冲区)、Selector(选择器)。NIO 是面向缓冲区编程的。数据读取到了一个它略微解决的缓冲区,须要时可在缓冲区中前后挪动,这就减少了处理过程中的灵活性,应用它能够提供非阻塞的高伸缩性网络。Java NIO 的非阻塞模式,使一个线程从某通道发送申请读取数据,然而它仅能失去目前可用数据,如果目前没有可用数据时,则阐明不会获取,而不是放弃线程阻塞,所以直到数据变为能够读取之前,该线程能够做其余事件。非阻塞写入同理。三大外围组件 Channel 的根本介绍NIO 的通道相似于流,但有如下区别: 通道是双向的能够进行读写,而流是单向的只能读,或者写通道能够实现异步读写数据通道能够从缓冲区读取数据,也能够写入数据到缓冲区四种通道: FileChannel :从文件中读写数据DatagramChannel:通过 UDP 协定,读写网络中的数据SocketChannel:能通过 TCP 协定来读写网络中数据,罕用于客户端ServerSocketChannel:监听 TCP 连贯,对每个新的连贯会创立一个 SocketChannelBuffer(缓冲区)根本介绍NIO 中的 Buffer 用于 NIO 通道(Channel)进行交互。 缓冲区实质上是一个能够读写数据的内存块,能够了解为是一个容器对象(含数组),该对象提供了一组办法,能够更轻松地应用内存块,缓冲区对象内置了一些机制,可能跟踪和记录缓冲区的状态变动状况。 当向 Buffer 写入数据时,Buffer 会记录下写了多少数据,一旦要读取数据,须要通过flip()办法将 Buffer 从写模式切换到读模式。在读模式下,能够读取之前写入到 Buffer 的所有数据。 当读完了所有数据,就须要清空缓存区,让它能够再次被写入。有两种形式能清空缓冲区,调用clear()或者compact()办法。 clear()办法会清空整个缓冲区。compact()办法只会革除曾经读过的数据。任何未读的数据都被移到缓冲区的起始处,新写入的数据将放到缓冲区未读数据的前面。 Channel 提供从文件、网络读取数据的渠道,然而读取或者都必须通过 Buffer。在 Buffer 子类中保护着一个对应类型的数组,用来存放数据。 Selector 的根本介绍Java 的 NIO 应用了非阻塞的 I/O 形式。能够用一个线程解决若干个客户端连贯,就会应用到 Selector(选择器)Selector 可能检测到多个注册通道上是否有事件产生(多个 Channel 以事件的模式注册到同一个 selector),如果有事件产生,便获取事件而后针对每个事件进行相应的解决只有在连贯真正有读写事件产生时,才会进行读写,缩小了零碎开销,并且不用为每个连贯都创立一个线程,不必保护多个线程防止了多线程之间上下文切换导致的开销Selector 的特点Netty 的 I/O 线程 NioEventLoop 聚合了 Selector(选择器 / 多路复用器),能够并发解决成千盈百个客户端连贯。 ...

December 30, 2021 · 14 min · jiezi

关于netty:netty系列之从零到壹搭建一个SOCKS代理服务器

简介上一篇文章,咱们讲到了netty对SOCKS音讯提供了SocksMessage对象的封装,并且辨别SOCKS4和SOCKS5,同时提供了连贯和响应的各种状态。 有了SOCKS音讯的封装之后,咱们还须要做些什么工作能力搭建一个SOCKS服务器呢? 应用SSH搭建SOCKS服务器其实最简略的方法就是应用SSH工具来建设SOCKS代理服务器。 先看下SSH建设SOCKS服务的命令: ssh -f -C -N -D bindaddress:port name@server-f 示意SSH作为守护过程进入后盾执行。 -N 示意不执行近程命令,只用于端口转发。 -D 示意是端口上的动静转发。这个命令反对SOCKS4和SOCKS5。 -C 示意发送前压缩数据。 bindaddress 本地服务器的绑定地址。 port 示意本地服务器的指定侦听端口。 name 示意ssh服务器登录名。 server示意ssh服务器地址。 下面命令的意思是,在本机建设端口绑定,而后将其转发到近程的代理服务器上。 比方咱们能够在本机开一个2000的端口,将其转发到近程168.121.100.23这台机子上: ssh -f -N -D 0.0.0.0:2000 root@168.121.100.23有了代理服务器之后,就能够应用了,首先介绍一个怎么在curl命令中应用SOCKS代理。 咱们想通过代理服务器,拜访www.flydean.com,该怎么做呢? curl -x socks5h://localhost:2000 -v -k -X GET http://www.flydean.com:80要想检测SOCKS的连贯,还能够应用netcat命令如下: ncat –proxy 127.0.0.1:2000 –proxy-type socks5 www.flydean.com 80 -nv应用netty搭建SOCKS服务器应用netty搭建SOCKS服务器的要害是应用netty服务器做中继,它须要建设两个连贯,一个是客户端到代理服务器的连贯,一个是代理服务器到指标地址的连贯。接下来,咱们一步一步探讨如何在netty中构建SOCKS服务器。 搭建服务器的根本步骤和一般的服务器基本一致,要留神的就是对音讯的编码、解码和在音讯读取处理过程中的转发。 encoder和decoder对于一种协定来说,最终要的就是对应的encoder和decoder,用于协定对象和ByteBuf之间进行转换。 netty提供的SOCKS转换器叫做SocksPortUnificationServerHandler。先看下它的定义: public class SocksPortUnificationServerHandler extends ByteToMessageDecoder它继承自ByteToMessageDecoder示意是ByteBuf和Socks对象之间的转换。 所以咱们在ChannelInitializer中只须要加上SocksPortUnificationServerHandler和自定义的处Socks音讯的handler即可: public void initChannel(SocketChannel ch) throws Exception { ch.pipeline().addLast( new LoggingHandler(LogLevel.DEBUG), new SocksPortUnificationServerHandler(), SocksServerHandler.INSTANCE); }等等,不对呀!有仔细的小伙伴可能发现了,SocksPortUnificationServerHandler只是一个decoder,咱们还短少一个encoder,用来将Socks对象转换老本ByteBuf,这个encoder在哪里呢? ...

December 29, 2021 · 2 min · jiezi

关于netty:netty系列之手持framecodec神器创建多路复用http2客户端

简介在之前的文章中,咱们实现了反对http2的netty服务器,并且应用反对http2的浏览器胜利的进行拜访。尽管浏览器十分通用,然而有时候咱们也须要应用特定的netty客户端去和服务器进行通信。 明天咱们来探讨一下netty客户端对http2的反对。 配置SslContext尽管http2并不强制要求反对TLS,然而古代浏览器都是须要在TLS的环境中开启http2,所以对于客户端来说,同样须要配置好反对http2的SslContext。客户端和服务器端配置SslContext的内容没有太大的区别,惟一的区别就是须要调用SslContextBuilder.forClient()而不是forServer()办法来获取SslContextBuilder,创立SslContext的代码如下: SslProvider provider = SslProvider.isAlpnSupported(SslProvider.OPENSSL)? SslProvider.OPENSSL : SslProvider.JDK; sslCtx = SslContextBuilder.forClient() .sslProvider(provider) .ciphers(Http2SecurityUtil.CIPHERS, SupportedCipherSuiteFilter.INSTANCE) // 因为咱们的证书是自生成的,所以须要信赖放行 .trustManager(InsecureTrustManagerFactory.INSTANCE) .applicationProtocolConfig(new ApplicationProtocolConfig( Protocol.ALPN, SelectorFailureBehavior.NO_ADVERTISE, SelectedListenerFailureBehavior.ACCEPT, ApplicationProtocolNames.HTTP_2, ApplicationProtocolNames.HTTP_1_1)) .build();如果应用SSL,那么ssl handler必须是pipline中的第一个handler,所以将SslContext退出到pipline中的代码如下: ch.pipeline().addFirst(sslCtx.newHandler(ch.alloc()));客户端的handler应用Http2FrameCodecnetty的channel默认只能接管ByteBuf音讯,对于http2来说,底层传输的是一个个的frame,间接操作底层的frame对于一般程序员来说并不是特地敌对,所以netty提供了一个Http2FrameCodec来对底层的http2 frame进行封装成Http2Frame对象,不便程序的解决。 在服务器端咱们应用Http2FrameCodecBuilder.forServer()来创立Http2FrameCodec,在客户端咱们应用Http2FrameCodecBuilder.forClient()来创立Http2FrameCodec: Http2FrameCodec http2FrameCodec = Http2FrameCodecBuilder.forClient() .initialSettings(Http2Settings.defaultSettings()) .build();而后将其退出到pipline中即可应用: ch.pipeline().addLast(http2FrameCodec);Http2MultiplexHandler和Http2MultiplexCodec咱们晓得对于http2来说一个TCP连贯中能够创立多个stream,每个stream又是由多个frame来组成的。思考到多路复用的状况,netty能够为每一个stream创立一个独自的channel,对于新创建的每个channel来说,都能够应用netty的ChannelInboundHandler来对channel的音讯进行解决,从而晋升netty解决http2的效率。 而这个对stream创立新channel的反对,在netty中有两个专门的类,他们是Http2MultiplexHandler和Http2MultiplexCodec。 他们的性能是一样的,Http2MultiplexHandler继承自Http2ChannelDuplexHandler,它必须和 Http2FrameCodec一起应用。而Http2MultiplexCodec自身就是继承自Http2FrameCodec,曾经联合了Http2FrameCodec的性能。 public final class Http2MultiplexHandler extends Http2ChannelDuplexHandler@Deprecatedpublic class Http2MultiplexCodec extends Http2FrameCodec 然而通过查看源代码,咱们发现Http2MultiplexCodec是不举荐应用的API,所以这里咱们次要介绍Http2MultiplexHandler。 对于Http2MultiplexHandler来说,每次新创建一个stream,都会创立一个新的对应的channel,应用程序应用这个新创建的channel来发送和接管Http2StreamFrame。 新创建的子channel会被注册到netty的EventLoop中,所以对于一个无效的子channel来说,并不是立即就会被匹配到HTTP/2 stream下来,而是当第一个Http2HeadersFrame胜利被发送或者接管之后,才会触发Event事件,进而进行绑定操作。 因为是子channel,所以对于connection level的事件,比方Http2SettingsFrame 和 Http2GoAwayFrame会首先被父channel进行解决,而后再播送到子channel中进行解决。 同时,尽管Http2GoAwayFrame 和 Http2ResetFrame示意近程节点曾经不再接管新的frame了,然而因为channel自身还可能有queue的音讯,所以须要期待Channel.read()为空之后,才会进行敞开操作。 另外对于子channel来说,因为不能晓得connection-level流控制window,所以如果有溢出的音讯会被缓存在父channel的buff中。 有了Http2MultiplexHandler,将其退出client的pipline就能够让客户端反对多路的channel了: ch.pipeline().addLast(new Http2MultiplexHandler(new SimpleChannelInboundHandler() { @Override protected void channelRead0(ChannelHandlerContext ctx, Object msg) { // 解决inbound streams log.info("Http2MultiplexHandler接管到音讯: {}",msg); } }))应用子channel发送音讯从下面的介绍咱们晓得,一旦应用了Http2MultiplexHandler,那么具体的音讯解决就是在子channel中了。那么怎么能力从父channel中获取子channel,而后应用子channel来发送信息呢? ...

December 9, 2021 · 1 min · jiezi

关于netty:Netty-实现HTTP文件服务器

一,需要 文件服务器应用HTTP协定对外提供服务。用户通过浏览器拜访文件服务器,首先对URL进行查看,若失败返回403谬误;若通过校验,以链接的形式关上当前目录,每个目录或文件都以超链接的模式展示,可递归拜访,并下载文件。 二,要害实现代码 ①文件服务器启动类 须要增加的通道处理器如下: @Override protected void initChannel(SocketChannel ch) throws Exception { ch.pipeline().addLast("http-decoder", new HttpRequestDecoder()); ch.pipeline().addLast("http-aggregator", new HttpObjectAggregator(65536)); ch.pipeline().addLast("http-encoder", new HttpResponseEncoder()); ch.pipeline().addLast("http-chunked", new ChunkedWriteHandler()); ch.pipeline().addLast("fileServerHandler", new HttpFileServerHandler(url)); }1) HttpRequestDecoder Decodes {@link ByteBuf}s into {@link HttpRequest}s and {@link HttpContent}s. 它负责把字节解码成Http申请。 2) HttpObjectAggregator A {@link ChannelHandler} that aggregates an {@link HttpMessage} and its following {@link HttpContent}s into a single {@link FullHttpRequest} or {@link FullHttpResponse} (depending on if it used to handle requests or responses) ...

November 28, 2021 · 5 min · jiezi

关于netty:还有这种好事netty自带http2的编码解码器framecodec

简介netty为咱们提供了很多http2的封装,让咱们能够轻松的搭建出一个反对http2的服务器。其中惟一须要咱们自定义的就是http2 handler。 在之前的文章中,咱们介绍了自定义http2handler继承自Http2ConnectionHandler并且实现Http2FrameListener。这种实现形式是netty目前比拟举荐的实现形式,明天给大家介绍的一种实现形式是netty中筹备替换继承Http2ConnectionHandler的实现形式,然而这种实现形式并不成熟,还在一直的欠缺中。 明天给大家介绍一下这种实现形式。 Http2FrameCodec这种实现形式的外围类是Http2FrameCodec。事实上Http2FrameCodec也是继承自Http2ConnectionHandler。 它的次要作用是将HTTP/2中的frames和Http2Frame对象进行映射。Http2Frame是netty中对应所有http2 frame的封装,这样就能够在后续的handler中专一于解决Http2Frame对象即可,从而解脱了http2协定的各种细节,能够缩小使用者的工作量。 对于每个进入的HTTP/2 frame,Http2FrameCodec都会创立一个Http2Frame对象,并且将其传递给channelRead办法,用于对该对象进行解决。 通过调用write办法,能够对outbound的 Http2Frame 转换成为http2 frame的格局。 Http2Frame、Http2FrameStream和Http2StreamFramenetty中有三个十分相似的类,他们是Http2Frame、Http2FrameStream和Http2StreamFrame。 咱们晓得netty中一个tcp连贯能够建设多个stream,Http2FrameStream就是和stream对应的类,这个类中蕴含了stream的id和stream以后的状态。 一个stream中又蕴含了多个音讯,每个音讯都是由多个frame组成的,所以Http2Frame是和这些frame对应的netty类。 Http2StreamFrame自身也是一个frame,事实上它继承自Http2Frame。 为什么会有这个类呢?因为对应frame自身来说,个别状况下它是和一个特定的stream相关联的,Http2StreamFrame示意这种关联关系,能够通过它的set stream办法来指定其关联的stream。如果想要该frame利用到整个连贯而不是特定的某个stream,如果是关联到整个连贯,那么stream()办法的返回就是null。 Http2FrameCodec的结构尽管Http2FrameCodec有构造函数,然而netty举荐应用Http2FrameCodecBuilder来结构它: Http2FrameCodecBuilder.forServer().build();能够看到Http2FrameCodecBuilder有一个forServer还有一个forClient办法。他们一个是应用在服务器端,一个是应用在客户端。 次要是通过外面的server属性来进行辨别。 Stream的生命周期frame codec将会向无效的stream发送和写入frames。之前讲过了 Http2StreamFrame 是和一个Http2FrameStream对象相关联的。 对于一个无效的stream来说,如果任意一方发送一个RST_STREAM frame,那么该stream就会被敞开。 或者发送方或者接管方任意一方发送的frame中带有END_STREAM标记,该stream也会被敞开。 流控制Http2FrameCodec提供了对流的自动控制,然而咱们依然须要做一些操作,来对window进行更新。 具体而言,当咱们在接管到Http2DataFrame音讯的时候,对音讯进行解决之后,须要增大window的大小,示意该data曾经被解决了,能够有更多的空间去包容新的数据。 也就是说须要向ctx中写入一个Http2WindowUpdateFrame,在这个Http2WindowUpdateFrame中须要传入解决的data的大小和对应stream的id,上面是一个解决data frame的例子: /** * 解决data frame音讯 */ private static void onDataRead(ChannelHandlerContext ctx, Http2DataFrame data){ Http2FrameStream stream = data.stream(); if (data.isEndStream()) { sendResponse(ctx, stream, data.content()); } else { // 不是end stream不发送,然而须要开释援用 data.release(); } // 解决完data,须要更新window frame,减少解决过的Data大小 ctx.write(new DefaultHttp2WindowUpdateFrame(data.initialFlowControlledBytes()).stream(stream)); }上的例子中,咱们向DefaultHttp2WindowUpdateFrame传入了对应的stream id,如果stream id为0,则示意解决的是整个connection,而不是独自的某个stream。 ...

November 25, 2021 · 1 min · jiezi

关于netty:Java-开发必备-IO与Netty原理精讲建议收藏

I/O技术在零碎设计、性能优化、中间件研发中的应用越来越重要,学习和把握I/O相干技术曾经不仅是一个Java攻城狮的加分技能,而是一个必备技能。本文将带你理解BIO/NIO/AIO的倒退历程及实现原理,并介绍以后风行框架Netty的基本原理。 一 Java I/O模型1 BIO(Blocking IO)BIO是同步阻塞模型,一个客户端连贯对应一个解决线程。在BIO中,accept和read办法都是阻塞操作,如果没有连贯申请,accept办法阻塞;如果无数据可读取,read办法阻塞。 2 NIO(Non Blocking IO)NIO是同步非阻塞模型,服务端的一个线程能够解决多个申请,客户端发送的连贯申请注册在多路复用器Selector上,服务端线程通过轮询多路复用器查看是否有IO申请,有则进行解决。 NIO的三大外围组件: Buffer:用于存储数据,底层基于数组实现,针对8种根本类型提供了对应的缓冲区类。 Channel:用于进行数据传输,面向缓冲区进行操作,反对双向传输,数据能够从Channel读取到Buffer中,也能够从Buffer写到Channel中。 Selector:选择器,当向一个Selector中注册Channel后,Selector 外部的机制就能够主动一直地查问(Select)这些注册的Channel是否有已就绪的 I/O 事件(例如可读,可写,网络连接实现等),这样程序就能够很简略地应用一个线程高效地治理多个Channel,也能够说治理多个网络连接,因而,Selector也被称为多路复用器。当某个Channel下面产生了读或者写事件,这个Channel就处于就绪状态,会被Selector监听到,而后通过SelectionKeys能够获取就绪Channel的汇合,进行后续的I/O操作。 Epoll是Linux下多路复用IO接口select/poll的加强版本,它能显著进步程序在大量并发连贯中只有大量沉闷的状况下的零碎CPU利用率,获取事件的时候,它毋庸遍历整个被侦听的描述符集,只有遍历那些被内核IO事件异步唤醒而退出Ready队列的描述符汇合就行了。 3 AIO(NIO 2.0)AIO是异步非阻塞模型,个别用于连接数较多且连接时间较长的利用,在读写事件实现后由回调服务去告诉程序启动线程进行解决。与NIO不同,当进行读写操作时,只需间接调用read或write办法即可。这两种办法均为异步的,对于读操作而言,当有流可读取时,操作系统会将可读的流传入read办法的缓冲区,并告诉应用程序;对于写操作而言,当操作系统将write办法传递的流写入结束时,操作系统被动告诉应用程序。能够了解为,read/write办法都是异步的,实现后会被动调用回调函数。 二 I/O模型演变1 传统I/O模型对于传统的I/O通信形式来说,客户端连贯到服务端,服务端接管客户端申请并响应的流程为:读取 -> 解码 -> 利用解决 -> 编码 -> 发送后果。服务端为每一个客户端连贯新建一个线程,建设通道,从而解决后续的申请,也就是BIO的形式。 这种形式在客户端数量一直减少的状况下,对于连贯和申请的响应会急剧下降,并且占用太多线程浪费资源,线程数量也不是没有下限的,会遇到各种瓶颈。尽管能够应用线程池进行优化,然而仍然有诸多问题,比方在线程池中所有线程都在解决申请时,无奈响应其余的客户端连贯,每个客户端仍旧须要专门的服务端线程来服务,即便此时客户端无申请,也处于阻塞状态无奈开释。基于此,提出了基于事件驱动的Reactor模型。 2 Reactor模型Reactor模式是基于事件驱动开发的,服务端程序处理传入多路申请,并将它们同步分派给申请对应的解决线程,Reactor模式也叫Dispatcher模式,即I/O多路复用对立监听事件,收到事件后散发(Dispatch给某过程),这是编写高性能网络服务器的必备技术之一。 Reactor模式以NIO为底层反对,外围组成部分包含Reactor和Handler: Reactor:Reactor在一个独自的线程中运行,负责监听和散发事件,分发给适当的处理程序来对I/O事件做出反馈。它就像公司的电话接线员,它接听来自客户的电话并将线路转移到适当的联系人。Handlers:处理程序执行I/O事件要实现的理论事件,Reactor通过调度适当的处理程序来响应 I/O 事件,处理程序执行非阻塞操作。相似于客户想要与之交谈的公司中的理论员工。依据Reactor的数量和Handler线程数量,能够将Reactor分为三种模型: 单线程模型 (单Reactor单线程)多线程模型 (单Reactor多线程)主从多线程模型 (多Reactor多线程)单线程模型 Reactor外部通过Selector监控连贯事件,收到事件后通过dispatch进行散发,如果是连贯建设的事件,则由Acceptor解决,Acceptor通过accept承受连贯,并创立一个Handler来解决连贯后续的各种事件,如果是读写事件,间接调用连贯对应的Handler来解决。 Handler实现read -> (decode -> compute -> encode) ->send的业务流程。 这种模型益处是简略,害处却很显著,当某个Handler阻塞时,会导致其余客户端的handler和accpetor都得不到执行,无奈做到高性能,只实用于业务解决十分疾速的场景,如redis读写操作。 多线程模型 主线程中,Reactor对象通过Selector监控连贯事件,收到事件后通过dispatch进行散发,如果是连贯建设事件,则由Acceptor解决,Acceptor通过accept接管连贯,并创立一个Handler来解决后续事件,而Handler只负责响应事件,不进行业务操作,也就是只进行read读取数据和write写出数据,业务解决交给一个线程池进行解决。 线程池调配一个线程实现真正的业务解决,而后将响应后果交给主过程的Handler解决,Handler将后果send给client。 单Reactor承当所有事件的监听和响应,而当咱们的服务端遇到大量的客户端同时进行连贯,或者在申请连贯时执行一些耗时操作,比方身份认证,权限查看等,这种刹时的高并发就容易成为性能瓶颈。 主从多线程模型 存在多个Reactor,每个Reactor都有本人的Selector选择器,线程和dispatch。 主线程中的mainReactor通过本人的Selector监控连贯建设事件,收到事件后通过Accpetor接管,将新的连贯调配给某个子线程。 子线程中的subReactor将mainReactor调配的连贯退出连贯队列中通过本人的Selector进行监听,并创立一个Handler用于解决后续事件。 Handler实现read -> 业务解决 -> send的残缺业务流程。 ...

November 2, 2021 · 1 min · jiezi

关于netty:netty系列之netty实现http2中的流控制

简介HTTP2绝对于http1.1来说一个重要的晋升就是流控制flowcontrol。为什么会有流控制呢?这是因为不论是哪种协定,客户端和服务器端在接收数据的时候都有一个缓冲区来长期存储临时解决不了的数据,然而缓冲区的大小是有限度的,所以有可能会呈现缓冲区溢出的状况,比方客户端向服务器端上传一个大的图片,就有可能导致服务器端的缓冲区溢出,从而导致一些额定的数据包失落。 为了防止缓冲区溢出,各个HTTP协定都提供了肯定的解决办法。 在HTTP1.1中,流量的管制依赖的是底层TCP协定,在客户端和服务器端建设连贯的时候,会应用零碎默认的设置来建设缓冲区。在数据进行通信的时候,会通知对方它的接管窗口的大小,这个接管窗口就是缓冲区中残余的可用空间。如果接管窗口大小为零,则阐明接管方缓冲区已满,则发送方将不再发送数据,直到客户端革除其外部缓冲区,而后申请复原数据传输。 HTTP2通过客户端和服务器端的利用中进行缓冲区大小音讯的传输,通过在应用层层面管制数据流,所以各个利用端能够自行管制流量的大小,从而实现更高的连贯效率。 本文将会介绍netty对http2流控制的反对。 http2中的流控制在简介中咱们也提到了,传统的HTTP1.1应用的是零碎底层的流量管制机制,具体来说就是TCP的流控制。然而TCP的流控制在HTTP2中就不够用了。因为HTTP2应用的是多路复用的机制,一个TCP连贯能够有多个http2连贯。所以对http2来说TCP自身的流控制机制太毛糙了,不够精密。 所以在HTTP2中,实现了更加精密的流控制机制,它容许客户端和服务器实现其本人的数据流和连贯级流控制。 具体的流程是这样的,当客户端和服务器端建设连贯之后,会发送Http2SettingsFrame,这个settings frame中蕴含了SETTINGS_INITIAL_WINDOW_SIZE,这个是发送端的窗口大小,用于 Stream 级别流控。流控制窗口的默认值设为65,535字节,然而接管方能够对其进行批改,最大值为2^31-1 字节。 建设好初始windows size之后,对于接管方来说,每次发送方发送data frame就会缩小window的的大小,而接管方每次发送WINDOW_UPDATE frame时候就会减少window的大小,从达到动态控制的目标。 netty对http2流控制的封装Http2FlowController从下面的介绍咱们晓得,http2对流管制是通过两个方面来施行的,第一个方面就是初始化的Http2SettingsFrame,通过设置SETTINGS_INITIAL_WINDOW_SIZE来管制初始window的大小。第二个方面就是在后续的WINDOW_UPDATE frame中对window的大小进行动静增减。 对于netty来说,这一切都是封装在Http2FlowController类中的。Http2FlowController是一个抽象类,它有两个实现,别离是Http2LocalFlowController和Http2RemoteFlowController。他们别离示意对inbound flow of DATA 和 outbound flow of DATA的解决。 Http2FlowController中次要有5个办法,别离是: set channelHandlerContext:绑定flowcontrol到ChannelHandlerContext上。set initialWindowSize:初始化window size,等同于设置SETTINGS_INITIAL_WINDOW_SIZE。get initialWindowSize: 返回初始化window size。windowSize: 获取以后的windowSize。incrementWindowSize: 减少flow control window的大小。接下来咱们看下他的两个实现类,有什么不一样的中央。 Http2LocalFlowControllerLocalFlowController用来对近程节点发过来的DATA frames做flow control。它有5个次要的办法。 set frameWriter: 用来设置发送WINDOW_UPDATE frames的frame writer。receiveFlowControlledFrame: 接管inbound DATA frame,并且对其进行flow control。consumeBytes: 示意利用曾经生产了肯定数目的bytes,能够承受更多从近程节点发过来的数据。flow control能够发送 WINDOW_UPDATE frame来重置window大小。unconsumedBytes: 接管到,然而未生产的bytes。initialWindowSize: 给定stream的初始window大小。Http2RemoteFlowControllerremoteFlowController用来解决发送给近程节点的outbound DATA frames。它提供了8个办法: get channelHandlerContext: 获取以后flow control的context.addFlowControlled: 将flow control payload增加到发送到近程节点的queue中。hasFlowControlled: 判断以后stream是否有 FlowControlled frames在queue中。writePendingBytes: 将流量控制器中的所有待处理数据写入流量管制限度。listener: 给 flow-controller增加listener。isWritable: 确定流是否有残余字节可用于流控制窗口。channelWritabilityChanged: context的writable状态是否变动。updateDependencyTree: 更新stream之间的依赖关系,因为stream是能够有父子构造的。流控制的应用flowControl相干的类次要被用在Http2Connection,Http2ConnectionDecoder,Http2ConnectionEncoder中,在建设http2连贯的时候起到相应的作用。 ...

October 29, 2021 · 1 min · jiezi

关于netty:netty系列之netty对http2消息的封装

简介无论是什么协定,如果要真正被应用的话,须要将该协定转换成为对应的语言才好真正的进行利用,本文将从http2音讯的构造登程,探讨一下netty对http2音讯的封装,带大家领略一下真正的框架应该做到什么水平。 http2音讯的构造http2和http1.1不同的是它应用了新的二进制分帧,通过客户端和服务器端建设数据流steam来进行客户端和服务器端之间音讯的交互。其中数据流是一个双向字节流,用来发送一条或者多条音讯。 音讯是客户端和服务端发送的一个逻辑上残缺的数据。依据数据大小的不同,能够将音讯划分为不同的帧Frame。也就是说message是由不同的frame组成的。 frame就是http2中进行通信的最小单位,依据上一节的介绍,咱们晓得frame有这样几种: DATA frameHEADERS framePRIORITY frameRST_STREAM frameSETTINGS acknowledgment frameSETTINGS framePING framePING acknowledgmentPUSH_PROMISE frameGO_AWAY frameWINDOW_UPDATE frameUnknown Frame咱们看一下http2中stream和frame的一个大体的构造: 在http2中,一个TCP连贯,能够承载多个数据流stream,多个stream中的不同frame能够交织发送。 每个frame通过stream id来标记其所属的stream。 有了下面的http2的基本概念,咱们接下来就看下netty对http2的封装了。 netty对http2的封装Http2Stream作为一个TCP连贯上面的最大的单位stream,netty中提供了接口Http2Stream。留神,Http2Stream是一个接口,它有两个实现类,别离是DefaultStream和ConnectionStream。 Http2Stream中有两个十分重要的属性,别离是id和state。 id后面曾经介绍了,是stream的惟一标记。这里要留神由客户端建设的 Stream ID 必须是奇数,而由服务端建设的 Stream ID 必须是偶数。另外Stream ID 为 0 的流有非凡的作用,它是CONNECTION_STREAM_ID,1 示意HTTP_UPGRADE_STREAM_ID。 state示意stream的状态,具体而言,stream有上面几个状态: IDLE(false, false), RESERVED_LOCAL(false, false), RESERVED_REMOTE(false, false), OPEN(true, true), HALF_CLOSED_LOCAL(false, true), HALF_CLOSED_REMOTE(true, false), CLOSED(false, false);为什么状态须要辨别local和remote呢?这是因为stream连贯的两端,所以有两端的状态。 和stream状态绝对应的就是http2的生命周期了。netty提供了Http2LifecycleManager来示意对http2生命周期的治理: void closeStreamLocal(Http2Stream stream, ChannelFuture future); void closeStreamRemote(Http2Stream stream, ChannelFuture future); void closeStream(Http2Stream stream, ChannelFuture future); ChannelFuture resetStream(ChannelHandlerContext ctx, int streamId, long errorCode, ChannelPromise promise); ChannelFuture goAway(ChannelHandlerContext ctx, int lastStreamId, long errorCode, ByteBuf debugData, ChannelPromise promise); void onError(ChannelHandlerContext ctx, boolean outbound, Throwable cause);别离是敞开stream,重置stream,回绝新建stream:goAway,和解决出错状态这几种。 ...

October 25, 2021 · 1 min · jiezi

关于netty:netty系列之使用netty实现支持http2的服务器

简介上一篇文章中,咱们提到了如何在netty中配置TLS,让他反对HTTP2。事实上TLS并不是https的一个必须要求,它只是倡议的规范。那么除了TLS之外,还须要如何设置能力让netty反对http2呢?一起来看看吧。 根本流程netty反对http2有两种状况,第一种状况是应用tls,在这种状况下须要增加一个ProtocolNegotiationHandler来对握手之后的协定进行协商,在协商之后,须要决定到底应用哪一种协定。 上一篇文章,咱们曾经介绍TLS反对http2的细节了,这里不再赘述,感兴趣的敌人能够查看我之前的文章。 如果不应用tls,那么有两种状况,一种是间接应用http1.1了,咱们须要为http1.1增加一个ChannelInboundHandler即可。 另一种状况就是应用clear text从HTTP1.1降级到HTTP2。 HTTP/2 ClearText也叫做h2c,咱们看一个简略的降级申请,首先是客户端申请: GET /index HTTP/1.1Host: server.flydean.comConnection: Upgrade, HTTP2-SettingsUpgrade: h2c HTTP2-Settings: (SETTINGS payload) 而后是服务器端的响应,如果服务器端不反对降级,则返回: HTTP/1.1 200 OK Content-length: 100Content-type: text/html(... HTTP/1.1 response ...)如果服务器反对降级,则返回: HTTP/1.1 101 Switching Protocols Connection: UpgradeUpgrade: h2c(... HTTP/2 response ...)CleartextHttp2ServerUpgradeHandler有了下面的根本流程,咱们只须要在netty中提供对应的handler类就能够解决netty对http2的反对了。 不过下面的降级流程看起来比较复杂,所以netty为咱们提供了一个封装好的类:CleartextHttp2ServerUpgradeHandler来实现h2c的性能。 这个类须要传入3个参数,别离是HttpServerCodec、HttpServerUpgradeHandler和ChannelHandler。 HttpServerCodec就是解决http server的编码类,个别咱们应用HttpServerCodec。 HttpServerUpgradeHandler是从http1.1降级到http2的解决类。 netty也提供了一个现成的类:HttpServerUpgradeHandler,来解决降级的编码。 HttpServerUpgradeHandler须要两个参数,一个是sourceCodec,也就是http原始的编码类HttpServerCodec,一个是用来返回UpgradeCodec的工厂类,返回netty自带的Http2ServerUpgradeCodec。 public HttpServerUpgradeHandler(SourceCodec sourceCodec, UpgradeCodecFactory upgradeCodecFactory) { this(sourceCodec, upgradeCodecFactory, 0); }ChannelHandler是真正解决HTTP2的handler,咱们能够依据须要对这个handler进行自定义。 有了UpgradeHandler,将其退出ChannelPipeline即可。 Http2ConnectionHandler不论是HttpServerUpgradeHandler,还是CleartextHttp2ServerUpgradeHandler,都须要传入一个真正可能解决http2的handler。这个handler就是Http2ConnectionHandler。 Http2ConnectionHandler是一个实现类,它曾经实现了解决各种inbound frame events的事件,而后将这些事件委托给 Http2FrameListener。 所以Http2ConnectionHandler须要跟Http2FrameListener配合应用。 这里要具体解说一下Http2FrameListener,它次要解决HTTP2 frame的各种事件。 先来看下http2FrameListener中提供的event trigger办法: 从上图能够看到,次要是各种frame的事件触发办法,其中http2中有这样几种frame: ...

October 22, 2021 · 1 min · jiezi

关于netty:netty系列之让TLS支持http2

简介咱们晓得尽管HTTP2协定并不强制应用HTTPS,然而对大多数浏览器来说,如果要应用HTTP2的话,则必须应用HTTPS,所以咱们须要理解如何在netty的TLS中反对http2。 TLS的扩大协定NPN和ALPNHTTP2协定是从spdy协定倒退而来的,无论是spdy还是http2都为了能在HTTPS的环境下工作,倒退进去了TLS协定的扩大。 他们别离叫做NPN(Next Protocol Negotiation) 和 ALPN (Application Layer Protocol Negotiation) 。 他们规定了在TLS协定握手之后,客户端和服务器端进行利用数据通信的协定。其中ALPN能够在客户端首次和服务器端进行握手的时候,就列出客户端反对的应用层数据协定,服务器端间接抉择即可,因而能够比NPN少一个交互流程,更加优良。 那么spdy和http2别离反对的协定都有哪些呢? netty提供了一个ApplicationProtocolNames类,在其中定义了各自对应的协定,其中ALPN对应了http2和http1.1,而sydy对应了spdy/1,spdy/2,spdy/3: /** * HTTP version 2 */ public static final String HTTP_2 = "h2"; /** * {@code "http/1.1"}: HTTP version 1.1 */ public static final String HTTP_1_1 = "http/1.1"; /** * {@code "spdy/3.1"}: SPDY version 3.1 */ public static final String SPDY_3_1 = "spdy/3.1"; /** * {@code "spdy/3"}: SPDY version 3 */ public static final String SPDY_3 = "spdy/3"; /** * {@code "spdy/2"}: SPDY version 2 */ public static final String SPDY_2 = "spdy/2"; /** * {@code "spdy/1"}: SPDY version 1 */ public static final String SPDY_1 = "spdy/1";SslProvider目前来说,netty中有两种SSL的实现形式,一种是JDK,一种是OPENSSL,不同的实现形式对TLS协定扩大的反对也不一样。它提供了一个isAlpnSupported办法,依据传入provider的不同来判断,是否反对ALPN。 ...

October 20, 2021 · 2 min · jiezi

关于netty:netty系列之分离websocket处理器

简介在上一篇文章中,咱们应用了netty构建了能够解决websocket协定的服务器,在这个服务器中,咱们构建了特制的handler用来解决HTTP或者websocket申请。 在一个handler中解决两种不同的申请,对于某些有代码洁癖的人可能忍耐不了。那么,有没有可能将一般的HTTP申请和websocket申请应用不同的handler来进行解决呢?答案是必定的。 netty的音讯解决咱们晓得netty中所有的音讯解决都是通过handler来实现的,为了不便起见,netty提供了一个简略的音讯解决类SimpleChannelInboundHandler,大家通过继承它来重写channelRead0办法即可: protected abstract void channelRead0(ChannelHandlerContext ctx, I msg) throws Exception;咱们再看一下SimpleChannelInboundHandler的定义: public abstract class SimpleChannelInboundHandler<I> extends ChannelInboundHandlerAdapter能够看到SimpleChannelInboundHandler自身是带有泛型I的,而这个I就是咱们要探讨的方向。 如果咱们要应用这个handler来解决所有的音讯,那么能够将I取值为Object。 如果咱们只须要解决String音讯,那么能够这样: public class StringHandler extends SimpleChannelInboundHandler<String> { @Override protected void channelRead0(ChannelHandlerContext ctx, String message) throws Exception { System.out.println(message); } }同样的,如果要同时解决HTTP和WebSocket音讯,只须要将I设置为不同的类型即可。 对于WebSocketFrame,咱们有: public class Server2FrameHandler extends SimpleChannelInboundHandler<WebSocketFrame> 对于FullHttpRequest,咱们有: public class Server2HttpHandler extends SimpleChannelInboundHandler<FullHttpRequest> 解决WebSocketFrame对于WebSocketFrame音讯,从上一节咱们晓得它有6种类型,别离是: BinaryWebSocketFrameCloseWebSocketFrameContinuationWebSocketFramePingWebSocketFramePongWebSocketFrameTextWebSocketFrame其中真正蕴含内容的是TextWebSocketFrame和BinaryWebSocketFrame,这里咱们对TextWebSocketFrame进行专门解决: protected void channelRead0(ChannelHandlerContext ctx, WebSocketFrame frame) throws Exception { if (frame instanceof TextWebSocketFrame) { // 将接管到的音讯转换成为大写 String request = ((TextWebSocketFrame) frame).text(); ctx.channel().writeAndFlush(new TextWebSocketFrame(request.toUpperCase(Locale.CHINA))); } else { String message = "不反对的Frame类型: " + frame.getClass().getName(); throw new UnsupportedOperationException(message); } }解决HTTP对于HTTP申请中的FullHttpRequest,咱们就装置失常的HTTP服务申请的解决流程来就行。 ...

September 29, 2021 · 1 min · jiezi

关于netty:netty系列之使用netty搭建websocket服务器

简介websocket是一个优良的协定,它是建设在TCP根底之上的,兼容HTTP的网络协议。通过Websocket咱们能够实现客户端和服务器端的即时通讯,罢黜了客户端屡次轮循带来的性能损耗。 既然websocket这么优良,那么怎么在netty中应用websocket呢? netty中的websocket尽管websocket是一个独自的和HTTP协定齐全不同的协定,然而在netty中还是将其放到了http包中。咱们回忆一下netty中对于各种协定的反对。如果要反对这种协定,必定须要一个decoder和encoder编码和解码器用于对协定进行编解码。将传输的数据从ByteBuf转换到协定类型,或者将协定类型转换成为ByteBuf。 这是netty的工作外围原理,也是后续自定义netty扩大的根底。 那么对于websocket来说,是怎么样的呢? websocket的版本WebSocket作为一种协定,天然不是凭空而来的,通过一直的倒退才到了明天的WebSocket协定。具体的webSocket的发展史咱们就不去深究了。咱们先看下netty提供的各种WebSocket的版本。 在WebSocketVersion类中,咱们能够看到: UNKNOWN(AsciiString.cached(StringUtil.EMPTY_STRING)), V00(AsciiString.cached("0")), V07(AsciiString.cached("7")), V08(AsciiString.cached("8")), V13(AsciiString.cached("13"));WebSocketVersion是一个枚举类型,它外面定义了websocket的4个版本,除了UNKNOWN之外,咱们能够看到websocket的版本有0,7,8,13这几个。 FrameDecoder和FrameEncoder咱们晓得websocket的音讯是通过frame来传递的,因为不同websocket的版本影响到的是frame的格局的不同。所以咱们须要不同的FrameDecoder和FrameEncoder来在WebSocketFrame和ByteBuf之间进行转换。 既然websocket有四个版本,那么绝对应的就有4个版本的decoder和encoder: WebSocket00FrameDecoderWebSocket00FrameEncoderWebSocket07FrameDecoderWebSocket07FrameEncoderWebSocket08FrameDecoderWebSocket08FrameEncoderWebSocket13FrameDecoderWebSocket13FrameEncoder至于每个版本之间的frame有什么区别,咱们这里就不细讲了,感兴趣的敌人能够关注我的后续文章。 相熟netty的敌人应该都晓得,不论是encoder还是decoder都是作用在channel中对音讯进行转换的。那么在netty中对websocket的反对是怎么样的呢? WebSocketServerHandshakernetty提供了一个WebSocketServerHandshaker类来对立应用encoder和decoder的应用。netty提供一个工厂类WebSocketServerHandshakerFactory依据客户端申请header的websocket版本不同,来返回不同的WebSocketServerHandshaker。 public WebSocketServerHandshaker newHandshaker(HttpRequest req) { CharSequence version = req.headers().get(HttpHeaderNames.SEC_WEBSOCKET_VERSION); if (version != null) { if (version.equals(WebSocketVersion.V13.toHttpHeaderValue())) { // Version 13 of the wire protocol - RFC 6455 (version 17 of the draft hybi specification). return new WebSocketServerHandshaker13( webSocketURL, subprotocols, decoderConfig); } else if (version.equals(WebSocketVersion.V08.toHttpHeaderValue())) { // Version 8 of the wire protocol - version 10 of the draft hybi specification. return new WebSocketServerHandshaker08( webSocketURL, subprotocols, decoderConfig); } else if (version.equals(WebSocketVersion.V07.toHttpHeaderValue())) { // Version 8 of the wire protocol - version 07 of the draft hybi specification. return new WebSocketServerHandshaker07( webSocketURL, subprotocols, decoderConfig); } else { return null; } } else { // Assume version 00 where version header was not specified return new WebSocketServerHandshaker00(webSocketURL, subprotocols, decoderConfig); } }同样的, 咱们能够看到,netty为websocket也定义了4种不同的WebSocketServerHandshaker。 ...

September 27, 2021 · 3 min · jiezi

关于netty:netty系列之在netty中处理CORS

简介CORS的全称是跨域资源共享,他是一个基于HTTP-header检测的机制,通过对HTTP-header进行管制,能够实现对跨域资源的权限治理性能。在之前的CORS详解文章中,咱们曾经对CORS有了根本的解释。 本文将会从netty的实现角度,解说如何在netty中实现CORS。 服务端的CORS配置相熟CORS的敌人应该晓得,CORS所有的操作都是在HTTP协定之上通过管制HTTP头来实现的。所以说如果要在服务器端实现CORS的反对,事实上也是对HTTP协定的头进行各种设置实现的。 为了不便大家的应用,netty提供了一个CorsConfig类,来对立CORS的头设置。 先看下CorsConfig类中定义的属性: private final Set<String> origins; private final boolean anyOrigin; private final boolean enabled; private final Set<String> exposeHeaders; private final boolean allowCredentials; private final long maxAge; private final Set<HttpMethod> allowedRequestMethods; private final Set<String> allowedRequestHeaders; private final boolean allowNullOrigin; private final Map<CharSequence, Callable<?>> preflightHeaders; private final boolean shortCircuit;这些属性和CORS的HTTP头设置是一一对应的。比如说origins示意的是容许的源,anyOrigin示意容许所有的源。 是和上面的设置对应的: Origin: <origin>exposeHeaders是和Access-Control-Expose-Headers一一对应的,示意服务器端容许客户端获取CORS资源的同时可能拜访到的header信息。其格局如下: Access-Control-Expose-Headers: <header-name>[, <header-name>]*allowCredentials示意是否开启CORS的权限认证。示意服务器端是否承受客户端带有credentials字段的申请。如果用在preflight申请中,则示意后续的实在申请是否反对credentials,其格局如下: Access-Control-Allow-Credentials: trueallowedRequestMethods示意拜访资源容许的办法,次要用在preflight request中。其格局如下: Access-Control-Allow-Methods: <method>[, <method>]*allowedRequestHeaders用在preflight request中,示意真正可能被用来做申请的header字段,其格局如下: Access-Control-Allow-Headers: <header-name>[, <header-name>]*当客户端发送OPTIONS办法给服务器的时候,为了平安起见,因为服务器并不一定可能承受这些OPTIONS的办法,所以客户端须要首先发送一个preflighted requests,期待服务器响应,等服务器确认之后,再发送实在的申请。咱们举一个例子。preflightHeaders示意的就是服务器容许额preflight的申请头。 shortCircuit示意申请是否是一个无效的CORS申请,如果申请被回绝之后,就会返回一个true。 ...

September 17, 2021 · 1 min · jiezi

关于netty:Netty源码-之-01-编译

下载源码切换分支 git checkout netty-4.1.67.Final申明环境变量 export JAVA_HOME=/usr/lib/jvm/java-11-openjdk-amd64解决microbench工程编译问题 报错信息 [ERROR] Failed to execute goal org.apache.maven.plugins:maven-compiler-plugin:3.8.0:compile (default-compile) on project netty-microbench: Compilation failure[ERROR] /home/dalton/d_arch/netty/source/netty/microbench/src/main/java/io/netty/handler/codec/http/DecodeHexBenchmark.java:[21,23] error: package org.jctools.util does not exist解决办法批改pom.xml,显式援用jct包 <dependency> <groupId>org.jctools</groupId> <artifactId>jctools-core</artifactId> <version>3.1.0</version></dependency>待续

September 7, 2021 · 1 min · jiezi

关于netty:netty系列之搭建HTTP上传文件服务器

简介上一篇的文章中,咱们讲到了如何从HTTP服务器中下载文件,和搭建下载文件服务器应该留神的问题,应用的GET办法。本文将会讨论一下罕用的向服务器提交数据的POST办法和如何向服务器上传文件。 GET办法上传数据依照HTTP的标准,PUT个别是向服务器上传数据,尽管不提倡,然而也能够应用GET向服务器端上传数据。 先看下GET客户端的构建中须要留神的问题。 GET申请实际上就是一个URI,URI前面带有申请的参数,netty提供了一个QueryStringEncoder专门用来构建参数内容: // HTTP申请 QueryStringEncoder encoder = new QueryStringEncoder(get); // 增加申请参数 encoder.addParam("method", "GET"); encoder.addParam("name", "flydean"); encoder.addParam("site", "www.flydean.com"); URI uriGet = new URI(encoder.toString());有了申请URI,就能够创立HttpRequest了,当然这个HttpRequest中还须要有对应的HTTP head数据: HttpRequest request = new DefaultHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.GET, uriGet.toASCIIString()); HttpHeaders headers = request.headers(); headers.set(HttpHeaderNames.HOST, host); headers.set(HttpHeaderNames.CONNECTION, HttpHeaderValues.CLOSE); headers.set(HttpHeaderNames.ACCEPT_ENCODING, HttpHeaderValues.GZIP + "," + HttpHeaderValues.DEFLATE); headers.set(HttpHeaderNames.ACCEPT_LANGUAGE, "zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2"); headers.set(HttpHeaderNames.REFERER, uriSimple.toString()); headers.set(HttpHeaderNames.USER_AGENT, "Netty Simple Http Client side"); headers.set(HttpHeaderNames.ACCEPT, "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8"); headers.set( HttpHeaderNames.COOKIE, ClientCookieEncoder.STRICT.encode( new DefaultCookie("name", "flydean"), new DefaultCookie("site", "www.flydean.com")) );咱们晓得HttpRequest中只有两局部数据,别离是HttpVersion和HttpHeaders。HttpVersion就是HTTP协定的版本号,HttpHeaders就是设置的header内容。 对于GET申请来说,因为所有的内容都蕴含在URI中,所以不须要额定的HTTPContent,间接发送HttpRequest到服务器就能够了。 ...

September 6, 2021 · 2 min · jiezi

关于netty:netty系列之搭建自己的下载文件服务器

简介上一篇文章咱们学习了如何在netty中搭建一个HTTP服务器,探讨了如何对客户端发送的申请进行解决和响应,明天咱们来讨论一下在netty中搭建文件服务器进行文件传输中应该留神的问题。 文件的content-type客户端向服务器端申请一个文件,服务器端在返回的HTTP头中会蕴含一个content-type的内容,这个content-type示意的是返回的文件类型。这个类型应该怎么确认呢? 一般来说,文件类型是依据文件的的扩展名来确认的,依据 RFC 4288的标准,所有的网络媒体类型都必须注册。apache也提供了一个文件MIME type和扩展名的映射关系表。 因为文件类型比拟多,咱们看几个比拟罕用到的类型如下: MIME type扩展名image/jpegjpgimage/jpegjpegimage/pngpngtext/plaintxt text conf def list log inimage/webpwebpapplication/vnd.ms-excelxlsapplication/vnd.openxmlformats-officedocument.spreadsheetml.sheetxlsxapplication/msworddocapplication/vnd.openxmlformats-officedocument.wordprocessingml.documentdocxapplication/vnd.openxmlformats-officedocument.presentationml.presentationpptxapplication/vnd.ms-powerpointpptapplication/pdfpdfJDK提供了一个MimetypesFileTypeMap的类,这个类提供了一个getContentType办法,能够依据申请的文件path信息,来推断其MIME type类型: private static void setContentTypeHeader(HttpResponse response, File file) { MimetypesFileTypeMap mimeTypesMap = new MimetypesFileTypeMap(); response.headers().set(HttpHeaderNames.CONTENT_TYPE, mimeTypesMap.getContentType(file.getPath())); }客户端缓存文件对于HTTP的文件申请来说,为了保障申请的速度,会应用客户端缓存的机制。比方客户端向服务器端申请一个文件A.txt。服务器在接管到该申请之后会将A.txt文件发送给客户端。 其申请流程如下: 步骤1:客户端申请服务器端的文件 =================== GET /file1.txt HTTP/1.1 步骤2:服务器端返回文件,并且附带额定的文件工夫信息: =================== HTTP/1.1 200 OK Date: Mon, 23 Aug 2021 17:52:30 GMT+08:00 Last-Modified: Tue, 10 Aug 2021 18:05:35 GMT+08:00 Expires: Mon, 23 Aug 2021 17:53:30 GMT+08:00 Cache-Control: private, max-age=60一般来说如果客户端是古代浏览器的话,就会把A.txt缓存起来。在下次调用的时候只须要在head中增加If-Modified-Since,询问服务器该文件是否被批改了即可,如果文件没有被批改,则服务器会返回一个304 Not Modified,客户端失去该状态之后就会应用本地的缓存文件。 ...

September 3, 2021 · 2 min · jiezi

关于netty:netty系列之自建客户端和HTTP服务器交互

简介上一篇文章,咱们搭建了一个反对中文的HTTP服务器,并且可能从浏览器拜访,并获取到相应的后果。尽管浏览器在日常的利用中很广泛,然而有时候咱们也有可能从自建的客户端来调用HTTP服务器的服务。 明天给大家介绍如何自建一个HTTP客户端来和HTTP服务器进行交互。 应用客户端构建申请在上一篇文章中,咱们应用浏览器来拜访服务器,并失去到了响应的后果,那么如何在客户端构建申请呢? netty中的HTTP申请能够分成两个局部,别离是HttpRequest和HttpContent。其中HttpRequest只蕴含了申请的版本号和音讯头的信息,HttpContent才蕴含了真正的申请内容信息。 然而如果要构建一个申请的话,须要同时蕴含HttpRequest和HttpContent的信息。netty提供了一个申请类叫做DefaultFullHttpRequest,这个类同时蕴含了两局部的信息,能够间接应用。 应用DefaultFullHttpRequest的构造函数,咱们就能够结构出一个HttpRequest申请如下: HttpRequest request = new DefaultFullHttpRequest( HttpVersion.HTTP_1_1, HttpMethod.GET, uri.getRawPath(), Unpooled.EMPTY_BUFFER);下面的代码中,咱们应用的协定是HTTP1.1,办法是GET,申请的content是一个空的buffer。 构建好根本的request信息之后,咱们可能还须要在header中增加一下额定的信息,比方connection,accept-encoding还有cookie的信息。 比方设置上面的信息: request.headers().set(HttpHeaderNames.HOST, host); request.headers().set(HttpHeaderNames.CONNECTION, HttpHeaderValues.CLOSE); request.headers().set(HttpHeaderNames.ACCEPT_ENCODING, HttpHeaderValues.GZIP);还有设置cookie: request.headers().set( HttpHeaderNames.COOKIE, ClientCookieEncoder.STRICT.encode( new DefaultCookie("name", "flydean"), new DefaultCookie("site", "www.flydean.com")));设置cookie咱们应用的是ClientCookieEncoder.encode办法,ClientCookieEncoder有两种encoder模式,一种是STRICT,一种是LAX。 在STRICT模式下,会对cookie的name和value进行校验和排序。 和encoder对应的就是ClientCookieDecoder,用于对cookie进行解析。 设置好咱们所有的request之后就能够写入到channel中了。 accept-encoding在客户端写入申请的时候,咱们在申请头上增加了accept-encoding,并将其值设置为GZIP,示意客户端接管的编码方式是GZIP。 如果服务器端发送了GZIP的编码内容之后,客户端怎么进行解析呢?咱们须要对GZIP的编码格局进行解码。 netty提供了HttpContentDecompressor类,能够对gzip或者deflate格局的编码进行解码。在解码之后,会同时批改响应头中的“Content-Encoding”和“Content-Length”。 咱们只须要将其增加到pipline中即可。 和它对应的类是HttpContentCompressor,用于对HttpMessage和HttpContent进行gzip或者deflate编码。 所以说HttpContentDecompressor应该被增加到client的pipline中,而HttpContentCompressor应该被增加到server端的pipline中。 server解析HTTP申请server须要一个handler来解析客户端申请过去的音讯。对于服务器来说,解析客户端的申请应该留神哪些问题呢? 首先要留神的是客户端100 Continue申请的问题。 在HTTP中有一个独特的性能叫做,100 (Continue) Status,就是说client在不确定server端是否会接管申请的时候,能够先发送一个申请头,并在这个头上加一个"100-continue"字段,然而临时还不发送申请body。直到接管到服务器端的响应之后再发送申请body。 如果服务器收到100Continue申请的话,间接返回确认即可: if (HttpUtil.is100ContinueExpected(request)) { send100Continue(ctx); } private static void send100Continue(ChannelHandlerContext ctx) { FullHttpResponse response = new DefaultFullHttpResponse(HTTP_1_1, CONTINUE, Unpooled.EMPTY_BUFFER); ctx.write(response); }如果不是100申请的话,server端就能够筹备要返回的内容了: 这里用一个StringBuilder来存储要返回的内容: ...

August 31, 2021 · 1 min · jiezi

关于netty:netty系列之轻轻松松搭个支持中文的服务器

简介之前讲了那么多对于netty的文章,都是讲netty的底层原理和实现,各位小伙伴肯定都在想了,看了这么多篇文章,netty到底无能啥呢?明天让咱们来应用netty简简单单搭一个反对中文的服务器,展现一下netty的威力。 netty的HTTP反对明天咱们搭的服务器是反对HTTP1.1的服务器。在netty中搭建服务器就像是拼房子,找到适合的工具就能够事倍功半。那么要搭建HTTP的房子,netty提供了什么样的工具呢? 在解说netty对HTTP的反对之前,咱们先看一下HTTP的版本倒退状况。 HTTP的全称是Hypertext Transfer Protocol,是在1989年World Wide Web倒退起来之后呈现的标准协议,用来在WWW上传输数据。HTTP/1.1是1997年在原始的HTTP协定根底上进行的补充和优化。 到了2015年,为了适应疾速发送的web利用和古代浏览器的需要,倒退出了新的HTTP/2协定,次要在手机浏览器、延时解决、图像处理和视频解决方面进行了优化。 基本上所有的古代浏览器都反对HTTP/2协定了,然而还有很多应用程序应用的是老的HTTP/1.1协定。netty为HTTP2和HTTP1提供了不同的反对包,对于HTTP1的反对包叫做netty-codec-http,对HTTP2反对的包叫做netty-codec-http2。 本文会解说netty对HTTP1的反对,将会在后续的文章中持续HTTP2的介绍。 netty-codec-http提供了对HTTP的十分有用的一些封装。 首先是代表HTTP中传输对象的类HttpObject,这个类代表着传输中的所有对象。继承这个类的对象有两个十分重要的对象,别离是HttpMessage和HttpContent。 HttpMessage可能跟我设想的不太一样,它实际上只蕴含了两局部内容,别离是HttpVersion和HttpHeaders,然而并不蕴含任何内容。 public interface HttpMessage extends HttpObject { HttpVersion protocolVersion(); HttpMessage setProtocolVersion(HttpVersion version); HttpHeaders headers();}这里HttpVersion只反对HTTP/1.0和HTTP/1.1协定。而HttpHeaders就是对HTTP申请中头对象的封装。 HttpMessage的子类是HttpRequest和HttpResponse,所以这两个类自身是不带申请内容的。 而具体申请的内容是在HttpContent中,HttpContent继承自ByteBufHolder,示意它两头能够带有ByteBuf的内容信息。 而HttpContent真正的实现类就是DefaultFullHttpRequest和DefaultFullHttpResponse,这两个内蕴含了HTTP头和HTTP申请响应内容信息。 那么问题来了,为什么要把HTTP头和HTTP内容离开呢? 这就波及到HTTP1.1中音讯传输中的压缩机制了。为了晋升传输的效率,一般来说在传输的的过程中都会对象音讯进行压缩,然而对于HTTP1.1来说,头部的内容是没方法压缩的,只能压缩content局部,所以须要区别对待。 netty中应用HTTP的原理咱们晓得netty底层是客户端和服务器端构建通道,通过通道来传输ByteBuf音讯。那么netty是怎么反对HTTP申请呢? 当客户端向服务器端发送HTTP申请之后,服务器端须要把接管到的数据应用解码器解码成为能够被应用程序应用的各种HttpObject对象,从而可能在应用程序中对其解析。 netty提供了HttpResponseEncoder和HttpRequestDecoder类,来对HTTP的音讯进行编码和解码。 如果不想别离应用两个类来进行编码和解码,netty还提供了HttpServerCodec类来进行编码和解码工作。 这个类蕴含了HttpRequestDecoder和HttpResponseEncoder两局部的工作,能够同时用来进行编码和解码。 100 (Continue) Status在HTTP中有一个独特的性能叫做,100 (Continue) Status,就是说client在不确定server端是否会接管申请的时候,能够先发送一个申请头,并在这个头上加一个"100-continue"字段,然而临时还不发送申请body。直到接管到服务器端的响应之后再发送申请body。 为了解决这种申请,netty提供了一个HttpServerExpectContinueHandler对象,用来解决100 Status的状况。 当然,如果你的客户端没有这种申请,那么能够间接应用HttpObjectAggregator来将HttpMessage和HttpContent和合并成为FullHttpRequest或者FullHttpResponse。 为netty搭建HTTP服务器有了下面的工作,咱们就能够应用netty搭建http服务器了。最要害的一点就是在HttpRequestServerInitializer增加对应的codec和自定义handler。 public void initChannel(SocketChannel ch) { ChannelPipeline p = ch.pipeline(); p.addLast(new HttpServerCodec()); p.addLast(new HttpServerExpectContinueHandler()); p.addLast(new HttpRequestServerHandler()); }在自定义的handler中,咱们须要实现一个性能,就是当收到客户端的申请时候,须要返回给客户端一段欢送语。 首先将取得的HttpObject转换成为HttpRequest对象,而后依据申请对象构建一个DefaultFullHttpResponse对象,而后设置该response对象的头,最初将该对象写到channel中。 对应的要害代码如下: private static final byte[] CONTENT = "欢送来到www.flydean.com!".getBytes(StandardCharsets.UTF_8); public void channelRead0(ChannelHandlerContext ctx, HttpObject msg) { if (msg instanceof HttpRequest) { HttpRequest req = (HttpRequest) msg; boolean keepAlive = HttpUtil.isKeepAlive(req); FullHttpResponse response = new DefaultFullHttpResponse(req.protocolVersion(), OK, Unpooled.wrappedBuffer(CONTENT)); response.headers()// .set(CONTENT_TYPE, TEXT_PLAIN) .set(CONTENT_TYPE, "text/plain;charset=utf-8") .setInt(CONTENT_LENGTH, response.content().readableBytes()); if (keepAlive) { if (!req.protocolVersion().isKeepAliveDefault()) { //设置header connection=keep-alive response.headers().set(CONNECTION, KEEP_ALIVE); } } else { // 如果keepAlive是false,则设置header connection=close response.headers().set(CONNECTION, CLOSE); } ChannelFuture f = ctx.write(response); if (!keepAlive) { f.addListener(ChannelFutureListener.CLOSE); } } }下面的要害代码中CONTENT蕴含了中文字符串,咱们应用getBytes将其转换成了UTF-8编码的byte数组。那么如果要想客户端可能正确辨认UTF-8编码,须要在response的header中设置内容类型文件为:"text/plain;charset=utf-8"。 ...

August 30, 2021 · 1 min · jiezi

关于netty:netty系列之在netty中使用protobuf协议

简介netty中有很多适配不同协定的编码工具,对于风行的google出品的protobuf也不例外。netty为其提供了ProtobufDecoder和ProtobufEncoder两个工具还有对应的frame detection,接下来咱们会通过一个例子来具体解说如何在netty中应用protobuf。 定义protobuf咱们举个最简略的例子,首先定义一个须要在网络中进行传输的message,这里咱们定义一个student对象,他有一个age和一个name属性,如下所示: syntax = "proto3";package com.flydean17.protobuf;option java_multiple_files = true;option java_package = "com.flydean17.protobuf";option java_outer_classname = "StudentWrapper";message Student { optional int32 age = 1; optional string name =2;}应用上面的命令,对其进行编译: protoc --experimental_allow_proto3_optional -I=. --java_out=. student.proto能够看到生成了3个文件,别离是Student,StudentOrBuilder和StudentWrapper。其中Student和StudentOrBuilder是咱们真正须要用到的对象。 定义handler在handler中,咱们次要进行对音讯进行解决,这里咱们在clientHandler中进行音讯的构建和发送,StudentClientHandler继承SimpleChannelInboundHandler并从新channelActive办法, 在该办法中咱们应用protobuf的语法,构建一个新的Student实例,并给他设置好age和name两个属性。 而后应用ctx.write和ctx.flush办法将其发送到server端: public void channelActive(ChannelHandlerContext ctx) throws Exception { // channel沉闷 //构建一个Student,并将其写入到channel中 Student student= Student.newBuilder().setAge(22).setName("flydean").build(); log.info("client发送音讯{}",student); ctx.write(student); ctx.flush(); }StudentServerHandler也是继承SimpleChannelInboundHandler,并重写channelRead0办法,当server端读取到student音讯的时候,日志输入,并将其回写到channel中,供clientHandler读取: public void channelRead0(ChannelHandlerContext ctx, Student student) throws Exception { log.info("server收到音讯{}",student); // 写入音讯 ChannelFuture future = ctx.write(student); }当client读取到音讯之后,间接日志输入,不再做进一步解决,到此,一轮client和server端的交互就实现了: ...

August 25, 2021 · 2 min · jiezi

关于netty:netty系列之netty中的懒人编码解码器

简介netty之所以弱小,是因为它内置了很多十分有用的编码解码器,通过应用这些编码解码器能够很不便的搭建出十分弱小的应用程序,明天给大家讲讲netty中最根本的内置编码解码器。 netty中的内置编码器在对netty的包进行引入的时候,咱们能够看到netty有很多以netty-codec结尾的artifactId,统计一下,有这么多个: netty-codecnetty-codec-httpnetty-codec-http2netty-codec-memcachenetty-codec-redisnetty-codec-socksnetty-codec-stompnetty-codec-mqttnetty-codec-haproxynetty-codec-dns总共10个codec包,其中netty-codec是最根底的一个,其余的9个是对不同的协定包进行的扩大和适配,能够看到netty反对罕用的和风行的协定格局,十分的弱小。因为codec的内容十分多,要解说他们也不是很容易,本文将会以netty-codec做一个例子,解说其中最根本的也是最通用的编码解码器。 应用codec要留神的问题尽管netty提供了很不便的codec编码解码器,然而正如咱们在前一篇文章中提到的,有些codec是须要和Frame detection一起配合应用的,先应用Frame detection将ByteBuf拆分成一个个代表实在数据的ByteBuf,再交由netty内置的codec或者自定义的codec进行解决,这样能力起到应有的成果。 netty内置的根本codecnetty中根本的codec有base64、bytes、compression、json、marshalling、protobuf、serialization、string和xml这几种。 上面将会一一进行解说。 base64这个codec是负责ByteBuf和base64过后的ByteBuf之间的转换。尽管都是从ByteBuf到ByteBuf,然而其中的内容产生了变动。 有两个要害的类,别离是Base64Encoder和Base64Decoder。因为Base64Decoder是一个MessageToMessageDecoder,所以须要应用一个DelimiterBasedFrameDecoder提前进行解决,罕用的例子如下: ChannelPipeline pipeline = ...; // Decoders pipeline.addLast("frameDecoder", new DelimiterBasedFrameDecoder(80, Delimiters.nulDelimiter())); pipeline.addLast("base64Decoder", new Base64Decoder()); // Encoder pipeline.addLast("base64Encoder", new Base64Encoder());bytesbytes是将bytes数组和ByteBuf之间进行转换,同样的在decode之前,也须要应用FrameDecoder,通常的应用形式如下: ChannelPipeline pipeline = ...; // Decoders pipeline.addLast("frameDecoder", new LengthFieldBasedFrameDecoder(1048576, 0, 4, 0, 4)); pipeline.addLast("bytesDecoder", new ByteArrayDecoder()); // Encoder pipeline.addLast("frameEncoder", new LengthFieldPrepender(4)); pipeline.addLast("bytesEncoder", new ByteArrayEncoder()); compressioncompression这个包的内容就比拟丰盛了,次要是对数据的压缩和解压缩服务。其反对的算法如下: brotliBzip2FastLZJdkZlibLz4LzfSnappyZlibZstandardcompression对于大数据量的传输特地有帮忙,通过压缩能够节俭传输的数据量,从而进步传输速度。 然而压缩是应用特定的算法来计算的,所以它是一个高CPU的操作,咱们在应用的时候须要兼顾网络速度和CPU性能,并从中失去均衡。 jsonjson这个包外面只有一个JsonObjectDecoder类,次要负责将Byte流的JSON对象或者数组转换成JSON对象和数组。 JsonObjectDecoder间接就是一个ByteToMessageDecoder的子类,所以它不须要FrameDecoder,它是依据括号的匹配来判断Byte数组的起始地位,从而辨别哪些Byte数据是属于同一个Json对象或者数组。 咱们如果心愿应用JSON来传输数据的话,这个类就十分有用了。 marshallingMarshalling的全称叫做JBoss Marshalling,它是JBoss出品的一个对象序列化的形式,然而JBoss Marshalling的最新API还是在2011-04-27,曾经有10年没更新了,是不是曾经被废除了? 所以这里咱们不具体介绍这个序列化的内容,感兴趣的小伙伴能够自行摸索。 protobufprotobuf大家应该都很相熟了,它是google出品的一种信息替换格局,能够将其看做是一种序列化的形式。它是语言中立、平台中立、可扩大的结构化数据序列化机制,和XML相似,然而比XML更小、更快、更简略。 netty对protobuf的反对在于能够将protobuf中的message和MessageLite对象跟ByteBuf进行转换。 protobuf的两个编码器也是message到message间接的转换,所以也须要应用frame detection。当然你也能够应用其余的frame detection比方LengthFieldPrepender和LengthFieldBasedFrameDecoder如下所示: ...

August 20, 2021 · 2 min · jiezi

关于netty:netty如何实现高并发

当一个连贯建设之后次要分为俩个步骤1.接管客户端得全副数据2.接管倒后处理业务流程返回response给客户端尔他们得辨别就在第一个步骤 BIO和NIO得辨别1.BIO是期待客户端发数据时候这个过程是阻塞得,造成了一个线程只能解决一个申请得状况,而机器是领有最大线程数的,从而无奈做倒高并发2.NIO则不同当socket建设好之后,Therad并不会阻塞socket,而是接管到申请之后把申请交给Selector,Selector会遍历socket,如果有socket创立好之后selector会告诉Thread,Thraead在解决业务返回给客户端,这个过程是不会阻塞的,所以Nio能够高并发 Netty为什么传输快Netty的传输快其实也是依赖了NIO的一个个性——零拷贝。咱们晓得,Java的内存有堆内存、栈内存和字符串常量池等等,其中堆内存是占用内存空间最大的一块,也是Java对象寄存的中央,个别咱们的数据如果须要从IO读取到堆内存,两头须要通过Socket缓冲区,也就是说一个数据会被拷贝两次能力达到他的的起点,如果数据量大,就会造成不必要的资源节约。Netty针对这种状况,应用了NIO中的另一大个性——零拷贝,当他须要接收数据的时候,他会在堆内存之外开拓一块内存,数据就间接从IO读到了那块内存中去,在netty外面通过ByteBuf能够间接对这些数据进行间接操作,从而放慢了传输速度。 netty的byteBuf 三种应用模式1.堆缓冲区堆缓冲区是ByteBuf最罕用的模式,他将数据存储在堆空间。2.间接缓冲区间接缓冲区是ByteBuf的另外一种罕用模式,他的内存调配都不产生在堆,jdk1.4引入的nio的ByteBuffer类容许jvm通过本地办法调用分配内存,这样做有两个益处 通过免去两头替换的内存拷贝, 晋升IO处理速度; 间接缓冲区的内容能够驻留在垃圾回收扫描的堆区以外。DirectBuffer 在 -XX:MaxDirectMemorySize=xxM大小限度下, 应用 Heap 之外的内存, GC对此”无能为力”,也就意味着躲避了在高负载下频繁的GC过程对利用线程的中断影响.3.复合缓冲区复合缓冲区相当于多个不同ByteBuf的视图,这是netty提供的,jdk不提供这样的性能

August 19, 2021 · 1 min · jiezi

关于netty:netty系列之内置的Frame-detection

[toc] 简介上篇文章咱们讲到了netty中怎么自定义编码和解码器,然而自定义实现起来还是挺简单的,个别没有非凡必要的状况下,大家都心愿越简略越好,其难点就是找到ByteBuf中的宰割点,将ByteBuf宰割成为一个个的能够解决的单元。明天本文讲讲netty中自带的宰割解决机制。 Frame detection在上一章,咱们提到了须要有一种伎俩来辨别ByteBuf中不同的数据,也就是说找到ByteBuf中不同数据的宰割点。如果首先将ByteBuf宰割成一个个的独立的ByteBuf,再对独立的ByteBuf进行解决就会简略很多。 netty中提供了4个宰割点的编码器,咱们能够称之为Frame detection,他们别离是DelimiterBasedFrameDecoder, FixedLengthFrameDecoder, LengthFieldBasedFrameDecoder, 和 LineBasedFrameDecoder。 这几个类都是ByteToMessageDecoder的子类,接下来咱们一一进行介绍。 DelimiterBasedFrameDecoder首先是DelimiterBasedFrameDecoder,看名字就晓得这个是依据delimiter对bytebuf进行宰割的解码器。什么是delimiter呢? netty中有一个Delimiters类,专门定义宰割的字符,次要有两个delimiter,别离是nulDelimiter和lineDelimiter: public static ByteBuf[] nulDelimiter() { return new ByteBuf[] { Unpooled.wrappedBuffer(new byte[] { 0 }) }; } public static ByteBuf[] lineDelimiter() { return new ByteBuf[] { Unpooled.wrappedBuffer(new byte[] { '\r', '\n' }), Unpooled.wrappedBuffer(new byte[] { '\n' }), }; }nullDelimiter用来解决0x00,次要用来解决Flash XML socket或者其余的相似的协定。 lineDelimiter用来解决回车和换行符,次要用来文本文件的解决中。 对于DelimiterBasedFrameDecoder来说,如果有多个delimiter的话,会抉择将ByteBuf宰割最短的那个,举个例子,如果咱们应用DelimiterBasedFrameDecoder(Delimiters.lineDelimiter()) ,因为lineDelimiter中实际上有两个宰割形式,回车+换行或者换行,如果遇到上面的状况: +--------------+ | ABC\nDEF\r\n | +--------------+DelimiterBasedFrameDecoder会抉择最短的宰割后果,也就说将下面的内容宰割成为: +-----+-----+ | ABC | DEF | +-----+-----+而不是 ...

August 19, 2021 · 2 min · jiezi

关于netty:netty系列之自定义编码和解码器要注意的问题

简介在之前的系列文章中,咱们提到了netty中的channel只承受ByteBuf类型的对象,如果不是ByteBuf对象的话,须要用编码和解码器对其进行转换,明天来聊一下netty自定义的编码和解码器实现中须要留神的问题。 自定义编码器和解码器的实现在介绍netty自带的编码器和解码器之前,通知大家怎么实现自定义的编码器和解码器。 netty中所有的编码器和解码器都是从ChannelInboundHandlerAdapter和ChannelOutboundHandlerAdapter衍生而来的。 对于ChannelOutboundHandlerAdapter来说,最重要的两个类是MessageToByteEncoder 和 MessageToMessageEncoder 。 MessageToByteEncoder是将音讯编码成为ByteBuf,这个类也是咱们自定义编码最罕用的类,间接继承这个类并实现encode办法即可。留神到这个类有一个泛型,这个泛型指定的就是音讯的对象类型。 例如咱们想将Integer转换成为ByteBuf,能够这样写: public class IntegerEncoder extends MessageToByteEncoder<Integer> { @Override public void encode(ChannelHandlerContext ctx, Integer msg, ByteBuf out) throws Exception { out.writeInt(msg); } }MessageToMessageEncoder是在音讯和音讯之间进行转换,因为音讯并不能间接写入到channel中,所以须要和MessageToByteEncoder配合应用。 上面是一个Integer到String的例子: public class IntegerToStringEncoder extends MessageToMessageEncoder<Integer> { @Override public void encode(ChannelHandlerContext ctx, Integer message, List<Object> out) throws Exception { out.add(message.toString()); } }对于ChannelInboundHandlerAdapter来说,最重要的两个类是ByteToMessageDecoder和MessageToMessageDecoder 。 ByteToMessageDecoder是将ByteBuf转换成对应的音讯类型,咱们须要继承这个类,并实现decode办法,上面是一个从ByteBuf中读取所有可读的字节,并将后果放到一个新的ByteBuf中, public class SquareDecoder extends ByteToMessageDecoder { @Override public void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception { out.add(in.readBytes(in.readableBytes())); } } MessageToMessageDecoder是音讯和音讯之间的转换,同样的只须要实现decode办法即可,如下从String转换到Integer: ...

August 18, 2021 · 2 min · jiezi

关于netty:netty系列之对聊天进行加密

工具与资源核心帮忙开发者更加高效的工作,提供围绕开发者全生命周期的工具与资源https://developer.aliyun.com/... 简介在之前的文章中,咱们讲到了怎么应用netty建设聊天室,然而这样的简略的聊天室太容易被窃听了,如果想要在外面说点悄悄话是很不平安的,怎么办呢?学过密码学的敌人可能就想到了一个解决办法,聊天的时候对音讯加密,解决的时候再对音讯解密即可。当然在netty中上述的工作都不须要咱们手动来实现,netty曾经提供了反对SSL的channel供咱们抉择,一起来看看吧。 PKI规范在讲netty的具体反对之前,咱们须要先理解一下公钥和私钥的加密规范体系PKI。PKI的全称是Public Key Infrastructure,也就是公钥体系。用于标准公钥私募进行加密解密的规定,从而便于不同零碎的对接。事实上PKI规范曾经有两代协定了。第一代的PKI规范次要是由美国RSA公司的公钥加密规范PKCS,国际电信联盟的ITU-T X.509,IETF的X.509,WAP和WPKI等规范组成。然而因为第一代PKI规范是基于形象语法符号ASN.1进行编码的,实现起来比较复杂和艰难,所以产生了第二代PKI规范。第二代PKI规范是由微软、VeriSign和webMethods三家公司在2001年公布的基于XML的密钥治理标准也叫做XKMS。事实上当初CA核心应用的最广泛的标准还是X.509系列和PKCS系列。X.509系列次要由X.209、X.500和X.509组成,其中X.509是由国际电信联盟(ITU-T)制订的数字证书规范。在X.500根底上进行了性能加强,X.509是在1988年公布的。X.509证书由用户公共密钥和用户标识符组成。此外还包含版本号、证书序列号、CA标识符、签名算法标识、签发者名称、证书有效期等信息。而PKCS是美国RSA公司的公钥加密规范,包含了证书申请、证书更新、证书作废表公布、扩大证书内容以及数字签名、数字信封的格局等方面的一系列相干协定。它定义了一系列从PKCS#1到PKCS#15的规范。其中最罕用的是PKCS#7、PKCS#12和PKCS#10。PKCS#7 是音讯申请语法,罕用于数字签名与加密,PKCS#12是集体音讯替换与打包语法次要用来生成公钥和私钥。PKCS#10是证书申请语法。 各类证书的后缀和转换操作过证书的敌人可能会对证书的后缀目迷五色,一般来说会有DER、CRT、CER、PEM这几种证书的后缀。DER示意证书的内容是用二进制进行编码的。PEM文件是一个文本文件,其内容是以“ – BEGIN -” 结尾的,Base64编码的字符。CRT和CER基本上是等价的,他们都是证书的扩大,也是文本文件,不同的是CRT通常用在liunx和unix零碎中,而CER通常用在windows零碎中。并且在windows零碎中,CER文件会被MS cryptoAPI命令辨认,能够间接显示导入和/或查看证书内容的对话框。KEY文件,次要用来保留PKCS#8规范的公钥和私钥。上面的命令能够用来查看文本证书内容: openssl x509 -in cert.pem -text -nooutopenssl x509 -in cert.cer -text -nooutopenssl x509 -in cert.crt -text -noout上面的命令能够用来查看二进制证书内容:openssl x509 -in cert.der -inform der -text -noout上面是常见的PEM和DER互相转换: PEM到DERopenssl x509 -in cert.crt -outform der-out cert.derDER到PEMopenssl x509 -in cert.crt -inform der -outform pem -out cert.pem netty中启动SSL server事实上这个题目是不对的,netty中启动的server还是原来那个server,只是对发送的音讯进行了加密解密解决。也就是说增加了一个专门进行SSL操作的Handler。netty中代表ssl处理器的类叫做SslHandler,它是SslContext工程类的一个外部类,所以咱们只须要创立好SslContext即可通过调用newHandler办法来返回SslHandler。让服务器端反对SSL的代码:ChannelPipeline p = channel.pipeline(); SslContext sslCtx = SslContextBuilder.forServer(...).build(); p.addLast("ssl", sslCtx.newHandler(channel.alloc()));让客户端反对SSL的代码:ChannelPipeline p = channel.pipeline(); SslContext sslCtx = SslContextBuilder.forClient().build(); p.addLast("ssl", sslCtx.newHandler(channel.alloc(), host, port));netty中SSL的实现有两种形式,默认状况下应用的是OpenSSL,如果OpenSSL不能够,那么将会应用JDK的实现。要创立SslContext,能够调用SslContextBuilder.forServer或者SslContextBuilder.forClient办法。这里以server为例,看下创立流程。SslContextBuilder有多种forServer的办法,这里取最简略的一个进行剖析: ...

August 16, 2021 · 1 min · jiezi

关于netty:netty系列之自定义编码解码器

简介在之前的netty系列文章中,咱们讲到了如何将对象或者String转换成为ByteBuf,通过应用netty自带的encoder和decoder能够实现十分不便的对象和ByteBuf之间的转换,而后就能够向channel中随便写入对象和字符串了。 应用netty自带的编码器当然很好,然而如果你有些非凡的需要,比方心愿在编码的过程中对数据进行变换,或者对对象的字段进行抉择,那么可能就须要自定义编码解码器了。 自定义编码器自定义编码器须要继承MessageToByteEncoder 类,并实现encode办法,在该办法中写入具体的编码逻辑。 本例咱们心愿计算2的N次方,据说将一张纸折叠100次能够达到地球到月亮的高度,这么大的数据一般的number必定是装不下的,咱们将会应用BigInteger来对这个微小的数字进行保留。 那么对于被编码器来说,则须要将这个BigInteger转换成为byte数组。同时在byte数组读取的过程中,咱们须要界定到底哪些byte数据是属于同一个BigInteger的,这就须要对写入的数据格式做一个约定。 这里咱们应用三局部的数据结构来示意一个BigInteger。第一局部是一个magic word也就是魔法词,这里咱们应用魔法词“N”,当读取到这个魔法词就示意接下来的数字是BigInteger。第二局部是示意bigInteger数字的byte数组的长度,获取到这个长度值,就能够读取到所有的byte数组值,最初将其转换成为BigInteger。 因为BigInteger是Number的子类,为了更加泛化编码器,咱们应用Number作为MessageToByteEncoder的泛型,外围编码代码如下: protected void encode(ChannelHandlerContext ctx, Number msg, ByteBuf out) { // 将number编码成为ByteBuf BigInteger v; if (msg instanceof BigInteger) { v = (BigInteger) msg; } else { v = new BigInteger(String.valueOf(msg)); } // 将BigInteger转换成为byte[]数组 byte[] data = v.toByteArray(); int dataLength = data.length; // 将Number进行编码 out.writeByte((byte) 'N'); // 魔法词 out.writeInt(dataLength); // 数组长度 out.writeBytes(data); // 最终的数据 }自定义解码器有了编码之后的byte数组,就能够在解码器中对其解码了。 上一节介绍了,编码过后的数据格式是魔法词N+数组长度+真正的数据。 其中魔法词长度是一个字节,数组长度是四个字节,后面局部总共是5个字节。所以在解码的时候,首先判断ByteBuf中可读字节的长度是否小于5,如果小于5阐明数据是有效的,能够间接return。 如果可读字节的长度大于5,则示意数据是无效的,能够进行数据的解码了。 解码过程中须要留神的是,并不是所有的数据都是咱们所心愿的格局,如果在读取的过程中读到了咱们不意识的格局,那么阐明这个数据并不是咱们想要的,则能够交由其余的handler进行解决。 然而对于ByteBuf来说,一旦调用read办法,就会导致reader index挪动地位,所以在真正的读取数据之前须要调用ByteBuf的markReaderIndex办法,对readerIndex进行记录。而后别离读取魔法词、数组长度和残余的数据,最初将数据转换成为BigInteger,如下所示: ...

August 16, 2021 · 2 min · jiezi

关于netty:netty系列之对聊天进行加密

简介在之前的文章中,咱们讲到了怎么应用netty建设聊天室,然而这样的简略的聊天室太容易被窃听了,如果想要在外面说点悄悄话是很不平安的,怎么办呢?学过密码学的敌人可能就想到了一个解决办法,聊天的时候对音讯加密,解决的时候再对音讯解密即可。 当然在netty中上述的工作都不须要咱们手动来实现,netty曾经提供了反对SSL的channel供咱们抉择,一起来看看吧。 PKI规范在讲netty的具体反对之前,咱们须要先理解一下公钥和私钥的加密规范体系PKI。PKI的全称是Public Key Infrastructure,也就是公钥体系。用于标准公钥私募进行加密解密的规定,从而便于不同零碎的对接。 事实上PKI规范曾经有两代协定了。 第一代的PKI规范次要是由美国RSA公司的公钥加密规范PKCS,国际电信联盟的ITU-T X.509,IETF的X.509,WAP和WPKI等规范组成。然而因为第一代PKI规范是基于形象语法符号ASN.1进行编码的,实现起来比较复杂和艰难,所以产生了第二代PKI规范。 第二代PKI规范是由微软、VeriSign和webMethods三家公司在2001年公布的基于XML的密钥治理标准也叫做XKMS。 事实上当初CA核心应用的最广泛的标准还是X.509系列和PKCS系列。 X.509系列次要由X.209、X.500和X.509组成,其中X.509是由国际电信联盟(ITU-T)制订的数字证书规范。在X.500根底上进行了性能加强,X.509是在1988年公布的。X.509证书由用户公共密钥和用户标识符组成。此外还包含版本号、证书序列号、CA标识符、签名算法标识、签发者名称、证书有效期等信息。 而PKCS是美国RSA公司的公钥加密规范,包含了证书申请、证书更新、证书作废表公布、扩大证书内容以及数字签名、数字信封的格局等方面的一系列相干协定。它定义了一系列从PKCS#1到PKCS#15的规范。 其中最罕用的是PKCS#7、PKCS#12和PKCS#10。PKCS#7 是音讯申请语法,罕用于数字签名与加密,PKCS#12是集体音讯替换与打包语法次要用来生成公钥和私钥。PKCS#10是证书申请语法。 各类证书的后缀和转换操作过证书的敌人可能会对证书的后缀目迷五色,一般来说会有DER、CRT、CER、PEM这几种证书的后缀。 DER示意证书的内容是用二进制进行编码的。 PEM文件是一个文本文件,其内容是以“ - BEGIN -” 结尾的,Base64编码的字符。 CRT和CER基本上是等价的,他们都是证书的扩大,也是文本文件,不同的是CRT通常用在liunx和unix零碎中,而CER通常用在windows零碎中。并且在windows零碎中,CER文件会被MS cryptoAPI命令辨认,能够间接显示导入和/或查看证书内容的对话框。 KEY文件,次要用来保留PKCS#8规范的公钥和私钥。 上面的命令能够用来查看文本证书内容: openssl x509 -in cert.pem -text -nooutopenssl x509 -in cert.cer -text -nooutopenssl x509 -in cert.crt -text -noout上面的命令能够用来查看二进制证书内容: openssl x509 -in cert.der -inform der -text -noout上面是常见的PEM和DER互相转换: PEM到DERopenssl x509 -in cert.crt -outform der-out cert.derDER到PEMopenssl x509 -in cert.crt -inform der -outform pem -out cert.pemnetty中启动SSL server事实上这个题目是不对的,netty中启动的server还是原来那个server,只是对发送的音讯进行了加密解密解决。也就是说增加了一个专门进行SSL操作的Handler。 netty中代表ssl处理器的类叫做SslHandler,它是SslContext工程类的一个外部类,所以咱们只须要创立好SslContext即可通过调用newHandler办法来返回SslHandler。 让服务器端反对SSL的代码: ChannelPipeline p = channel.pipeline(); SslContext sslCtx = SslContextBuilder.forServer(...).build(); p.addLast("ssl", sslCtx.newHandler(channel.alloc()));让客户端反对SSL的代码: ...

August 15, 2021 · 1 min · jiezi

关于netty:netty系列之使用UDP协议

简介在之前的系列文章中,咱们到了应用netty做聊天服务器,聊天服务器应用的SocketChannel,也就是说底层的协定应用的是Scoket。明天咱们将会给大家介绍如何在netty中应用UDP协定。 UDP协定UDP( User Datagram Protocol ),也叫用户数据报协定。 UDP 的次要性能和亮点并不在于它引入了什么个性,而在于它疏忽的那些个性:不保障音讯交付,不保障交付程序,不跟踪连贯状态,不须要拥塞管制。 咱们来看一下UDP的数据包: UDP是一种无连贯的协定,发送者只管发送数据包即可,并不负责解决和保证数据是否胜利发送,数据是否被解决实现等。它的惟一作用就是发送。 在JDK中示意UDP的有一个专门的类叫做:java.net.DatagramPacket,在NIO中还有一个java.nio.channels.DatagramChannel,专门负责解决UDP的channel。 这里咱们要将的是netty,netty中对于UDP协定也有下面的两个类,名字尽管是一样的,然而对应的包不同。他们别离是: io.netty.channel.socket.DatagramPacket 和 io.netty.channel.socket.DatagramChannel,当然netty中的这两个类是对JDK自带类的加强。 先看一下netty中DatagramPacket的定义: public class DatagramPacket extends DefaultAddressedEnvelope<ByteBuf, InetSocketAddress> implements ByteBufHolder DatagramPacket类实现了ByteBufHolder接口,示意它外面寄存的是ByteBuf。而后他又继承自DefaultAddressedEnvelope,这个类是对地址的封装,其中ByteBuf示意传递音讯的类型,InetSocketAddress示意指标的地址,它是一个IP地址+端口号的封装。 从下面的UDP协定咱们晓得,UDP只须要晓得指标地址和对应的音讯即可,所以DatagramPacket中蕴含的数据曾经够用了。 DatagramChannel是用来传递DatagramPacket的,因为DatagramChannel是一个接口,所以个别应用NioDatagramChannel作为真正应用的类。 String和ByteBuf的转换之前咱们讲到过,netty中的channel只承受ByteBuf数据类型,如果间接写入String会报错,之前的系列文章中,咱们讲过两种解决办法,第一种是应用ObjectEncoder和ObjectDecoder在写入ByteBuf之前,对对象进行序列化,这一种不仅适宜String,也适宜Object对象。 第二种是应用StringEncoder和StringDecoder专门解决String的encode和decode,这种解决只能解决String的转换,对Object有效。 如果你不想应用这些encoder和decoder还能够间接应用ByteBuf和String进行转换。 比方要将String写入ByteBuf能够调用Unpooled.copiedBuffer的命令如下: Unpooled.copiedBuffer("开始播送", CharsetUtil.UTF_8)将ByteBuf转换成为String则能够调用: byteBuf.toString(CharsetUtil.UTF_8)构建DatagramPacketDatagramPacket总共能够承受三个参数,别离是要发送的数据data,要接管数据包的地址和要发送数据包的地址。 这里咱们并不关怀发送数据包的地址,那么只须要两个参数即可,对于客户端来说,咱们发送一个”开始播送“的音讯给服务器端,通知服务器端能够向客户发送回复音讯了,如下所示: new DatagramPacket( Unpooled.copiedBuffer("开始播送", CharsetUtil.UTF_8), SocketUtils.socketAddress("255.255.255.255", PORT))上咱们应用SocketUtils.socketAddress创立了一个非凡的地址,255.255.255.255是一个非凡的播送地址,意味着所有的主机,因为咱们的客户端并不知道服务器的地址,所以应用255.255.255.255来播送。 构建好的DatagramPacket,外面有一个sender()办法,能够用来获取客户端的地址,所以在服务器端能够这样构建要发送的packge: new DatagramPacket( Unpooled.copiedBuffer("播送: " + nextQuote(), CharsetUtil.UTF_8), packet.sender())启动客户端和服务器UDP的客户端和服务器启动和socket略微有所不同,如果是socket,那么应用的channel是NioSocketChannel,如果是UDP,则应用的是NioDatagramChannel。如下是服务器端启动的代码: EventLoopGroup group = new NioEventLoopGroup(); try { Bootstrap b = new Bootstrap(); b.group(group) .channel(NioDatagramChannel.class) .option(ChannelOption.SO_BROADCAST, true) .handler(new UDPServerHandler()); b.bind(PORT).sync().channel().closeFuture().await(); } finally { group.shutdownGracefully(); }留神,这里咱们须要设置ChannelOption.SO_BROADCAST为true,因为UDP是以播送的模式发送音讯的。客户端的实现和socket略微有所不同,上面是客户端的启动实现: ...

August 14, 2021 · 1 min · jiezi

关于netty:netty系列之文本聊天室

简介通过之前的系列文章,咱们曾经晓得了netty的运行原理,还介绍了根本的netty服务搭建流程和音讯处理器的写法。明天本文会给大家介绍一个更加简单的例子,文本聊天室。 聊天室的工作流程明天要介绍的是文本聊天室,对于文本聊天室来说,首先须要建设一个服务器,用于解决各个客户端的连贯,对于客户端来说,须要建设和服务器的连贯,而后向服务器输出聊天信息。服务器收到聊天信息之后,会对音讯进行响应,并将音讯返回至客户端,这样一个聊天室的流程就实现了。 文本处理器之前的文章中,咱们有提到过,netty的传输只反对ByteBuf类型,对于聊天室间接输出的字符串是不反对的,须要对字符串进行encode和decode转换。 之前咱们介绍的encode和decode的类叫做ObjectDecoder和ObjectEncoder。明天咱们再介绍两个专门解决字符串的StringDecoder和StringEncoder。 StringEncoder要比ObjectEncoder简略很多,因为对于对象来说,咱们还须要在Byte数组的头部设置Byte数组的大小,从而保障对象所有数据读取正确。对于String来说,就比较简单了,只须要保障一次读入的数据都是字符串即可。 StringEncoder继承自MessageToMessageEncoder,其外围的encode代码如下: protected void encode(ChannelHandlerContext ctx, CharSequence msg, List<Object> out) throws Exception { if (msg.length() == 0) { return; } out.add(ByteBufUtil.encodeString(ctx.alloc(), CharBuffer.wrap(msg), charset)); }从下面的代码能够看出,外围实际上是调用了ByteBufUtil.encodeString办法,将String转换成了ByteBuf。 对于字符串编码来说,还须要界定一个编码的范畴,比方咱们须要晓得须要一次编码多少字符串,一般来说咱们通过回车符来界定一次字符串输出的完结。 netty也提供了这样的十分便当的类叫做DelimiterBasedFrameDecoder,通过传入不同的Delimiter,咱们能够将输出拆分成不同的Frame,从而对一行字符串进行解决。 new DelimiterBasedFrameDecoder(8192, Delimiters.lineDelimiter()))我再看一下StringDecoder的外围代码,StringDecoder继承自MessageToMessageDecoder: protected void decode(ChannelHandlerContext ctx, ByteBuf msg, List<Object> out) throws Exception { out.add(msg.toString(charset)); }通过调用ByteBuf的toString办法,将BuyteBuf转换成为字符串,并且输入到channel中。 初始化ChannelHandler在initChannel的时候,咱们须要向ChannelPipeline中增加无效的Handler。对于本例来说,须要增加StringDecoder、StringEncoder、DelimiterBasedFrameDecoder和真正解决音讯的自定义handler。 咱们将初始化Pipeline的操作都放在一个新的ChatServerInitializer类中,这个类继承自ChannelInitializer,其外围的initChannel办法如下: public void initChannel(SocketChannel ch) throws Exception { ChannelPipeline pipeline = ch.pipeline(); // 增加行分割器 pipeline.addLast(new DelimiterBasedFrameDecoder(8192, Delimiters.lineDelimiter())); // 增加String Decoder和String Encoder,用来进行字符串的转换 pipeline.addLast(DECODER); pipeline.addLast(ENCODER); // 最初增加真正的处理器 pipeline.addLast(SERVER_HANDLER); }ChatServerInitializer在Bootstrap中的childHandler中进行增加: ...

August 13, 2021 · 2 min · jiezi

关于netty:netty系列之自动重连

简介咱们在应用客户端和服务器端连贯的过程中,可能会因为各种问题导致客户端和服务器的连贯产生中断,遇到这种状况,个别状况下咱们须要应用监控程序去监听客户端和服务器端的连贯,如果第一工夫发现连贯断开了,就须要手动去重连。比拟麻烦,明天给大家介绍一种netty中主动重连的形式。 应用netty建设连贯要应用netty建设连贯,首先须要启动服务器,通常来说服务器通过应用ServerBootstrap来启动服务器,如下所示: // 绑定端口并启动ChannelFuture f = b.bind(PORT).sync();对于客户端来说,能够通过Bootstrap按如下的形式启动: // 连贯服务器ChannelFuture f = b.connect(HOST, PORT).sync();主动重连贯的原理那么当客户端和服务器端的连贯断了之后,如何主动重连呢? 对于客户端来说,主动重连只须要再次调用Bootstrap的connect办法即可。当初的关键问题在于,如何找到从新调用connect的机会。 咱们晓得,不管server还是client,对于音讯的解决都须要注册专门解决音讯的handler。 对于读取音讯来说,个别须要继承ChannelInboundHandlerAdapter,在这个handler中定义了很多和channel生命周期无关的办法,咱们能够从这些生命周期的办法动手。 一般来说客户端和服务器连贯的状态是这的: CHANNEL REGISTERED--》CHANNEL ACTIVE --》 READ --》READ COMPLETE --》 CHANNEL INACTIVE --》 CHANNEL UNREGISTERED 客户端和服务器端的连贯如果敞开的话,则会触发CHANNEL INACTIVE 和 CHANNEL UNREGISTERED 两个事件,这样咱们在客户端重写上面两个办法,在办法中退出重连的逻辑即可。 @Override public void channelInactive(final ChannelHandlerContext ctx) { println("连贯断开:" + ctx.channel().remoteAddress()); } @Override public void channelUnregistered(final ChannelHandlerContext ctx) throws Exception { println("sleep:" + ReconnectClient.RECONNECT_DELAY + 's'); ctx.channel().eventLoop().schedule(() -> { println("重连贯: " + ReconnectClient.HOST + ':' + ReconnectClient.PORT); ReconnectClient.connect(); }, ReconnectClient.RECONNECT_DELAY, TimeUnit.SECONDS); }在channelInactive办法中,咱们只是打印了一些日志。次要逻辑在channelUnregistered办法中,在这个办法中咱们首先通过ctx获取到以后的channel,而后拿到channel中的eventLoop,而后调用它的schedule办法,在给定的工夫后从新调用connect()办法。 ...

August 12, 2021 · 1 min · jiezi

关于netty:netty系列之使用POJO替代buf

简介在之前的文章中咱们提到了,对于NioSocketChannel来说,它不接管最根本的string音讯,只接管ByteBuf和FileRegion。然而ByteBuf是以二进制的模式进行解决的,对于程序员来说太不直观了,解决起来也比拟麻烦,有没有可能间接解决java简略对象呢?本文将会探讨一下这个问题。 decode和encode比方咱们须要间接向channel中写入一个字符串,在之前的文章中,咱们晓得这是不能够的,会报上面的谬误: DefaultChannelPromise@57f5c075(failure: java.lang.UnsupportedOperationException: unsupported message type: String (expected: ByteBuf, FileRegion))也就说ChannelPromise只承受ByteBuf和FileRegion,那么怎么做呢? 既然ChannelPromise只承受ByteBuf和FileRegion,那么咱们就须要把String对象转换成ByteBuf即可。 也就是说在写入String之前把String转换成ByteBuf,当要读取数据的时候,再把ByteBuf转换成String。 咱们晓得ChannelPipeline中能够增加多个handler,并且管制这些handler的程序。 那么咱们的思路就进去了,在ChannelPipeline中增加一个encode,用于数据写入的是对数据进行编码成ByteBuf,而后再增加一个decode,用于在数据写出的时候对数据进行解码成对应的对象。 encode,decode是不是很相熟?对了,这就是对象的序列化。 对象序列化netty中对象序列化是要把传输的对象和ByteBuf间接相互转换,当然咱们能够本人实现这个转换对象。然而netty曾经为咱们提供了不便的两个转换类:ObjectEncoder和ObjectDecoder。 先看ObjectEncoder,他的作用就是将对象转换成为ByteBuf。 这个类很简略,咱们对其剖析一下: public class ObjectEncoder extends MessageToByteEncoder<Serializable> { private static final byte[] LENGTH_PLACEHOLDER = new byte[4]; @Override protected void encode(ChannelHandlerContext ctx, Serializable msg, ByteBuf out) throws Exception { int startIdx = out.writerIndex(); ByteBufOutputStream bout = new ByteBufOutputStream(out); ObjectOutputStream oout = null; try { bout.write(LENGTH_PLACEHOLDER); oout = new CompactObjectOutputStream(bout); oout.writeObject(msg); oout.flush(); } finally { if (oout != null) { oout.close(); } else { bout.close(); } } int endIdx = out.writerIndex(); out.setInt(startIdx, endIdx - startIdx - 4); }}ObjectEncoder继承了MessageToByteEncoder,而MessageToByteEncoder又继承了ChannelOutboundHandlerAdapter。为什么是OutBound呢?这是因为咱们是要对写入的对象进行转换,所以是outbound。 ...

August 11, 2021 · 2 min · jiezi

关于netty:netty系列之基于流的数据传输

简介咱们晓得由两种数据的传输方式,别离是字符流和字节流,字符流的意思是传输的对象就是字符串,格局曾经被设置好了,发送方和接管方依照特定的格局去读取就行了,而字节流是指将数据作为最原始的二进制字节来进行传输。 明天给大家介绍一下在netty中的基于流的数据传输。 package和byte相熟TCP/IP协定的同学应该晓得,在TCP/IP中,因为底层协定有反对的数据包的最大值,所以对于大数据传输来说,须要对数据进行拆分和封包解决,并将这些拆分组装过的包进行发送,最初在接管方对这些包进行组合。在各个包中有固定的构造,所以接管方能够很分明的晓得到底应该组合多少个包作为最终的后果。 那么对于netty来说,channel中传输的是ByteBuf,实际上最最最底层的就是byte数组。对于这种byte数组来说,接管方并不知道到底应该组合多少个byte来合成原来的音讯,所以须要在接收端对收到的byte进行组合,从而生成最终的数据。 那么对于netty中的byte数据流应该怎么组合呢?咱们接下来看两种组合办法。 手动组合这种组合的形式的基本思路是结构一个指标大小的ByteBuf,而后将接管到的byte通过调用ByteBuf的writeBytes办法写入到ByteBuf中。最初从ByteBuf中读取对应的数据。 比方咱们想从服务端发送一个int数字给客户端,一般来说int是32bits,而后一个byte是8bits,那么一个int就须要4个bytes组成。 在server端,能够建设一个byte的数组,数组中蕴含4个元素。将4个元素的byte发送给客户端,那么客户端该如何解决呢? 首先咱们须要建设一个clientHander,这个handler应该继承ChannelInboundHandlerAdapter,并且在其handler被增加到ChannelPipeline的时候初始化一个蕴含4个byte的byteBuf。 handler被增加的时候会触发一个handlerAdded事件,所以咱们能够这样写: private ByteBuf buf; @Override public void handlerAdded(ChannelHandlerContext ctx) { //创立一个4个byte的缓冲器 buf = ctx.alloc().buffer(4); }上例中,咱们从ctx调配了一个4个字节的缓冲器,并将其赋值给handler中的公有变量buf。 当handler执行结束,从ChannelPipeline中删除的时候,会触发handlerRemoved事件,在这个事件中,咱们能够对调配的Bytebuf进行清理,通常来说,能够调用其release办法,如下所示: public void handlerRemoved(ChannelHandlerContext ctx) { buf.release(); // 开释buf buf = null; }而后最要害的一步就是从channel中读取byte并将其放到4个字节的byteBuf中。在之前的文章中咱们提到了,能够在channelRead办法中,解决音讯读取的逻辑。 public void channelRead(ChannelHandlerContext ctx, Object msg) { ByteBuf m = (ByteBuf) msg; buf.writeBytes(m); // 写入一个byte m.release(); if (buf.readableBytes() >= 4) { // 曾经凑够4个byte,将4个byte组合称为一个int long result = buf.readUnsignedInt(); ctx.close(); } }每次触发channelRead办法,都会将读取到的一个字节的byte通过调用writeBytes办法写入buf中。当buf的可读byte大于等于4个的时候就阐明4个字节曾经读满了,能够对其进行操作了。 ...

August 10, 2021 · 2 min · jiezi

关于netty:netty系列之EventHandler和Pipeline

简介上一节咱们解说了netty中的Channel,晓得了channel是事件处理器和内部联通的桥梁。明天本文将会具体解说netty的剩下几个十分总要的局部Event、Handler和PipeLine。 ChannelPipelinepipeLine是连贯Channel和handler的桥梁,它实际上是一个filter的实现,用于管制其中handler的解决形式。 当一个channel被创立的时候,和它对应的ChannelPipeline也会被创立。 先看下ChannelPipeline的定义: public interface ChannelPipeline extends ChannelInboundInvoker, ChannelOutboundInvoker, Iterable 首先ChannelPipeline继承自Iterable,示意它是可遍历的,而遍历的后果就是其中一个个的Handler。 作为一个合格的Iterable,ChannelPipeline提供了一系列的add和remote办法,通过这些办法就能够向ChannelPipeline中增加或者移除Handler。因为ChannelPipeline是一个filter,而过滤器是须要指定对应的filter的程序的,所以ChannelPipeline中有addFirst和addLast这种增加不同程序的办法。 而后能够看到ChannelPipeline继承了ChannelInboundInvoker和ChannelOutboundInvoker两个接口。 先看一张channelPipeline的工作流程图: 能够看出ChannelPipeline次要有两种操作,一种是读入Inbound,一种是写出OutBound。 对于Socket.read()这样的读入操作,调用的实际上就是ChannelInboundInvoker中的办法。对于内部的IO写入的申请,调用的就是ChannelOutboundInvoker中的办法。 留神,Inbound和outbound的解决程序是相同的,比方上面的例子: ChannelPipeline p = ...; p.addLast("1", new InboundHandlerA()); p.addLast("2", new InboundHandlerB()); p.addLast("3", new OutboundHandlerA()); p.addLast("4", new OutboundHandlerB()); p.addLast("5", new InboundOutboundHandlerX());下面的代码中咱们向ChannelPipeline增加了5个handler,其中2个InboundHandler,2个OutboundHandler和一个同时解决In和Out的Handler。 那么当channel遇到inbound event的时候,就会依照1,2,3,4,5的程序进行解决,然而只有InboundHandler能力解决Inbound事件,所以,真正执行的程序是1,2,5。 同样的当channel遇到outbound event的时候,会依照5,4,3,2,1的程序进行执行,然而只有outboundHandler能力解决Outbound事件,所以真正执行的程序是5,4,3. 简略的说,ChannelPipeline指定了Handler的执行程序。 ChannelHandlernetty是一个事件驱动的框架,所有的event都是由Handler来进行解决的。ChannelHandler能够解决IO、拦挡IO或者将event传递给ChannelPipeline中的下一个Handler进行解决。 ChannelHandler的构造很简略,只有三个办法,别离是: void handlerAdded(ChannelHandlerContext ctx) throws Exception;void handlerRemoved(ChannelHandlerContext ctx) throws Exception;void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception;依据inbound和outbound事件的不同,ChannelHandler能够分为两类,别离是ChannelInboundHandler 和ChannelOutboundHandler. 因为这两个都是interface,实现起来比拟麻烦,所以netty为大家提供了三个默认的实现:ChannelInboundHandlerAdapter,ChannelOutboundHandlerAdapter和ChannelDuplexHandler。后面两个很好了解,别离是inbound和outbound,最初一个能够同时解决inbound和outbound。 ChannelHandler是由ChannelHandlerContext提供的,并且和ChannelPipeline的交互也是通过ChannelHandlerContext来进行的。 ChannelHandlerContextChannelHandlerContext能够让ChannelHandler和ChannelPipeline或者其余的Handler进行交互。它就是一个上下文环境,使得Handler和Channel能够相互作用。 如能够在ChannelHandlerContext中,调用channel()取得绑定的channel。能够通过调用handler()取得绑定的Handler。通过调用fire*办法来触发Channel的事件。 看下ChannelHandlerContext的定义: public interface ChannelHandlerContext extends AttributeMap, ChannelInboundInvoker, ChannelOutboundInvoker 能够看到他是一个AttributeMap用来存储属性,还是一个ChannelInboundInvoker和ChannelOutboundInvoker用来触发和流传相应的事件。 ...

August 7, 2021 · 2 min · jiezi

关于netty:netty系列之netty中的Channel详解

简介Channel是连贯ByteBuf和Event的桥梁,netty中的Channel提供了对立的API,通过这种对立的API,netty能够轻松的对接多种传输类型,如OIO,NIO等。明天本文将会介绍Channel的应用和Channel相干的一些概念。 Channel详解Channel是什么? Channel是一个连贯网络输出和IO解决的桥梁。你能够通过Channel来判断以后的状态,是open还是connected,还能够判断以后Channel反对的IO操作,还能够应用ChannelPipeline对Channel中的音讯进行解决。 先看下Channel的定义: public interface Channel extends AttributeMap, ChannelOutboundInvoker, Comparable<Channel> {能够看到Channel是一个接口,它继承了AttributeMap, ChannelOutboundInvoker, Comparable三个类。Comparable示意这个类能够用来做比拟。AttributeMap用来存储Channel的各种属性。ChannelOutboundInvoker次要负责Channel和内部 SocketAddress 进行连贯和对写。 再看下channel中定义的办法: 能够看出channel中定义的办法是多种多样的,这些办法都有些什么特点呢?接下来一一为您解说。 异步IO和ChannelFuturenetty中所有的IO都是异步IO,也就是说所有的IO都是立刻返回的,返回的时候,IO可能还没有完结,所以须要返回一个ChannelFuture,当IO有后果之后,会去告诉ChannelFuture,这样就能够取出后果了。 ChannelFuture是java.util.concurrent.Future的子类,它除了能够拿到线程的执行后果之外,还对其进行了扩大,退出了当前任务状态判断、期待工作执行和增加listener的性能。 其余的性能都很好了解,它的冲破在于能够对ChannelFuture增加listener,咱们列出一个增加listener的办法: Future<V> addListeners(GenericFutureListener<? extends Future<? super V>>... listeners);增加的Listener会在future执行完结之后,被告诉。不须要本人再去调用get期待future完结。这里实际上就是异步IO概念的实现,不须要被动去调用,当你实现之后来告诉我就行。十分的美妙! ChannelFuture 有两个状态:uncompleted或者completed,别离代表工作的执行状态。 当一个IO刚开始的时候,返回一个ChannelFuture对象,这个对象的初始状态是uncompleted。留神,这个状态下的IO是还未开始工作的状态。当IO实现之后,不论是succeeded, failed 或者 cancelled状态,ChannelFuture的状态都会转换成为completed。 下图展现的是ChannelFuture状态和IO状态的对应图: +---------------------------+ | Completed successfully | +---------------------------+ +----> isDone() = true |+--------------------------+ | | isSuccess() = true | | Uncompleted | | +===========================+ +--------------------------+ | | Completed with failure | | isDone() = false | | +---------------------------+ | isSuccess() = false |----+----> isDone() = true | | isCancelled() = false | | | cause() = non-null | | cause() = null | | +===========================+ +--------------------------+ | | Completed by cancellation | ...

August 6, 2021 · 2 min · jiezi

关于netty:netty系列之netty中的ByteBuf详解

简介netty中用于进行信息承载和交换的类叫做ByteBuf,从名字能够看出这是Byte的缓存区,那么ByteBuf都有哪些个性呢?一起来看看。 ByteBuf详解netty提供了一个io.netty.buffer的包,该包外面定义了各种类型的ByteBuf和其衍生的类型。 netty Buffer的根底是ByteBuf类,这是一个抽象类,其余的Buffer类基本上都是由该类衍生而得的,这个类也定义了netty整体Buffer的基调。 先来看下ByteBuf的定义: public abstract class ByteBuf implements ReferenceCounted, Comparable<ByteBuf> {ByteBuf实现了两个接口,别离是ReferenceCounted和Comparable。Comparable是JDK自带的接口,示意该类之间是能够进行比拟的。而ReferenceCounted示意的是对象的援用统计。当一个ReferenceCounted被实例化之后,其援用count=1,每次调用retain() 办法,就会减少count,调用release() 办法又会缩小count。当count减为0之后,对象将会被开释,如果试图拜访被开释过后的对象,则会报拜访异样。 如果一个对象实现了ReferenceCounted,并且这个对象外面蕴含的其余对象也实现了ReferenceCounted,那么当容器对象的count=0的时候,其外部的其余对象也会被调用release()办法进行开释。 综上,ByteBuf是一个能够比拟的,能够计算援用次数的对象。他提供了序列或者随机的byte拜访机制。 留神的是,尽管JDK中有自带的ByteBuffer类,然而netty中的 ByteBuf 算是对Byte Buffer的从新实现。他们没有关联关系。创立一个BuffByteBuf是一个抽象类,并不能间接用来实例化,尽管能够应用ByteBuf的子类进行实例化操作,然而netty并不举荐。netty举荐应用io.netty.buffer.Unpooled来进行Buff的创立工作。Unpooled是一个工具类,能够为ByteBuf调配空间、拷贝或者封装操作。 上面是创立几个不同ByteBuf的例子: import static io.netty.buffer.Unpooled.*; ByteBuf heapBuffer = buffer(128); ByteBuf directBuffer = directBuffer(256); ByteBuf wrappedBuffer = wrappedBuffer(new byte[128], new byte[256]); ByteBuf copiedBuffer = copiedBuffer(ByteBuffer.allocate(128));下面咱们看到了4种不同的buff构建形式,一般的buff、directBuffer、wrappedBuffer和copiedBuffer。 一般的buff是固定大小的堆buff,而directBuffer是固定大小的direct buff。direct buff应用的是堆外内存,省去了数据到内核的拷贝,因而效率比一般的buff要高。 wrappedBuffer是对现有的byte arrays或者byte buffers的封装,能够看做是一个视图,当底层的数据发生变化的时候,Wrapped buffer中的数据也会发生变化。 Copied buffer是对现有的byte arrays、byte buffers 或者 string的深拷贝,所以它和wrappedBuffer是不同的,Copied buffer和原数据之间并不共享数据。 随机拜访Buff相熟汇合的敌人应该都晓得,要想随机拜访某个汇合,肯定是通过index来拜访的,ByteBuf也一样,能够通过capacity或得其容量,而后通过getByte办法随机拜访其中的byte,如下所示: //随机拜访 ByteBuf buffer = heapBuffer; for (int i = 0; i < buffer.capacity(); i ++) { byte b = buffer.getByte(i); System.out.println((char) b); }序列读写读写要比拜访简单一点,ByteBuf 提供了两个index用来定位读和写的地位,别离是readerIndex 和 writerIndex ,两个index别离管制读和写的地位。 ...

August 4, 2021 · 2 min · jiezi

关于netty:netty系列之netty初探

简介咱们罕用浏览器来拜访web页面失去相干的信息,通常来说应用的都是HTTP或者HTTPS协定,这些协定的实质上都是IO,客户端的申请就是In,服务器的返回就是Out。然而在目前的协定框架中,并不能齐全满足咱们所有的需要。比方应用HTTP下载大文件,可能须要长连贯期待等。咱们也晓得IO形式有多种多样的,包含同步IO,异步IO,阻塞IO和非阻塞IO等。不同的IO形式其性能也是不同的,而netty就是一个基于异步事件驱动的NIO框架。 本系列文章将会探讨netty的具体应用,通过原理+例子的具体联合,让大家理解和意识netty的魅力。 netty介绍netty是一个优良的NIO框架,大家对IO的第一映像应该是比较复杂,尤其是跟各种HTTP、TCP、UDP协定打交道,应用起来非常复杂。然而netty提供了对这些协定的敌对封装,通过netty能够疾速而且简洁的进行IO编程。netty易于开发、性能优良同时兼具稳定性和灵活性。如果你心愿开发高性能的服务,那么应用netty总是没错的。 netty的最新版本是4.1.66.Final,事实上这个版本是官网举荐的最稳固的版本,netty还有5.x的版本,然而官网并不举荐。 如果要在我的项目中应用,则能够引入上面的代码: <dependency> <groupId>io.netty</groupId> <artifactId>netty-all</artifactId> <version>4.1.66.Final</version> </dependency>上面咱们将会从一个最简略的例子,体验netty的魅力。 netty的第一个服务器什么叫做服务器?可能对外提供服务的程序就能够被称为是服务器。建设服务器是所有对外服务的第一步,怎么应用netty建设一个服务器呢?服务器次要负责解决各种服务端的申请,netty提供了一个ChannelInboundHandlerAdapter的类来解决这类申请,咱们只须要继承这个类即可。 在NIO中每个channel都是客户端和服务器端沟通的通道。ChannelInboundHandlerAdapter定义了在这个channel上可能呈现一些事件和状况,如下图所示: 如上图所示,channel上能够呈现很多事件,比方建设连贯,敞开连贯,读取数据,读取实现,注册,勾销注册等。这些办法都是能够被重写的,咱们只须要新建一个类,继承ChannelInboundHandlerAdapter即可。 这里咱们新建一个FirstServerHandler类,并重写channelRead和exceptionCaught两个办法,第一个办法是从channel中读取音讯,第二个办法是对异样进行解决。 public class FirstServerHandler extends ChannelInboundHandlerAdapter { @Override public void channelRead(ChannelHandlerContext ctx, Object msg) { // 对音讯进行解决 ByteBuf in = (ByteBuf) msg; try { log.info("收到音讯:{}",in.toString(io.netty.util.CharsetUtil.US_ASCII)); }finally { ReferenceCountUtil.release(msg); } } @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) { // 异样解决 log.error("出现异常",cause); ctx.close(); }}下面例子中,咱们收到音讯后调用release()办法将其开释,并不进行理论的解决。调用release办法是在音讯应用实现之后罕用的做法。下面代码将msg进行了ByteBuf的强制转换,如果并不想进行转换的话,能够间接这样应用: try { // 音讯解决 } finally { ReferenceCountUtil.release(msg); }在异样解决办法中,咱们打印出异样信息,并敞开异样的上下文。 有了Handler,咱们须要新建一个Server类用来应用Handler创立channel和接管音讯。接下来咱们看一下netty的音讯解决流程。 ...

August 3, 2021 · 2 min · jiezi

关于Netty:如何用Netty写一个高性能的分布式服务框架

简介: Netty 是一个致力于创立高性能网络应用程序的成熟的 IO 框架。相比拟与间接应用底层的 Java IO API,不须要先成为网络专家就能够基于 Netty 去构建简单的网络应用。业界常见的波及到网络通信的相干中间件大部分基于 Netty 实现网络层。 作者 | 家纯起源 | 阿里技术公众号 一 什么是 Netty? 能做什么?Netty 是一个致力于创立高性能网络应用程序的成熟的 IO 框架。 相比拟与间接应用底层的 Java IO API,你不须要先成为网络专家就能够基于 Netty 去构建简单的网络应用。 业界常见的波及到网络通信的相干中间件大部分基于 Netty 实现网络层。 二 设计一个分布式服务框架1 Architecture 2 近程调用的流程 启动服务端(服务提供者)并公布服务到注册核心。启动客户端(服务消费者)并去注册核心订阅感兴趣的服务。客户端收到注册核心推送的服务地址列表。调用者发动调用,Proxy从服务地址列表中抉择一个地址并将申请信息 <group,providerName,version>,methodName,args[] 等信息序列化为字节数组并通过网络发送到该地址上。服务端收到收到并反序列化申请信息,依据 <group,providerName,version> 从本地服务字典里查找到对应providerObject,再依据 <methodName,args[]> 通过反射调用指定办法,并将办法返回值序列化为字节数组返回给客户端。客户端收到响应信息再反序列化为 Java 对象后由 Proxy 返回给办法调用者。以上流程对办法调用者是通明的,所有看起来就像本地调用一样。 3 近程调用客户端图解 重要概念:RPC三元组 <ID,Request,Response>。 PS: 若是 netty4.x 的线程模型,IO Thread(worker) —> Map<InvokeId,Future> 代替全局 Map 能更好的防止线程竞争。 4 近程调用服务端图解 5 近程调用传输层图解 6 设计传输层协定栈协定头 ...

June 24, 2021 · 4 min · jiezi

关于netty:Netty项目实战高并发弹幕系统已在云服务上运行许久

 欢送微信搜寻并关注“小猴子的技术笔记”公众号 私信我 支付丰盛的视频学习材料! 对于之前学习Netty始终都是处于实践阶段,并没有太多的生产教训。搜查一些实战我的项目也发现大多是弹幕或者聊天。为了测验学习效果,就编写了一套弹幕零碎放在公网上,通过理论拜访来解决和学习Netty。 弹幕零碎应用的是SpringBoot-2.4.3、Thymeleaf、Netty、WebSocket目前曾经放到私有云上近一个月之久,抗住了不同级别的攻打,能够用于理论的生产之中。 我的项目地址:“http://barrage.houry.top:8080... 开源地址:“https://gitee.com/MonkeyBroth... 对于弹幕零碎的打造我这里总结几个点,心愿大家在利用其余畛域的时候也可能留神到。 首先:咱们在创立“EventLoopGroup”的时候须要自定义咱们的线程工厂。之所以要自定义咱们的线程工厂就是因为,如果未来零碎产生异样了,我心愿在日志中可能看到是哪个线程池产生的异样!“EventLoopGroup”的结构参数是反对咱们传入自定义的线程工厂的。 就像上面这样,我为boss和worker线程池别离指定了名字。 EventLoopGroup boss = new NioEventLoopGroup(1,new NettyThreadFactory("netty-boss"));EventLoopGroup worker = new NioEventLoopGroup(2, new NettyThreadFactory("netty-worker")); 另外,咱们还须要配置一下咱们的“logback.xml”中打印日志长度的配置,如果应用默认值的话,那么就会呈现像上面这样的状况:日志信息残缺不全。 其次,咱们晓得Websocket和咱们的Netty服务是长链接,所以肯定要做心跳检测。兴许有些人会纳闷,我不做心跳检测也可能检测到连贯的断开和连贯啊?是的,的确是可能检测到的。不过更过的时候咱们的心跳检测一个是为了开释资源,一个是为了杜绝歹意连贯占用资源! 就好比这样的一个例子:网络上有人是能够通过程序扫描服务器IP和端口的,而后歹意建设Socket连贯并且不传输任何信息。短暂继续的建设连贯,服务器的文件描述符终究会被耗尽。有其余真正的业务再上来的时候就没有方法在进行连贯了,也就是说:服务器变相瘫痪了!这在生产中是一个很重大的问题,因而肯定要应用心跳检测来判断用户的连贯是否还在存活,如果不存活就间接剔除,开释资源。 我上面给出了一个简略的心跳检测的例子,也是我云服务器上目前在跑的一个代码。我这里就是判断了如果10秒之内没有任何的数据传输进来,我就给他敞开调相应的channel通道。 pipeline.addLast("idleStateHandler", new IdleStateHandler(10, 0, 0, TimeUnit.SECONDS));@Overridepublic void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception { if (evt instanceof IdleStateEvent) { IdleStateEvent event = (IdleStateEvent) evt; switch (event.state()) { case READER_IDLE: log.info("[没有接管到:{}的信息心跳信息,将断开连接回收资源]", ctx.toString()); ctx.channel().close(); break; case WRITER_IDLE: log.info("写闲暇"); break; case ALL_IDLE: log.info("读写闲暇"); break; default: log.info("非法状态"); throw new IllegalStateException("非法状态!"); } }} 而后在生产上一段时间之后,就能够发现:互联网还是不平安啊!我的服务常常被扫描,而后歹意建设连贯。如果没有心跳检测机制的话,那么零碎预计早就瘫痪了。 既然加了心跳检测,那么一旦有连贯上来,前端就须要有定时工作来进行数据的发送了。至于发送的工夫距离和发送的内容,能够依据本人的需要来定义。 与之对应的就是咱们在Netty读的时候须要对数据类型做一个判断,如果是心跳信息那么就间接过滤掉。 须要留神的点就这么多了,大家能够去下载源码进行钻研,也能够和我一起探索,。也心愿你从中学到货色之后分享给大家。 ...

June 2, 2021 · 1 min · jiezi

关于netty:长连接网关技术专题五喜马拉雅自研亿级API网关技术实践

本文由喜马拉雅技术团队原创分享,原题《喜马拉雅自研网关架构实际》,有改变。 1、引言网关是一个比拟成熟的产品,基本上各大互联网公司都会有网关这个中间件,来解决一些私有业务的上浮,而且能疾速的更新迭代。如果没有网关,要更新一个私有个性,就要推动所有业务方都更新和公布,那是效率极低的事,有网关后,这所有都变得不是问题。 喜马拉雅也是一样,用户数增长达到 6 亿多的级别,Web 服务个数达到500+,目前咱们网关日解决 200 亿+次调用,单机 QPS 顶峰达到 4w+。 网关除了要实现最根本的性能反向代理外,还有私有个性,比方黑白名单,流控,鉴权,熔断,API 公布,监控和报警等。咱们还依据业务方的需要实现了流量调度,流量 Copy,预公布,智能化升降级,流量预热等相干性能。 从技术上来说,喜马拉雅API网关的技术演进路线图大抵如下: 本文将分享在喜马拉雅API网关在亿级流量前提下,进行的技术演进倒退历程和实际经验总结。 学习交换: 即时通讯/推送技术开发交换5群:215477170 [举荐]挪动端IM开发入门文章:《新手入门一篇就够:从零开发挪动端IM》开源IM框架源码:https://github.com/JackJiang2...(本文同步公布于:http://www.52im.net/thread-35... 2、专题目录本文是系列文章的第5篇,总目录如下: 《长连贯网关技术专题(一):京东京麦的生产级TCP网关技术实际总结》《长连贯网关技术专题(二):知乎千万级并发的高性能长连贯网关技术实际》《长连贯网关技术专题(三):手淘亿级挪动端接入层网关的技术演进之路》《长连贯网关技术专题(四):爱奇艺WebSocket实时推送网关技术实际》《长连贯网关技术专题(五):喜马拉雅自研亿级API网关技术实际》(* 本文)3、第1版:Tomcat NIO+Async Servlet网关在架构设计时最为关键点,就是网关在接管到申请,调用后端服务时不能阻塞 Block,否则网关的吞吐量很难下来,因为最耗时的就是调用后端服务这个近程调用过程。 如果这里是阻塞的,Tomcat 的工作线程都 block 住了,在期待后端服务响应的过程中,不能去解决其余的申请,这个中央肯定要异步。 架构图如下: 这版咱们实现独自的 Push 层,作为网关收到响应后,响应客户端时,通过这层实现,和后端服务的通信是 HttpNioClient,对业务的反对黑白名单,流控,鉴权,API 公布等性能。 然而这版只是性能上达到网关的要求,解决能力很快就成了瓶颈,单机 QPS 到 5K 的时候,就会不停的 Full GC。 前面通过 Dump 线上的堆剖析,发现全是 Tomcat 缓存了很多 HTTP 的申请,因为 Tomcat 默认会缓存 200 个 requestProcessor,每个 prcessor 都关联了一个 request。 还有就是 Servlet 3.0 Tomcat 的异步实现会呈现内存透露,前面通过缩小这个配置,成果显著。 但性能必定就降落了,总结了下,基于 Tomcat 做为接入端,有如下几个问题。 Tomcat 本身的问题: 1)缓存太多,Tomcat 用了很多对象池技术,内存无限的状况下,流量一高很容易触发 GC;2)内存 Copy,Tomcat 的默认是用堆内存,所以数据须要读到堆内,而咱们后端服务是 Netty,有堆外内存,须要通过数次 Copy;3)Tomcat 还有个问题是读 body 是阻塞的, Tomcat 的 NIO 模型和 reactor 模型不一样,读 body 是 block 的。这里再分享一张 Tomcat buffer 的关系图: ...

May 31, 2021 · 2 min · jiezi

关于Netty:Netty编解码

解码ByteToMessageDecoder 解码步骤 累加字节流到cumulation调用子类的decode办法解析(子类实现了各种解码形式)将解析到的ByteBuf向下流传public static final Cumulator MERGE_CUMULATOR = new Cumulator() { @Override public ByteBuf cumulate(ByteBufAllocator alloc, ByteBuf cumulation, ByteBuf in) { ByteBuf buffer; // 如果累加器容量不够,就进行扩容 if (cumulation.writerIndex() > cumulation.maxCapacity() - in.readableBytes() || cumulation.refCnt() > 1) { buffer = expandCumulation(alloc, cumulation, in.readableBytes()); } else { buffer = cumulation; } // 累加数据 buffer.writeBytes(in); in.release(); return buffer; }};public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { if (msg instanceof ByteBuf) { CodecOutputList out = CodecOutputList.newInstance(); try { ByteBuf data = (ByteBuf) msg; first = cumulation == null; // 第一次写入累加器 if (first) { cumulation = data; } else { // 累加数据 cumulation = cumulator.cumulate(ctx.alloc(), cumulation, data); } // 解析,将解析的后果传入out(out是一个List) callDecode(ctx, cumulation, out); } catch (DecoderException e) { throw e; } catch (Throwable t) { throw new DecoderException(t); } finally { if (cumulation != null && !cumulation.isReadable()) { numReads = 0; cumulation.release(); cumulation = null; } else if (++ numReads >= discardAfterReads) { numReads = 0; discardSomeReadBytes(); } int size = out.size(); decodeWasNull = !out.insertSinceRecycled(); // 将解析到的内容向下流传 fireChannelRead(ctx, out, size); out.recycle(); } } else { ctx.fireChannelRead(msg); }}protected void callDecode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) { try { while (in.isReadable()) { int outSize = out.size(); // 如果曾经解析出对象 if (outSize > 0) { // 流传事件 fireChannelRead(ctx, out, outSize); // 清空out out.clear(); if (ctx.isRemoved()) { break; } outSize = 0; } int oldInputLength = in.readableBytes(); decode(ctx, in, out); if (ctx.isRemoved()) { break; } // outSize == out.size()表明本次解析没有解析出新的数据 if (outSize == out.size()) { // 没有从累加器读取数据 // 以上两种状况同时产生,阐明累加器中的内容不足以拼装成一个残缺的数据包 // 所以就要进行解析,让累加器取获取更多的数据 if (oldInputLength == in.readableBytes()) { break; } else { continue; } } if (oldInputLength == in.readableBytes()) { throw new DecoderException( StringUtil.simpleClassName(getClass()) + ".decode() did not read anything but decoded a message."); } if (isSingleDecode()) { break; } } } catch (DecoderException e) { throw e; } catch (Throwable cause) { throw new DecoderException(cause); }}decoder1.基于固定长度的解码器-FixedLengthFrameDecoder ...

April 30, 2021 · 5 min · jiezi

关于Netty:NettyByteBuf

ByteBuf分类 Pooled: 每次申请内存都是从曾经调配好的内存池中取Unpooled: 每次申请内存都重新分配 Heap: 应用jvm的堆内存Direct: 操作系统间接内存 Unsafe: 调用native办法间接操作内存非Unsafe: 通过jdk的api间接操作内存 Unsafe形式: 调用jdk的unsafe实例,依据根底偏移量+index算出总偏移量 static byte getByte(byte[] data, int index) { return UNSAFE.getByte(data, BYTE_ARRAY_BASE_OFFSET + index);}非Unsafe形式: 就是通过数组下标获取 static byte getByte(byte[] memory, int index) { return memory[index];}Unsafe与下面两种分类形式不同,用户无奈决定用哪种形式;是否选用unsafe是由Netty判断能不能拿到底层的unsafe决定的。 if (PlatformDependent.hasUnsafe()) { buf = UnsafeByteBufUtil.newUnsafeDirectByteBuf(this, initialCapacity, maxCapacity);} else { buf = new UnpooledDirectByteBuf(this, initialCapacity, maxCapacity);}UnpooledByteBufAllocator调配形式比较简单,就是每次申请内存的时候都重新分配一块新的内存返回。 重点看PooledByteBufAllocator内存调配流程 PooledByteBufAllocator分配内存流程以堆外内存非配为例 protected ByteBuf newDirectBuffer(int initialCapacity, int maxCapacity) { // 拿到线程独有的PoolThreadCache PoolThreadCache cache = threadCache.get(); PoolArena<ByteBuffer> directArena = cache.directArena; ByteBuf buf; if (directArena != null) { buf = directArena.allocate(cache, initialCapacity, maxCapacity); } else { if (PlatformDependent.hasUnsafe()) { buf = UnsafeByteBufUtil.newUnsafeDirectByteBuf(this, initialCapacity, maxCapacity); } else { buf = new UnpooledDirectByteBuf(this, initialCapacity, maxCapacity); } } return toLeakAwareBuffer(buf);}先通过threadCache拿到一个PoolThreadCache类型的属性,threadCache是PoolThreadLocalCache类型的,PoolThreadLocalCache继承自netty自定义的FastThreadLocal,相似jdk的ThreadLocal,每个线程都有本人的PoolThreadLocalCache,这样每个线程也就有本人的PoolThreadCache。 ...

April 30, 2021 · 10 min · jiezi

关于Netty:netty

根本组件 NioEventLoop: 监听客户端连贯和解决客户端读写Channel: 对一个Socket连贯的封装,可进行数据的读写Pipline: 对数据的逻辑解决链ChannelHandler: Pipline里的一个解决逻辑ByteBuf: 字节缓冲的容器Netty服务端启动步骤: 1.创立服务端Channel创立JDK定义的Channel,将它包装成netty的Channel,并创立一些根本组件绑定在Channel上 newChannel办法里通过反射调用clazz.newInstance()返回一个Channel对象,这个clazz的类型是在创立ServerBootStrap的时候传入的NioServerSocketChannel。 ServerBootstrap b = new ServerBootstrap();b.group(bossGroup, workerGroup) .channel(NioServerSocketChannel.class) .childOption(ChannelOption.TCP_NODELAY, true) .childAttr(AttributeKey.newInstance("childAttr"), "childAttrValue") .handler(new ServerHandler()) .childHandler(null);NioServerSocketChannel的创立流程: 2.初始化服务端Channel初始化一些根本属性,增加一些逻辑解决 3.注册Selector将JDK的Channel注册到事件轮询器Selector上,并把netty的服务端Channel作为一个attachment绑定在JDK的Channel上 4.端口绑定 调用JDK API,实现对本地端口的监听 AbstractChannel#bind public final void bind(final SocketAddress localAddress, final ChannelPromise promise) { ... // wasActive示意端口绑定之前的状态 boolean wasActive = isActive(); try { // 调用javaChannel().bind将JDK的channel绑定到指定端口 doBind(localAddress); } catch (Throwable t) { safeSetFailure(promise, t); closeIfClosed(); return; } // 端口绑定之后,isActive返回true if (!wasActive && isActive()) { invokeLater(new Runnable() { @Override public void run() { pipeline.fireChannelActive(); } }); } safeSetSuccess(promise);}fireChannelActive办法流传事件调用到doBeginRead办法,增加selectionKey感兴趣的事件。就是在服务端端口绑定胜利之后,开始承受连贯申请 ...

April 30, 2021 · 2 min · jiezi

关于netty:浅聊Linux的五种IO模型

微信搜寻:码农StayUp 主页地址:https://gozhuyinglong.github.io 源码分享:https://github.com/gozhuyinglong/blog-demos在日常 Coding 中,多多少少都会接触到网络 IO,就会想要深刻理解一下。看了很多文章,总是云里雾里的感觉,直到读了《UNIX网络编程 卷1:套接字联网API》中的介绍后,才恍然大悟。这里就给大家分享一下,如有不对,欢送指出。 1. 概念阐明为了便于了解前面的内容,咱们先来理解一些概念。 1.1 SocketSocket 中文翻译为套接字,是计算机网络中过程间进行双向通信的端点的形象。一个 Socket 代表了网络通信的一端,是由操作系统提供的过程间通信机制。 在操作系统中,通常会为应用程序提供一组利用程序接口,称为 Socket 接口(Socket API)。应用程序能够通过 Socket 接口,来应用网络 Socket,以进行数据的传输。一个 Socket 由IP地址和端口组成,即:Socket 地址 = IP地址 : 端口号。在同一台计算机上,TCP 协定与 UDP 协定能够同时应用雷同的端口(Port),而互不烦扰。要想实现网络通信,至多须要一对 Socket,其中一个运行在客户端,称之为 Client Socket;另一个运行在服务器端,称之为 Server Socket。Socket 之间的连贯过程能够分为三个步骤:(1)服务器监听;(2)客户端连贯;(3)连贯确认。 1.2 Socket 缓冲区每个 Socket 被创立后,都会在内核中调配两个缓冲区:输出缓冲区和输入缓冲区。 通过 Socket 发送数据并不会立刻向网络中传输数据,而是先将数据写入到输入缓冲区中,再由 TCP 协定将数据从输入缓冲区发送到指标主机。通过 Socket 接收数据也是如此,也是从输出缓冲区中读取数据,而不是间接从网络中读取。 1.3 用户空间、内核空间、零碎调用操作系统的过程空间能够分为用户空间(User Space)和内核空间(Kernel Space),它们须要不同的执行权限。 大多数零碎交互式操作须要在内核空间中运行,比方设施 IO 操作。咱们的利用程序运行在用户空间,是不具备零碎级的间接操作权限的。如果应用程序想要拜访系统核心性能,必须通过零碎调用(System Call)来实现。比方调用recv()函数,会将输出缓冲区中的内容拷贝到用户缓冲区。零碎调用运行在内核空间,是操作系统为应用程序提供的接口。 上面列举了一些 Linux 操作系统中的零碎调用接口(局部函数前面章节会用到): socketcall socket零碎调用socket 建设socketbind 绑定socket到端口connect 连贯近程主机accept 响应socket连贯申请send 通过socket发送信息sendto 发送UDP信息recv 通过socket接管信息recvfrom 接管UDP信息listen 监听socket端口select 对多路同步IO进行轮询shutdown 敞开socket上的连贯sigaction 设置对指定信号的解决办法1.4 阻塞与非阻塞阻塞与非阻塞,用于形容调用者在期待返回后果时的状态。 ...

April 25, 2021 · 1 min · jiezi

关于netty:Netty入门-基于netty的websocket聊天室

我的项目构造 服务端public class MyServer { public static void main(String[] args) throws Exception{ // 负责连贯的NioEventLoopGroup线程数为1 EventLoopGroup bossGroup = new NioEventLoopGroup(1); EventLoopGroup wokerGroup = new NioEventLoopGroup(); try{ ServerBootstrap serverBootstrap = new ServerBootstrap(); serverBootstrap.group(bossGroup,wokerGroup).channel(NioServerSocketChannel.class) // 退出日志 .handler(new LoggingHandler(LogLevel.INFO)) // 自定义channel初始化器 .childHandler(new WebSocketChannelInitializer()); // 绑定本机的8005端口 ChannelFuture channelFuture = serverBootstrap.bind(new InetSocketAddress(8005)).sync(); // 异步回调-敞开事件 channelFuture.channel().closeFuture().sync(); }finally { bossGroup.shutdownGracefully(); wokerGroup.shutdownGracefully(); } }channel初始化器 public class WebSocketChannelInitializer extends ChannelInitializer<SocketChannel> { @Override protected void initChannel(SocketChannel ch) throws Exception { ChannelPipeline pipeline = ch.pipeline(); //websocket协定自身是基于http协定的,所以这边也要应用http解编码器 pipeline.addLast(new HttpServerCodec()); //以块的形式来写的处理器 pipeline.addLast(new ChunkedWriteHandler()); //netty是基于分段申请的,HttpObjectAggregator的作用是将申请分段再聚合,参数是聚合字节的最大长度 pipeline.addLast(new HttpObjectAggregator(8192)); // 应用websocket协定 //ws://server:port/context_path //参数指的是contex_path pipeline.addLast(new WebSocketServerProtocolHandler("/world")); //websocket定义了传递数据的中frame类型 pipeline.addLast(new TextWebSocketFrameHandler()); }}WebSocketChannelInitializerchannel初始化器 ...

March 12, 2021 · 2 min · jiezi

关于netty:netty实战模仿bilibili开发的弹幕系统

 欢送大家搜寻“小猴子的技术笔记”关注我的公众号,支付丰盛面试材料和学习材料。 公众号回复“电子书”支付超多、超全电子书籍。 公众号回复“分布式”支付分布式学习视频。 我写了一个收费的图片压缩工具:“http://images.houry.top/index” 欢送大家应用。 我写了一个netty弹幕零碎:“http://bullet-screen.houry.top:8080/index“ 后盾回复“netty弹幕”获取源码 前言零碎基于springboot-2.4.3、thymeleaf、websocket、netty打造的高并发弹幕零碎。 效果图 欢送大家搜寻“小猴子的技术笔记”关注我的公众号,支付丰盛面试材料和学习材料。 公众号回复“电子书”支付超多、超全电子书籍。 公众号回复“分布式”支付分布式学习视频。 我写了一个收费的图片压缩工具:“http://images.houry.top/index” 欢送大家应用。 我写了一个netty弹幕零碎:“http://bullet-screen.houry.top:8080/index“ 后盾回复“netty弹幕”获取源码

March 12, 2021 · 1 min · jiezi

关于netty:IO模型

IO模型IO模型就是说用什么样的通道进行数据的发送和接管,Java共反对3种网络编程IO模式:BIO,NIO,AIO BIO (Blocking IO)同步阻塞IO模型,一个客户端对应一个服务端 服务端: @Slf4jpublic class BIOServer { public static void main(String[] args) throws IOException { ServerSocket serverSocket = new ServerSocket(8080); while (true) { log.info("服务端已启动,期待连贯"); // 阻塞 Socket socket = serverSocket.accept(); log.info("客户端已连贯"); // 单线程解决链接 // handler(socket); // 多线程解决链接 new Thread(new Runnable() { @SneakyThrows @Override public void run() { log.info("local-thread-{}", Thread.currentThread().getName()); handler(socket); } }).start(); } } private static void handler(Socket socket) throws IOException { byte[] bytes = new byte[1024]; log.info("获取客户端发送数据"); // 接收数据,阻塞办法,没有数据可读时就阻塞 int read = socket.getInputStream().read(bytes); if (read != -1) { log.info("接管客户数据: {}", new String(bytes, 0, read)); } // 响应客户端 OutputStream outputStream = socket.getOutputStream(); outputStream.write("server is connecting".getBytes()); outputStream.flush(); }}客户端: ...

February 25, 2021 · 3 min · jiezi

关于netty:netty客户端发送数据时异常被隐藏

问题形容    最近须要用netty实现一个中间件通信,开始为了先疾速把客户端和服务端通信的demo实现,只是采纳了字符串的编解码形式(StringEncoder,StringDecoder)。客户端和服务端能够失常互发数据,所有运行失常。    然而字符串的编解码并不适宜业务实体类的传输,为了疾速实现实体类传输,所以决定采纳jboss-marshalling-serial序列化形式先实现demo,然而在客户端发送数据时,服务端却无奈收到数据,客户端控制台也没有任何异样信息。    先看整个demo实现代码,再查找问题起因。(先提前阐明,示例代码是完全正确无逻辑bug的) pom依赖`<dependencies> <!--只是用到了外面的日志框架--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter</artifactId> </dependency> <dependency> <groupId>io.netty</groupId> <artifactId>netty-all</artifactId> <version>4.1.56.Final</version> </dependency> <dependency> <groupId>org.jboss.marshalling</groupId> <artifactId>jboss-marshalling-serial</artifactId> <version>2.0.10.Final</version> </dependency></dependencies>` * 1* 2* 3* 4* 5* 6* 7* 8* 9* 10* 11* 12* 13* 14* 15* 16* 17* 18* 19jboss-marshalling-serial序列化工具类netty提供的Marshalling编解码器采纳音讯头和音讯体的形式JBoss Marshalling是一个Java对象序列化包,对jdk默认的序列化框架进行优化,但又放弃跟Serializable接口的兼容,同时减少了一些可调用的参数和附加的个性通过测试发现序列化后的流较protostuff,MessagePack还是比拟大的,序列化和反序列化的类必须是同一个类,否则抛出异样: io.netty.handler.codec.DecoderException: java.lang.ClassNotFoundException: com.bruce.netty.rpc.entity.UserInfo`public final class MarshallingCodeFactory { private static final InternalLogger log = InternalLoggerFactory.getInstance(MarshallingCodeFactory.class); /** 创立Jboss marshalling 解码器 */ public static MyMarshallingDecoder buildMarshallingDecoder() { //参数serial示意创立的是Java序列化工厂对象,由jboss-marshalling-serial提供 MarshallerFactory factory = Marshalling.getProvidedMarshallerFactory("serial"); MarshallingConfiguration configuration = new MarshallingConfiguration(); configuration.setVersion(5); DefaultUnmarshallerProvider provider = new DefaultUnmarshallerProvider(factory, configuration); return new MyMarshallingDecoder(provider, 1024); } /** 创立Jboss marshalling 编码器 */ public static MarshallingEncoder buildMarshallingEncoder() { MarshallerFactory factory = Marshalling.getProvidedMarshallerFactory("serial"); MarshallingConfiguration configuration = new MarshallingConfiguration(); configuration.setVersion(5); DefaultMarshallerProvider provider = new DefaultMarshallerProvider(factory, configuration); return new MarshallingEncoder(provider); } public static class MyMarshallingDecoder extends MarshallingDecoder { public MyMarshallingDecoder(UnmarshallerProvider provider, int maxObjectSize) { super(provider, maxObjectSize); } @Override protected Object decode(ChannelHandlerContext ctx, ByteBuf in) throws Exception { log.info("读取数据长度:{}", in.readableBytes()); return super.decode(ctx, in); } }}` * 1* 2* 3* 4* 5* 6* 7* 8* 9* 10* 11* 12* 13* 14* 15* 16* 17* 18* 19* 20* 21* 22* 23* 24* 25* 26* 27* 28* 29* 30* 31* 32* 33* 34服务端代码实现服务端业务处理器:(实在场景中不要在io线程执行耗时业务逻辑解决) ...

January 11, 2021 · 6 min · jiezi

关于netty:Netty心跳检测

服务端: package HeatBeat;import io.netty.bootstrap.ServerBootstrap;import io.netty.channel.ChannelFuture;import io.netty.channel.ChannelInitializer;import io.netty.channel.ChannelPipeline;import io.netty.channel.nio.NioEventLoopGroup;import io.netty.channel.socket.SocketChannel;import io.netty.channel.socket.nio.NioServerSocketChannel;import io.netty.handler.logging.LoggingHandler;import io.netty.handler.timeout.IdleStateHandler;import java.util.concurrent.TimeUnit;public class MyServer { public static void main(String[] args) throws InterruptedException { NioEventLoopGroup bossGroup = new NioEventLoopGroup(1); NioEventLoopGroup workGroup = new NioEventLoopGroup(); try{ ServerBootstrap serverBootstrap = new ServerBootstrap(); serverBootstrap.group(bossGroup,workGroup) .channel(NioServerSocketChannel.class) .handler(new LoggingHandler()) .childHandler(new ChannelInitializer<SocketChannel>() { @Override protected void initChannel(SocketChannel socketChannel) throws Exception { ChannelPipeline pipeline = socketChannel.pipeline(); pipeline.addLast(new IdleStateHandler(3,5,7, TimeUnit.SECONDS)); pipeline.addLast(new MyServerHandler()); } }); ChannelFuture channelFuture = serverBootstrap.bind(7000).sync(); channelFuture.channel().closeFuture().sync(); }finally{ bossGroup.shutdownGracefully(); workGroup.shutdownGracefully(); } }} 服务端handler: ...

January 11, 2021 · 1 min · jiezi

关于netty:Netty心跳检测

服务端: package HeatBeat;import io.netty.bootstrap.ServerBootstrap;import io.netty.channel.ChannelFuture;import io.netty.channel.ChannelInitializer;import io.netty.channel.ChannelPipeline;import io.netty.channel.nio.NioEventLoopGroup;import io.netty.channel.socket.SocketChannel;import io.netty.channel.socket.nio.NioServerSocketChannel;import io.netty.handler.logging.LoggingHandler;import io.netty.handler.timeout.IdleStateHandler;import java.util.concurrent.TimeUnit;public class MyServer { public static void main(String[] args) throws InterruptedException { NioEventLoopGroup bossGroup = new NioEventLoopGroup(1); NioEventLoopGroup workGroup = new NioEventLoopGroup(); try{ ServerBootstrap serverBootstrap = new ServerBootstrap(); serverBootstrap.group(bossGroup,workGroup) .channel(NioServerSocketChannel.class) .handler(new LoggingHandler()) .childHandler(new ChannelInitializer<SocketChannel>() { @Override protected void initChannel(SocketChannel socketChannel) throws Exception { ChannelPipeline pipeline = socketChannel.pipeline(); pipeline.addLast(new IdleStateHandler(3,5,7, TimeUnit.SECONDS)); pipeline.addLast(new MyServerHandler()); } }); ChannelFuture channelFuture = serverBootstrap.bind(7000).sync(); channelFuture.channel().closeFuture().sync(); }finally{ bossGroup.shutdownGracefully(); workGroup.shutdownGracefully(); } }} 服务端handler: ...

January 11, 2021 · 1 min · jiezi

关于netty:Netty

BIO:同步阻塞。服务器实现模式为 一个连贯一个线程,也就是当客户端有申请连贯的时候就须要启动一个线程进行解决,如果这个连贯不做任何事件,会造成不必要的线程开销阻塞:server.accept(),inputStream.read(bytes) 单线程状况下只能有一个客户端 用线城池能够用多个客户端连贯,每一个客户端应用一个scoket 进行保护,应用线程池对scoket进行存储,十分耗费性能NIO:同步非阻塞。服务器实现模式为一个申请一个线程,客户端发送的连贯申请都会注册到多路复用器上,多路复用器会轮询到连贯有 I/O 申请时才启动一个线程进行解决非阻塞关键字:ServerSocketChannel,ScoketChannel,Selector,SelectionKey

January 2, 2021 · 1 min · jiezi

关于netty:Netty源码解析-FastThreadLocal与HashedWheelTimer

Netty源码剖析系列文章已靠近序幕,本文再来剖析Netty中两个常见组件:FastThreadLoca与HashedWheelTimer。源码剖析基于Netty 4.1.52 FastThreadLocalFastThreadLocal比较简单。FastThreadLocal和FastThreadLocalThread是配套应用的。FastThreadLocalThread继承了Thread,FastThreadLocalThread#threadLocalMap 是一个InternalThreadLocalMap,该InternalThreadLocalMap对象只能用于以后线程。InternalThreadLocalMap#indexedVariables是一个数组,寄存了以后线程所有FastThreadLocal对应的值。而每个FastThreadLocal都有一个index,用于定位InternalThreadLocalMap#indexedVariables。 FastThreadLocal#get public final V get() { // #1 InternalThreadLocalMap threadLocalMap = InternalThreadLocalMap.get(); // #2 Object v = threadLocalMap.indexedVariable(index); if (v != InternalThreadLocalMap.UNSET) { return (V) v; } // #3 return initialize(threadLocalMap);}#1 获取该线程的InternalThreadLocalMap如果是FastThreadLocalThread,间接获取FastThreadLocalThread#threadLocalMap。否则,从UnpaddedInternalThreadLocalMap.slowThreadLocalMap获取该线程InternalThreadLocalMap。留神,UnpaddedInternalThreadLocalMap.slowThreadLocalMap是一个ThreadLocal,这里理论回退到应用ThreadLocal了。#2 每个FastThreadLocal都有一个index。通过该index,获取InternalThreadLocalMap#indexedVariables中寄存的值#3 找不到值,通过initialize办法构建新对象。 能够看到,FastThreadLocal中连hash算法都不必,通过下标获取对应的值,复杂度为log(1),天然很快啦。 HashedWheelTimerHashedWheelTimer是Netty提供的工夫轮调度器。工夫轮是一种充分利用线程资源进行批量化任务调度的调度模型,可能高效的治理各种延时工作。简略说,就是将延时工作寄存到一个环形队列中,并通过执行线程定时执行该队列的工作。 例如,环形队列上有60个格子,执行线程每秒挪动一个格子,则环形队列每轮可寄存1分钟内的工作。当初有两个定时工作task1,32秒后执行task2,2分25秒后执行而执行线程以后位于第6格子则task1放到32+6=38格,轮数为0task2放到25+6=31个,轮数为2执行线程将执行以后格子轮数为0的工作,并将其余工作轮数减1。 毛病,工夫轮调度器的工夫精度不高。因为工夫轮算法的精度取决于执行线程挪动速度。例如下面例子中执行线程每秒挪动一个格子,则调度精度小于一秒的工作就无奈准时调用。 HashedWheelTimer关键字段 // 工作执行器,负责执行工作Worker worker = new Worker();// 工作执行线程Thread workerThread;// HashedWheelTimer状态, 0 - init, 1 - started, 2 - shut downint workerState;// 工夫轮队列,应用数组实现HashedWheelBucket[] wheel;// 暂存新增的工作Queue<HashedWheelTimeout> timeouts = PlatformDependent.newMpscQueue();// 已勾销工作Queue<HashedWheelTimeout> cancelledTimeouts = PlatformDependent.newMpscQueue();增加提早工作 HashedWheelTimer#newTimeout ...

November 29, 2020 · 2 min · jiezi

关于netty:Netty源码解析-对象池Recycler实现原理

因为在Java中创立一个实例的耗费不小,很多框架为了进步性能都应用对象池,Netty也不例外。本文次要剖析Netty对象池Recycler的实现原理。 源码剖析基于Netty 4.1.52 缓存对象治理Recycler的外部类Stack负责管理缓存对象。Stack关键字段 // Stack所属主线程,留神这里应用了WeakReferenceWeakReference<Thread> threadRef; // 主线程回收的对象DefaultHandle<?>[] elements;// elements最大长度int maxCapacity;// elements索引int size;// 非主线程回收的对象volatile WeakOrderQueue head; Recycler将一个Stack划分给某个主线程,主线程间接从Stack#elements中存取对象,而非主线程回收对象则存入WeakOrderQueue中。threadRef字段应用了WeakReference,当主线程沦亡后,该字段指向对象就能够被垃圾回收。 DefaultHandle,对象的包装类,在Recycler中缓存的对象都会包装成DefaultHandle类。 head指向的WeakOrderQueue,用于寄存其余线程的对象 WeakOrderQueue次要属性 // Head#link指向Link链表首对象Head head; // 指向Link链表尾对象Link tail;// 指向WeakOrderQueue链表下一对象WeakOrderQueue next;// 所属线程WeakReference<Thread> owner;Link中也有一个DefaultHandle<?>[] elements字段,负责存储数据。留神,Link继承了AtomicInteger,AtomicInteger的值存储elements的最新索引。 WeakOrderQueue也是属于某个线程,并且WeakOrderQueue继承了WeakReference<Thread>,当所属线程沦亡时,对应WeakOrderQueue也能够被垃圾回收。留神:每个WeakOrderQueue都只属于一个Stack,并且只属于一个非主线程。thread2要寄存对象到Stack1中,只能寄存在WeakOrderQueue1thread1要寄存对象到Stack2中,只能寄存在WeakOrderQueue3 回收对象DefaultHandle#recycle -> Stack#push void push(DefaultHandle<?> item) { Thread currentThread = Thread.currentThread(); if (threadRef.get() == currentThread) { // #1 pushNow(item); } else { // #2 pushLater(item, currentThread); }}#1 以后线程是主线程,间接将对象退出到Stack#elements中。#2 以后线程非主线程,须要将对象放到对应的WeakOrderQueue中 private void pushLater(DefaultHandle<?> item, Thread thread) { ... // #1 Map<Stack<?>, WeakOrderQueue> delayedRecycled = DELAYED_RECYCLED.get(); WeakOrderQueue queue = delayedRecycled.get(this); if (queue == null) { // #2 if (delayedRecycled.size() >= maxDelayedQueues) { delayedRecycled.put(this, WeakOrderQueue.DUMMY); return; } // #3 if ((queue = newWeakOrderQueue(thread)) == null) { return; } delayedRecycled.put(this, queue); } else if (queue == WeakOrderQueue.DUMMY) { // #4 return; } // #5 queue.add(item);}#1 DELAYED_RECYCLED是一个FastThreadLocal,能够了解为Netty中的ThreadLocal优化类。它为每个线程保护了一个Map,存储每个Stack和对应WeakOrderQueue。所有这里获取的delayedRecycled变量是仅用于以后线程的。而delayedRecycled.get获取的WeakOrderQueue,是以Thread + Stack作为维度辨别的,只能是一个线程操作。#2 以后WeakOrderQueue数量超出限度,增加WeakOrderQueue.DUMMY作为标记#3 结构一个WeakOrderQueue,退出到Stack#head指向的WeakOrderQueue链表中,并放入DELAYED_RECYCLED。这时是须要一下同步操作的。#4 遇到WeakOrderQueue.DUMMY标记对象,间接摈弃对象#5 将缓存对象增加到WeakOrderQueue中。 ...

November 22, 2020 · 3 min · jiezi

关于netty:Netty

netty服务端的初始化//类加载初始化办法@PostConstructpublic void start() { try { //创立netty服务器对象 ServerBootstrap bootstrap = new ServerBootstrap(); bootstrap.option(ChannelOption.SO_BACKLOG, 1024); bootstrap.option(ChannelOption.TCP_NODELAY, true); bootstrap.group(boss, work); bootstrap.channel(NioServerSocketChannel.class); bootstrap.childOption(ChannelOption.SO_KEEPALIVE, true); bootstrap.childHandler(myChannelInitializer); logger.info("netty服务器启动"); ChannelFuture future = bootstrap.bind(port).sync(); //ChannelFuture future2 = bootstrap.bind(port2).sync(); } catch (InterruptedException e) { logger.error("服务器启动失败"); //当服务器启动失败得时候由netty的调度性能来敞开netty的服务器 work.shutdownGracefully(); boss.shutdownGracefully(); }}1.(ChannelOption.SO_BACKLOG, 1024 )示意标识当服务器申请解决线程全满时,用于长期寄存已实现[三次握手]的申请的队列的最大长度。如果未设置或所设置的值小于1,Java将应用默认值为502.(ChannelOption.TCP_NODELAY, true)TCP_NODELAY就是用于启用或对于[Nagle算法],如果要求高实时性,有数据发送时就马上发送,就将该选项设置为true敞开[Nagle算法],如果要缩小发送次数缩小网络交互,就设置为false等累积肯定大小后再发送。默认为false。3.bootstrap.group(boss, work);是初始化两个线程,一个线程负责承受新的连贯,一个负责解决读写;是开启了;核心思想:轮询,没有连贯立即返回不要耽搁其余的客户端连贯,不会阻塞;一个线程不会浪费资源;4.bootstrap.channel(NioServerSocketChannel.class);申明利用反射机制来获取NioServerSocketChannel失去实例5.bootstrap.childOption(ChannelOption.SO_KEEPALIVE, true);当设置为true的时候,TCP会实现监控连贯是否无效,当连贯处于闲暇状态的时候,超过了2个小时,本地的TCP实现会发送一个数据包给近程的 socket,如果近程没有发回响应,TCP会继续尝试11分钟,晓得响应为止,如果在12分钟的时候还没响应,TCP尝试敞开socket连贯6.bootstrap.childHandler(myChannelInitializer);初始化客户端Handler~~~~7.ChannelFuture future = bootstrap.bind(port).sync()为服务端netty绑定端口

November 20, 2020 · 1 min · jiezi

关于netty:Netty源码解析-PoolChunk实现原理jemalloc-3的算法

后面文章曾经分享了Netty如何援用jemalloc 4算法治理内存。本文次要分享Netty 4.1.52之前版本中,PoolChunk如何应用jemalloc 3算法治理内存。感兴趣的同学能够比照两种算法。源码剖析基于Netty 4.1.29 首先阐明PoolChunk内存组织形式。PoolChunk的内存大小默认是16M,它将内存组织成为一颗完满二叉树。二叉树的每一层每个节点所代表的内存大小都是均等的,并且每一层节点所代表的内存大小总和加起来都是16M。每一层节点可分配内存是父节点的1/2。整颗二叉树的总层数为12,层数从0开始。 示意图如下 先看一下PoolChunk的构造函数 PoolChunk(PoolArena<T> arena, T memory, int pageSize, int maxOrder, int pageShifts, int chunkSize, int offset) { unpooled = false; this.arena = arena; this.memory = memory; this.pageSize = pageSize; this.pageShifts = pageShifts; this.maxOrder = maxOrder; this.chunkSize = chunkSize; this.offset = offset; unusable = (byte) (maxOrder + 1); log2ChunkSize = log2(chunkSize); subpageOverflowMask = ~(pageSize - 1); freeBytes = chunkSize; assert maxOrder < 30 : "maxOrder should be < 30, but is: " + maxOrder; maxSubpageAllocs = 1 << maxOrder; // Generate the memory map. memoryMap = new byte[maxSubpageAllocs << 1]; depthMap = new byte[memoryMap.length]; int memoryMapIndex = 1; for (int d = 0; d <= maxOrder; ++ d) { // move down the tree one level at a time int depth = 1 << d; for (int p = 0; p < depth; ++ p) { // in each level traverse left to right and set value to the depth of subtree memoryMap[memoryMapIndex] = (byte) d; depthMap[memoryMapIndex] = (byte) d; memoryMapIndex ++; } } subpages = newSubpageArray(maxSubpageAllocs);}unpooled: 是否应用内存池arena:该PoolChunk所属的PoolArenamemory:底层的内存块,对于堆内存,它是一个byte数组,对于间接内存,它是(jvm)ByteBuffer,但无论是哪种模式,其内存大小默认都是16M。pageSize:叶子节点大小,默认为8192,即8K。maxOrder:示意二叉树最大的层数,从0开始。默认为11。chunkSize:整个PoolChunk的内存大小,默认为16777216,即16M。offset:底层内存对齐偏移量,默认为0。unusable:示意节点已被调配,不必了,默认为12。freeBytes:闲暇内存字节数。每个PoolChunk都要按内存使用率关联到一个PoolChunkList上,内存使用率正是通过freeBytes计算。maxSubpageAllocs:叶子节点数量,默认为2048,即2^11。 ...

November 15, 2020 · 3 min · jiezi

关于netty:Netty源码解析-PoolSubpage实现原理

后面文章说了PoolChunk如何治理Normal内存块,本文分享PoolSubpage如何治理Small内存块。源码剖析基于Netty 4.1.52 内存治理算法PoolSubpage负责管理Small内存块。一个PoolSubpage中的内存块size都雷同,该size对应SizeClasses#sizeClasses表格的一个索引index。新创建的PoolSubpage都必须退出到PoolArena#smallSubpagePools[index]链表中。PoolArena#smallSubpagePools是一个PoolSubpage数组,数组中每个元素都是一个PoolSubpage链表,PoolSubpage之间能够通过next,prev组成链表。感兴趣的同学能够参考《内存对齐类SizeClasses》。 留神,Small内存size并不一定小于pageSize(默认为8K)默认Small内存size <= 28672(28KB)对于Normal内存块,Small内存块,pageSize,可参考《PoolChunk实现原理》。 PoolSubpage实际上就是PoolChunk中的一个Normal内存块,大小为其治理的内存块size与pageSize最小公倍数。PoolSubpage应用位图的形式治理内存块。PoolSubpage#bitmap是一个long数组,其中每个long元素上每个bit位都能够代表一个内存块是否应用。 内存调配调配Small内存块有两个步骤 PoolChunk中调配PoolSubpage。如果PoolArena#smallSubpagePools中曾经有对应的PoolSubpage缓冲,则不须要该步骤。 PoolSubpage上分配内存块PoolChunk#allocateSubpage private long allocateSubpage(int sizeIdx) { // #1 PoolSubpage<T> head = arena.findSubpagePoolHead(sizeIdx); synchronized (head) { //allocate a new run // #2 int runSize = calculateRunSize(sizeIdx); //runSize must be multiples of pageSize // #3 long runHandle = allocateRun(runSize); if (runHandle < 0) { return -1; } // #4 int runOffset = runOffset(runHandle); int elemSize = arena.sizeIdx2size(sizeIdx); PoolSubpage<T> subpage = new PoolSubpage<T>(head, this, pageShifts, runOffset, runSize(pageShifts, runHandle), elemSize); subpages[runOffset] = subpage; // #5 return subpage.allocate(); }}#1 这里波及批改PoolArena#smallSubpagePools中的PoolSubpage链表,须要同步操作#2 计算内存块size和pageSize最小公倍数#3 调配一个Normal内存块,作为PoolSubpage的底层内存块,大小为Small内存块size和pageSize最小公倍数#4 构建PoolSubpagerunOffset,即Normal内存块偏移量,也是该PoolSubpage在整个Chunk中的偏移量elemSize,Small内存块size#5 在subpage上分配内存块 ...

November 8, 2020 · 3 min · jiezi

关于netty:Netty源码解析-PoolChunk实现原理

本文次要分享Netty中PoolChunk如何治理内存。源码剖析基于Netty 4.1.52 内存治理算法首先阐明PoolChunk内存组织形式。PoolChunk的内存大小默认是16M,Netty将它划分为2048个page,每个page为8K。PoolChunk上能够调配Small内存块。Normal内存块大小必须是page的倍数。 PoolChunk通过runsAvail字段治理内存块。runsAvail是PriorityQueue<Long>数组,其中PriorityQueue寄存的是handle。handle能够了解为一个句柄,保护一个内存块的信息,由以下局部组成 o: runOffset ,在chunk中page偏移索引,从0开始,15bits: size,以后地位可调配的page数量,15bitu: isUsed,是否应用?, 1bite: isSubpage,是否在subpage中, 1bitb: bitmapIdx,内存块在subpage中的索引,不在subpage则为0, 32bit后面《内存对齐类SizeClasses》文章说过,SizeClasses将sizeClasses表格中isMultipageSize为1的行取出能够组成一个新表格,这里称为Page表格 runsAvail数组默认长度为40,每个地位index上放的handle代表了存在一个可用内存块,并且可调配pageSize大于等于(pageIdx=index)上的pageSize,小于(pageIdex=index+1)的pageSize。如runsAvail[11]上的handle的size可调配pageSize可能为16 ~ 19,如果runsAvail[11]上handle的size为18,如果该handle调配了7个page,剩下的11个page,这时要将handle挪动runsAvail[8](当然,handle的信息要调整)。这时如果要找调配6个page,就能够从runsAvail[5]开始查找runsAvail数组,如果后面runsAvail[5]~runsAvail[7]都没有handle,就找到了runsAvail[8]。调配6个page之后,剩下的5个page,handle挪动runsAvail[4]。 先看一下PoolChunk的构造函数 PoolChunk(PoolArena<T> arena, T memory, int pageSize, int pageShifts, int chunkSize, int maxPageIdx, int offset) { // #1 unpooled = false; this.arena = arena; this.memory = memory; this.pageSize = pageSize; this.pageShifts = pageShifts; this.chunkSize = chunkSize; this.offset = offset; freeBytes = chunkSize; runsAvail = newRunsAvailqueueArray(maxPageIdx); runsAvailMap = new IntObjectHashMap<Long>(); subpages = new PoolSubpage[chunkSize >> pageShifts]; // #2 int pages = chunkSize >> pageShifts; long initHandle = (long) pages << SIZE_SHIFT; insertAvailRun(0, pages, initHandle); cachedNioBuffers = new ArrayDeque<ByteBuffer>(8);}unpooled: 是否应用内存池arena:该PoolChunk所属的PoolArenamemory:底层的内存块,对于堆内存,它是一个byte数组,对于间接内存,它是(jvm)ByteBuffer,但无论是哪种模式,其内存大小默认都是16M。pageSize:page大小,默认为8K。chunkSize:整个PoolChunk的内存大小,默认为16777216,即16M。offset:底层内存对齐偏移量,默认为0。runsAvail:初始化runsAvailrunsAvailMap:记录了每个内存块开始地位和完结地位的runOffset和handle映射。 ...

November 7, 2020 · 3 min · jiezi

关于netty:Netty源码解析-内存池与PoolArena

咱们晓得,Netty应用间接内存实现Netty零拷贝以晋升性能,但间接内存的创立和开释可能须要波及零碎调用,是比拟低廉的操作,如果每个申请都创立和开释一个间接内存,那性能必定是不能满足要求的。这时就须要应用内存池。即从零碎中申请一大块内存,再在下面调配每个申请所需的内存。 Netty中的内存池次要波及PoolArena,PoolChunk与PoolSubpage。本文次要剖析PoolArena的作用与实现。源码剖析基于Netty 4.1.52 接口关系ByteBufAllocator,内存分配器,负责为ByteBuf分配内存, 线程平安。PooledByteBufAllocator,池化内存分配器,默认的ByteBufAllocator,事后从操作系统中申请一大块内存,在该内存上分配内存给ByteBuf,能够进步性能和减小内存碎片。UnPooledByteBufAllocator,非池化内存分配器,每次都从操作系统中申请内存。 RecvByteBufAllocator,接管内存分配器,为Channel读入的IO数据调配一块大小正当的buffer空间。具体性能交由外部接口Handle定义。它次要是针对Channel读入场景增加一些操作,如guess,incMessagesRead,lastBytesRead等等。ByteBuf,调配好的内存块,能够间接应用。 上面只关注PooledByteBufAllocator,它是Netty中默认的内存分配器,也是了解Netty内存机制的难点。 内存调配后面文章《ChannelPipeline机制与读写过程》中剖析了数据读取过程,NioByteUnsafe#read public final void read() { ... final RecvByteBufAllocator.Handle allocHandle = recvBufAllocHandle(); allocHandle.reset(config); ByteBuf byteBuf = null; ... byteBuf = allocHandle.allocate(allocator); allocHandle.lastBytesRead(doReadBytes(byteBuf)); ...}recvBufAllocHandle办法返回AdaptiveRecvByteBufAllocator.HandleImpl。(AdaptiveRecvByteBufAllocator,PooledByteBufAllocator都在DefaultChannelConfig中初始化) AdaptiveRecvByteBufAllocator.HandleImpl#allocate -> AbstractByteBufAllocator#ioBuffer -> PooledByteBufAllocator#directBuffer -> PooledByteBufAllocator#newDirectBuffer protected ByteBuf newDirectBuffer(int initialCapacity, int maxCapacity) { // #1 PoolThreadCache cache = threadCache.get(); PoolArena<ByteBuffer> directArena = cache.directArena; final ByteBuf buf; if (directArena != null) { // #2 buf = directArena.allocate(cache, initialCapacity, maxCapacity); } else { // #3 buf = PlatformDependent.hasUnsafe() ? UnsafeByteBufUtil.newUnsafeDirectByteBuf(this, initialCapacity, maxCapacity) : new UnpooledDirectByteBuf(this, initialCapacity, maxCapacity); } return toLeakAwareBuffer(buf);}AbstractByteBufAllocator#ioBuffer办法会判断以后零碎是否反对unsafe。反对时应用间接内存,不反对则应用堆内存。这里只关注间接内存的实现。#1 从以后线程缓存中获取对应内存池PoolArena#2 在以后线程内存池上分配内存#3 线程缓存不存在,只能应用非池化内存分配内存了 ...

November 1, 2020 · 3 min · jiezi

关于netty:Netty源码解析-内存对齐类SizeClasses

在学习Netty内存池之前,咱们先理解一下Netty的内存对齐类SizeClasses,它为Netty内存池中的内存块提供大小对齐,索引计算等服务办法。源码剖析基于Netty 4.1.52 Netty内存池中每个内存块size都合乎如下计算公式size = 1 << log2Group + nDelta * (1 << log2Delta)log2Group:内存块分组nDelta:增量乘数log2Delta:增量大小的log2值 SizeClasses初始化后,将计算chunkSize(内存池每次向操作系统申请内存块大小)范畴内每个size的值,保留到sizeClasses字段中。sizeClasses是一个表格(二维数组),共有7列,含意如下index:内存块size的索引log2Group:内存块分组,用于计算对应的sizelog2Delata:增量大小的log2值,用于计算对应的sizenDelta:增量乘数,用于计算对应的sizeisMultipageSize:示意size是否为page的倍数isSubPage:示意是否为一个subPage类型log2DeltaLookup:如果size存在位图中的,记录其log2Delta,未应用 sizeClasses负责计算sizeClasses表格 private int sizeClasses() { int normalMaxSize = -1; int index = 0; int size = 0; // #1 int log2Group = LOG2_QUANTUM; int log2Delta = LOG2_QUANTUM; int ndeltaLimit = 1 << LOG2_SIZE_CLASS_GROUP; // #2 int nDelta = 0; while (nDelta < ndeltaLimit) { size = sizeClass(index++, log2Group, log2Delta, nDelta++); } log2Group += LOG2_SIZE_CLASS_GROUP; // #3 while (size < chunkSize) { nDelta = 1; while (nDelta <= ndeltaLimit && size < chunkSize) { size = sizeClass(index++, log2Group, log2Delta, nDelta++); normalMaxSize = size; } log2Group++; log2Delta++; } //chunkSize must be normalMaxSize assert chunkSize == normalMaxSize; //return number of size index return index;}LOG2_QUANTUM=4LOG2_SIZE_CLASS_GROUP=2#1 log2Group,log2Delta都是从LOG2_QUANTUM开始ndeltaLimit为2^LOG2_SIZE_CLASS_GROUP,即内存块size以4个为一组进行分组#2 初始化第0组nDelta从0开始sizeClass办法计算每个size大小留神:第0组后log2Group减少LOG2_SIZE_CLASS_GROUP,而log2Delta不变#3 初始化前面的sizenDelta从1开始每组log2Group+1,log2Delta+1 ...

October 30, 2020 · 9 min · jiezi

关于netty:Netty源码解析-零拷贝机制与ByteBuf

本文来分享Netty中的零拷贝机制以及内存缓冲区ByteBuf的实现。源码剖析基于Netty 4.1.52 Netty中的零拷贝Netty中零拷贝机制次要有以下几种1.文件传输类DefaultFileRegion#transferTo,调用FileChannel#transferTo,间接将文件缓冲区的数据发送到指标Channel,缩小用户缓冲区的拷贝(通过linux的sendfile函数)。应用read 和 write过程如下 应用sendfile 能够看到,应用sendfile函数能够缩小数据拷贝以及用户态,内核态的切换 可参考: 操作系统和Web服务器那点事儿 2.Netty中提供了一些操作内存缓冲区的办法,如Unpooled#wrappedBuffer办法,将byte数据,(jvm)ByteBuffer转换为ByteBufCompositeByteBuf#addComponents办法,合并ByteBufByteBuf#slice办法,提取ByteBuf中局部数据片段ByteBuf#duplicate,复制一个内存缓冲区这些办法都是基于对象援用的操作,并没有内存拷贝,而是内存共享 3.应用堆外内存(jvm)ByteBuffer对Socket读写如果应用JVM的堆内存读取Socket数据,JVM会将Socket数据读取到间接内存,再拷贝一份到堆内存中,写入数据到Socket也须要将堆内存拷贝一份到间接内存中,而后才写入Socket中。因为操作系统进行io操作须要一个稳固的间断空间的字节空间, 然而java堆上的字节空间会随着gc进行而进行挪动, 如果操作系统读取堆上的空间, 就会出错。应用堆外内存能够防止该拷贝操作。留神,这里从内核缓冲区拷贝到用户缓冲区的操作并不能省略,毕竟咱们须要对数据进行操作,所以还是要拷贝到用户态的。可参考: 知乎--Java NIO中,对于DirectBuffer,HeapBuffer的疑难知乎--Java NIO direct buffer的劣势在哪儿? ByteBufByteBuf是用于与Channel交互的内存缓冲区,提供程序拜访和随机拜访。Netty4中将ByteBuf调整为抽象类,从而晋升吞吐量。 1.ByteBuffer先理解一下ByteBuffer,ByteBuffer是JVM提供的字节内存缓冲区。ByteBuf是在ByteBuffer上进行的扩大,底层还是应用ByteBuffer。 ByteBuffer有两个子类,DirectByteBuffer和HeapByteBuffer。HeapByteBuffer应用ByteBuffer#hb(byte[])存储数据。DirectByteBuffer是堆外内存,应用的是操作系统的间接内存,它保护了一个援用address指向了底层数据,从而操作数据。(并没有应用ByteBuffer#buff) Buffer外围属性 int position; //以后操作地位。int mark; //为某一读过的地位做标记,便于某些时候回退到该地位。int capacity; //初始化时候的容量。int limit; // 读写的限度地位,读写超出该地位会报错读写操作都是基于position,并以limit为限度的。mark,position,limit,capacity关系如下 0 <= mark <= position <= limit <= capacityByteBuffer提供了如下办法调整这些标记地位: clearlimit = position = 0个别在把数据写入Buffer前调用 fliplimit = positionposition = 0个别在从Buffer读出数据前调用 rewindposition=0limit不变个别在把数据重写入Buffer前调用。 compacting革除曾经读过的数据。任何未读的数据都被移到缓冲区的起始处,新写入的数据将放到缓冲区未读数据的前面 ByteBuffer还提供了一些操作缓冲区的办法 duplicate创立新字节缓冲区,共享以后缓冲区内容 slice创立新字节缓冲区,共享以后缓冲区内容子序列。 Netty的ByteBuf应用readerIndex标记读地位,writerIndex标记写地位,比(jvm)ByteBuffer设计更优雅。 +-------------------+------------------+------------------+ | discardable bytes | readable bytes | writable bytes | | | (CONTENT) | | +-------------------+------------------+------------------+ | | | | 0 <= readerIndex <= writerIndex <= capacityByteBuf提供readerIndex/writerIndex等办法获取或设置这两个值,十分直观。另外,ByteBuf提供了如下办法操作缓冲区 ...

October 29, 2020 · 2 min · jiezi

关于netty:Netty源码解析-ChannelOutboundBuffer实现与Flush过程

后面文章说了,ChannelHandlerContext#write只是将数据缓存到ChannelOutboundBuffer,等到ChannelHandlerContext#flush时,再将ChannelOutboundBuffer缓存的数据写到Channel中。本文分享Netty中ChannelOutboundBuffer的实现以及Flush过程。源码剖析基于Netty 4.1 每个Channel的AbstractUnsafe#outboundBuffer 都保护了一个ChannelOutboundBuffer。ChannelOutboundBuffer,出站数据缓冲区,负责缓存ChannelHandlerContext#write的数据。通过链表治理数据,链表节点为外部类Entry。 关键字段如下 Entry tailEntry; // 链表最初一个节点,新增的节点增加其后。Entry unflushedEntry; // 链表中第一个未刷新的节点Entry flushedEntry; // 链表中第一个已刷新但数据未写入的节点int flushed; // 已刷新但数据未写入的节点数ChannelHandlerContext#flush操作前,须要先刷新一遍待处理的节点(次要是统计本次ChannelHandlerContext#flush操作能够写入多少个节点数据),从unflushedEntry开始。刷新实现后应用flushedEntry标记第一个待写入的节点,flushed为待写入节点数。 后面分享Netty读写过程的文章说过,AbstractUnsafe#write解决写操作时,会调用ChannelOutboundBuffer#addMessage将数据缓存起来 public void addMessage(Object msg, int size, ChannelPromise promise) { // #1 Entry entry = Entry.newInstance(msg, size, total(msg), promise); if (tailEntry == null) { flushedEntry = null; } else { Entry tail = tailEntry; tail.next = entry; } tailEntry = entry; if (unflushedEntry == null) { unflushedEntry = entry; } incrementPendingOutboundBytes(entry.pendingSize, false);}#1 构建一个Entry,留神,这里应用了对象池RECYCLER,前面有文章具体解析。次要是更新tailEntry和unflushedEntry#2 如果以后缓存数量超过阀值WriteBufferWaterMark#high,更新unwritable标记为true,并触发pipeline.fireChannelWritabilityChanged()办法。因为ChannelOutboundBuffer链表没有大小限度,一直累积数据可能导致 OOM,为了防止这个问题,咱们能够在unwritable标记为true时,不再持续缓存数据。Netty只会更新unwritable标记,并不阻止数据缓存,咱们能够依据须要实现该性能。示例如下 ...

October 25, 2020 · 4 min · jiezi

关于netty:Netty源码解析-客户端启动过程

上一篇文章分享了Netty服务端启动过程,本文持续分享Netty客户端启动过程。源码剖析基于Netty 4.1 Connect客户端启动过程比较简单,次要是Connect操作。Netty客户端启动疏导类是Bootstrap,同样继承了AbstractBootstrap,它只有一个EventLoopGroup,下文称为ConnectGroup。 Bootstrap#connect -> doResolveAndConnect -> doResolveAndConnect0 private ChannelFuture doResolveAndConnect0(final Channel channel, SocketAddress remoteAddress, final SocketAddress localAddress, final ChannelPromise promise) { try { final EventLoop eventLoop = channel.eventLoop(); // #1 final AddressResolver<SocketAddress> resolver = this.resolver.getResolver(eventLoop); ... final Future<SocketAddress> resolveFuture = resolver.resolve(remoteAddress); if (resolveFuture.isDone()) { final Throwable resolveFailureCause = resolveFuture.cause(); if (resolveFailureCause != null) { channel.close(); promise.setFailure(resolveFailureCause); } else { // #2 doConnect(resolveFuture.getNow(), localAddress, promise); } return promise; } ... } catch (Throwable cause) { promise.tryFailure(cause); } return promise;}#1 AddressResolver负责解析SocketAddress。它能够做一些地址转换工作。如Netty提供了RoundRobinInetAddressResolver,能够对上游服务集群进行轮询调用。Bootstrap#resolver是一个AddressResolverGroup,它负责结构AddressResolver,默认应用DefaultAddressResolverGroup。#2 调用doConnect,执行Connect操作。 ...

October 15, 2020 · 2 min · jiezi

关于netty:netty学习笔记05netty之NIO的聊天室简易版

NIO 简易版聊天室1. server端:package com.niewj.niochat;import java.io.IOException;import java.net.InetSocketAddress;import java.nio.ByteBuffer;import java.nio.channels.*;import java.util.Iterator;import java.util.Set;/** * Created by niewj on 2020/9/22 15:18 */public class NioChatServer { public static final int PORT = 7777; public static final int BUF_SIZE = 512; private Selector selector; private ServerSocketChannel serverChannel; public static void main(String[] args) throws IOException { NioChatServer server = new NioChatServer(); server.init(); server.listen(); } private void listen() throws IOException { // 关联胜利后 轮询监听客户端连贯 while (true) { // 阻塞办法: 直到有注册的事件产生 int readyChannels = selector.select(); if (readyChannels == 0) { continue; } // 收集监听的事件 Set<SelectionKey> keys = selector.selectedKeys(); Iterator<SelectionKey> iter = keys.iterator(); while (iter.hasNext()) { SelectionKey key = iter.next(); if (key.isAcceptable()) { // 解决-accept事件 handleAcceptable(key); } if (key.isReadable()) { // 解决-read 事件 readDataFromSocket(key); } /** 留神每次 iter.remove(): * Selector不会本人移除SelectionKey实例, 必须在解决完Channel时本人移除。 * 下次该通道就绪时,Selector会再次将其放入已选择键集中。 */

September 25, 2020 · 1 min · jiezi

关于netty:这可能是目前最透彻的Netty原理架构解析

本文基于 Netty 4.1 开展介绍相干实践模型,应用场景,根本组件、整体架构,知其然且知其所以然,心愿给大家在理论开发实际、学习开源我的项目方面提供参考。 Netty 是一个异步事件驱动的网络应用程序框架,用于疾速开发可保护的高性能协定服务器和客户端。 JDK 原生 NIO 程序的问题 JDK 原生也有一套网络应用程序 API,然而存在一系列问题,次要如下: NIO 的类库和 API 繁冗,应用麻烦。你须要熟练掌握 Selector、ServerSocketChannel、SocketChannel、ByteBuffer 等。须要具备其余的额定技能做铺垫。例如相熟 Java 多线程编程,因为 NIO 编程波及到 Reactor 模式,你必须对多线程和网路编程十分相熟,能力编写出高质量的 NIO 程序。可靠性能力不齐,开发工作量和难度都十分大。例如客户端面临断连重连、网络闪断、半包读写、失败缓存、网络拥塞和异样码流的解决等等。 NIO 编程的特点是性能开发绝对容易,然而可靠性能力补齐工作量和难度都十分大。JDK NIO 的 Bug。例如臭名远扬的 Epoll Bug,它会导致 Selector 空轮询,最终导致 CPU 100%。 官网宣称在 JDK 1.6 版本的 update 18 修复了该问题,然而直到 JDK 1.7 版本该问题仍旧存在,只不过该 Bug 产生概率升高了一些而已,它并没有被基本解决。Netty 的特点 Netty 对 JDK 自带的 NIO 的 API 进行封装,解决上述问题,次要特点有: 设计优雅,实用于各种传输类型的对立 API 阻塞和非阻塞 Socket;基于灵便且可扩大的事件模型,能够清晰地拆散关注点;高度可定制的线程模型 - 单线程,一个或多个线程池;真正的无连贯数据报套接字反对(自 3.1 起)。使用方便,具体记录的 Javadoc,用户指南和示例;没有其余依赖项,JDK 5(Netty 3.x)或 6(Netty 4.x)就足够了。高性能,吞吐量更高,提早更低;缩小资源耗费;最小化不必要的内存复制。平安,残缺的 SSL/TLS 和 StartTLS 反对。社区沉闷,不断更新,社区沉闷,版本迭代周期短,发现的 Bug 能够被及时修复,同时,更多的新性能会被退出。Netty 常见应用场景 ...

September 25, 2020 · 5 min · jiezi

关于netty:Netty之旅四你一定看得懂的Netty客户端启动源码分析

前言后面小飞曾经解说了NIO和Netty服务端启动,这一讲是Client的启动过程。 源码系列的文章仍旧还是遵循大白话+画图的格调来解说,本文Netty源码及当前的文章版本都基于:4.1.22.Final 本篇是以NettyClient启动为切入点,带大家一步步进入Netty源码的世界。 Client启动流程揭秘1、探秘的入口:netty-client demo这里用netty-exmaple中的EchoClient来作为例子: public final class EchoClient { public static void main(String[] args) throws Exception { EventLoopGroup group = new NioEventLoopGroup(); try { Bootstrap b = new Bootstrap(); b.group(group) .channel(NioSocketChannel.class) .option(ChannelOption.TCP_NODELAY, true) .handler(new ChannelInitializer<SocketChannel>() { @Override public void initChannel(SocketChannel ch) throws Exception { ChannelPipeline p = ch.pipeline(); p.addLast(new EchoClientHandler()); } }); ChannelFuture f = b.connect(HOST, PORT).sync(); f.channel().closeFuture().sync(); } finally { group.shutdownGracefully(); } }}代码没有什么独特的中央,咱们上一篇文章时也梳理过Netty网络编程的一些套路,这里就不再赘述了。(遗记的小朋友能够查看Netty系列文章中查找~) 下面的客户端代码尽管简略, 然而却展现了Netty 客户端初始化时所需的所有内容: ...

September 23, 2020 · 5 min · jiezi

关于netty:Netty源码解析-PoolChunk与PoolSubpage内存管理

上一篇文章分享了PoolArena如何通过PoolChunk,PoolSubpage治理内存。本文则分享PoolChunk,PoolSubpage中如何分配内存。源码剖析基于Netty 4.1 首先阐明PoolChunk内存组织形式。PoolChunk的内存大小默认是16M,它将内存组织成为一颗完满二叉树。二叉树的每一层每个节点所代表的内存大小都是均等的,并且每一层节点所代表的内存大小总和加起来都是16M。每一层节点可分配内存是父节点的1/2。整颗二叉树的总层数为12,层数从0开始。 示意图如下 (下标 -> 层数,最大可调配块大小) 先看一下PoolChunk的构造函数 PoolChunk(PoolArena<T> arena, T memory, int pageSize, int maxOrder, int pageShifts, int chunkSize, int offset) { unpooled = false; this.arena = arena; this.memory = memory; this.pageSize = pageSize; this.pageShifts = pageShifts; this.maxOrder = maxOrder; this.chunkSize = chunkSize; this.offset = offset; unusable = (byte) (maxOrder + 1); log2ChunkSize = log2(chunkSize); subpageOverflowMask = ~(pageSize - 1); freeBytes = chunkSize; assert maxOrder < 30 : "maxOrder should be < 30, but is: " + maxOrder; maxSubpageAllocs = 1 << maxOrder; // Generate the memory map. memoryMap = new byte[maxSubpageAllocs << 1]; depthMap = new byte[memoryMap.length]; int memoryMapIndex = 1; for (int d = 0; d <= maxOrder; ++ d) { // move down the tree one level at a time int depth = 1 << d; for (int p = 0; p < depth; ++ p) { // in each level traverse left to right and set value to the depth of subtree memoryMap[memoryMapIndex] = (byte) d; depthMap[memoryMapIndex] = (byte) d; memoryMapIndex ++; } } subpages = newSubpageArray(maxSubpageAllocs);}unpooled: 是否应用内存池arena:该PoolChunk所属的PoolArenamemory:底层的内存块,对于堆内存,它是一个byte数组,对于间接内存,它是(jvm)ByteBuffer,但无论是哪种模式,其内存大小默认都是16M。pageSize:叶子节点大小,默认为8192,即8K。maxOrder:示意二叉树最大的层数,从0开始。默认为11。chunkSize:整个PoolChunk的内存大小,默认为16777216,即16M。offset:为了内存对齐而应用的偏移数量,默认为0。unusable:示意节点已被调配,不必了,默认为12。freeBytes:闲暇内存字节数。每个PoolChunk都要按内存使用率关联到一个PoolChunkList上,内存使用率正是通过freeBytes计算。maxSubpageAllocs:叶子节点数量,默认为2048,即2^11。 ...

September 20, 2020 · 6 min · jiezi

关于netty:Netty之旅三Netty服务端启动源码分析一梭子带走

Netty服务端启动流程源码剖析 前记哈喽,自从上篇《Netty之旅二:口口相传的高性能Netty到底是什么?》后,迟迟两周才开启明天的Netty源码系列。源码剖析的第一篇文章,下一篇我会分享客户端的启动过程源码剖析。通过源码的浏览,咱们将会晓得,Netty 服务端启动的调用链是十分长的,同时必定也会发现一些新的问题,随着咱们源码浏览的不断深入,置信这些问题咱们也会一一攻破。 废话不多说,间接上号! 一、从EchoServer示例动手 示例从哪里来?任何开源框架都会有本人的示例代码,Netty源码也不例外,如模块netty-example中就包含了最常见的EchoServer示例,上面通过这个示例进入服务端启动流程篇章。 public final class EchoServer { static final boolean SSL = System.getProperty("ssl") != null; static final int PORT = Integer.parseInt(System.getProperty("port", "8007")); public static void main(String[] args) throws Exception { // Configure SSL. final SslContext sslCtx; if (SSL) { SelfSignedCertificate ssc = new SelfSignedCertificate(); sslCtx = SslContextBuilder.forServer(ssc.certificate(), ssc.privateKey()).build(); } else { sslCtx = null; } // 1. 申明Main-Sub Reactor模式线程池:EventLoopGroup // Configure the server. EventLoopGroup bossGroup = new NioEventLoopGroup(1); EventLoopGroup workerGroup = new NioEventLoopGroup(); // 创立 EchoServerHandler 对象 final EchoServerHandler serverHandler = new EchoServerHandler(); try { // 2. 申明服务端启动疏导器,并设置相干属性 ServerBootstrap b = new ServerBootstrap(); b.group(bossGroup, workerGroup) .channel(NioServerSocketChannel.class) .option(ChannelOption.SO_BACKLOG, 100) .handler(new LoggingHandler(LogLevel.INFO)) .childHandler(new ChannelInitializer<SocketChannel>() { @Override public void initChannel(SocketChannel ch) throws Exception { ChannelPipeline p = ch.pipeline(); if (sslCtx != null) { p.addLast(sslCtx.newHandler(ch.alloc())); } //p.addLast(new LoggingHandler(LogLevel.INFO)); p.addLast(serverHandler); } }); // 3. 绑定端口即启动服务端,并同步期待 // Start the server. ChannelFuture f = b.bind(PORT).sync(); // 4. 监听服务端敞开,并阻塞期待 // Wait until the server socket is closed. f.channel().closeFuture().sync(); } finally { // 5. 优雅地敞开两个EventLoopGroup线程池 // Shut down all event loops to terminate all threads. bossGroup.shutdownGracefully(); workerGroup.shutdownGracefully(); } }}[代码行18、19]申明Main-Sub Reactor模式线程池:EventLoopGroup创立两个 EventLoopGroup 对象。其中,bossGroup用于服务端承受客户端的连贯,workerGroup用于进行客户端的 SocketChannel 的数据读写。 ...

September 15, 2020 · 9 min · jiezi