< 2021SC@SDUSC > netty use -- implementation of simple chat room

Keywords: Java Netty

2021SC@SDUSC

preface

In the initial use of the previous blog netty, we simply wrote the code between the client and the server to realize two-way communication. This time, we made some improvements on the basis of the last code and preliminarily realized the function of chat room.

1, Server

1,Server

The Server class itself has not changed much, but the addition method of ServerInitializer has changed from the original internal class method to the new method, which is to separate the ServerInitializer class for management convenience.

package com.homework.server_client.server;

import com.homework.server_client.server.handler.ServerInitializer;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;

public class Server {

    private final int PORT;

    public Server(int port) {
        this.PORT = port;
    }

    public void run() {
        //Create two thread pools
        EventLoopGroup bossGroup = new NioEventLoopGroup(1);
        EventLoopGroup workerGroup = new NioEventLoopGroup(8);
        try {
            //Bootstrap for netty
            ServerBootstrap bootstrap = new ServerBootstrap();
            //Set parentGroup and childGroup
            bootstrap.group(bossGroup, workerGroup)
                    //Specify Channel type
                    .channel(NioServerSocketChannel.class)
                    //Sets the maximum number of connections in the queue
                    .option(ChannelOption.SO_BACKLOG, 128)
                    //Monitor whether the connection is valid
                    .childOption(ChannelOption.SO_KEEPALIVE, true)
                    //After the client connection is successful, add a handler. Note that the channelinitiator will delete itself after successful registration
                    .childHandler(new ServerInitializer());
            //Bind the port for the server and wait for synchronization
            ChannelFuture future = bootstrap.bind(PORT).sync();
            future.channel().closeFuture().sync();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            bossGroup.shutdownGracefully();
            workerGroup.shutdownGracefully();
        }
    }

    public static void main(String[] args) {
        new Server(8080).run();
    }

}

2,ServerInitializer

For ease of management, the ServerHandler class is written independently, rather than in the form of an internal class. The ServerInitializer class itself has not changed much.

package com.homework.server_client.server.handler;

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

public class ServerInitializer extends ChannelInitializer<SocketChannel> {

    @Override
    protected void initChannel(SocketChannel channel) throws Exception {
        ChannelPipeline pipeline = channel.pipeline();
        //Add codec
        pipeline.addLast("encoder", new StringEncoder(CharsetUtil.UTF_8));
        pipeline.addLast("decoder", new StringDecoder(CharsetUtil.UTF_8));
        //Custom handler
        pipeline.addLast(new ServerHandler());
    }

}

3,ServerHandler

All channels are managed through the GROUP variable. The type is set < channel >, and the mass sending of messages is realized in this way. Since the HashSet class does not guarantee synchronization, thread safety is guaranteed through ReentrantLock. In addition, only the GROUP sending mechanism of messages is implemented, not the separate sending mechanism.

package com.homework.server_client.server.handler;

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

import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class ServerHandler extends SimpleChannelInboundHandler<String> {

    /**
     * Define a Channel group to manage all channels
     */
    private static final Set<Channel> GROUP = new HashSet<>();
    private final Lock LOCK = new ReentrantLock();

    /**
     * Indicates that the connection is established. The first one is executed and the current Channel is added to the group
     * @param ctx
     * @throws Exception
     */
    @Override
    public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
        Channel channel = ctx.channel();
        Lock lock = this.LOCK;
        lock.lock();
        try {
            // Push the messages added by the channel to other channels
            this.writeAndFlush(channel.remoteAddress() + "Join the chat room.");
            GROUP.add(channel);
            System.out.println(" Current number: " + GROUP.size());
        } finally {
            lock.unlock();
        }
    }

    /**
     * channel Disconnect
     * @param ctx
     * @throws Exception
     */
    @Override
    public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {
        Channel channel = ctx.channel();
        Lock lock = this.LOCK;
        lock.lock();
        try {
            // Push the message that the channel leaves to other channels
            GROUP.remove(channel);
            this.writeAndFlush(channel.remoteAddress() + "Leave the chat room.");
            System.out.println(" Current number: " + GROUP.size());
        } finally {
            lock.unlock();
        }
    }

    /**
     * Override the channelRead0 method to read the message
     * @param ctx
     * @param msg
     * @throws Exception
     */
    @Override
    protected void channelRead0(ChannelHandlerContext ctx, String msg) throws Exception {
        Channel channel = ctx.channel();
        Lock lock = this.LOCK;
        lock.lock();
        try {
            for (Channel ch : GROUP) {
                if (ch != channel) {
                    ch.writeAndFlush("come from" + channel.remoteAddress() + "News of: " + msg);
                } else {
                    ch.writeAndFlush("Already sent: " + msg);
                }
            }
        } finally {
            lock.unlock();
        }
    }

    /**
     * When an exception is caught, close the channel
     * @param ctx
     * @param cause
     * @throws Exception
     */
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        ctx.close();
    }

    private void writeAndFlush(String msg) {
        for (Channel channel : GROUP) {
            channel.writeAndFlush(msg);
        }
    }

}

2, Client

1,Client

The original function of sending messages in the Client has been transferred to the ClientHandler class. In other aspects, the changes are similar to those in the Server.

package com.homework.server_client.client;

import com.homework.server_client.client.handler.ClientInitializer;
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioSocketChannel;

public class Client {

    private static final int PORT = 8080;
    private static final String HOST = "127.0.0.1";

    public Client() {}

    public void run() {
        //Create a thread pool. Note that unlike the server, the client only needs one thread pool
        EventLoopGroup workerGroup = new NioEventLoopGroup(8);
        try {
            Bootstrap bootstrap = new Bootstrap();
            bootstrap.group(workerGroup)
                    //Specify Channel type
                    .channel(NioSocketChannel.class)
                    .handler(new ClientInitializer());
            ChannelFuture future = bootstrap.connect(HOST, PORT).sync();
            future.channel().closeFuture().sync();
        } catch (InterruptedException exception) {
            exception.printStackTrace();
        } finally {
            workerGroup.shutdownGracefully();
        }
    }

    public static void main(String[] args) {
        new Client().run();
    }

}

2,ClientInitializer

The change of ClientInitializer is similar to that of ServerInitializer.

package com.homework.server_client.client.handler;

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

public class ClientInitializer extends ChannelInitializer<SocketChannel> {

    @Override
    protected void initChannel(SocketChannel channel) throws Exception {
        ChannelPipeline pipeline = channel.pipeline();
        pipeline.addLast("encoder", new StringEncoder(CharsetUtil.UTF_8));
        pipeline.addLast("decoder", new StringDecoder(CharsetUtil.UTF_8));
        pipeline.addLast(new ClientHandler());
    }

}

3,ClientHandler

Finally, the ClientHandler class adds the channelActive method, which is to create a thread that constantly accepts input messages, bounded by line breaks, and sends them to the server.

package com.homework.server_client.client.handler;

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

import java.util.Scanner;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class ClientHandler extends SimpleChannelInboundHandler<String> {

    //Print messages sent by the server
    @Override
    protected void channelRead0(ChannelHandlerContext ctx, String msg) throws Exception {
        System.out.println(msg);
    }

    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        //Create other threads to handle IO events
        ExecutorService executor = Executors.newSingleThreadExecutor();
        executor.submit(() -> {
            Scanner scanner = new Scanner(System.in);
            while (scanner.hasNextLine()) {
                String message = scanner.nextLine();
                ctx.writeAndFlush(message);
            }
        });
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        System.out.println(cause.getMessage());
        ctx.channel().closeFuture().sync();
    }

}

summary

Based on the previous code, the chat room function is simply realized by using netty. However, the functions are relatively few. Only the mass sending function of messages is realized, and there is no separate sending function of messages. In addition, anyone can join the chat room without permission control, and there is only one chat room itself. After everyone joins, it is the same chat room. In short, it is basically a simple demo written when learning netty.

Posted by bughunter2 on Fri, 05 Nov 2021 19:17:07 -0700