Netty starts from 0 --- section 3: chat demo based on netty

1. Requirements of chat program

Through the previous study, we can see that the client and server can interact normally, so can we make a chat applet ourselves? Now we can define some requirements ourselves, such as:
1. After the server receives a message from the client establishment request, we need to push the message to other clients, which is called XX join.
2. When a channel enters the active state, we print a message on the server console: XX goes online. When a channel enters the inactive state, we print a message on the console: XX goes offline.
3. When a client goes offline, we need to push it to other clients, which is called so and so quit.
4. When a client sends a message to the server, the server needs to push the message to all connected clients together with the sender of the message. If the client judges that the message is sent by itself, it will display itself, otherwise it will display the code of other clients.

2 main method and initializer

We still need to construct the main method, initializer and processor. The main method is the same as before, as follows:

package test03.server;

import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;

public class MyServer03
{
   public static void main(String[] args) throws Exception{
      EventLoopGroup bossGroup = new NioEventLoopGroup();
      EventLoopGroup workerGroup = new NioEventLoopGroup();
      try
      {
         ServerBootstrap serverBootstrap=new ServerBootstrap();
         serverBootstrap.group(bossGroup,workerGroup).channel(NioServerSocketChannel.class).childHandler(new MyServer03Initializer());
         ChannelFuture channelFuture=serverBootstrap.bind(9999).sync();
         channelFuture.channel().closeFuture().sync();
      } finally
      {
         bossGroup.shutdownGracefully();
         workerGroup.shutdownGracefully();
      }
   }
}

Initializer:

package test03.server;

import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.socket.SocketChannel;
import io.netty.handler.codec.DelimiterBasedFrameDecoder;
import io.netty.handler.codec.Delimiters;
import io.netty.handler.codec.string.StringDecoder;
import io.netty.handler.codec.string.StringEncoder;
import io.netty.util.CharsetUtil;

public class MyServer03Initializer extends ChannelInitializer<SocketChannel>
{
   protected void initChannel(SocketChannel socketChannel) throws Exception
   {
      ChannelPipeline pipeline = socketChannel.pipeline();
      pipeline.addLast(new DelimiterBasedFrameDecoder(4096,Delimiters.lineDelimiter()));
      pipeline.addLast(new StringDecoder(CharsetUtil.UTF_8));
      pipeline.addLast(new StringEncoder(CharsetUtil.UTF_8));
      // Custom processor
      pipeline.addLast("myServerHandler", new MyServer03Handler());
   }
}

In the coming period of study, we may come into contact with many processors in netty. These processors can be used first in the early stage of study, and we can have an in-depth understanding of them in the later stage of study. As you can see here, we use a new processor: DelimiterBasedFrameDecoder.

3 custom processor

In the custom processor, we have come across several methods before: handlerAdded (create connection), channelRegistered (register channel), channelActive (channel is active), channelInactive (channel is inactive), channelUnregistered (log off channel), and actually a handlerRemoved (disconnect).
First, we need to handle it when the client establishes a link, that is, handler added. The logic to be handled here is to establish a link with the current client and broadcast the link to other linked clients. So to achieve this requirement, we need a collection to save all clients.
In fact, netty provides a collection of player channels, ChannelGroup, which is used to store channels that need to process business. Therefore, we define a collection to store channels.

private static ChannelGroup channelGroup = new DefaultChannelGroup(GlobalEventExecutor.INSTANCE);

At this time, store the channel when handlerAdded, and then access the corresponding channel. Channelgroup and GlobalEventExecutor can be explained later. Now we only need to know that the instantiated channelgroup after passing GlobalEventExecutor as a parameter is global and thread safe.
In this case, it can be handled in handlerAdded as follows:

/**
 * Link with client
 * */
@Override
public void handlerAdded(ChannelHandlerContext ctx) throws Exception
{
   Channel channel = ctx.channel();
   channelGroup.writeAndFlush("[user] - "+ channel.remoteAddress() + " Join!\n");
   channelGroup.add(channel);
}

netty provides the same methods as channels for ChannelGroup, such as write, flush and writeAdnFlush. These methods will traverse all channels in ChannelGroup and execute the same methods, which will greatly save development efficiency in actual development. Similarly, when a client disconnects a link, we can handle it in handlerRemoved.

/**
 * Disconnect from client
 * */
@Override
public void handlerRemoved(ChannelHandlerContext ctx) throws Exception
{
   Channel channel = ctx.channel();
   channelGroup.writeAndFlush("[user] - "+ channel.remoteAddress() + " sign out!\n");
}

In the handlerRemoved method, the remove method of the ChannelGroup can not be executed, because the current group is Global, and netty will find and delete the removed channel in the ChannelGroup when handlerRemoved is executed.
In channelActive and channelInactive, we can print online and offline:

@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception
{
   System.out.println(ctx.channel().remoteAddress()+"go online");
}

@Override
public void channelInactive(ChannelHandlerContext ctx) throws Exception
{
   System.out.println(ctx.channel().remoteAddress()+"Offline");
}

Implementation of 4channelRead0

The channelRead0 method is executed when each client sends a message to the server. The logic we need to deal with in this method is to broadcast to all clients when we receive the message, in which "self: + content" is sent to the sender of the message, and "sender information: + content" is sent to other users. At this time, we still need to traverse the ChannelGroup to send.

protected void channelRead0(ChannelHandlerContext channelHandlerContext,String s) throws Exception
{
   Channel channel = channelHandlerContext.channel();
   for(Channel ch :channelGroup){
      if(channel == ch){
         channel.writeAndFlush("own:" + s + "\n");
      }else{
         ch.writeAndFlush(channel.remoteAddress() + "Send message:" + s + "\n");
      }
   }
}

We traverse the group to determine whether the elements are equal to the sender of the current message. If they are equal, they are regarded as the same person, otherwise they are regarded as other clients. So far, the server of the simple chat server has been completed, in which the code of the entry class and the class of the initializer are above, and the code of the processor is as follows:

package test03.server;

import io.netty.channel.Channel;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.channel.group.ChannelGroup;
import io.netty.channel.group.DefaultChannelGroup;
import io.netty.util.concurrent.GlobalEventExecutor;

import java.util.UUID;

public class MyServer03Handler extends SimpleChannelInboundHandler<String>
{
   private static ChannelGroup channelGroup = new DefaultChannelGroup(GlobalEventExecutor.INSTANCE);
   
   /**
    * Method to execute after exception
    * */
   @Override
   public void exceptionCaught(ChannelHandlerContext ctx,Throwable cause) throws Exception
   {
      //cause.printStackTrace();
      ctx.close();
   }
   
   protected void channelRead0(ChannelHandlerContext channelHandlerContext,String s) throws Exception
   {
      Channel channel = channelHandlerContext.channel();
      for(Channel ch :channelGroup){
         if(channel == ch){
            channel.writeAndFlush("own:" + s + "\n");
         }else{
            ch.writeAndFlush(channel.remoteAddress() + "Send message:" + s + "\n");
         }
      }
   }
   
   @Override
   public void channelActive(ChannelHandlerContext ctx) throws Exception
   {
      System.out.println(ctx.channel().remoteAddress()+"go online");
   }
   
   @Override
   public void channelInactive(ChannelHandlerContext ctx) throws Exception
   {
      System.out.println(ctx.channel().remoteAddress()+"Offline");
   }
   
   /**
    * Link with client
    * */
   @Override
   public void handlerAdded(ChannelHandlerContext ctx) throws Exception
   {
      Channel channel = ctx.channel();
      channelGroup.writeAndFlush("[user] - "+ channel.remoteAddress() + " Join!\n");
      channelGroup.add(channel);
   }
   
   /**
    * Disconnect from client
    * */
   @Override
   public void handlerRemoved(ChannelHandlerContext ctx) throws Exception
   {
      Channel channel = ctx.channel();
      channelGroup.writeAndFlush("[user] - "+ channel.remoteAddress() + " sign out!\n");
   }
}

5 client

The client is still similar to the server. The slight difference is that the client needs to listen for an event of a keyboard input stream, because we need to input a message on the console and send it, so we use the IO stream in java.

package test03.client;

import io.netty.bootstrap.Bootstrap;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioSocketChannel;

import java.io.BufferedReader;
import java.io.InputStreamReader;

public class MyClient03
{
   public static void main(String[] args) throws Exception{
      EventLoopGroup eventLoopGroup = new NioEventLoopGroup();
      try{
         Bootstrap bootstrap = new Bootstrap();
         bootstrap.group(eventLoopGroup).channel(NioSocketChannel.class).handler(new MyClient03Initialize());
         
         Channel channel = bootstrap.connect("127.0.0.1",9999).sync().channel();
         
         BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
         while(true){
            channel.writeAndFlush(br.readLine() + "\r\n");
         }
      }finally
      {
         eventLoopGroup.shutdownGracefully();
      }
   }
}

The entry class code is as above. After the Bootstrap binding completes the ip and port, you need to obtain a channel, suspend the readline of a bufferedreader, wait for input, and then send the content to the server.
The initializer is the same as the server:

package test03.client;

import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.socket.SocketChannel;
import io.netty.handler.codec.DelimiterBasedFrameDecoder;
import io.netty.handler.codec.Delimiters;
import io.netty.handler.codec.string.StringDecoder;
import io.netty.handler.codec.string.StringEncoder;
import io.netty.util.CharsetUtil;

public class MyClient03Initialize extends ChannelInitializer<SocketChannel>
{
   protected void initChannel(SocketChannel socketChannel) throws Exception
   {
      ChannelPipeline pipeline = socketChannel.pipeline();
      pipeline.addLast(new DelimiterBasedFrameDecoder(4096,Delimiters.lineDelimiter()));
      pipeline.addLast(new StringDecoder(CharsetUtil.UTF_8));
      pipeline.addLast(new StringEncoder(CharsetUtil.UTF_8));
      // Custom processor
      pipeline.addLast("myClient02Handler",new MyClient03Handler());
   }
}

Without complex logic in the processor, the received message can be printed out:

package test03.client;

import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;

public class MyClient03Handler extends SimpleChannelInboundHandler<String>
{
   @Override
   public void exceptionCaught(ChannelHandlerContext ctx,Throwable cause) throws Exception
   {
      cause.printStackTrace();
      ctx.close();
   }
   
   protected void channelRead0(ChannelHandlerContext channelHandlerContext,String s) throws Exception
   {
      System.out.println(s);
   }
}

At this point, a simple chat server can run. The program structure is as follows:

6 operation procedure

First start the Server, then start the first client program, and the Server gets the print message:


You can see that the first client is assigned 50708 port. By using "netstat -ano|findstr 9999" to view port 9999, you can see that 9999 port is listening, and 9999 and 50708 ports are linked to each other:
Now start the second client and the server will get the print message:
It can be seen that the second client is assigned port 51616, and the first client gets broadcast:
By viewing the 9999 port, you can see that 9999 is now linked to 5070851616 respectively:
Start the third client and the server will get the print:
It can be seen that the third client is assigned to port 51915, and the first and second clients receive the join reminder respectively:

You can see that 9999 is linked to 501085161651915 respectively by checking the port occupancy:
Now the server and client are started and completed respectively, and then input text in client 1 and send it. The printing of client 1:
Printing of client 2:
Printing of client 3:
Then input and send the text through the client 2, and the printing of the client 2:
Printing of client 1:
Printing of client 3:
Then close client 2 and the server Prints:
Client 1 print:
Client 3 print:
View the port call and see that 51616 has been logged off:
So far, the third demo simple chat program has been developed and tested.

Keywords: Java Netty server

Added by nashsaint on Wed, 23 Feb 2022 05:11:35 +0200