• 1. An overview of the
  • 2. Build Netty servers and clients
  • 3. Communication protocol
  • 4. Message distribution
  • 5. Disconnect and reconnect
  • 6. Heartbeat mechanism and idle detection
  • 7. Authentication logic
  • 8. Logic alone
  • 9. Group chat logic
  • 666. The eggs
  • This article provides a sweep of complete code examples below



    Original is not easy, give point ne Zha hey, blunt duck together!

    1. An overview of the

    Netty is a Java open source framework.

    Netty provides an asynchronous, event-driven network application framework and tools to rapidly develop high-performance and reliable network server and client programs.

    In other words, Netty is a niO-based client/server programming framework that allows you to quickly and easily develop a network application, such as a client/server application that implements a certain protocol.

    Netty simplifies and streamlines the programming and development of network applications, such as TCP and UDP Socket services.

    Now, let’s create three new projects, as shown below:

    Three projects

    • Lab-67-netty-demo-server Project: Set up netty server.
    • Lab-67-netty-demo-client Project: Build netty client.
    • Lab-67-netty-demo-common Project: Provide basic netTY encapsulation, message codec and distribution functions.

    We also provide examples of common Netty features:

    • Heartbeat mechanism to implement server to client survival detection.
    • The client can reconnect to the server through disconnection.

    Don’t beep. Just do it.

    Friendship hint: may fat friends worry, without Netty foundation is not able to read this article? !

    Nai nai’s idea, look! Hard look, according to the code to build their own ha ~ at the end of the article, Nai will provide a wave of Netty basic entry article.

    2. Build Netty servers and clients

    In this section, we first use Netty to build the core code of the server and client, so that fat friends have an initial understanding of the project code.

    2.1 Building the Netty Server

    Create lab-67-netty-Demo-server project to set up netty server. As shown below:

    The project structure

    Below, we will only briefly look at the code under the Server package, to avoid too much information, through the fat friends bald.

    2.1.1 NettyServer

    Create NettyServer class, Netty server. The code is as follows:

    @Componentpublic class NettyServer {    private Logger logger = LoggerFactory.getLogger(getClass());    @Value("${netty.port}") private Integer port; @Autowired private NettyServerHandlerInitializer nettyServerHandlerInitializer; */ private EventLoopGroup bossGroup = new NioEventLoopGroup(); /** * WorkloopGroup = new NioEventLoopGroup(); /** * WorkloopGroup = new NioEventLoopGroup(); /** * Netty Server Channel */ private Channel channel; /** * Start Netty Server */ @postconstruct Public void start() throws InterruptedException {// <2.1> Create ServerBootstrap ServerBootstrap bootstrap = new ServerBootstrap(); // <2.2> Set ServerBootstrap properties bootstrap.group(bossGroup, WorkerGroup) / / < 2.2.1 > set two EventLoopGroup object. The channel (NioServerSocketChannel. Class) / / < 2.2.2 > specifies the channel to the service side Nioserversocketchannel. localAddress(new InetSocketAddress(port)) // <2.2.3> Sets the port of the Netty Server .option(channeloption.so_backlog, 1024) // <2.2.4> Server accept queue size. ChildOption (channeloption.so_keepalive,true) // <2.2.5> TCP Keepalive mechanism to implement the TCP heartbeat Keepalive function. ChildOption (channeloption. TCP_NODELAY,true) / / < 2.2.6 > allow smaller packets to send, reduce the delay. The childHandler (nettyServerHandlerInitializer); // <2> Start ChannelFuture future = bootstrap.bind().sync();if (future.isSuccess()) {            channel = future.channel();            logger.info("[start][Netty Server starts on port {}]", port); } /** * Disable Netty Server */ @predestroy public voidshutdown() {// <3.1> Stop the Netty Serverif(channel ! = null) { channel.close(); } / / < 3.2 > elegant closed two EventLoopGroup object bossGroup. ShutdownGracefully (); workerGroup.shutdownGracefully(); }}Copy the code

    ① Add the @Component annotation to the class, leaving the creation of NettyServer to Spring’s management.

    • The port property reads the netty. Port configuration item of the application.yml configuration file.
    • #start() method, add @postConstruct annotation, start Netty server.
    • Add the @predestroy annotation to shutdown the Netty server.

    The #start() method is used to start the Netty Server.

    <2.1>, create ServerBootstrap class, Netty provides the Server startup class, convenient for us to initialize the Server.

    <2.2>, set various properties of ServerBootstrap.

    Friendship tip: here involves more Netty component knowledge, Nai nai first with simple language description, the follow-up fat friends in the end of the Netty basic entry article, supplementary study oh.

    <2.2.1> call #group(EventLoopGroup parentGroup, EventLoopGroup childGroup) to set bossGroup and workerGroup. Among them:

    • BossGroup property: Boss thread group used by the server to accept client connections.
    • WorkerGroup property: The Worker thread group used by the server to receive data reads and writes from the client.

    Netty adopts the model of multiple Reactor and multiple threads, so the server can accept more data read and write ability of clients. The reason is:

    Create a bossGroup thread group for receiving client connections to prevent frequent data reads and writes from connected clients from affecting new client connections.

    Create a workerGroup thread group that is dedicated to receiving reads and writes from clients. Multiple threads can read and write data from clients and support more clients.

    For those who are interested in the Reactor model, please read the following article.

    At <2.2.2>, call #channel(Class<? Extends C> channelClass) method, which sets up the use of the NioServerSocketChannel class, which is the NIO server TCP Socket implementation class defined by Netty.

    At <2.2.3>, call the #localAddress(SocketAddress localAddress) method to set the server port.

    <2.2.4>, call option#(ChannelOption

    option, T value) to set the size of the connection queue for the server to accept the client. TCP establishes a connection through a three-way handshake. After the first handshake is complete, the handshake is added to the connection queue on the server.

    Problem set: For more on this topic, check out the backlog argument for TCP Sockets.

    <2.2.5>, call #childOption(ChannelOption

    childOption, T value) method, TCP Keepalive mechanism, to achieve the TCP level heartbeat alive function.

    Problem set: For more on this topic, you can read the following article “Getting to the bottom of TCP Keepalive”.

    At <2.2.6>, the #childOption(ChannelOption

    childOption, T value) method is called to allow smaller packets to be sent and reduce latency.

    Exercises: for more information on Socket programming, see the TCP_NODELAY option.

    < 2.2.7 >, call # childHandler (ChannelHandler childHandler) method, set up a client connection Channel processor to NettyServerHandlerInitializer. Later we in the “2.1.2 NettyServerHandlerInitializer” section.

    <2.3>, call #bind() + #sync() to bind the port and wait for the synchronization to succeed, that is, start the server.

    The shutdown() method is used to shutdown the Netty Server.

    <3.1>, call Channel’s #close() method to shut down the Netty Server, so that the client can no longer connect.

    <3.2>, the EventLoopGroup is gracefully disabled by calling the # ShutdownExceptionexceptionGroup () method. For example, thread pools in them.

    2.1.2 NettyServerHandlerInitializer

    Before we see NettyServerHandlerInitializer code, we need to understand the Netty ChannelHandler component, is used to deal with the Channel of various events. There are a wide range of events, such as connections, data reads and writes, exceptions, data transformations, and so on.

    ChannelHandler has many subclasses, including a special ChannelInitializer that implements custom initialization logic when creating a Channel. Here we create NettyServerHandlerInitializer class, inherited ChannelInitializer abstract class, the code is as follows:

    @ Componentpublic class NettyServerHandlerInitializer extends ChannelInitializer < Channel > {/ * * * * / private heartbeat timeout time static final Integer READ_TIMEOUT_SECONDS = 3 * 60; @Autowired private MessageDispatcher messageDispatcher; @Autowired private NettyServerHandler nettyServerHandler; @override protected void initChannel(Channel ch) {//  = ch.pipeline(); // <2> Add a bunch of nettyServerHandlers to ChannelPipeline // idle detection. AddLast (new) ReadTimeoutHandler(READ_TIMEOUT_SECONDS, Timeunit.seconds)) // encoder. AddLast (new InvocationEncoder()) // decoder. AddLast (new InvocationDecoder()) // message dispenser .addLast(messageDispatcher) // Server handler.addLast(nettyServerHandler); }}Copy the code

    When each client completes the connection with the server, the server creates a Channel corresponding to it. At this point, the NettyServerHandlerInitializer # will be executed initChannel (c) Channel method, customize initialization.

    Note: the created client Channel is not the same as the NioServerSocketChannel in section 2.1.1 NettyServer.

    The ch argument in the #initChannel(Channel CH) method is the client Channel created at this point.

    ① <1>, call Channel #pipeline() method, get the client Channel corresponding Channel pipeline. A ChannelPipeline consists of a series of channel handlers, or ChannelHandler chains. Thus, all events on a Channel property are processed by ChannelHandler on the ChannelPipeline.

    ② <2> add five channelhandlers to the ChannelPipeline. The function of each one depends on the comment on it. The details will be explained in the following sections.

    2.1.3 NettyServerHandler

    Create NettyServerHandler, inherit ChannelInboundHandlerAdapter, realize the client Channel to establish a connection, disconnection, exception handling. The code is as follows:

    @[email protected] class NettyServerHandler extends ChannelInboundHandlerAdapter { private Logger logger = LoggerFactory.getLogger(getClass()); @Autowired private NettyChannelManager channelManager; @override public void channelActive(ChannelHandlerContext CTX) {// Add channelManager.add(ctx.channel()) from the manager; } @override public void channelUnregistered(ChannelHandlerContext CTX) {// Remove from the manager channelManager.remove(ctx.channel()); } @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) { logger.error("[exceptionCaught][Connection ({}) abnormal]", ctx.channel().id(), cause); // Disconnect ctx.channel().close(); }}Copy the code

    Add the @channelHandler.Sharable annotation to the class to indicate that the ChannelHandler can be used by multiple channels.

    ② The channelManager property is the manager of the client Channel we implement.

    • The #channelActive(ChannelHandlerContext CTX) method is added to the Channel by calling the # Add (Channel Channel) method of NettyChannelManager when the connection between client and server is completed.
    • #channelUnregistered(ChannelHandlerContext CTX) method, which calls NettyChannelManager’s # Add (Channel Channel) method when the client and server are disconnected, Remove from it.

    Specific NettyChannelManager source code, we in “2.1.4 NettyChannelManager” section to take a look ~

    #exceptionCaught(ChannelHandlerContext CTX, Throwable cause); Disconnect from the client.

    2.1.4 NettyChannelManager

    Create a NettyChannelManager class that provides two functions.

    1 Client Channel management. The code is as follows:

    @Componentpublic class NettyChannelManager {    /**     * {@link Channel#attr(AttributeKey)} */ Private static final AttributeKey
           
             CHANNEL_ATTR_KEY_USER = attributekey.newinstance ("user"); private Logger logger = LoggerFactory.getLogger(getClass()); /** * Channel mapping */ private ConcurrentMap
            
              Channels = new ConcurrentHashMap<>(); /** * User mapping to Channel. * * It is used to obtain the corresponding Channel of the user. In this way, we can send messages to the specified user. */ private ConcurrentMap
             
               userChannels = new ConcurrentHashMap<>(); ** @param Channel Channel */ public void add(Channel Channel) { channels.put(channel.id(), channel); Logger. info("[add][a connection ({}) joins]", channel.id()); } /** * addUser to {@link #userChannels} ** @param channel channel * @param user */ public void addUser(channel) channel, String user) { Channel existChannel = channels.get(channel.id()); If (existChannel == null) {logger.error("[addUser][connection ({}) does not exist]", channel.id()); return; } // Set the attribute channel.attr(CHANNEL_ATTR_KEY_USER).set(user); // Add to userChannels userchannels.put (user, channel); {@link #channels} {@link #userChannels} ** @param Channel Channel */ public void Remove (Channel Channel) {// Remove channels Channels.remove (channel.id()); // Remove userChannels if (channel.hasattr (CHANNEL_ATTR_KEY_USER)) { userChannels.remove(channel.attr(CHANNEL_ATTR_KEY_USER).get()); } logger. The info (" [remove] [a connection ({}) to leave] ", channel. The id ()); }}
             ,>
            ,>
           Copy the code

    ② Send messages to client channels. The code is as follows:

    @ComponentPublic Class NettyChannelManager {/** * Sends messages to the specified user ** @param user * @Param Invocation Message body */ public void Send (String user, Invocation) {// Get the user's Channel Channel Channel = userChannels.get(user);if (channel == null) {            logger.error("[send][connection does not exist]");            return;        }        if(! channel.isActive()) { logger.error("[send][connection ({}) not active]", channel.id());            return; } // Send the message channel.writeAndFlush(Invocation); } /** * @Invocation message body */ public void sendAll(Invocation) {for (Channel channel : channels.values()) {            if(! channel.isActive()) { logger.error("[send][connection ({}) not active]", channel.id());                return; } // Send the message channel.writeAndFlush(Invocation); }}}Copy the code

    2.1.5 Importing dependencies

    Create the pom.xml file to introduce Netty dependencies.

    <? xml version="1.0" encoding="UTF-8"? ><project xmlns="http://maven.apache.org/POM/4.0.0"         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <parent> <artifactId>lab-67-netty-demo</artifactId> <groupId>cn.iocoder.springboot.labs</groupId> The < version > 1.0 - the SNAPSHOT < / version > < / parent > < modelVersion > 4.0.0 < / modelVersion > <artifactId>lab-67-netty-demo-server</artifactId> <properties> <! Version >2.2.4.RELEASE</spring.boot.version> <! Target > <maven.compiler.source>1.8</maven.compiler.source> </properties> <dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>${spring.boot.version}</version>                <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement> <dependencies> <! <dependency> <groupId>org.springframework. Boot </groupId> <artifactId>spring-boot-starter</artifactId> </dependency> <! --> <dependency> <groupId> io.ty </groupId> <artifactId> Netty </artifactId> < version > 4.1.50. Final < / version > < / dependency > <! - the introduction of netty - demo - common packaging - > < the dependency > < groupId > cn. Iocoder. Springboot. LABS < / groupId > < artifactId > lab - 67 - netty - demo - common < / artifactId > < version > 1.0 - the SNAPSHOT < / version > < / dependency > </dependencies></project>Copy the code

    2.1.6 NettyServerApplication

    Create NettyServerApplication class, NettyServer startup class. The code is as follows:

    @SpringBootApplicationpublic class NettyServerApplication {    public static void main(String[] args) {        SpringApplication.run(NettyServerApplication.class, args);    }}Copy the code

    2.1.7 Simple test

    Execute the NettyServerApplication class to start the NettyServer Server. The log is as follows:

    . / / omit other log 00:16:38 2020-06-21. 41948-801 the INFO [main] c.i.s.l.n.server.Net tyServer: [start] [Netty Server start in port 8888] 00:16:38 2020-06-21. 41948-893 the INFO [main] c.i.s.l.n.Net tyServerApplication: Started NettyServerApplicationin0.96 seconds (JVM is runningfor 1.4)Copy the code

    Netty Server starts on port 8888.

    2.2 Building a Netty Client

    Create lab-67-netty-Demo-client project to build netty client. As shown below:

    The project structure

    Below, we will only briefly look at the client package under the code, to avoid too much information, the bald penetration of fat friends.

    2.2.1 NettyClient

    Create NettyClient class, Netty client. The code is as follows:

    @ComponentPublic Class NettyClient {private static Final Integer RECONNECT_SECONDS = 20; @componentPublic class NettyClient {private static final Integer RECONNECT_SECONDS = 20; private Logger logger = LoggerFactory.getLogger(getClass()); @Value("${netty.server.host}")    private String serverHost;    @Value("${netty.server.port}") private Integer serverPort; @Autowired private NettyClientHandlerInitializer nettyClientHandlerInitializer; */ private EventLoopGroup eventGroup = new NioEventLoopGroup(); /** * Netty Client Channel */ private volatile Channel channel; /** * Start Netty Server */ @postconstruct public void start() throws InterruptedException {// <2.1> Create the Bootstrap object, Bootstrap = new Bootstrap(); // <2.2> bootstrap.group(eventGroup) // <2.2.1> Set an EventLoopGroup object. Channel (nioSocketchannel.class) // <2.2.2> specify Niosocketchannel. remoteAddress(serverHost, ServerPort) // <2.2.3> Specify the server address to connect to. Option (channeloption.so_keepalive,true) // <2.2.4> TCP Keepalive mechanism to implement the TCP heartbeat Keepalive function. Option (channeloption. TCP_NODELAY,true) / / < 2.2.5 > allow smaller packets to send, reduce the delay. The handler (nettyClientHandlerInitializer); // <2.3> Connect to the server and wait asynchronously for success, i.e. start the client bootstrap.connect().addListener(new)ChannelFutureListener() {@override public void operationComplete(ChannelFuture Future) throws Exception {// The connection failedif(! future.isSuccess()) { logger.error("[start][Netty Client failed to connect to server ({}:{})]", serverHost, serverPort);                    reconnect();                    return; } // channel = future.channel(); logger.info("[start][Netty Client connects to server ({}:{}) successfully]", serverHost, serverPort); }}); } public voidreconnect() {/ /... Omit the code for now. } /** * Disable Netty Server */ @predestroy public voidshutdown() {// <3.1> Stop the Netty Clientif (channel != null) {            channel.close();        }        // <3.2> 优雅关闭一个 EventLoopGroup 对象        eventGroup.shutdownGracefully();    }    /**     * 发送消息     *     * @param invocation 消息体     */    public void send(Invocation invocation) {        if (channel == null) {            logger.error("[send][connection does not exist]");            return;        }        if(! channel.isActive()) { logger.error("[send][connection ({}) not active]", channel.id());            return; } // Send the message channel.writeAndFlush(Invocation); }}Copy the code

    Friendship tip: the overall code, and “2.1.1 NettyServer” equivalent, and is basically consistent.

    ① Add the @Component annotation to the class, leaving the creation of NettyClient to Spring’s management.

    • The serverHost and serverPort properties, read the netty.server.host and netty.server.port configuration items of the application.yml configuration file.
    • #start() method, add @postConstruct annotation, start Netty client.
    • The #shutdown() method adds the @predestroy annotation to close the Netty client.

    # 2. Let’s look at in more detail the start () method code, how to realize the Netty Client startup, set up and the server connection.

    <2.1>, create the Bootstrap class, the Client startup class provided by Netty, so that we can initialize the Client.

    <2.2>, set various properties of Bootstrap.

    <2.2.1>, call #group(EventLoopGroup Group) method, set the use of eventGroup thread group, realize the client to the server connection, data read and write.

    At <2.2.2>, call #channel(Class<? Extends C> channelClass) method, which sets up the use of the NIO SocketChannel class, which is the NIO server TCP Client implementation class defined by Netty.

    <2.2.3> call the #remoteAddress(SocketAddress localAddress) method to set the address to connect to the server.

    <2.2.4>, call #option(ChannelOption

    childOption, T value) method, TCP Keepalive mechanism, to achieve the TCP level heartbeat alive function.

    At <2.2.5>, the #childOption(ChannelOption

    childOption, T value) method is called to allow smaller packets to be sent and reduce latency.

    < 2.2.7 >, call # handler (ChannelHandler childHandler) method, set their own Channel processor to NettyClientHandlerInitializer. Later we in the “2.2.2 NettyClientHandlerInitializer” section.

    At <2.3>, call the #connect() method to connect to the server and asynchronously wait for success, that is, start the client. At the same time, add a callback listener, ChannelFutureListener, to call the #reconnect() method if the connection to the server fails. We’ll look at the code for the #reconnect() method later.

    #shutdown() Netty Client shutdown() Netty Client shutdown()

    <3.1> Close the Netty Client by calling Channel’s #close() method, so that the Client is disconnected from the server.

    <3.2>, the EventLoopGroup is gracefully disabled by calling the # ShutdownExceptionexceptionGroup () method. For example, thread pools in them.

    (4) # Send (Invocation) method to send messages to the server.

    Because NettyClient is a client, there is no need to use “2.1.4 NettyChannelManager” like NettyServer to maintain a collection of channels.

    2.2.2 NettyClientHandlerInitializer

    Create NettyClientHandlerInitializer class, inherited ChannelInitializer abstract class, implementation and service side after the connection is established, and add the corresponding ChannelHandler processor. The code is as follows:

    @ Componentpublic class NettyClientHandlerInitializer extends ChannelInitializer < Channel > {/ * * * * / private heartbeat timeout time static final Integer READ_TIMEOUT_SECONDS = 60; @Autowired private MessageDispatcher messageDispatcher; @Autowired private NettyClientHandler nettyClientHandler; @override protected void initChannel(Channel ch) {ch.pipeline() // Idle check.addlast (new) IdleStateHandler(READ_TIMEOUT_SECONDS, 0, 0)).addlast (new ReadTimeoutHandler(3 * READ_TIMEOUT_SECONDS)) // encoder. AddLast (new InvocationEncoder()) // decoder .addlast (new InvocationDecoder()) // messageDispatcher.addlast (messageDispatcher) // client processor.addlast (nettyClientHandler); }}Copy the code

    Code “and” 2.1.2 NettyServerHandlerInitializer basic same, the difference is that free testing for an additional IdleStateHandler, client NettyClientHandler replace the processor.

    2.2.3 NettyClientHandler

    Create NettyClientHandler class to handle client Channel disconnection or exception. The code is as follows:

    @[email protected] class NettyClientHandler extends ChannelInboundHandlerAdapter { private Logger logger = LoggerFactory.getLogger(getClass()); @Autowired private NettyClient nettyClient; @override public void channelInactive(ChannelHandlerContext CTX) throws Exception {// Initiate nettyClient.reconnect(); // Continue to fire the event super.channelInactive(CTX); } @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) { logger.error("[exceptionCaught][Connection ({}) abnormal]", ctx.channel().id(), cause); // Disconnect ctx.channel().close(); } @override public void userEventTriggered(ChannelHandlerContext CTX, Object Event) throws Exception {// Initiate a heartbeat to the server when idleif (event instanceof IdleStateEvent) {            logger.info("[userEventTriggered][triggered heartbeat]");            HeartbeatRequest heartbeatRequest = new HeartbeatRequest();            ctx.writeAndFlush(new Invocation(HeartbeatRequest.TYPE, heartbeatRequest))                    .addListener(ChannelFutureListener.CLOSE_ON_FAILURE);        } else{ super.userEventTriggered(ctx, event); }}}Copy the code

    Add the @channelHandler.Sharable annotation to the class to indicate that the ChannelHandler can be used by multiple channels.

    The #channelInactive(ChannelHandlerContext CTX) method is used to reconnect the client to the server when the client is disconnected.

    #exceptionCaught(ChannelHandlerContext CTX, Throwable cause); Disconnect from the client.

    (4) #userEventTriggered(ChannelHandlerContext CTX, Object Event) : This function is triggered when the client is triggered. We’ll talk more about that in a minute.

    2.2.4 Importing dependencies

    Create the pom.xml file to introduce Netty dependencies.

    <? xml version="1.0" encoding="UTF-8"? ><project xmlns="http://maven.apache.org/POM/4.0.0"         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <parent> <artifactId>lab-67-netty-demo</artifactId> <groupId>cn.iocoder.springboot.labs</groupId> The < version > 1.0 - the SNAPSHOT < / version > < / parent > < modelVersion > 4.0.0 < / modelVersion > <artifactId>lab-67-netty-demo-client</artifactId> <properties> <! Version >2.2.4.RELEASE</spring.boot.version> <! Target > <maven.compiler.source>1.8</maven.compiler.source> </properties> <dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>${spring.boot.version}</version>                <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement> <dependencies> <! <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <! --> <dependency> <groupId> io.ty </groupId> <artifactId> Netty </artifactId> < version > 4.1.50. Final < / version > < / dependency > <! - the introduction of netty - demo - common packaging - > < the dependency > < groupId > cn. Iocoder. Springboot. LABS < / groupId > < artifactId > lab - 67 - netty - demo - common < / artifactId > < version > 1.0 - the SNAPSHOT < / version > < / dependency > </dependencies></project>Copy the code

    2.2.5 NettyClientApplication

    Create the NettyClientApplication class and the NettyClient startup class. The code is as follows:

    @SpringBootApplicationpublic class NettyClientApplication {    public static void main(String[] args) {        SpringApplication.run(NettyClientApplication.class, args);    }}Copy the code

    2.2.6 Simple Test

    Run the NettyClientApplication class to start the NettyClient. The log is as follows:

    . / / omit other log 09:06:12 2020-06-21. 44029-205 the INFO] [ntLoopGroup - 2-1 c.i.s.l.n.client.Net tyClient: [start][Netty Client successfully connected to server (127.0.0.1:8888)]Copy the code

    At the same time, the Netty Server detects that a client is connected, and the following log is displayed:

    The 2020-06-21 09:06:12. 41948-268 the INFO [ntLoopGroup - 3-1] c.i.s.l.n.server.Net tyChannelManager: [add] [a connection (db652822) join]Copy the code

    2.3 summary

    At this point, we have built the Netty server and client. Because the API provided by Netty is so convenient, we don’t have to deal with as much low-level and detailed code as we would with NIO directly.

    However, the above content is only the appetizer of this article, the feature is about to begin! Delightfully, keep reading, Ollie!