If the article is slow to load, you can visit my personal blog: Article address

1. Netty thread model

2. Set up the Netty server

  • 1) Build a pair of master/slave thread pools
  • 2) Create the server startup class
  • 3) Start the class for the server and bind Channel, master and slave thread groups
  • 4) Set up the helper class initializer that handles tasks from the thread pool
  • 5) Listen to start and shut down the server

Set the Channel initializer

Each Channel is a pipeline composed of one or more handlers.



You can think of a pipe as one big interceptor, and each handler as several small interceptors that can block requests layer by layer as they come in!

3. Getting started Case 1

3.1 Netty Server Startup Class

/ * * *@Auther: csp1999
 * @Date: 2020/09/21/22:33
 * @Description: Netty server startup class: realize the client to send a request, the server to respond to */
public class HelloNettyServer {

    public static final int PORT = 8888;

    public static void main(String[] args) throws InterruptedException {

        // 1. Create a pair of master/slave thread pools
        // Main thread pool (boss) : used to receive requests from clients without doing any processing (boss, only responsible for management without doing any specific work)
        EventLoopGroup boosGroup = new NioEventLoopGroup();
        // Slave pool (employee) : The main pool dumps all the tasks to slave pool to execute the tasks
        EventLoopGroup workerGroup = new NioEventLoopGroup();

        try {
            // 2. Create a server startup class
            ServerBootstrap serverBootstrap = new ServerBootstrap();
            // 3. Start the class for the server and bind Channel, master and slave thread groups
            serverBootstrap.group(boosGroup, workerGroup)// Bind master/slave thread groups
                    .channel(NioServerSocketChannel.class)// Bind channel: niO bidirectional channel for client and server communication
                    // 4. Set up the helper class initializer that handles tasks from the thread pool
                    // Child handler, used to process tasks from the thread pool
                    .childHandler(new HelloNettyServerInitializer());
            
            // 5. Listen to start and stop the server
            // Start the service, set the port number, and set the startup mode to sync
            ChannelFuture channelFuture = serverBootstrap.bind(PORT).sync();
            // Listen for closed channels and set it to synchronous mode
            channelFuture.channel().closeFuture().sync();
        } finally {
            // Close gracefullyboosGroup.shutdownGracefully(); workerGroup.shutdownGracefully(); }}}Copy the code

3.2 Netty Service Initializer

/ * * *@Auther: csp1999
 * @Date: 2020/09/22/17:28
 * @Description: Netty service initializer * After the Netty server starts the class binding Channel and binds the initializer in the Channel child handler, it executes the initializer's initialization method */
public class HelloNettyServerInitializer extends ChannelInitializer<SocketChannel> {

    /** * The channel initializer method that needs to be overridden after inheriting ChannelInitializer<SocketChannel>@param socketChannel
     * @throws Exception
     */
    @Override
    protected void initChannel(SocketChannel socketChannel) throws Exception {
        // Get the corresponding pipeline through SocketChannel
        // A handler can be added to the pipeline pipeline
        ChannelPipeline channelPipeline = socketChannel.pipeline();

        /* * Pipeline handler * HttpServerCodec is a helper class provided by Netty itself, which can be interpreted as an interceptor * * When a request is sent to the server, we need to decode the response to the client for encoding */
        // Add a handler --> HttpServerCodec
        channelPipeline.addLast("HttpServerCodec".new HttpServerCodec());
        // Add the custom helper class handler to render Hello netty~ for the client browser
        channelPipeline.addLast("CustomHandler".newCustomHandler()); }}Copy the code

3.3 Customizing the helper class Handler

/ * * *@Auther: csp1999
 * @Date: 2020/09/22 / *@Description: custom helper class handler: used to give the client browser rendering hello netty ~ * need to inherit SimpleChannelInboundHandler < HttpObject > abstract class, and write the corresponding method. * /
public class CustomHandler extends SimpleChannelInboundHandler<HttpObject> {

    /** * Reads and outputs data from a channel **@param: CTX context: A channel can be retrieved from the context, or the response result can be rendered back to the client *@param: httpObject Message type *@return: void
     * @create: 2020/9/22 instructed *@author: csp1999
     */
    @Override
    protected void channelRead0(ChannelHandlerContext ctx, HttpObject httpObject) throws Exception {
        / / for the channel
        Channel channel = ctx.channel();
        // Prints the remote address of the access request on the console
        System.out.println("Remote address:" + channel.remoteAddress());
        // Define what to send to the client
        ByteBuf content = Unpooled.copiedBuffer("hello netty ~", CharsetUtil.UTF_8);
        // Build HTTP Response: used to respond content to the client's browser page!
        FullHttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK, content);
        // Set the data type and content length for the response
        response.headers().set(HttpHeaderNames.CONTENT_TYPE, "text/plain");// Set the data type for the response
        response.headers().set(HttpHeaderNames.CONTENT_LENGTH, content.readableBytes());// Set the content length for the response

        // Render the response to an HTML page (client browser page)
        ctx.writeAndFlush(response);
    }

    @Override
    public void channelRegistered(ChannelHandlerContext ctx) throws Exception {
        System.out.println("Channel registration...");
    }

    @Override
    public void channelUnregistered(ChannelHandlerContext ctx) throws Exception {
        System.out.println("Channel removed...");
    }

    @Override
    public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
        System.out.println("CustomHandler add...");
    }

    @Override
    public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {
        System.out.println("CustomHandler removed...");
    }

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

    @Override
    public void channelInactive(ChannelHandlerContext ctx) throws Exception {
        System.out.println("Channel not active...");
    }

    @Override
    public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
        System.out.println("Channel data read completed...");
    }

    @Override
    public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
        System.out.println("User event triggered...");
    }

    @Override
    public void channelWritabilityChanged(ChannelHandlerContext ctx) throws Exception {
        System.out.println("Channel writable...");
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        System.out.println("Channel exception catch..."); }}Copy the code

3.4 Starting a Test

  • Run the Netty service startup class
  • Browser: http://localhost:8888/
  • The results are as follows:



View console print:



Test successful!

3.5 Process Summary of Case 1

4. Getting Started Case 2 Netty + WebSocket

WebSocket:

  • It is a persistent protocol
  • You can actively feedback the server information to the client in real time

4.1 Setting up the server startup class

/ * * *@Auther: csp1999
 * @Date: 2020/09/23/9:51
 * @Description: Websocket + Netty server startup class */
public class WebSocketServer {

    private static final int PORT = 8088;

    public static void main(String[] args) throws InterruptedException {

        // 1. Create a pair of master/slave thread pools
        // Main thread pool (boss) : used to receive requests from clients without doing any processing (boss, only responsible for management without doing any specific work)
        EventLoopGroup mainGroup = new NioEventLoopGroup();
        // Slave pool (employee) : The main pool dumps all the tasks to slave pool to execute the tasks
        EventLoopGroup subGroup = new NioEventLoopGroup();

        try {
            // 2. Create a server startup class
            ServerBootstrap serverBootstrap = new ServerBootstrap();
            // 3. Start the class for the server and bind Channel, master and slave thread groups
            serverBootstrap.group(mainGroup, subGroup)// Bind master/slave thread groups
                    .channel(NioServerSocketChannel.class)// Bind channel: niO bidirectional channel for client and server communication
                    // 4. Set up the helper class initializer that handles tasks from the thread pool
                    // Child handler, used to process tasks from the thread pool
                    .childHandler(new WebSocketServerInitializer());
            
            // 5. Listen to start and stop the server
            // Start the service, set the port number, and set the startup mode to sync
            ChannelFuture channelFuture = serverBootstrap.bind(PORT).sync();
            // Listen for closed channels and set it to synchronous mode
            channelFuture.channel().closeFuture().sync();
        }finally {
            // Gracefully close the thread poolmainGroup.shutdownGracefully(); subGroup.shutdownGracefully(); }}}Copy the code

4.2 Creating a Service Initializer

/ * * *@Auther: csp1999
 * @Date: 2020/09/23 / * he said@Description: WebSocket + Netty service initializer * After the Netty server starts the class binding Channel and binds the initializer in the Channel child handler, it executes the initializer's initialization method */
public class WebSocketServerInitializer extends ChannelInitializer<SocketChannel> {

    /** * The channel initializer method that needs to be overridden after inheriting ChannelInitializer<SocketChannel>@param socketChannel
     * @throws Exception
     */
    @Override
    protected void initChannel(SocketChannel socketChannel) throws Exception {

        // Get the client/server pipeline from SocketChannel
        // A handler can be added to the pipeline pipeline
        ChannelPipeline channelPipeline = socketChannel.pipeline();

        // Add a handler --> HttpServerCodec: HTTP codec required by webSocket based on the HTTP protocol
        channelPipeline.addLast(new HttpServerCodec());

        /* * There are some streams generated over HTTP that we need to process * so we need to use Netty to support reading and writing of this big stream * this class is: ChunkedWriteHandler */
        // Add a handler --> ChunkedWriteHandler
        channelPipeline.addLast(new ChunkedWriteHandler());
        // Add a handler --> HttpObjectAggregator: Aggregate httpMessage into response or Request
        // Set the maximum content data length to maxContentLength: 1024 * 64
        channelPipeline.addLast(new HttpObjectAggregator(1024 * 64));

        Routing address * * * based on HTTP/websocket add WebSocketServerProtocolHandler will help deal with some complicated things * such as heavy processing handshake action: handshaking (close, ping, ping) : Ping + ping = Heartbeat * For Websockets, the data is transmitted as frames. Different data types correspond to different frames */
        / / add a handler - > WebSocketServerProtocolHandler:
        // websocketPath: /ws can be customized
        channelPipeline.addLast(new WebSocketServerProtocolHandler("/ws"));

        // Add a handler --> custom handler for handling chat messages
        channelPipeline.addLast(newChatHandler()); }}Copy the code

4.3 Custom Message Processing Assistant class (Handler)

/ * * *@Auther: csp1999
 * @Date: 2020/09/23/10:49
 * @Description: custom helper class handler: used for processing the chat message handler * need to inherit SimpleChannelInboundHandler < TextWebSocketFrame > abstract class, and write the corresponding method. * Frame is the carrier of the message. This class is called TextWebSocketFrame */
public class ChatHandler extends SimpleChannelInboundHandler<TextWebSocketFrame> {

    // The ChannelGroup is used to record and manage all clients
    // A Client corresponds to a Channel ~
    private static ChannelGroup clients = new DefaultChannelGroup(GlobalEventExecutor.INSTANCE);

    /** * (this method is triggered when the client is created) : ** Add this channel to the channel group ** when the client is connected to the server@param: CTX context: A channel can be retrieved from the context, or the response result can be rendered back to the client *@return: void
     * @create: 2020/9/23 11:01
     * @author: csp1999
     */
    @Override
    public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
        / / for the channel
        Channel channel = ctx.channel();
        // Add the channel to the ChannelGroup
        clients.add(channel);
    }

    /** * (this method is triggered when the browser is closed (the user leaves the client) : * Removes the channel from the channel group ** when the client disconnects from the server@param: ctx
     * @return: void
     * @create: 2020/9/23 11:01
     * @author: csp1999
     */
    @Override
    public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {
        // Get the client's channel
        Channel channel = ctx.channel();

        /* * Remove the channel from the channel management group * * If the user closes the corresponding browser, the channel will be removed automatically, * * so the channel will be removed without calling remove method, * * therefore: Remove (channel); This line of code */
        clients.remove(channel);
        System.out.println("The client is disconnected. The long ID corresponding to the channel is:" + ctx.channel().id().asLongText());
        System.out.println("The client is disconnected, and the short ID of the channel is:" + ctx.channel().id().asShortText());
    }

    /** * Reads and outputs data from a channel **@paramCTX CTX context: The channel can be retrieved from the context, or the response result can be rendered back to the client *@paramMessage transmitted by the MSG client: The message type is TextWebSocketFrame *@throws Exception
     */
    @Override
    protected void channelRead0(ChannelHandlerContext ctx, TextWebSocketFrame msg) throws Exception {
        // Get the message transferred by the client
        String content = msg.text();
        System.out.println("Content of message received by server:" + content);
        System.out.println("IP is." + ctx.channel().remoteAddress());

        // Refresh the data to the client
        clients.writeAndFlush(
                new TextWebSocketFrame(
                        "[Server at:]" + LocalDateTime.now()
                                + "Received a message containing:"+ content ) ); }}Copy the code

4.5 WebSocket apis

// 1. Initialize the WebSocket, ws: consistent with the protocol prefix specified by the back-end Netty
var socket = new WebSocket("ws//ip:[port]");

// 2. Lifecycle methods:
onopen()// This method is fired only once when the client and server establish a connection
onmessage()// This method is triggered whenever the server pushes a message to the client
onerror()// This method is triggered when the connection between the client and server fails
onclose()This method is triggered when the client and server connection is closed

// 3. Active approach
Socket.send()// The client sends a message to the server
Socket.close()// Connect the client port to the server
Copy the code

4.6 The front-end integrates WebSocket to send messages to the server

<! DOCTYPEhtml>
<html>
	<head>
		<meta charset="utf-8" />
		<title>Netty Real-time communication</title>
	</head>
	<body>
		<div id="app">Send a message:<input type="text" id="msgContent" />
			<button onclick="CHAT.chat()">Message is sent</button>
			<hr>Receiving messages:<div id="receiveMsg"></div>
		</div>
	</body>
</html>
<script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.4.0/jquery.min.js"></script>
<script src="https://unpkg.com/vue@next"></script>
<script src="https://cdn.bootcdn.net/ajax/libs/axios/0.20.0/axios.js"></script>
<script>
	window.CHAT = {
		socket: null.// Define a webSocket initialization method
		init: function() {
			// Check whether the browser supports Websocket
			if (window.WebSocket) {
				// 1. If yes, create a WebSocket object
				CHAT.socket = new WebSocket("ws://localhost:8088/ws")

				// This method is fired only once when the client and server establish a connection
				CHAT.socket.onopen = function() {
					console.log("Connection established successfully!")};This method is triggered when the client and server connection is closed
				CHAT.socket.close = function() {
					console.log("Connection closed!")};// This method is triggered when the connection between the client and server fails
				CHAT.socket.onerror = function() {
					console.log("Abnormal connection!")};// This method is triggered whenever the server pushes a message to the client
				CHAT.socket.onmessage = function(data) {
					// data indicates the data that the server responds to the client
					console.log("Receive message:" + data.data)
					var receiveMsg = document.getElementById("receiveMsg");
					var html = receiveMsg.innerHTML;
					// The newly received message content is embedded in the page
					receiveMsg.innerHTML = html + "<br/>"+ data.data; }}else {
				console.log("Your browser does not support the Websocket protocol")}},// Encapsulate the socket.send () method
		chat: function() {
			// Get the input in the send message box
			var msgContent = document.getElementById("msgContent").value;
			
			// Send the message content entered by the client to the serverCHAT.socket.send(msgContent); }}// Execute custom webSocket initialization methods
	CHAT.init()
</script>
Copy the code

4.7 Starting the Test in Case 2

Front-end page test back-end send:



The back-end Netty server receives the message and looks at the console print:



Test successful!

4.8 Message Sending Process Analysis

For WebSocket + Netty server internal processing process, in case 1 process summary inside analysis!

5. Extension: Chat APP actual combat — chat function part of the code

5.1 Front-end Code

  • Chat page chate. HTML: chate. HTML code to view
  • Chatlist. HTML: Chatlist. HTML code to view
  • Front-end encapsulated objects as well as logic method app.js: app.js code view

5.2 Back-end code

For the complete back-end code, see: Back-end code Address

Message-related entity classes
  • DataContent: Message body entity class (internally encapsulated UserChatMsg)
  • UserChatMsg: Message content entity class
WebSocketServer Server primary boot class
/ * * *@Auther: csp1999
 * @Date: 2020/09/23/9:51
 * @Description: WebSocket + Netty server boot class (here embedded in SpringBoot, along with the SpringBoot main function boot) */
@Component
public class WebSocketServer {

    /** ** WebSocketServer server instantiation method * (private) decorates, external cannot call */ directly
    private static class SingletionWSServer {
        static final WebSocketServer instance = new WebSocketServer();
    }

    /** * External callable WebSocketServer instance * is called in the NettyBooter class, making it start the Netty server ** (public) modifier along with the main springBoot function@return* /
    public static WebSocketServer getInstance(a) {
        return SingletionWSServer.instance;
    }

    /** * main thread pool */
    private EventLoopGroup mainGroup;

    /** * from the thread pool */
    private EventLoopGroup subGroup;

    /** * Server driver class */
    private ServerBootstrap serverBootstrap;

    /** * ChannelFuture */
    private ChannelFuture future;

    /** * port */
    private static final int PORT = 8088;

    /** * Private network IP address */
    //private static final String HOST = "172.17.218.21"; // Server internal IP address
    private static final String HOST = "192.168.1.6";/ / the machine IP

    public WebSocketServer(a) {
        // Main thread group: used to receive client request links without any processing
        mainGroup = new NioEventLoopGroup();
        // Slave thread group: the main thread group gives tasks to slave thread groups to do
        subGroup = new NioEventLoopGroup();
        // Define the server driver class
        serverBootstrap = new ServerBootstrap();
        // Server driver classes bind master and slave thread groups
        serverBootstrap.group(mainGroup, subGroup)
                // Server driver classes bind niO bidirectional channels
                .channel(NioServerSocketChannel.class)
                // The server driver class initializes the child handler (used to handle tasks from the thread pool)
                .childHandler(new WebSocketServerInitialzer());
    }

    public void start(a) {
        this.future = serverBootstrap.bind(PORT);// The server driver class binds the port and IP
        //this.future = serverBootstrap.bind(HOST,PORT); // The server driver class binds the port and IP
        if (future.isSuccess()) {
            System.out.println("Netty started successfully..."); }}}Copy the code
The initializer WebSocketServerInitialzer service
/ * * *@Auther: csp1999
 * @Date: 2020/09/23 / * he said@Description: WebSocket + Netty service initializer * After the Netty server starts the class binding Channel and binds the initializer in the Channel child handler, it executes the initializer's initialization method */
public class WebSocketServerInitialzer extends ChannelInitializer<SocketChannel> {
    @Override
    protected void initChannel(SocketChannel socketChannel) throws Exception {
        // Get the client/server pipeline
        ChannelPipeline channelPipeline = socketChannel.pipeline();

        // Websocket The HTTP codec required by the HTTP protocol
        channelPipeline.addLast(new HttpServerCodec());

        /* * There are some streams generated over HTTP that we need to process * so we need to use Netty to support reading and writing of this big stream * this class is: ChunkedWriteHandler */
        channelPipeline.addLast(new ChunkedWriteHandler());
        // Aggregate httpMessage into response or request
        channelPipeline.addLast(new HttpObjectAggregator(1024 * 64));

        / / = = = = = = = = = = = = = = = = = = = = = = = = = = = increase heart support = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
        /* * Netty's own read/write handler * Disconnects from the client if no read/write heartbeat (ALL) is sent to the server within 1 minute. * If there is idle read/write, no processing is done. * Parameter 1: idle read time unit s * Parameter 1: Write idle time unit s * Parameter 1: write idle time unit s */
        channelPipeline.addLast(new IdleStateHandler(8.16.32));

        / / = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =

        // Custom idle status monitoring handler
        channelPipeline.addLast(new HeartBeatHandler());

        Routing address * * * based on HTTP/websocket add WebSocketServerProtocolHandler will help deal with some complicated things * such as heavy processing handshake action: handshaking (close, ping, ping) : Ping +ping= Heartbeat * For websockets, frAMS are transmitted. Different data types have different FRAMS */
        channelPipeline.addLast(new WebSocketServerProtocolHandler("/websocket"));

        // Custom handler
        channelPipeline.addLast(newChatHandler()); }}Copy the code
Each client user ID corresponds to a Channel
/ * * *@Auther: csp1999
 * @Date: at 12:30 2020/09/25 / *@Description: user-Channel pipe relational entity class (used to associate a client-acquired pipe Channel with a user's userID) */
public class UserChannelRelation {

    Key: user ID * value: Channel Channel */
    private static HashMap<String, Channel> manage = new HashMap<>();

    public static void put(String senderId, Channel channel) {
        manage.put(senderId, channel);
    }

    public static Channel get(String senderId) {
        return manage.get(senderId);
    }

    // For printout tests
    public static void output(a) {
        for (Map.Entry<String, Channel> entry : manage.entrySet()) {
            System.out.println("UserId: " + entry.getKey()
                    + ",ChannelId: "+ entry.getValue().id().asLongText()); }}}Copy the code
ChatHandler custom message processing assistant class
/ * * *@Auther: csp1999
 * @Date: 2020/09/23/10:49
 * @Description: custom helper class handler: used for processing the chat message handler * need to inherit SimpleChannelInboundHandler < TextWebSocketFrame > abstract class, and write the corresponding method. * Frame is the carrier of the message. This class is called TextWebSocketFrame */
public class ChatHandler extends SimpleChannelInboundHandler<TextWebSocketFrame> {

    ChannelGroup * Each Client corresponds to a Channel ~ */
    public static ChannelGroup userClients = new DefaultChannelGroup(GlobalEventExecutor.INSTANCE);

    /** * Reads and outputs data from a channel **@paramCTX CTX context: The channel can be retrieved from the context, or the response result can be rendered back to the client *@paramMessage transmitted by the MSG client: The message type is TextWebSocketFrame *@throws Exception
     */
    @Override
    protected void channelRead0(ChannelHandlerContext ctx, TextWebSocketFrame msg) throws Exception {
        // Get the message transferred by the client
        String content = msg.text();

        // 1. Obtain the message sent by the client and transfer it to the DataContent object type
        // Method 1:
        DataContent dataContent = JSON.parseObject(content, DataContent.class);
        // Method 2: DataContent DataContent = jsonutils.jsonTopojo (content, DataContent.
        Integer action = dataContent.getAction();// Message behavior
        Channel channel = ctx.channel();// The client generates a channel after the request, which is retrieved from the CTX context object

        // 2. Determine the message type (behavior) and process different services according to different types
        / / MsgActionEnum. CONNECT. Type = = 1, "the first time (or reconnection) initialization connection" behavior
        if (action.equals(MsgActionEnum.CONNECT.type)) {
            // 2.1 Initialize channel when webSocket is opened for the first time, associate channel with the sender's userID, and store UserChannelRelation
            String senderId = dataContent.getUserChatMsg().getSenderId();
            UserChannelRelation.put(senderId, channel);/ / a hashMap collection

            //UserChannelRelation.output(); / / test

        } else if (action.equals(MsgActionEnum.CHAT.type)) {// Chat messages
            / / MsgActionEnum. CONNECT. Type = = 2, "chat messages" behavior

            2.2 Save the chat record to the database and mark the signed status of the message (unsigned)
            UserChatMsg userChatMsg = dataContent.getUserChatMsg();
            String msgContent = userChatMsg.getMsg();
            String senderId = userChatMsg.getSenderId();
            String receiverId = userChatMsg.getReceiverId();
            // Call userService to save the message to the database and mark the message as unreceived
            // Note: The chatHandler is not taken over by the Spring container and cannot be injected to get the userService
            // Therefore: use the SpringUtils tool class for manual injection
            UserService userService = (UserService) SpringUtil.getBean("userServiceImpl");
            String msgId = userService.saveMsg(userChatMsg);// Save chat messages to the database
            userChatMsg.setMsgId(msgId);

            DataContent dataContentMsg = new DataContent();
            dataContentMsg.setUserChatMsg(userChatMsg);// Save the user chat message content to the full message content entity class

            // Send a message
            // Get the receiver's channel from the global user's channel relationship
            Channel receiveChannel = UserChannelRelation.get(receiverId);
            if (receiveChannel == null) {
                // Offline user
            } else {
                // If receiveChannel is not empty, check whether the corresponding channel exists from the channelGroup
                Channel findChannel = userClients.find(receiveChannel.id());
                if(findChannel ! =null) {
                    // The user is online
                    // Method 1: Write the message content to the channel and refresh
                    JSONObject jsonObject = new JSONObject();
                    String jsonString = jsonObject.toJSONString(dataContentMsg);
                    receiveChannel.writeAndFlush(
                            new TextWebSocketFrame(
                                    jsonString
                                    // Jsonutils. objectToJson(dataContent))); }else {
                    // Offline user}}}else if (action.equals(MsgActionEnum.SIGNED.type)) {// The message is received
            / / MsgActionEnum. CONNECT. Type = = 3, "message to sign for" behavior

            // 2.3 Signature message Type: Modify the signature state of the corresponding message in the database (signed)
            UserService userService = (UserService) SpringUtil.getBean("userServiceImpl");
            // The extension field in a signed message represents the comma interval between the id of the message to be signed
            String msgIdStr = dataContent.getExtend();
            String[] msgsId = msgIdStr.split(",");

            List<String> msgIdList = new ArrayList<>();
            for (String msgId : msgsId) {
                if (!StringUtils.isEmpty(msgId)) {
                    msgIdList.add(msgId);
                }
            }
            // Console prints output to view
            msgIdList.forEach(msgId -> {
                //System.out.println(msgId);
            });

            if(! msgIdList.isEmpty()) {// Message batch signature, update markuserService.updateMsgSigned(msgIdList); }}else if (action.equals(MsgActionEnum.KEEPALIVE.type)) {// The client holds the heartbeat
            / / MsgActionEnum. CONNECT. Type = = 4, "the client to keep the heart"

            // 2.4 Received heartbeat message
            System.out.println("Received from channel [" + channel + "] heartbeat packet"); }}/** * (this method is triggered when the client is created) : ** Add this channel to the channel group ** when the client is connected to the server@param: CTX context: A channel can be retrieved from the context, or the response result can be rendered back to the client *@return: void
     * @create: 2020/9/23 11:01
     * @author: csp1999
     */
    @Override
    public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
        / / for the channel
        Channel channel = ctx.channel();
        // Add the channel to the channel management group
        userClients.add(channel);// Add channels with heartbeat links to the userClients unified directory and manage
    }

    /** * (this method is triggered when the browser is closed (the user leaves the client) : * Removes the channel from the channel group ** when the client disconnects from the server@param: CTX context: A channel can be retrieved from the context, or the response result can be rendered back to the client *@return: void
     * @create: 2020/9/23 11:01
     * @author: csp1999
     */
    @Override
    public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {
        / / get channelId
        String chanelId = ctx.channel().id().asShortText();
        System.out.println("Client removed: channel ID:" + chanelId);

        // Remove channel from userClients
        userClients.remove(ctx.channel());
        /* * Remove the channel from the channel management group * If the user closes the corresponding browser, the channel will be removed automatically. * So the channel is removed even if the remove method is not called, * so can not write clients. Remove (channel); System.out.println(" client is disconnected, channel is long id: "+ ctx.channel().id().aslongText ()); System.out.println(" the client is disconnected, the short ID of the channel is: "+ ctx.channel().id().asshorttext ()); * /
    }

    /** * If the client fails to connect to the netty server, remove it from the channelGroup@paramCTX context: A channel can be retrieved from the context, or the response result can be rendered back to the client *@param cause
     * @throws Exception
     */
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        cause.printStackTrace();
        // Close the connection and remove it from the channelGroup if an exception occursctx.channel().close(); userClients.remove(ctx.channel()); }}Copy the code
Start the Netty + WebSocket server with the SpringBoot main function
/** * During the startup of the IOC container, when all the beans have been processed, the Spring IOC container has an action to publish the event. * Let our bean implement the ApplicationListener interface so that when an event is published, [Spring] 's IOC container takes the instance object of the container as the event source class and finds the listener of the event from it. The onApplicationEvent(E Event) method in the ApplicationListener interface instance is called, */
@Component
public class NettyBooter implements ApplicationListener<ContextRefreshedEvent> {

    @Override
    public void onApplicationEvent(ContextRefreshedEvent event) {
        if (event.getApplicationContext().getParent() == null) {
            try {
                WebSocketServer.getInstance().start();// Start the Netty server with the springboot main function
            } catch(Exception e) { e.printStackTrace(); }}}}Copy the code