問道Netty。持續更新。。。

概念

Zero Copy

  • 0拷貝,速度快
  • 操作數據時, 不需要將數據 buffer 從一個內存區域拷貝到另一個內存區域. 因爲少了一次內存的拷貝, 因此 CPU 的效率就得到的提升.
  • 在 OS 層面上的 Zero-copy 通常指避免在 用戶態(User-space) 與 內核態(Kernel-space) 之間來回拷貝數據. 例如 Linux 提供的 mmap 系統調用, 它可以將一段用戶空間內存映射到內核空間, 當映射成功後, 用戶對這段內存區域的修改可以直接反映到內核空間; 同樣地, 內核空間對這段區域的修改也直接反映用戶空間. 正因爲有這樣的映射關係, 我們就不需要在 用戶態(User-space) 與 內核態(Kernel-space) 之間拷貝數據, 提高了數據傳輸的效率.

而需要注意的是, Netty 中的 Zero-copy 與上面我們所提到到 OS 層面上的 Zero-copy 不太一樣, Netty的 Zero-coyp 完全是在用戶態(Java 層面)的, 它的 Zero-copy 的更多的是偏向於 優化數據操作 這樣的概念.

實操

代碼

服務器

新建Maven項目:
在這裏插入圖片描述
添加插件:

		<plugin>
          <groupId>org.codehaus.mojo</groupId>
          <artifactId>exec-maven-plugin</artifactId>
          <version>1.6.0</version>
          <executions>
            <execution>
<!--              // 可以添加對應的執行階段-->
              <phase>test</phase>
<!--              ...-->
              <goals>
<!--                // 指定來的 goal爲java,表示運行java程序-->
                <goal>java</goal>
              </goals>
            </execution>
          </executions>
          <configuration>
<!--            // 指定了運行的main class-->
            <mainClass>com.cc.netty.server.EchoServer</mainClass>
<!--            // 執行運行 main class的參數-->
<!--            // 其實就是傳入main方法的String[]-->
            <arguments>
              <argument>argument1</argument>
<!--              ...-->
            </arguments>
<!--            // 運行java的程序的系統參數-->
            <systemProperties>
              <systemProperty>
                <key>myproperty</key>
                <value>myvalue</value>
              </systemProperty>
<!--              ...-->
            </systemProperties>
          </configuration>
        </plugin>

執行mvn exec:java:
報錯:在這裏插入圖片描述
說明插件裏的參數有問題。

  • 不好用,用下面的簡單代碼:
  • 服務端:
package com.cc.netty.test;


import io.netty.bootstrap.ServerBootstrap;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
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 io.netty.handler.timeout.ReadTimeoutHandler;
import io.netty.util.CharsetUtil;
import io.netty.util.ReferenceCountUtil;

import java.util.Scanner;

/**
 * netty服務端
 * NioEventLoopGroup是一個處理I/O操作的多線程事件循環。
 * Netty爲不同類型的傳輸提供了各種EventLoopGroup實現。
 * 我們在這個例子中實現了一個服務器端應用程序,
 * 因此將使用兩個NioEventLoopGroup。第一個通常被稱爲“boss”,接受傳入的連接。第二個通常稱爲“worker”,
 * 在boss接受連接並將接受的連接註冊到worker之後,處理接受連接的流量。使用多少線程以及如何將它們映射到創建的通道取決於EventLoopGroup實現,
 * 甚至可以通過構造函數進行配置。
 * <p>
 * ServerBootstrap是一個設置服務器的助手類。您可以直接使用通道設置服務器。但是,請注意這是一個冗長的過程,在大多數情況下您不需要這樣做。
 * 在這裏,我們指定使用NioServerSocketChannel類,該類用於實例化一個新通道以接受傳入連接。
 * <p>
 * 這裏指定的處理程序總是由新接受的通道計算。ChannelInitializer是用於幫助用戶配置新通道的特殊處理程序。
 * 您很可能希望通過添加一些處理程序(如DiscardServerHandler)來實現您的網絡應用程序,來配置新通道的ChannelPipeline。
 * 隨着應用程序變得複雜,您可能會向管道中添加更多的處理程序,並最終將這個匿名類提取到頂級類中。
 * <p>
 * 您還可以設置特定於通道實現的參數。我們正在編寫TCP/IP服務器,因此我們可以設置套接字選項,如tcpNoDelay和keepAlive。
 * 請參閱ChannelOption的apidocs和特定的ChannelConfig實現,以獲得受支持的ChannelOptions的概述。
 * <p>
 * 你注意到option()和childOption()了嗎?option()用於接收傳入連接的NioServerSocketChannel。
 * childOption()用於父服務器通道接受的通道,在本例中是NioServerSocketChannel。
 * 我們現在準備好出發了。剩下的就是綁定到端口並啓動服務器。在這裏,我們綁定到機器中所有NICs(網絡接口卡)的端口8080。
 * 現在,您可以任意次數地調用bind()方法(使用不同的綁定地址)。
 * <p>
 * telnet 可以測試服務器是否工作
 * telnet localhost 8080
 */
public class NettyServer {
    public static void main(String[] args) throws Exception {
        new NettyServer().run(8666);
    }

    //新建一個netty服務器
    public void run(int port) throws Exception {
        //NioEventLoopGroup是一個處理I/O操作的多線程循環
        EventLoopGroup bossGroup = new NioEventLoopGroup();
        EventLoopGroup workGroup = new NioEventLoopGroup();
        System.out.println("準備運行端口:" + port);
        try {
            //服務器的設置助手類
            ServerBootstrap serverBootstrap = new ServerBootstrap();
            serverBootstrap = serverBootstrap.group(bossGroup, workGroup)
                    .channel(NioServerSocketChannel.class)
                    .option(ChannelOption.SO_BACKLOG, 128)
                    .childOption(ChannelOption.SO_KEEPALIVE, true)
                    //這裏指定的處理程序總是由新接受的通道計算。
                    .childHandler(new ChildChannelHandler());
            //綁定端口,同步等待成功
            ChannelFuture future = serverBootstrap.bind(port).sync();
            //等待服務監聽端口關閉
            future.channel().closeFuture().sync();
        } finally {
            //退出,釋放線程資源
            workGroup.shutdownGracefully();
            bossGroup.shutdownGracefully();
        }
    }
}

//ChannelInitializer是用於幫助用戶配置新通道的特殊處理程序
class ChildChannelHandler extends ChannelInitializer<SocketChannel> {

    //請求到達後調用
    protected void initChannel(SocketChannel socketChannel) throws Exception {
//        ByteBuf byteBuf= Unpooled.copiedBuffer("$".getBytes());
//        socketChannel.pipeline().addLast(new DelimiterBasedFrameDecoder(1024,byteBuf));
        socketChannel.pipeline().addLast(new StringDecoder());//進行字符串的編解碼設置
        socketChannel.pipeline().addLast(new StringEncoder());
        socketChannel.pipeline().addLast(new ReadTimeoutHandler(60));//設置超時時間
        socketChannel.pipeline().addLast(new DiscardServerHandler());
    }
}

/**
 * 服務器類型設置
 **/
class DiscardServerHandler extends ChannelHandlerAdapter {
    @Override
    //只要接收到數據,就會調用channelRead()方法
    public void channelRead(ChannelHandlerContext ctx, Object msg) {

        try {
            //ByteBuf是一個引用計數的對象,必須通過release()方法顯式地釋放它
            System.out.println(String.format("%s === %s","收到信息",msg));
//            ByteBuf in = (ByteBuf) msg;
            System.out.println("傳輸內容是");
//            System.out.println(in.toString(CharsetUtil.UTF_8));
            //返回信息
            ByteBuf resp = Unpooled.copiedBuffer("服務端收到信息,ack$".getBytes());
            ctx.writeAndFlush(resp);
//            Scanner scanner = Scanner.
        } finally {
            System.out.println(msg);
            ReferenceCountUtil.release(msg);
        }
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        // 出現異常就關閉
        cause.printStackTrace();
        ctx.close();
    }

}
  • 客戶端:
package com.cc.netty.test;


import io.netty.bootstrap.Bootstrap;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.codec.DelimiterBasedFrameDecoder;
import io.netty.util.CharsetUtil;
import io.netty.util.ReferenceCountUtil;

/**
 * netty 編寫的客戶端
 */
public class TimeClient {
    public static void main(String[] args) throws Exception {
        new TimeClient().connect(8666, "localhost");
    }

    public void connect(int port, String host) throws Exception {
        //配置客戶端
        System.out.println("連接=>" + host + ":" + port);
        EventLoopGroup eventLoopGroup = new NioEventLoopGroup();
        try {
            Bootstrap bootstrap = new Bootstrap();
            bootstrap.group(eventLoopGroup).channel(NioSocketChannel.class)
                    .option(ChannelOption.TCP_NODELAY, true)
                    .handler(new ChannelInitializer<SocketChannel>() {
                        protected void initChannel(SocketChannel socketChannel) throws Exception {
                            ByteBuf byteBuf = Unpooled.copiedBuffer("$".getBytes());
                            socketChannel.pipeline().addLast(new DelimiterBasedFrameDecoder(1024, byteBuf));
                            socketChannel.pipeline().addLast(new TimeClientHandler());
                        }
                    });
            //綁定端口,同步等待成功
            ChannelFuture future = bootstrap.connect(host, port).sync();
            //等待服務監聽端口關閉
            future.channel().closeFuture().sync();
        } finally {
            //優雅退出,釋放線程資源
            eventLoopGroup.shutdownGracefully();
        }
    }
}

class TimeClientHandler extends ChannelHandlerAdapter {
    private byte[] req;

    public TimeClientHandler() {
        req = "中國必勝!武漢加油!".getBytes();
//        req = "$tmb00035ET3318/08/22 11:5704026.75,027.31,20.00,20.00$".getBytes();
    }

    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        ByteBuf message = null;
        for (int i = 0; i < 5; i++) {
            message = Unpooled.buffer(req.length);
            message.writeBytes(req);
            ctx.writeAndFlush(message);
        }
    }

    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) {
        try {
            ByteBuf in = (ByteBuf) msg;
            System.out.println(in.toString(CharsetUtil.UTF_8));
        } finally {
            ReferenceCountUtil.release(msg);
        }
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        // 出現異常就關閉
        cause.printStackTrace();
        ctx.close();
    }

}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章