主頁 > 後端開發 > 【Netty實戰】1~3章學習筆記

【Netty實戰】1~3章學習筆記

2023-06-01 07:38:10 後端開發

1. Netty總體結構

1.1 Netty簡介

? Netty是一款用于創建高性能網路應用程式的高級框架,它的基于 Java NIO 的異步的和事件驅動的實作,保證了高負載下應用程式性能的最大化和可伸縮性,

? 其次,Netty 也包含了一組設計模式,將應用程式邏輯從網路層解耦,簡化了開發程序,同時也最大限度地提高了可測驗性、模塊化以及代碼的可重用性,

1.2 Netty組件

Netty主要包含以下幾個組件:

img

1.2.1 Netty 網路抽象的代表組件

Channel

? 個人理解:Channel類似對Socket的封裝,代表一個網路連接(類似WebServer專案中的http_conn類),可以進行讀寫操作,

? 以往基本的 I/O 操作(bind()、connect()、read()和 write())依賴于底層網路傳輸所提供的原語,而Netty 的 Channel 介面所提供的 API,大大地降低了直接使用 Socket 類的復雜性,

不同協議、不同的阻塞型別的連接都有不同的 Channel 型別與之對應,常用的 Channel 型別:

  • NioSocketChannel,NIO的客戶端 TCP Socket 連接,

  • NioServerSocketChannel,NIO的服務器端 TCP Socket 連接,

  • NioDatagramChannel, UDP 連接,

  • NioSctpChannel,客戶端 Sctp 連接,

  • NioSctpServerChannel,Sctp 服務器端連接,

    這些通道涵蓋了 UDP 和 TCP 網路 IO 以及檔案 IO,

EventLoop

? 有了 Channel 連接服務,連接之間可以訊息流動,如果服務器發出的訊息稱作“出站”訊息,服務器接受的訊息稱作“入站”訊息,那么訊息的“出站”/“入站”就會產生事件(Event)

? 例如:連接已激活;資料讀取;用戶事件;例外事件;打開鏈接;關閉鏈接等等,

? 有了事件,就需要一個機制去監控和協調事件,這個機制(組件)就是EventLoop

? Netty 通過觸發事件將 Selector 從應用程式中抽象出來,消除了所有本來將需要手動撰寫的派發代碼,在內部,將會為每個 Channel 分配一個 EventLoop,用以處理所有事件,包括:

  • ? 注冊感興趣的事件;
  • ? 將事件派發給 ChannelHandler;
  • ? 安排進一步的動作,

? 每個 Channel 都會被分配到一個 EventLoop,一個 EventLoop 可以服務于多個 Channel,

? 每個 EventLoop 會占用一個 Thread,同時這個 Thread 會處理 EventLoop 上面發生的所有 IO 操作和事件,

? 個人理解,可以把EventLoop看作一個執行緒,而EventLoopGroup就是一個執行緒池,

? EventLoopGroup通過負載均衡演算法選擇某個EventLoop去系結Channel,

? EventLoop通過其Selector選擇器(類似epoll)去監聽其系結的Channel上發生的事件,并根據事件型別的不同,交給相應的ChannelHandler處理,

? 在Netty的執行緒模型為基于事件驅動的 Reactor 模型,可以通過配置實作幾種不同型別的Reactor模型,如 單執行緒模型、多執行緒模型和主從執行緒模型等,因此可能用到數量不同的EventLoopGroup執行緒池,如主從執行緒模型就會用到兩個EventLoopGroup,一個用于監聽從客戶端發來的連接,一個用于讀寫資料、處理業務等,

? EventLoop 定義了 Netty 的核心抽象,用于處理連接的生命周期中所發生的事件,Channel、EventLoop、Thread 以及 EventLoopGroup 之間的關系如下圖所示:

這些關系是:

  • 一個 EventLoopGroup 包含一個或者多個 EventLoop
  • 一個 EventLoop 在它的生命周期內只和一個 Thread 系結;
  • 所有由 EventLoop 處理的 I/O 事件都將在它專有的 Thread 上被處理;
  • 一個 Channel 在它的生命周期內只注冊于一個 EventLoop
  • 一個 EventLoop 可能會被分配給一個或多個 Channel

注意,在這種設計中,一個給定 Channel 的 I/O 操作都是由相同的 Thread 執行的,實際上消除了對于同步的需要,

? EventLoopGroup類似于一個執行緒池,其中放著多個EventLoop,每個EventLoop都是一個單獨的執行緒,并且每個EventLoop都有一個Selector,用于監聽Channel狀態的變化(類似epoll),

? EventLoop監聽Channel的實作原理如下圖所示:

? 個人思考:為了便于理解,可以視為每個作業執行緒(EventLoop)都有屬于自己的epoll(Selector),

? 相比之下,之前的WebServer專案所有執行緒共用一個epoll:監聽客戶端連接的主執行緒,以及【決議請求、產生回應】的作業執行緒都共用一個epoll,

? 在web Server中主執行緒呼叫epoll_wait等待客戶端的連接,對于新到的連接呼叫accept系統呼叫生成一個用于通信的socket檔案描述符,并把這個檔案描述符與一個類似Channelhttp連接物件(自己封裝的一個類)進行系結,然后注冊進epoll,
當有網路中有資料到達這個用于通信的socket檔案描述符的讀緩沖區,或服務器已經正確生成response并注冊了寫就緒事件時,epoll會將這個檔案描述符拿出,交給一個作業執行緒執行相應的操作,由于作業執行緒與主執行緒共用一個epoll,因此一個作業執行緒也只能處理一個連接

? 在Netty中:如果使用主從執行緒模型,此時需要兩個EventLoopGroup,第一組將只包含一個 ServerChannel,代表服務器自身的已系結到某個本地埠的正在監聽的套接字,而第二組將包含所有已創建的用來處理傳入客戶端連接(對于每個服務器已經接受的連接都有一個)的 Channel(類似用于通信的socket檔案描述符),如下圖所示,
與 ServerChannel 相關聯的 EventLoopGroup 將分配一個負責為傳入的連接創建Channel 的EventLoop,一旦連接被接受,第二個EventLoopGroup 就會給它的 Channel分配一個 EventLoop,
與WebServer專案不同的是,在該執行緒模型中,Netty中每個作業執行緒EventLoop都有自己的Selector(可理解為epoll),因此一個EventLoop可以處理多個Channel

? 為方便理解,貼上書中的兩段示例代碼:

  • 未使用 Netty 的異步網路編程
//總體流程與WebServer專案中的main.cpp一致
public class PlainNioServer
{
    public void serve(int port) throws IOException
    {
        ServerSocketChannel serverChannel = ServerSocketChannel.open();
        serverChannel.configureBlocking(false);
        ServerSocket ssocket = serverChannel.socket();
        InetSocketAddress address = new InetSocketAddress(port);
        //將服務器系結到選定的埠
        ssocket.bind(address);
        //打開Selector來處理Channel,只有一個Selector
        Selector selector = Selector.open();
        //將ServerSocket注冊到Selector以接收連接
        serverChannel.register(selector, SelectionKey.OP_ACCEPT);
        final ByteBuffer msg = ByteBuffer.wrap("Hi!\r\n".getBytes());
        for (;;)
        {
            try
            {
                //阻塞,等待需要處理的新事件
                selector.select();
            }
            catch (IOException ex)
            {
                ex.printStackTrace();
			   // handle exception
                break;
            }
            //獲取所有接收事件的SelectionKey 實體
            Set<SelectionKey> readyKeys = selector.selectedKeys();
            Iterator<SelectionKey> iterator = readyKeys.iterator();
            while (iterator.hasNext())
            {
                SelectionKey key = iterator.next();
                iterator.remove();
                try
                {
                    //檢查事件是否是一個新的已經就緒可以被接受的連接(相當于判斷從epoll中取出的socketfd是否為當前服務端的fd)
                    if (key.isAcceptable())
                    {
                        ServerSocketChannel server =
                            (ServerSocketChannel)key.channel();
                        //accept客戶端
                        SocketChannel client = server.accept();
                        client.configureBlocking(false);
                        //將客戶端socket注冊到Selector選擇器
                        client.register(selector, SelectionKey.OP_WRITE |
                                        SelectionKey.OP_READ, msg.duplicate());
                        System.out.println(
                            "Accepted connection from " + client);
                    }
                    //檢查之前接收的socket是否已經準備好寫資料
                    if (key.isWritable())
                    {
                        SocketChannel client =
                            (SocketChannel)key.channel();
                        ByteBuffer buffer =
                            (ByteBuffer)key.attachment();
                        while (buffer.hasRemaining())
                        {
                            //寫資料到socket
                            if (client.write(buffer) == 0)
                            {
                                break;
                            }
                        }
                        //關閉連接
                        client.close();
                    }
                }
                catch (IOException ex)
                {
                    key.cancel();
                    try
                    {
                        key.channel().close();
                    }
                    catch (IOException cex)
                    {
						// ignore on close
                    }
                }
            }
        }
    }
}

  • 使用 Netty 的異步網路處理
public class NettyNioServer
{
    public void server(int port) throws Exception
    {
        final ByteBuf buf = Unpooled.copiedBuffer("Hi!\r\n",
        Charset.forName("UTF-8"));
        //為非阻塞模式使用NioEventLoopGroup
        //Netty 內置了一些可開箱即用的傳輸,不僅有NIO,還有Netty自己實作的epoll等
        EventLoopGroup group = new NioEventLoopGroup();
        try {
            //創建ServerBootstrap
            ServerBootstrap b = new ServerBootstrap();
            //此處沒有使用Reactor主從執行緒模型,只用了一個EventLoopGroup執行緒池
            b.group(group).channel(NioServerSocketChannel.class)
            .localAddress(new InetSocketAddress(port))
             //指定 ChannelInitializer,對于每個已接受的連接都呼叫它
            .childHandler(new ChannelInitializer<SocketChannel>()
            {
                @Override
                public void initChannel(SocketChannel ch)
                throws Exception
                {
                    //添加 ChannelInboundHandlerAdapter 以接收和處理事件
                    ch.pipeline().addLast(
                        new ChannelInboundHandlerAdapter()
                    {
                        @Override
                        public void channelActive(
                            ChannelHandlerContext ctx) throws Exception
                        {
                            //將訊息寫到客戶端,并添加ChannelFutureListener,以便訊息一被寫完就關閉連接
                            ctx.writeAndFlush(buf.duplicate())
                            .addListener(
                                ChannelFutureListener.CLOSE);
                        }
                    });
                }
            });
            //系結服務器以接受連接
            ChannelFuture f = b.bind().sync();
            f.channel().closeFuture().sync();
        } finally {
            //釋放所有的資源
            group.shutdownGracefully().sync();
        }
    }
}

ChannelFuture

? Netty 中所有的 I/O 操作都是異步的,因為一個操作可能不會立即回傳,所以我們需要一種用于在之后的某個時間點確定其結果的方法,為此,Netty 提供了ChannelFuture 介面,其 addListener()方法注冊了一個ChannelFutureListener,以便在某個操作完成時(無論是否成功)得到通知,

? 說人話就是ChannelFuture用于在異步操作執行完成后,記錄結果并發送通知,

1.2.2 管理資料流以及執行應用程式處理邏輯的組件

ChannelHandler

? 從應用程式開發人員的角度來看,Netty 的主要組件是 ChannelHandler,它充當了所有處理入站和出站資料的應用程式邏輯的容器,

? ChannelHandler 的方法是由網路事件(其中術語“事件”的使用非常廣泛)觸發的,事實上,ChannelHandler 可專門用于幾乎任何型別的動作,例如將資料從一種格式轉換為另外一種格式,或者處理轉換程序中所拋出的例外,

ChannelPipeline

? 將多個ChannelHandler串聯起來,形成一個處理鏈,用于處理Channel中的事件,當 Channel 被創建時,它會被自動地分配到它專屬的 ChannelPipeline

? ChannelHandler安裝到 ChannelPipeline 中的程序如下所示(流程與上面Netty 的異步網路處理代碼一致):

  • 一個ChannelInitializer的實作被注冊到了ServerBootstrap中 ;
  • ChannelInitializer.initChannel()方法被呼叫時,ChannelInitializer將在 ChannelPipeline 中安裝一組自定義的 ChannelHandler;
  • ChannelInitializer 將它自己從 ChannelPipeline 中移除,

? ChannelHandler 的觸發順序是根據 pipeline().addLast() 的注冊順序來確定的,在 Netty 中,ChannelPipeline 是一個事件處理的鏈表,每個 ChannelHandler 負責處理特定的事件,當一個事件被觸發時,Netty 會按照 ChannelPipeline 中 ChannelHandler 的添加順序依次呼叫它們的事件處理方法,因此,如果想要控制事件處理的順序,可以通過添加 ChannelHandler 的順序來實作,

? 當ChannelHandler被添加到ChannelPipeline時,它將會被分配一個ChannelHandlerContext,通過使用作為引數傳遞到每個方法的 ChannelHandlerContext,事件可以被傳遞給當前ChannelHandler鏈中的下一個 ChannelHandler,因為你有時會忽略那些不感興趣的事件,所以 Netty提供了抽象基類ChannelInboundHandlerAdapter ChannelOutboundHandlerAdapters,通過呼叫 ChannelHandlerContext 上的對應方法,每個都提供了簡單地將事件傳遞給下一個ChannelHandler的方法的實作,隨后,你可以通過重寫你所感興趣的那些方法來擴展這些類,

? 個人理解:可以將ChannelHandlerContext類比為Linux中的socket檔案描述符,在Netty框架中,ChannelHandlerContext是一個背景關系物件,用于表示一個通道的背景關系資訊,包括該通道的事件處理器、通道的配置資訊以及與該通道相關的一些狀態資訊,類似于Linux中的socket檔案描述符,它可以用于發送和接收資料,以及進行其他與通道相關的操作,但是需要注意的是,ChannelHandlerContext并不是一個作業系統底層的檔案描述符,而是Netty框架中的一個抽象概念,

具體實體會在后面給出,

Bootstrap和ServerBootstrap

? Bootstrap和ServerBootstrap分別用于啟動客戶端和服務端,

? 在Netty中,有兩種型別的引導:一種用于客戶端(簡單地稱為 Bootstrap),而另一種(ServerBootstrap)用于服務器,它們都包含EventLoopGroup和一個ChannelPipeline,用于管理連接和處理事件,無論你的應用程式使用哪種協議或者處理哪種型別的資料,唯一決定它使用哪種引導類的是它是作為一個客戶端還是作為一個服務器,

1.3 Netty執行流程

? 1. 創建一個EventLoopGroup:EventLoopGroup是Netty中的一個執行緒池,用于處理所有的I/O操作,

? 2.創建一個ServerBootstrap(客戶端為Bootstrap):ServerBootstrap是Netty中用于創建服務器的類,它包含了一些配置項,如埠號、協議等,

? 3.配置ServerBootstrap:通過ServerBootstrap的一些方法,如group()、channel()、option()、handler()等,來配置ServerBootstrap,

? 4.系結埠號:呼叫ServerBootstrap的bind()方法來系結埠號,啟動服務器,

? 5.創建一個ChannelPipeline:當一個客戶端連接到服務器時,Netty會為該連接創建一個Channel物件,每個Channel物件都包含一個ChannelPipeline,ChannelPipeline是一個事件處理器鏈,它包含了一系列的ChannelHandler,

? 6.添加ChannelHandler:通過呼叫ChannelPipeline的addLast()方法,將自定義的ChannelHandler添加到ChannelPipeline中,

? 7.處理事件:當有事件發生時,Netty會將事件封裝成一個物件,并將該物件傳遞給ChannelPipeline中的第一個ChannelHandler,然后由ChannelHandler處理事件,或將事件傳遞給下一個ChannelHandler,

? 8.發送回應:當ChannelHandler處理完事件后,可以通過ChannelHandlerContext的write()方法將回應發送給客戶端,

? 9.關閉連接:當客戶端關閉連接時,Netty會將該事件封裝成一個物件,并將該物件傳遞給ChannelPipeline中的最后一個ChannelHandler,然后由ChannelHandler處理該事件,釋放資源,

2. Netty整體架構

? Netty使用了典型的三層網路架構,Reactor 通信調度層 -> 職責鏈 PipeLine -> 業務邏輯處理層

? Reactor層主要監聽網路的讀寫和連接操作,負責將網路層的資料 讀取到記憶體緩沖區中,然后觸發各種網路事件,例如連接創建、連接激活、讀事 件、寫事件等等,將這些事件觸發到 PipeLine 中,由 PipeLine 充當的職責鏈來 進行后續的處理,涉及到的類包括:Reactor 執行緒 NioEventLoop 以及其父類、NioSocketChannel/NioServerSocketChannel 以及其父 類、ByteBuffer 以及由其衍生出來的各種 Buffer、Unsafe 以及其衍生出的各種內 部類等

? Pipeline層負責事件在職責鏈中有序的傳播,職責鏈可以選擇監聽和處理自己關心的事件,它可以攔截處理和向 后 / 向前傳播事件,不同的應用的 Handler 節點的功能也不同,通常情況下,往往會開發編解碼 Hanlder 用于訊息的編解碼,它可以將外部的協議訊息轉換成內部 的 POJO 物件,這樣上層業務側只需要關心處理業務邏輯即可,不需要感知底層 的協議差異和執行緒模型差異,實作了架構層面的分層隔離,

? Service層有純粹的業務邏輯 處理,例如訂單處理;也有應用層協議管理,例如 HTTP 協議、FTP 協議等(在書中還未看到),

img

3. Netty實體

Echo服務器與客戶端的主要功能為:服務器接收到客戶端發送的請求,將訊息原封不動回傳,如下圖所示:

所有的 Netty 服務器都需要以下兩部分,

  • 至少一個 ChannelHandler—該組件實作了服務器對從客戶端接收的資料的處理,即它的業務邏輯,
  • 引導—這是配置服務器的啟動代碼,至少,它會將服務器系結到它要監聽連接請求的埠上,

在本小節的剩下部分,我們將描述 Echo 服務器的業務邏輯以及引導代碼,

3.1 maven結構

	<groupId>org.example</groupId>
    <artifactId>netty-demo-1</artifactId>
    <packaging>pom</packaging>
    <version>1.0-SNAPSHOT</version>
    <modules>
        <module>server</module>
        <module>client</module>
    </modules>
	<!-- ...省略引入的依賴及插件等 -->

3.2 server模塊

? server模塊包含兩個.java檔案,其中EchoServer為服務器的主流程,EchoServerHandler為業務邏輯,

具體作用如注釋所示:

//EchoServerHandler
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.util.CharsetUtil;
/*
* @Sharable:標示一個ChannelHandler 可以被多個 Channel 安全地共享
* EchoServerHandler:處理業務邏輯
* */
@ChannelHandler.Sharable
public class EchoServerHandler extends ChannelInboundHandlerAdapter {   //繼承 ChannelInboundHandlerAdapter 類,以定義回應入站事件的方法
    @Override
    public void  channelRead(ChannelHandlerContext ctx, Object msg)
    {
        //客戶端寫來的訊息會被放入msg中
        ByteBuf in = (ByteBuf) msg;
        System.out.println("Server received: " + in.toString(CharsetUtil.UTF_8));
        //將收到的msg原封不動寫回去
        ctx.write(in);
    }

    @Override
    public void channelReadComplete(ChannelHandlerContext ctx)
    {
        //ctx.writeAndFlush(Unpooled.EMPTY_BUFFER) 表示將一個空的緩沖區寫入到當前的 ChannelHandlerContext 中,并將其發送給客戶端,
        //addListener(ChannelFutureListener.CLOSE) 表示在發送完成后添加一個監聽器,當發送完成后會自動關閉當前的連接,
        ctx.writeAndFlush(Unpooled.EMPTY_BUFFER).addListener(ChannelFutureListener.CLOSE);
    }

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

//EchoServer
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.EventLoop;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;

import java.net.InetSocketAddress;

public class EchoServer {
    private final int port;

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

    public static void main(String[] args)  throws Exception
    {
        if(args.length != 1)
        {
            //設定埠值(如果埠引數的格式不正確,則拋出一個NumberFormatException)
            System.err.println("Usage: " + EchoServer.class.getSimpleName() + " <port>");
            int port = Integer.parseInt(args[0]);
            //呼叫服務器的start方法
            new EchoServer(port).start();
        }
    }

    public void start() throws Exception
    {
        final EchoServerHandler serverHandler = new EchoServerHandler();
        //創建EventLoopGroup
        EventLoopGroup group = new NioEventLoopGroup();
        try {
            //創建ServerBootstrap
            ServerBootstrap b = new ServerBootstrap();
            b.group(group)
                    .channel(NioServerSocketChannel.class)  //適用于NIO傳輸的Channel型別
                    .localAddress(new InetSocketAddress(port))  //設定socket埠和地址,服務器系結該地址監聽新的請求

                    //此處當一個新的連接被接受時,一個新的子 Channel 將會被創建
                    //而 ChannelInitializer 將會把一個你的EchoServerHandler 的實體添加到該 Channel 的 ChannelPipeline 中,
                    //如EchoServerHandler類所示,這個 ChannelHandler 將會收到有關入站訊息的通知
                    .childHandler(new ChannelInitializer<SocketChannel>(){ //此處SocketChannel是netty的不是nio的
                        //添加一個EchoServerHandler 到子Channel的 ChannelPipeline
                        @Override
                        public void initChannel(SocketChannel ch)
                                throws Exception {
                            ch.pipeline().addLast(serverHandler);
                        }
                    });
            //異步地系結服務器,呼叫sync()方法阻塞等待,直到系結完成
            ChannelFuture f = b.bind().sync();
            //獲取 Channel 的CloseFuture,并且阻塞當前執行緒直到它完成
            f.channel().closeFuture().sync();

        }
        finally {
            //關閉 EventLoopGroup,釋放所有的資源
            group.shutdownGracefully().sync();
        }
    }
}

3.3 client模塊

? client模塊同樣包含兩個.java檔案,其中EchoClient為客戶端的主流程,EchoClientHandler為業務邏輯,

//EchoClientHandler
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.util.CharsetUtil;

@ChannelHandler.Sharable    //標記該類的實體可以被多個 Channel 共享
public class EchoClientHandler extends SimpleChannelInboundHandler<ByteBuf>
{
        /*
           channelActive()——在到服務器的連接已經建立之后將被呼叫;
                這確保了資料將會被盡可能快地寫入服務器,其在這個場景下是一個編碼了字串"Netty rocks!"的位元組緩沖區

           channelRead0() —當從服務器接收到一條訊息時被呼叫;
                需要注意的是,由服務器發送的訊息可能會被分塊接收,
                也就是說,如果服務器發送了 5 位元組,那么不能保證這 5 位元組會被一次性接收,
                即使是對于這么少量的資料,channelRead0()方法也可能會被呼叫兩次,第一次使用一個持有 3 位元組的 ByteBuf(Netty 的位元組容器),第二次使用一個持有 2 位元組的 ByteBuf,

           exceptionCaught()——在處理程序中引發例外時被呼叫,
         */
        @Override
        public void channelActive(ChannelHandlerContext ctx)
        {
            /*
            Netty 中的 Unpooled 是一個工具類,用于創建不需要池化的 ByteBuf 實體,
            ByteBuf 是 Netty 中的緩沖區型別,用于存盤資料,
            與池化的 ByteBuf 實體不同,不需要池化的 ByteBuf 實體是一次性的,使用完后會被釋放,
            因此,如果你需要創建一些臨時的 ByteBuf 實體,可以使用 Unpooled 來創建,
            例如,如果你需要將一個字串轉換為 ByteBuf,
            可以使用 Unpooled.copiedBuffer(str, Charset.defaultCharset()) 方法來創建一個不需要池化的 ByteBuf 實體,
             */
            ctx.writeAndFlush(Unpooled.copiedBuffer("Netty rocks!", CharsetUtil.UTF_8));
        }

        @Override
        protected void channelRead0(ChannelHandlerContext channelHandlerContext, ByteBuf in) throws Exception {
            System.out.println("Client received: " + in.toString(CharsetUtil.UTF_8));
        }

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

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

import java.net.InetSocketAddress;

public class EchoClient {
    private final String host;
    private final int port;

    public EchoClient(String host,int port)
    {
        this.host = host;
        this.port = port;
    }

    public void start() throws Exception
    {
        EventLoopGroup group = new NioEventLoopGroup();
        try
        {
            Bootstrap b = new Bootstrap();
            //指定 EventLoopGroup 以處理客戶端事件;需要適用于 NIO 的實作
            b.group(group)
                    //適用于NIO傳輸的Channel型別,注意不是NioServerSocketChannel
                    .channel(NioSocketChannel.class)
                    //設定服務器的InetSocketAddress
                    .remoteAddress(new InetSocketAddress(host,port))
                    //在創建Channel時,向 ChannelPipeline中添加一個 EchoClientHandler 實體
                    .handler(new ChannelInitializer<SocketChannel>() {
                       @Override
                       public void initChannel(SocketChannel ch) throws Exception
                       {
                           ch.pipeline().addLast(new EchoClientHandler());
                       }
                    });
            ChannelFuture f = b.connect().sync();
            f.channel().closeFuture().sync();   //阻塞,直到 Channel 關閉
        }
        finally {
            //關閉執行緒池并且釋放所有的資源
            group.shutdownGracefully().sync();
        }
    }

    public static void main(String[] args) throws Exception
    {
        if(args.length != 2)
        {
            System.err.println(
                    "Usage: " + EchoClient.class.getSimpleName() +  "<host><port>"
            );
            return;
        }

        String host = args[0];
        int port = Integer.parseInt(args[1]);
        new EchoClient(host,port).start();
    }
}

轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/553936.html

標籤:Java

上一篇:Spring MVC官方檔案學習筆記(二)之DispatcherServlet

下一篇:返回列表

標籤雲
其他(160069) Python(38189) JavaScript(25469) Java(18168) C(15235) 區塊鏈(8268) C#(7972) AI(7469) 爪哇(7425) MySQL(7219) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5873) 数组(5741) R(5409) Linux(5344) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4580) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2434) ASP.NET(2403) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) .NET技术(1979) 功能(1967) Web開發(1951) HtmlCss(1950) C++(1928) python-3.x(1918) 弹簧靴(1913) xml(1889) PostgreSQL(1879) .NETCore(1863) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 【C++】Microsoft C++、C 和匯編程式檔案

    ......

    uj5u.com 2020-09-10 00:57:23 more
  • 例外宣告

    相比于斷言適用于排除邏輯上不可能存在的狀態,例外通常是用于邏輯上可能發生的錯誤。 例外宣告 Item 1:當函式不可能拋出例外或不能接受拋出例外時,使用noexcept 理由 如果不打算拋出例外的話,程式就會認為無法處理這種錯誤,并且應當盡早終止,如此可以有效地阻止例外的傳播與擴散。 示例 //不可 ......

    uj5u.com 2020-09-10 00:57:27 more
  • Codeforces 1400E Clear the Multiset(貪心 + 分治)

    鏈接:https://codeforces.com/problemset/problem/1400/E 來源:Codeforces 思路:給你一個陣列,現在你可以進行兩種操作,操作1:將一段沒有 0 的區間進行減一的操作,操作2:將 i 位置上的元素歸零。最終問:將這個陣列的全部元素歸零后操作的最少 ......

    uj5u.com 2020-09-10 00:57:30 more
  • UVA11610 【Reverse Prime】

    本人看到此題沒有翻譯,就附帶了一個自己的翻譯版本 思考 這一題,它的第一個要求是找出所有 $7$ 位反向質數及其質因數的個數。 我們應該需要質數篩篩選1~$10^{7}$的所有數,這里就不慢慢介紹了。但是,重讀題,我們突然發現反向質數都是 $7$ 位,而將它反過來后的數字卻是 $6$ 位數,這就說明 ......

    uj5u.com 2020-09-10 00:57:36 more
  • 統計區間素數數量

    1 #pragma GCC optimize(2) 2 #include <bits/stdc++.h> 3 using namespace std; 4 bool isprime[1000000010]; 5 vector<int> prime; 6 inline int getlist(int ......

    uj5u.com 2020-09-10 00:57:47 more
  • C/C++編程筆記:C++中的 const 變數詳解,教你正確認識const用法

    1、C中的const 1、區域const變數存放在堆疊區中,會分配記憶體(也就是說可以通過地址間接修改變數的值)。測驗代碼如下: 運行結果: 2、全域const變數存放在只讀資料段(不能通過地址修改,會發生寫入錯誤), 默認為外部聯編,可以給其他源檔案使用(需要用extern關鍵字修飾) 運行結果: ......

    uj5u.com 2020-09-10 00:58:04 more
  • 【C++犯錯記錄】VS2019 MFC添加資源不懂如何修改資源宏ID

    1. 首先在資源視圖中,添加資源 2. 點擊新添加的資源,復制自動生成的ID 3. 在解決方案資源管理器中找到Resource.h檔案,編輯,使用整個專案搜索和替換的方式快速替換 宏宣告 4. Ctrl+Shift+F 全域搜索,點擊查找全部,然后逐個替換 5. 為什么使用搜索替換而不使用屬性視窗直 ......

    uj5u.com 2020-09-10 00:59:11 more
  • 【C++犯錯記錄】VS2019 MFC不懂的批量添加資源

    1. 打開資源頭檔案Resource.h,在其中預先定義好宏 ID(不清楚其實ID值應該設定多少,可以先新建一個相同的資源項,再在這個資源的ID值的基礎上遞增即可) 2. 在資源視圖中選中專案資源,按F7編輯資源檔案,按 ID 型別 相對路徑的形式添加 資源。(別忘了先把檔案拷貝到專案中的res檔案 ......

    uj5u.com 2020-09-10 01:00:19 more
  • C/C++編程筆記:關于C++的參考型別,專供新手入門使用

    今天要講的是C++中我最喜歡的一個用法——參考,也叫別名。 參考就是給一個變數名取一個變數名,方便我們間接地使用這個變數。我們可以給一個變數創建N個參考,這N + 1個變數共享了同一塊記憶體區域。(參考型別的變數會占用記憶體空間,占用的記憶體空間的大小和指標型別的大小是相同的。雖然參考是一個物件的別名,但 ......

    uj5u.com 2020-09-10 01:00:22 more
  • 【C/C++編程筆記】從頭開始學習C ++:初學者完整指南

    眾所周知,C ++的學習曲線陡峭,但是花時間學習這種語言將為您的職業帶來奇跡,并使您與其他開發人員區分開。您會更輕松地學習新語言,形成真正的解決問題的技能,并在編程的基礎上打下堅實的基礎。 C ++將幫助您養成良好的編程習慣(即清晰一致的編碼風格,在撰寫代碼時注釋代碼,并限制類內部的可見性),并且由 ......

    uj5u.com 2020-09-10 01:00:41 more
最新发布
  • 【Netty實戰】1~3章學習筆記

    # 1. Netty總體結構 ## 1.1 Netty簡介 ? Netty是一款用于創建高性能網路應用程式的高級框架。它的基于 Java NIO 的異步的和事件驅動的實作,保證了高負載下應用程式性能的最大化和可伸縮性。 ? 其次,Netty 也包含了一組**設計模式**,將應用程式邏輯從網路層解耦, ......

    uj5u.com 2023-06-01 07:38:10 more
  • Spring MVC官方檔案學習筆記(二)之DispatcherServlet

    **1.DispatcherServlet入門** (1) Spring MVC是以前端控制器模式(即圍繞著一個中央的Servelt, DispatcherServlet)進行設計的,這個DispatcherServlet為請求的處理提供了一個共用的演算法,即它都會將實際的請求處理作業委托給那些可配置 ......

    uj5u.com 2023-06-01 07:37:57 more
  • 別再滿屏找日志了!推薦一款 IDEA 日志管理插件,看日志輕松多了!

    ![](https://img2023.cnblogs.com/other/1218593/202305/1218593-20230531145646615-374710580.png) ## **1.簡介** Grep Console是一款方便開發者對idea控制臺輸出日志進行個性化管理的插件。 ......

    uj5u.com 2023-06-01 07:36:36 more
  • StampedLock:高并發場景下一種比讀寫鎖更快的鎖

    摘要:在讀多寫少的環境中,有沒有一種比ReadWriteLock更快的鎖呢?有,那就是JDK1.8中新增的StampedLock! 本文分享自華為云社區《【高并發】高并發場景下一種比讀寫鎖更快的鎖》,作者: 冰 河。 什么是StampedLock? ReadWriteLock鎖允許多個執行緒同時讀取共 ......

    uj5u.com 2023-06-01 07:31:08 more
  • BIO、NIO、AIO區別詳解

    ###BIO:同步阻塞 主執行緒發起io請求后,需要等待當前io操作完成,才能繼續執行。 ###NIO:同步非阻塞 引入selector、channel、等概念,當主執行緒發起io請求后,輪詢的查看系統是否準備好執行io操作,沒有準備好則主執行緒不會阻塞會繼續執行,準備好主執行緒會阻塞等待io操作完成。 # ......

    uj5u.com 2023-06-01 07:30:33 more
  • Java中泛型詳解,非常詳細

    # 前言 在前面的幾篇文章中,詳細地給大家介紹了Java里的集合。但在介紹集合時,我們涉及到了泛型的概念卻并沒有詳細學習,**所以今天我們要花點時間給大家專門講解什么是泛型、泛型的作用、用法、特點等內容。** 有些粉絲朋友,在之前就一直很好奇,比如List中的 部分到底是什么?有啥用?為什么要加這個 ......

    uj5u.com 2023-05-31 10:10:59 more
  • Rust Web 全堆疊開發之 Web Service 中的錯誤處理

    # Rust Web 全堆疊開發之 Web Service 中的錯誤處理 ## Web Service 中的統一錯誤處理 ### Actix Web Service 自定義錯誤型別 -> 自定義錯誤轉為 HTTP Response - 資料庫 - 資料庫錯誤 - 串行化 - serde 錯誤 - I/ ......

    uj5u.com 2023-05-31 10:10:55 more
  • 驅動開發:內核決議PE結構匯出表

    在筆者的上一篇文章`《驅動開發:內核特征碼掃描PE代碼段》`中`LyShark`帶大家通過封裝好的`LySharkToolsUtilKernelBase`函式實作了動態獲取內核模塊基址,并通過`ntimage.h`頭檔案中提供的系列函式決議了指定內核模塊的`PE節表`引數,本章將繼續延申這個話題,實... ......

    uj5u.com 2023-05-31 10:10:44 more
  • keycloak~自定義登出介面

    keycloak提供了登出的介面,不過它是一個post方法,需要你根據client_id,client_secret及refresh_token進行登出操作的,有時不太靈活,所以我又自己封裝了一下,通過客戶端瀏覽器上存盤的session_id進行會話登出。 # kc提供的logout * api:{ ......

    uj5u.com 2023-05-31 10:10:35 more
  • Java中泛型詳解,非常詳細

    # 前言 在前面的幾篇文章中,詳細地給大家介紹了Java里的集合。但在介紹集合時,我們涉及到了泛型的概念卻并沒有詳細學習,**所以今天我們要花點時間給大家專門講解什么是泛型、泛型的作用、用法、特點等內容。** 有些粉絲朋友,在之前就一直很好奇,比如List中的 部分到底是什么?有啥用?為什么要加這個 ......

    uj5u.com 2023-05-31 10:09:33 more