Spring boot + websocket + netty to push messages

Keywords: Programming Netty socket codec JSON

Thank you for your reference- http://bjbsair.com/2020-04-01/tech-info/18392.html

One of the application scenarios of Netty in the project: the message push function can be used to push messages to all users, or to specify a user. The spring boot project is created. The background server uses Netty technology, and the front page uses WebSocket technology.

General realization idea:

When the front-end uses webSocket to create a connection with the server, it passes the user ID to the server and associates the user ID with the channel for storage. Meanwhile, it puts the channel into the channel group. If you need to send messages to all users, you can directly execute the writeAndFlush() method of the channel group. If you need to send messages to the specified users, you can query the corresponding channel according to the user ID, Then execute the writeAndFlush() method to get the message pushed by the server, and display the message content to the text field

The following is the specific code implementation. Basically, each step of operation is provided with notes. It should be easier to understand with notes.

**Step 0: * * introduce the dependency of Netty and a toolkit (only json tools are used, which can be replaced by other json tools)

<dependency>  
  <groupId>io.netty</groupId>  
  <artifactId>netty-all</artifactId>   
  <version>4.1.33.Final</version>  
</dependency>  
  
<dependency>  
  <groupId>cn.hutool</groupId>  
  <artifactId>hutool-all</artifactId>  
  <version>5.2.3</version>  
</dependency>

**Step 1: * * define a channel group in NettyConfig, manage all channels, and define a map to manage the corresponding relationship between users and channels.

package com.sixj.nettypush.config;  
  
import io.netty.channel.Channel;  
import io.netty.channel.group.ChannelGroup;  
import io.netty.channel.group.DefaultChannelGroup;  
import io.netty.util.concurrent.GlobalEventExecutor;  
  
import java.util.concurrent.ConcurrentHashMap;  
  
/**  
 * @author sixiaojie  
 * @date 2020-03-28-15:07  
 */  
public class NettyConfig {  
    /**  
     * Define a channel group and manage all channels  
     * GlobalEventExecutor.INSTANCE Is a global event executor, a single example  
     */  
    private static ChannelGroup channelGroup = new DefaultChannelGroup(GlobalEventExecutor.INSTANCE);  
  
    /**  
     * Store the corresponding information between the user and Chanel to send messages to the specified user  
     */  
    private static ConcurrentHashMap<String,Channel> userChannelMap = new ConcurrentHashMap<>();  
  
    private NettyConfig() {}  
      
    /**  
     * Get channel group  
     * @return  
     */  
    public static ChannelGroup getChannelGroup() {  
        return channelGroup;  
    }  
  
    /**  
     * Get user channel map  
     * @return  
     */  
    public static ConcurrentHashMap<String,Channel> getUserChannelMap(){  
        return userChannelMap;  
    }  

**Step 2: * * create a netty server, define two eventloopgroups, and define the tcp connection requests of the secondary client of bossGroup. workGroup is responsible for the read and write operations with the client before. It should be noted that a new thread needs to be opened to execute the netty server, otherwise the main thread will be blocked, and other controller interfaces of the project will not be called at that time.

package com.sixj.nettypush.websocket;  
  
import io.netty.bootstrap.ServerBootstrap;  
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.NioServerSocketChannel;  
import io.netty.handler.codec.http.HttpObjectAggregator;  
import io.netty.handler.codec.http.HttpServerCodec;  
import io.netty.handler.codec.http.websocketx.WebSocketServerProtocolHandler;  
import io.netty.handler.codec.serialization.ObjectEncoder;  
import io.netty.handler.stream.ChunkedWriteHandler;  
import org.slf4j.Logger;  
import org.slf4j.LoggerFactory;  
import org.springframework.beans.factory.annotation.Autowired;  
import org.springframework.beans.factory.annotation.Value;  
import org.springframework.stereotype.Component;  
  
import javax.annotation.PostConstruct;  
import javax.annotation.PreDestroy;  
import java.net.InetSocketAddress;  
  
/**  
 * @author sixiaojie  
 * @date 2020-03-28-13:44  
 */  
  
@Component  
public class NettyServer{  
    private static final Logger log = LoggerFactory.getLogger(NettyServer.class);  
    /**  
     * webSocket Protocol name  
     */  
    private static final String WEBSOCKET_PROTOCOL = "WebSocket";  
  
    /**  
     * Port number  
     */  
    @Value("${webSocket.netty.port:58080}")  
    private int port;  
  
    /**  
     * webSocket Route  
     */  
    @Value("${webSocket.netty.path:/webSocket}")  
    private String webSocketPath;  
  
    @Autowired  
    private WebSocketHandler webSocketHandler;  
  
    private EventLoopGroup bossGroup;  
    private EventLoopGroup workGroup;  
  
    /**  
     * start-up  
     * @throws InterruptedException  
     */  
    private void start() throws InterruptedException {  
        bossGroup = new NioEventLoopGroup();  
        workGroup = new NioEventLoopGroup();  
        ServerBootstrap bootstrap = new ServerBootstrap();  
        // For the tcp connection request of the secondary client of bossGroup, workGroup is responsible for the read and write operations before the client  
        bootstrap.group(bossGroup,workGroup);  
        // Set channel of NIO type  
        bootstrap.channel(NioServerSocketChannel.class);  
        // Set listening port  
        bootstrap.localAddress(new InetSocketAddress(port));  
        // A channel is created when the connection arrives  
        bootstrap.childHandler(new ChannelInitializer<SocketChannel>() {  
  
            @Override  
            protected void initChannel(SocketChannel ch) throws Exception {  
                // The Handler in the pipeline management channel is used to handle business  
                // The webSocket protocol itself is based on the http protocol, so we also need to use the http codec here  
                ch.pipeline().addLast(new HttpServerCodec());  
                ch.pipeline().addLast(new ObjectEncoder());  
                // A processor written in blocks  
                ch.pipeline().addLast(new ChunkedWriteHandler());  
                /*  
                Explain:  
                1,http Data is segmented during transmission, and HttpObjectAggregator can aggregate multiple segments  
                2,That's why when a browser sends a lot of data, it sends multiple http requests  
                 */  
                ch.pipeline().addLast(new HttpObjectAggregator(8192));  
                /*  
                Explain:  
                1,Corresponding to webSocket, its data is passed in the form of frame  
                2,When the browser requests, ws://localhost:58080/xxx indicates the requested uri  
                3,The core function is to upgrade http protocol to ws protocol and maintain long connection  
                */  
                ch.pipeline().addLast(new WebSocketServerProtocolHandler(webSocketPath, WEBSOCKET_PROTOCOL, true, 65536 * 10));  
                // Custom handler, handling business logic  
                ch.pipeline().addLast(webSocketHandler);  
  
            }  
        });  
        // After the configuration is completed, start binding the server, and block by calling the sync synchronization method until the binding is successful  
        ChannelFuture channelFuture = bootstrap.bind().sync();  
        log.info("Server started and listen on:{}",channelFuture.channel().localAddress());  
        // Monitor the closed channel  
        channelFuture.channel().closeFuture().sync();  
    }  
  
    /**  
     * Release resources  
     * @throws InterruptedException  
     */  
    @PreDestroy  
    public void destroy() throws InterruptedException {  
        if(bossGroup != null){  
            bossGroup.shutdownGracefully().sync();  
        }  
        if(workGroup != null){  
            workGroup.shutdownGracefully().sync();  
        }  
    }  
    @PostConstruct()  
    public void init() {  
        //A new thread needs to be opened to execute the netty server  
        new Thread(() -> {  
            try {  
                start();  
            } catch (InterruptedException e) {  
                e.printStackTrace();  
            }  
        }).start();  
    }  
}

Step 3: implement the WebSocketHandler of the business, and see the notes for the implementation logic

package com.sixj.nettypush.websocket;  
  
import cn.hutool.json.JSONObject;  
import cn.hutool.json.JSONUtil;  
import com.sixj.nettypush.config.NettyConfig;  
import io.netty.channel.ChannelHandler;  
import io.netty.channel.ChannelHandlerContext;  
import io.netty.channel.SimpleChannelInboundHandler;  
import io.netty.handler.codec.http.websocketx.TextWebSocketFrame;  
import io.netty.util.AttributeKey;  
import org.slf4j.Logger;  
import org.slf4j.LoggerFactory;  
import org.springframework.stereotype.Component;  
  
  
/**  
 * TextWebSocketFrame Type, representing a text frame  
 * @author sixiaojie  
 * @date 2020-03-28-13:47  
 */  
@Component  
@ChannelHandler.Sharable  
public class WebSocketHandler extends SimpleChannelInboundHandler<TextWebSocketFrame> {  
    private static final Logger log = LoggerFactory.getLogger(NettyServer.class);  
  
    /**  
     * Once connected, the first is executed  
     * @param ctx  
     * @throws Exception  
     */  
    @Override  
    public void handlerAdded(ChannelHandlerContext ctx) throws Exception {  
        log.info("handlerAdded Be called"+ctx.channel().id().asLongText());  
        // Add to channelGroup channel group  
        NettyConfig.getChannelGroup().add(ctx.channel());  
    }  
  
    /**  
     * Read data  
     */  
    @Override  
    protected void channelRead0(ChannelHandlerContext ctx, TextWebSocketFrame msg) throws Exception {  
        log.info("Server received message:{}",msg.text());  
  
        // Get user ID and associate with channel  
        JSONObject jsonObject = JSONUtil.parseObj(msg.text());  
        String uid = jsonObject.getStr("uid");  
        NettyConfig.getUserChannelMap().put(uid,ctx.channel());  
  
        // The user ID is added to the channel as a custom attribute, which is convenient to obtain the user ID in the channel at any time  
        AttributeKey<String> key = AttributeKey.valueOf("userId");  
        ctx.channel().attr(key).setIfAbsent(uid);  
  
        // Reply message  
        ctx.channel().writeAndFlush(new TextWebSocketFrame("Server connection succeeded!"));  
    }  
  
    @Override  
    public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {  
        log.info("handlerRemoved Be called"+ctx.channel().id().asLongText());  
        // Delete channel  
        NettyConfig.getChannelGroup().remove(ctx.channel());  
        removeUserId(ctx);  
    }  
  
    @Override  
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {  
        log.info("Exception:{}",cause.getMessage());  
        // Delete channel  
        NettyConfig.getChannelGroup().remove(ctx.channel());  
        removeUserId(ctx);  
        ctx.close();  
    }  
  
    /**  
     * Delete the corresponding relationship between user and channel  
     * @param ctx  
     */  
    private void removeUserId(ChannelHandlerContext ctx){  
        AttributeKey<String> key = AttributeKey.valueOf("userId");  
        String userId = ctx.channel().attr(key).get();  
        NettyConfig.getUserChannelMap().remove(userId);  
    }  
}

**Step 4: * * specific message push interface

public interface PushService {  
    /**  
     * Push to specified user  
     * @param userId  
     * @param msg  
     */  
    void pushMsgToOne(String userId,String msg);  
  
    /**  
     * Push to all users  
     * @param msg  
     */  
    void pushMsgToAll(String msg);  
}

Interface implementation class:

import java.util.concurrent.ConcurrentHashMap;  
  
/**  
 * @author sixiaojie  
 * @date 2020-03-30-20:10  
 */  
@Service  
public class PushServiceImpl implements PushService {  
  
    @Override  
    public void pushMsgToOne(String userId, String msg){  
        ConcurrentHashMap<String, Channel> userChannelMap = NettyConfig.getUserChannelMap();  
        Channel channel = userChannelMap.get(userId);  
        channel.writeAndFlush(new TextWebSocketFrame(msg));  
    }  
    @Override  
    public void pushMsgToAll(String msg){  
        NettyConfig.getChannelGroup().writeAndFlush(new TextWebSocketFrame(msg));  
    }  
}

controller:

package com.sixj.nettypush.controller;  
  
import com.sixj.nettypush.service.PushService;  
import org.springframework.beans.factory.annotation.Autowired;  
import org.springframework.web.bind.annotation.PostMapping;  
import org.springframework.web.bind.annotation.RequestMapping;  
import org.springframework.web.bind.annotation.RequestParam;  
import org.springframework.web.bind.annotation.RestController;  
  
/**  
 * @author sixiaojie  
 * @date 2020-03-30-20:08  
 */  
@RestController  
@RequestMapping("/push")  
public class PushController {  
  
    @Autowired  
    private PushService pushService;  
  
    /**  
     * Push to all users  
     * @param msg  
     */  
    @PostMapping("/pushAll")  
    public void pushToAll(@RequestParam("msg") String msg){  
        pushService.pushMsgToAll(msg);  
    }  
    /**  
     * Push to specified user  
     * @param userId  
     * @param msg  
     */  
    @PostMapping("/pushOne")  
    public void pushMsgToOne(@RequestParam("userId") String userId,@RequestParam("msg") String msg){  
        pushService.pushMsgToOne(userId,msg);  
    }  
  
}

**Step 5: * * front end html page

<!DOCTYPE html>  
<html lang="en">  
<head>  
    <meta charset="UTF-8">  
    <title>Title</title>  
</head>  
<body>  
<script>  
    var socket;  
    // Determine whether the current browser supports webSocket  
    if(window.WebSocket){  
        socket = new WebSocket("ws://192.168.174.25:58080/webSocket")  
        // Equivalent to the read event of the channel, ev receives the message sent back by the server  
        socket.onmessage = function (ev) {  
            var rt = document.getElementById("responseText");  
            rt.value = rt.value + "\n" + ev.data;  
        }  
        // Equivalent to connection on  
        socket.onopen = function (ev) {  
            var rt = document.getElementById("responseText");  
            rt.value =  "Connection turned on..."  
            socket.send(  
                JSON.stringify({  
                    // The connection succeeds. The user ID is passed to the server  
                    uid: "123456"  
                })  
            );  
        }  
        // Equivalent to connection closing  
        socket.onclose = function (ev) {  
            var rt = document.getElementById("responseText");  
            rt.value = rt.value + "\n" + "Connection closed...";  
        }  
    }else{  
        alert("Current browser does not support webSocket")  
    }  
  
  
</script>  
    <form onsubmit="return false">  
        <textarea id="responseText" style="height: 150px; width: 300px;"></textarea>  
        <input type="button" value="Empty content" onclick="document.getElementById('responseText').value=''">  
    </form>  
</body>  
</html>

So far, all the code has been written. Test it

Run the html file first, and you will see the message "server connection succeeded!" returned by the server to the front end , the back-end log will print the message received by the server: {"uid":"123456"}

Then use postman to test the pushed interface

Thank you for your reference- http://bjbsair.com/2020-04-01/tech-info/18392.html

One of the application scenarios of Netty in the project: the message push function can be used to push messages to all users, or to specify a user. The spring boot project is created. The background server uses Netty technology, and the front page uses WebSocket technology.

General realization idea:

When the front-end uses webSocket to create a connection with the server, it will pass the user ID to the server and associate the user ID with the channel for storage. Meanwhile, it will put the channel into the channel group. If it needs to send messages to all users, it will directly execute the writeAndFlush() method of the channel group. If it needs to send messages to the specified users, it will query the corresponding channel according to the user ID, Then execute the writeAndFlush() method to get the message pushed by the server, and display the message content to the text field

The following is the specific code implementation. Basically, each step of operation is provided with notes. It should be easier to understand with notes.

**Step 0: * * introduce the dependency of Netty and a toolkit (only json tools are used, which can be replaced by other json tools)

<dependency>  
  <groupId>io.netty</groupId>  
  <artifactId>netty-all</artifactId>   
  <version>4.1.33.Final</version>  
</dependency>  
  
<dependency>  
  <groupId>cn.hutool</groupId>  
  <artifactId>hutool-all</artifactId>  
  <version>5.2.3</version>  
</dependency>

**Step 1: * * define a channel group in NettyConfig, manage all channels, and define a map to manage the corresponding relationship between users and channels.

package com.sixj.nettypush.config;  
  
import io.netty.channel.Channel;  
import io.netty.channel.group.ChannelGroup;  
import io.netty.channel.group.DefaultChannelGroup;  
import io.netty.util.concurrent.GlobalEventExecutor;  
  
import java.util.concurrent.ConcurrentHashMap;  
  
/**  
 * @author sixiaojie  
 * @date 2020-03-28-15:07  
 */  
public class NettyConfig {  
    /**  
     * Define a channel group and manage all channels  
     * GlobalEventExecutor.INSTANCE Is a global event executor, a single example  
     */  
    private static ChannelGroup channelGroup = new DefaultChannelGroup(GlobalEventExecutor.INSTANCE);  
  
    /**  
     * Store the corresponding information between the user and Chanel to send messages to the specified user  
     */  
    private static ConcurrentHashMap<String,Channel> userChannelMap = new ConcurrentHashMap<>();  
  
    private NettyConfig() {}  
      
    /**  
     * Get channel group  
     * @return  
     */  
    public static ChannelGroup getChannelGroup() {  
        return channelGroup;  
    }  
  
    /**  
     * Get user channel map  
     * @return  
     */  
    public static ConcurrentHashMap<String,Channel> getUserChannelMap(){  
        return userChannelMap;  
    }  

**Step 2: * * create a netty server, define two eventloopgroups, and define the tcp connection requests of the secondary client of bossGroup. workGroup is responsible for the read and write operations with the client before. It should be noted that a new thread needs to be opened to execute the netty server, otherwise the main thread will be blocked, and other controller interfaces of the project will not be called at that time.

package com.sixj.nettypush.websocket;  
  
import io.netty.bootstrap.ServerBootstrap;  
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.NioServerSocketChannel;  
import io.netty.handler.codec.http.HttpObjectAggregator;  
import io.netty.handler.codec.http.HttpServerCodec;  
import io.netty.handler.codec.http.websocketx.WebSocketServerProtocolHandler;  
import io.netty.handler.codec.serialization.ObjectEncoder;  
import io.netty.handler.stream.ChunkedWriteHandler;  
import org.slf4j.Logger;  
import org.slf4j.LoggerFactory;  
import org.springframework.beans.factory.annotation.Autowired;  
import org.springframework.beans.factory.annotation.Value;  
import org.springframework.stereotype.Component;  
  
import javax.annotation.PostConstruct;  
import javax.annotation.PreDestroy;  
import java.net.InetSocketAddress;  
  
/**  
 * @author sixiaojie  
 * @date 2020-03-28-13:44  
 */  
  
@Component  
public class NettyServer{  
    private static final Logger log = LoggerFactory.getLogger(NettyServer.class);  
    /**  
     * webSocket Protocol name  
     */  
    private static final String WEBSOCKET_PROTOCOL = "WebSocket";  
  
    /**  
     * Port number  
     */  
    @Value("${webSocket.netty.port:58080}")  
    private int port;  
  
    /**  
     * webSocket Route  
     */  
    @Value("${webSocket.netty.path:/webSocket}")  
    private String webSocketPath;  
  
    @Autowired  
    private WebSocketHandler webSocketHandler;  
  
    private EventLoopGroup bossGroup;  
    private EventLoopGroup workGroup;  
  
    /**  
     * start-up  
     * @throws InterruptedException  
     */  
    private void start() throws InterruptedException {  
        bossGroup = new NioEventLoopGroup();  
        workGroup = new NioEventLoopGroup();  
        ServerBootstrap bootstrap = new ServerBootstrap();  
        // For the tcp connection request of the secondary client of bossGroup, workGroup is responsible for the read and write operations before the client  
        bootstrap.group(bossGroup,workGroup);  
        // Set channel of NIO type  
        bootstrap.channel(NioServerSocketChannel.class);  
        // Set listening port  
        bootstrap.localAddress(new InetSocketAddress(port));  
        // A channel is created when the connection arrives  
        bootstrap.childHandler(new ChannelInitializer<SocketChannel>() {  
  
            @Override  
            protected void initChannel(SocketChannel ch) throws Exception {  
                // The Handler in the pipeline management channel is used to handle business  
                // The webSocket protocol itself is based on the http protocol, so we also need to use the http codec here  
                ch.pipeline().addLast(new HttpServerCodec());  
                ch.pipeline().addLast(new ObjectEncoder());  
                // A processor written in blocks  
                ch.pipeline().addLast(new ChunkedWriteHandler());  
                /*  
                Explain:  
                1,http Data is segmented during transmission, and HttpObjectAggregator can aggregate multiple segments  
                2,That's why when a browser sends a lot of data, it sends multiple http requests  
                 */  
                ch.pipeline().addLast(new HttpObjectAggregator(8192));  
                /*  
                Explain:  
                1,Corresponding to webSocket, its data is passed in the form of frame  
                2,When the browser requests, ws://localhost:58080/xxx indicates the requested uri  
                3,The core function is to upgrade http protocol to ws protocol and maintain long connection  
                */  
                ch.pipeline().addLast(new WebSocketServerProtocolHandler(webSocketPath, WEBSOCKET_PROTOCOL, true, 65536 * 10));  
                // Custom handler, handling business logic  
                ch.pipeline().addLast(webSocketHandler);  
  
            }  
        });  
        // After the configuration is completed, start binding the server, and block by calling the sync synchronization method until the binding is successful  
        ChannelFuture channelFuture = bootstrap.bind().sync();  
        log.info("Server started and listen on:{}",channelFuture.channel().localAddress());  
        // Monitor the closed channel  
        channelFuture.channel().closeFuture().sync();  
    }  
  
    /**  
     * Release resources  
     * @throws InterruptedException  
     */  
    @PreDestroy  
    public void destroy() throws InterruptedException {  
        if(bossGroup != null){  
            bossGroup.shutdownGracefully().sync();  
        }  
        if(workGroup != null){  
            workGroup.shutdownGracefully().sync();  
        }  
    }  
    @PostConstruct()  
    public void init() {  
        //A new thread needs to be opened to execute the netty server  
        new Thread(() -> {  
            try {  
                start();  
            } catch (InterruptedException e) {  
                e.printStackTrace();  
            }  
        }).start();  
    }  
}

Step 3: implement the WebSocketHandler of the business, and see the notes for the implementation logic

package com.sixj.nettypush.websocket;  
  
import cn.hutool.json.JSONObject;  
import cn.hutool.json.JSONUtil;  
import com.sixj.nettypush.config.NettyConfig;  
import io.netty.channel.ChannelHandler;  
import io.netty.channel.ChannelHandlerContext;  
import io.netty.channel.SimpleChannelInboundHandler;  
import io.netty.handler.codec.http.websocketx.TextWebSocketFrame;  
import io.netty.util.AttributeKey;  
import org.slf4j.Logger;  
import org.slf4j.LoggerFactory;  
import org.springframework.stereotype.Component;  
  
  
/**  
 * TextWebSocketFrame Type, representing a text frame  
 * @author sixiaojie  
 * @date 2020-03-28-13:47  
 */  
@Component  
@ChannelHandler.Sharable  
public class WebSocketHandler extends SimpleChannelInboundHandler<TextWebSocketFrame> {  
    private static final Logger log = LoggerFactory.getLogger(NettyServer.class);  
  
    /**  
     * Once connected, the first is executed  
     * @param ctx  
     * @throws Exception  
     */  
    @Override  
    public void handlerAdded(ChannelHandlerContext ctx) throws Exception {  
        log.info("handlerAdded Be called"+ctx.channel().id().asLongText());  
        // Add to channelGroup channel group  
        NettyConfig.getChannelGroup().add(ctx.channel());  
    }  
  
    /**  
     * Read data  
     */  
    @Override  
    protected void channelRead0(ChannelHandlerContext ctx, TextWebSocketFrame msg) throws Exception {  
        log.info("Server received message:{}",msg.text());  
  
        // Get user ID and associate with channel  
        JSONObject jsonObject = JSONUtil.parseObj(msg.text());  
        String uid = jsonObject.getStr("uid");  
        NettyConfig.getUserChannelMap().put(uid,ctx.channel());  
  
        // The user ID is added to the channel as a custom attribute, which is convenient to obtain the user ID in the channel at any time  
        AttributeKey<String> key = AttributeKey.valueOf("userId");  
        ctx.channel().attr(key).setIfAbsent(uid);  
  
        // Reply message  
        ctx.channel().writeAndFlush(new TextWebSocketFrame("Server connection succeeded!"));  
    }  
  
    @Override  
    public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {  
        log.info("handlerRemoved Be called"+ctx.channel().id().asLongText());  
        // Delete channel  
        NettyConfig.getChannelGroup().remove(ctx.channel());  
        removeUserId(ctx);  
    }  
  
    @Override  
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {  
        log.info("Exception:{}",cause.getMessage());  
        // Delete channel  
        NettyConfig.getChannelGroup().remove(ctx.channel());  
        removeUserId(ctx);  
        ctx.close();  
    }  
  
    /**  
     * Delete the corresponding relationship between user and channel  
     * @param ctx  
     */  
    private void removeUserId(ChannelHandlerContext ctx){  
        AttributeKey<String> key = AttributeKey.valueOf("userId");  
        String userId = ctx.channel().attr(key).get();  
        NettyConfig.getUserChannelMap().remove(userId);  
    }  
}

**Step 4: * * specific message push interface

public interface PushService {  
    /**  
     * Push to specified user  
     * @param userId  
     * @param msg  
     */  
    void pushMsgToOne(String userId,String msg);  
  
    /**  
     * Push to all users  
     * @param msg  
     */  
    void pushMsgToAll(String msg);  
}

Interface implementation class:

import java.util.concurrent.ConcurrentHashMap;  
  
/**  
 * @author sixiaojie  
 * @date 2020-03-30-20:10  
 */  
@Service  
public class PushServiceImpl implements PushService {  
  
    @Override  
    public void pushMsgToOne(String userId, String msg){  
        ConcurrentHashMap<String, Channel> userChannelMap = NettyConfig.getUserChannelMap();  
        Channel channel = userChannelMap.get(userId);  
        channel.writeAndFlush(new TextWebSocketFrame(msg));  
    }  
    @Override  
    public void pushMsgToAll(String msg){  
        NettyConfig.getChannelGroup().writeAndFlush(new TextWebSocketFrame(msg));  
    }  
}

controller:

package com.sixj.nettypush.controller;  
  
import com.sixj.nettypush.service.PushService;  
import org.springframework.beans.factory.annotation.Autowired;  
import org.springframework.web.bind.annotation.PostMapping;  
import org.springframework.web.bind.annotation.RequestMapping;  
import org.springframework.web.bind.annotation.RequestParam;  
import org.springframework.web.bind.annotation.RestController;  
  
/**  
 * @author sixiaojie  
 * @date 2020-03-30-20:08  
 */  
@RestController  
@RequestMapping("/push")  
public class PushController {  
  
    @Autowired  
    private PushService pushService;  
  
    /**  
     * Push to all users  
     * @param msg  
     */  
    @PostMapping("/pushAll")  
    public void pushToAll(@RequestParam("msg") String msg){  
        pushService.pushMsgToAll(msg);  
    }  
    /**  
     * Push to specified user  
     * @param userId  
     * @param msg  
     */  
    @PostMapping("/pushOne")  
    public void pushMsgToOne(@RequestParam("userId") String userId,@RequestParam("msg") String msg){  
        pushService.pushMsgToOne(userId,msg);  
    }  
  
}

**Step 5: * * front end html page

<!DOCTYPE html>  
<html lang="en">  
<head>  
    <meta charset="UTF-8">  
    <title>Title</title>  
</head>  
<body>  
<script>  
    var socket;  
    // Determine whether the current browser supports webSocket  
    if(window.WebSocket){  
        socket = new WebSocket("ws://192.168.174.25:58080/webSocket")  
        // Equivalent to the read event of the channel, ev receives the message sent back by the server  
        socket.onmessage = function (ev) {  
            var rt = document.getElementById("responseText");  
            rt.value = rt.value + "\n" + ev.data;  
        }  
        // Equivalent to connection on  
        socket.onopen = function (ev) {  
            var rt = document.getElementById("responseText");  
            rt.value =  "Connection turned on..."  
            socket.send(  
                JSON.stringify({  
                    // The connection succeeds. The user ID is passed to the server  
                    uid: "123456"  
                })  
            );  
        }  
        // Equivalent to connection closing  
        socket.onclose = function (ev) {  
            var rt = document.getElementById("responseText");  
            rt.value = rt.value + "\n" + "Connection closed...";  
        }  
    }else{  
        alert("Current browser does not support webSocket")  
    }  
  
  
</script>  
    <form onsubmit="return false">  
        <textarea id="responseText" style="height: 150px; width: 300px;"></textarea>  
        <input type="button" value="Empty content" onclick="document.getElementById('responseText').value=''">  
    </form>  
</body>  
</html>

So far, all the code has been written. Test it

Run the html file first, and you will see the message "server connection succeeded!" returned by the server to the front end , the back-end log will print the message received by the server: {"uid":"123456"}

Then use postman to test the pushed interface

Thank you for your reference- http://bjbsair.com/2020-04-01/tech-info/18392.html

One of the application scenarios of Netty in the project: the message push function can be used to push messages to all users, or to specify a user. The spring boot project is created. The background server uses Netty technology, and the front page uses WebSocket technology.

General realization idea:

When the front-end uses webSocket to create a connection with the server, it will pass the user ID to the server and associate the user ID with the channel for storage. Meanwhile, it will put the channel into the channel group. If it needs to send messages to all users, it will directly execute the writeAndFlush() method of the channel group. If it needs to send messages to the specified users, it will query the corresponding channel according to the user ID, Then execute the writeAndFlush() method to get the message pushed by the server, and display the message content to the text field

The following is the specific code implementation. Basically, each step of operation is provided with notes. It should be easier to understand with notes.

**Step 0: * * introduce the dependency of Netty and a toolkit (only json tools are used, which can be replaced by other json tools)

<dependency>  
  <groupId>io.netty</groupId>  
  <artifactId>netty-all</artifactId>   
  <version>4.1.33.Final</version>  
</dependency>  
  
<dependency>  
  <groupId>cn.hutool</groupId>  
  <artifactId>hutool-all</artifactId>  
  <version>5.2.3</version>  
</dependency>

**Step 1: * * define a channel group in NettyConfig, manage all channels, and define a map to manage the corresponding relationship between users and channels.

package com.sixj.nettypush.config;  
  
import io.netty.channel.Channel;  
import io.netty.channel.group.ChannelGroup;  
import io.netty.channel.group.DefaultChannelGroup;  
import io.netty.util.concurrent.GlobalEventExecutor;  
  
import java.util.concurrent.ConcurrentHashMap;  
  
/**  
 * @author sixiaojie  
 * @date 2020-03-28-15:07  
 */  
public class NettyConfig {  
    /**  
     * Define a channel group and manage all channels  
     * GlobalEventExecutor.INSTANCE Is a global event executor, a single example  
     */  
    private static ChannelGroup channelGroup = new DefaultChannelGroup(GlobalEventExecutor.INSTANCE);  
  
    /**  
     * Store the corresponding information between the user and Chanel to send messages to the specified user  
     */  
    private static ConcurrentHashMap<String,Channel> userChannelMap = new ConcurrentHashMap<>();  
  
    private NettyConfig() {}  
      
    /**  
     * Get channel group  
     * @return  
     */  
    public static ChannelGroup getChannelGroup() {  
        return channelGroup;  
    }  
  
    /**  
     * Get user channel map  
     * @return  
     */  
    public static ConcurrentHashMap<String,Channel> getUserChannelMap(){  
        return userChannelMap;  
    }  

**Step 2: * * create a netty server, define two eventloopgroups, and define the tcp connection requests of the secondary client of bossGroup. workGroup is responsible for the read and write operations with the client before. It should be noted that a new thread needs to be opened to execute the netty server, otherwise the main thread will be blocked, and other controller interfaces of the project will not be called at that time.

package com.sixj.nettypush.websocket;  
  
import io.netty.bootstrap.ServerBootstrap;  
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.NioServerSocketChannel;  
import io.netty.handler.codec.http.HttpObjectAggregator;  
import io.netty.handler.codec.http.HttpServerCodec;  
import io.netty.handler.codec.http.websocketx.WebSocketServerProtocolHandler;  
import io.netty.handler.codec.serialization.ObjectEncoder;  
import io.netty.handler.stream.ChunkedWriteHandler;  
import org.slf4j.Logger;  
import org.slf4j.LoggerFactory;  
import org.springframework.beans.factory.annotation.Autowired;  
import org.springframework.beans.factory.annotation.Value;  
import org.springframework.stereotype.Component;  
  
import javax.annotation.PostConstruct;  
import javax.annotation.PreDestroy;  
import java.net.InetSocketAddress;  
  
/**  
 * @author sixiaojie  
 * @date 2020-03-28-13:44  
 */  
  
@Component  
public class NettyServer{  
    private static final Logger log = LoggerFactory.getLogger(NettyServer.class);  
    /**  
     * webSocket Protocol name  
     */  
    private static final String WEBSOCKET_PROTOCOL = "WebSocket";  
  
    /**  
     * Port number  
     */  
    @Value("${webSocket.netty.port:58080}")  
    private int port;  
  
    /**  
     * webSocket Route  
     */  
    @Value("${webSocket.netty.path:/webSocket}")  
    private String webSocketPath;  
  
    @Autowired  
    private WebSocketHandler webSocketHandler;  
  
    private EventLoopGroup bossGroup;  
    private EventLoopGroup workGroup;  
  
    /**  
     * start-up  
     * @throws InterruptedException  
     */  
    private void start() throws InterruptedException {  
        bossGroup = new NioEventLoopGroup();  
        workGroup = new NioEventLoopGroup();  
        ServerBootstrap bootstrap = new ServerBootstrap();  
        // For the tcp connection request of the secondary client of bossGroup, workGroup is responsible for the read and write operations before the client  
        bootstrap.group(bossGroup,workGroup);  
        // Set channel of NIO type  
        bootstrap.channel(NioServerSocketChannel.class);  
        // Set listening port  
        bootstrap.localAddress(new InetSocketAddress(port));  
        // A channel is created when the connection arrives  
        bootstrap.childHandler(new ChannelInitializer<SocketChannel>() {  
  
            @Override  
            protected void initChannel(SocketChannel ch) throws Exception {  
                // The Handler in the pipeline management channel is used to handle business  
                // The webSocket protocol itself is based on the http protocol, so we also need to use the http codec here  
                ch.pipeline().addLast(new HttpServerCodec());  
                ch.pipeline().addLast(new ObjectEncoder());  
                // A processor written in blocks  
                ch.pipeline().addLast(new ChunkedWriteHandler());  
                /*  
                Explain:  
                1,http Data is segmented during transmission, and HttpObjectAggregator can aggregate multiple segments  
                2,That's why when a browser sends a lot of data, it sends multiple http requests  
                 */  
                ch.pipeline().addLast(new HttpObjectAggregator(8192));  
                /*  
                Explain:  
                1,Corresponding to webSocket, its data is passed in the form of frame  
                2,When the browser requests, ws://localhost:58080/xxx indicates the requested uri  
                3,The core function is to upgrade http protocol to ws protocol and maintain long connection  
                */  
                ch.pipeline().addLast(new WebSocketServerProtocolHandler(webSocketPath, WEBSOCKET_PROTOCOL, true, 65536 * 10));  
                // Custom handler, handling business logic  
                ch.pipeline().addLast(webSocketHandler);  
  
            }  
        });  
        // After the configuration is completed, start binding the server, and block by calling the sync synchronization method until the binding is successful  
        ChannelFuture channelFuture = bootstrap.bind().sync();  
        log.info("Server started and listen on:{}",channelFuture.channel().localAddress());  
        // Monitor the closed channel  
        channelFuture.channel().closeFuture().sync();  
    }  
  
    /**  
     * Release resources  
     * @throws InterruptedException  
     */  
    @PreDestroy  
    public void destroy() throws InterruptedException {  
        if(bossGroup != null){  
            bossGroup.shutdownGracefully().sync();  
        }  
        if(workGroup != null){  
            workGroup.shutdownGracefully().sync();  
        }  
    }  
    @PostConstruct()  
    public void init() {  
        //A new thread needs to be opened to execute the netty server  
        new Thread(() -> {  
            try {  
                start();  
            } catch (InterruptedException e) {  
                e.printStackTrace();  
            }  
        }).start();  
    }  
}

Step 3: implement the WebSocketHandler of the business, and see the notes for the implementation logic

package com.sixj.nettypush.websocket;  
  
import cn.hutool.json.JSONObject;  
import cn.hutool.json.JSONUtil;  
import com.sixj.nettypush.config.NettyConfig;  
import io.netty.channel.ChannelHandler;  
import io.netty.channel.ChannelHandlerContext;  
import io.netty.channel.SimpleChannelInboundHandler;  
import io.netty.handler.codec.http.websocketx.TextWebSocketFrame;  
import io.netty.util.AttributeKey;  
import org.slf4j.Logger;  
import org.slf4j.LoggerFactory;  
import org.springframework.stereotype.Component;  
  
  
/**  
 * TextWebSocketFrame Type, representing a text frame  
 * @author sixiaojie  
 * @date 2020-03-28-13:47  
 */  
@Component  
@ChannelHandler.Sharable  
public class WebSocketHandler extends SimpleChannelInboundHandler<TextWebSocketFrame> {  
    private static final Logger log = LoggerFactory.getLogger(NettyServer.class);  
  
    /**  
     * Once connected, the first is executed  
     * @param ctx  
     * @throws Exception  
     */  
    @Override  
    public void handlerAdded(ChannelHandlerContext ctx) throws Exception {  
        log.info("handlerAdded Be called"+ctx.channel().id().asLongText());  
        // Add to channelGroup channel group  
        NettyConfig.getChannelGroup().add(ctx.channel());  
    }  
  
    /**  
     * Read data  
     */  
    @Override  
    protected void channelRead0(ChannelHandlerContext ctx, TextWebSocketFrame msg) throws Exception {  
        log.info("Server received message:{}",msg.text());  
  
        // Get user ID and associate with channel  
        JSONObject jsonObject = JSONUtil.parseObj(msg.text());  
        String uid = jsonObject.getStr("uid");  
        NettyConfig.getUserChannelMap().put(uid,ctx.channel());  
  
        // The user ID is added to the channel as a custom attribute, which is convenient to obtain the user ID in the channel at any time  
        AttributeKey<String> key = AttributeKey.valueOf("userId");  
        ctx.channel().attr(key).setIfAbsent(uid);  
  
        // Reply message  
        ctx.channel().writeAndFlush(new TextWebSocketFrame("Server connection succeeded!"));  
    }  
  
    @Override  
    public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {  
        log.info("handlerRemoved Be called"+ctx.channel().id().asLongText());  
        // Delete channel  
        NettyConfig.getChannelGroup().remove(ctx.channel());  
        removeUserId(ctx);  
    }  
  
    @Override  
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {  
        log.info("Exception:{}",cause.getMessage());  
        // Delete channel  
        NettyConfig.getChannelGroup().remove(ctx.channel());  
        removeUserId(ctx);  
        ctx.close();  
    }  
  
    /**  
     * Delete the corresponding relationship between user and channel  
     * @param ctx  
     */  
    private void removeUserId(ChannelHandlerContext ctx){  
        AttributeKey<String> key = AttributeKey.valueOf("userId");  
        String userId = ctx.channel().attr(key).get();  
        NettyConfig.getUserChannelMap().remove(userId);  
    }  
}

**Step 4: * * specific message push interface

public interface PushService {  
    /**  
     * Push to specified user  
     * @param userId  
     * @param msg  
     */  
    void pushMsgToOne(String userId,String msg);  
  
    /**  
     * Push to all users  
     * @param msg  
     */  
    void pushMsgToAll(String msg);  
}

Interface implementation class:

import java.util.concurrent.ConcurrentHashMap;  
  
/**  
 * @author sixiaojie  
 * @date 2020-03-30-20:10  
 */  
@Service  
public class PushServiceImpl implements PushService {  
  
    @Override  
    public void pushMsgToOne(String userId, String msg){  
        ConcurrentHashMap<String, Channel> userChannelMap = NettyConfig.getUserChannelMap();  
        Channel channel = userChannelMap.get(userId);  
        channel.writeAndFlush(new TextWebSocketFrame(msg));  
    }  
    @Override  
    public void pushMsgToAll(String msg){  
        NettyConfig.getChannelGroup().writeAndFlush(new TextWebSocketFrame(msg));  
    }  
}

controller:

package com.sixj.nettypush.controller;  
  
import com.sixj.nettypush.service.PushService;  
import org.springframework.beans.factory.annotation.Autowired;  
import org.springframework.web.bind.annotation.PostMapping;  
import org.springframework.web.bind.annotation.RequestMapping;  
import org.springframework.web.bind.annotation.RequestParam;  
import org.springframework.web.bind.annotation.RestController;  
  
/**  
 * @author sixiaojie  
 * @date 2020-03-30-20:08  
 */  
@RestController  
@RequestMapping("/push")  
public class PushController {  
  
    @Autowired  
    private PushService pushService;  
  
    /**  
     * Push to all users  
     * @param msg  
     */  
    @PostMapping("/pushAll")  
    public void pushToAll(@RequestParam("msg") String msg){  
        pushService.pushMsgToAll(msg);  
    }  
    /**  
     * Push to specified user  
     * @param userId  
     * @param msg  
     */  
    @PostMapping("/pushOne")  
    public void pushMsgToOne(@RequestParam("userId") String userId,@RequestParam("msg") String msg){  
        pushService.pushMsgToOne(userId,msg);  
    }  
  
}

**Step 5: * * front end html page

<!DOCTYPE html>  
<html lang="en">  
<head>  
    <meta charset="UTF-8">  
    <title>Title</title>  
</head>  
<body>  
<script>  
    var socket;  
    // Determine whether the current browser supports webSocket  
    if(window.WebSocket){  
        socket = new WebSocket("ws://192.168.174.25:58080/webSocket")  
        // Equivalent to the read event of the channel, ev receives the message sent back by the server  
        socket.onmessage = function (ev) {  
            var rt = document.getElementById("responseText");  
            rt.value = rt.value + "\n" + ev.data;  
        }  
        // Equivalent to connection on  
        socket.onopen = function (ev) {  
            var rt = document.getElementById("responseText");  
            rt.value =  "Connection turned on..."  
            socket.send(  
                JSON.stringify({  
                    // The connection succeeds. The user ID is passed to the server  
                    uid: "123456"  
                })  
            );  
        }  
        // Equivalent to connection closing  
        socket.onclose = function (ev) {  
            var rt = document.getElementById("responseText");  
            rt.value = rt.value + "\n" + "Connection closed...";  
        }  
    }else{  
        alert("Current browser does not support webSocket")  
    }  
  
  
</script>  
    <form onsubmit="return false">  
        <textarea id="responseText" style="height: 150px; width: 300px;"></textarea>  
        <input type="button" value="Empty content" onclick="document.getElementById('responseText').value=''">  
    </form>  
</body>  
</html>

So far, all the code has been written. Test it

Run the html file first, and you will see the message "server connection succeeded!" returned by the server to the front end , the back-end log will print the message received by the server: {"uid":"123456"}

Then use postman to test the pushed interface

Thank you for your reference- http://bjbsair.com/2020-04-01/tech-info/18392.html

One of the application scenarios of Netty in the project: the message push function can be used to push messages to all users, or to specify a user. The spring boot project is created. The background server uses Netty technology, and the front page uses WebSocket technology.

General realization idea:

When the front-end uses webSocket to create a connection with the server, it will pass the user ID to the server and associate the user ID with the channel for storage. Meanwhile, it will put the channel into the channel group. If it needs to send messages to all users, it will directly execute the writeAndFlush() method of the channel group. If it needs to send messages to the specified users, it will query the corresponding channel according to the user ID, Then execute the writeAndFlush() method to get the message pushed by the server, and display the message content to the text field

The following is the specific code implementation. Basically, each step of operation is provided with notes. It should be easier to understand with notes.

**Step 0: * * introduce the dependency of Netty and a toolkit (only json tools are used, which can be replaced by other json tools)

<dependency>  
  <groupId>io.netty</groupId>  
  <artifactId>netty-all</artifactId>   
  <version>4.1.33.Final</version>  
</dependency>  
  
<dependency>  
  <groupId>cn.hutool</groupId>  
  <artifactId>hutool-all</artifactId>  
  <version>5.2.3</version>  
</dependency>

**Step 1: * * define a channel group in NettyConfig, manage all channels, and define a map to manage the corresponding relationship between users and channels.

package com.sixj.nettypush.config;  
  
import io.netty.channel.Channel;  
import io.netty.channel.group.ChannelGroup;  
import io.netty.channel.group.DefaultChannelGroup;  
import io.netty.util.concurrent.GlobalEventExecutor;  
  
import java.util.concurrent.ConcurrentHashMap;  
  
/**  
 * @author sixiaojie  
 * @date 2020-03-28-15:07  
 */  
public class NettyConfig {  
    /**  
     * Define a channel group and manage all channels  
     * GlobalEventExecutor.INSTANCE Is a global event executor, a single example  
     */  
    private static ChannelGroup channelGroup = new DefaultChannelGroup(GlobalEventExecutor.INSTANCE);  
  
    /**  
     * Store the corresponding information between the user and Chanel to send messages to the specified user  
     */  
    private static ConcurrentHashMap<String,Channel> userChannelMap = new ConcurrentHashMap<>();  
  
    private NettyConfig() {}  
      
    /**  
     * Get channel group  
     * @return  
     */  
    public static ChannelGroup getChannelGroup() {  
        return channelGroup;  
    }  
  
    /**  
     * Get user channel map  
     * @return  
     */  
    public static ConcurrentHashMap<String,Channel> getUserChannelMap(){  
        return userChannelMap;  
    }  

**Step 2: * * create a netty server, define two eventloopgroups, and define the tcp connection requests of the secondary client of bossGroup. workGroup is responsible for the read and write operations with the client before. It should be noted that a new thread needs to be opened to execute the netty server, otherwise the main thread will be blocked, and other controller interfaces of the project will not be called at that time.

package com.sixj.nettypush.websocket;  
  
import io.netty.bootstrap.ServerBootstrap;  
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.NioServerSocketChannel;  
import io.netty.handler.codec.http.HttpObjectAggregator;  
import io.netty.handler.codec.http.HttpServerCodec;  
import io.netty.handler.codec.http.websocketx.WebSocketServerProtocolHandler;  
import io.netty.handler.codec.serialization.ObjectEncoder;  
import io.netty.handler.stream.ChunkedWriteHandler;  
import org.slf4j.Logger;  
import org.slf4j.LoggerFactory;  
import org.springframework.beans.factory.annotation.Autowired;  
import org.springframework.beans.factory.annotation.Value;  
import org.springframework.stereotype.Component;  
  
import javax.annotation.PostConstruct;  
import javax.annotation.PreDestroy;  
import java.net.InetSocketAddress;  
  
/**  
 * @author sixiaojie  
 * @date 2020-03-28-13:44  
 */  
  
@Component  
public class NettyServer{  
    private static final Logger log = LoggerFactory.getLogger(NettyServer.class);  
    /**  
     * webSocket Protocol name  
     */  
    private static final String WEBSOCKET_PROTOCOL = "WebSocket";  
  
    /**  
     * Port number  
     */  
    @Value("${webSocket.netty.port:58080}")  
    private int port;  
  
    /**  
     * webSocket Route  
     */  
    @Value("${webSocket.netty.path:/webSocket}")  
    private String webSocketPath;  
  
    @Autowired  
    private WebSocketHandler webSocketHandler;  
  
    private EventLoopGroup bossGroup;  
    private EventLoopGroup workGroup;  
  
    /**  
     * start-up  
     * @throws InterruptedException  
     */  
    private void start() throws InterruptedException {  
        bossGroup = new NioEventLoopGroup();  
        workGroup = new NioEventLoopGroup();  
        ServerBootstrap bootstrap = new ServerBootstrap();  
        // For the tcp connection request of the secondary client of bossGroup, workGroup is responsible for the read and write operations before the client  
        bootstrap.group(bossGroup,workGroup);  
        // Set channel of NIO type  
        bootstrap.channel(NioServerSocketChannel.class);  
        // Set listening port  
        bootstrap.localAddress(new InetSocketAddress(port));  
        // A channel is created when the connection arrives  
        bootstrap.childHandler(new ChannelInitializer<SocketChannel>() {  
  
            @Override  
            protected void initChannel(SocketChannel ch) throws Exception {  
                // The Handler in the pipeline management channel is used to handle business  
                // The webSocket protocol itself is based on the http protocol, so we also need to use the http codec here  
                ch.pipeline().addLast(new HttpServerCodec());  
                ch.pipeline().addLast(new ObjectEncoder());  
                // A processor written in blocks  
                ch.pipeline().addLast(new ChunkedWriteHandler());  
                /*  
                Explain:  
                1,http Data is segmented during transmission, and HttpObjectAggregator can aggregate multiple segments  
                2,That's why when a browser sends a lot of data, it sends multiple http requests  
                 */  
                ch.pipeline().addLast(new HttpObjectAggregator(8192));  
                /*  
                Explain:  
                1,Corresponding to webSocket, its data is passed in the form of frame  
                2,When the browser requests, ws://localhost:58080/xxx indicates the requested uri  
                3,The core function is to upgrade http protocol to ws protocol and maintain long connection  
                */  
                ch.pipeline().addLast(new WebSocketServerProtocolHandler(webSocketPath, WEBSOCKET_PROTOCOL, true, 65536 * 10));  
                // Custom handler, handling business logic  
                ch.pipeline().addLast(webSocketHandler);  
  
            }  
        });  
        // After the configuration is completed, start binding the server, and block by calling the sync synchronization method until the binding is successful  
        ChannelFuture channelFuture = bootstrap.bind().sync();  
        log.info("Server started and listen on:{}",channelFuture.channel().localAddress());  
        // Monitor the closed channel  
        channelFuture.channel().closeFuture().sync();  
    }  
  
    /**  
     * Release resources  
     * @throws InterruptedException  
     */  
    @PreDestroy  
    public void destroy() throws InterruptedException {  
        if(bossGroup != null){  
            bossGroup.shutdownGracefully().sync();  
        }  
        if(workGroup != null){  
            workGroup.shutdownGracefully().sync();  
        }  
    }  
    @PostConstruct()  
    public void init() {  
        //A new thread needs to be opened to execute the netty server  
        new Thread(() -> {  
            try {  
                start();  
            } catch (InterruptedException e) {  
                e.printStackTrace();  
            }  
        }).start();  
    }  
}

Step 3: implement the WebSocketHandler of the business, and see the notes for the implementation logic

package com.sixj.nettypush.websocket;  
  
import cn.hutool.json.JSONObject;  
import cn.hutool.json.JSONUtil;  
import com.sixj.nettypush.config.NettyConfig;  
import io.netty.channel.ChannelHandler;  
import io.netty.channel.ChannelHandlerContext;  
import io.netty.channel.SimpleChannelInboundHandler;  
import io.netty.handler.codec.http.websocketx.TextWebSocketFrame;  
import io.netty.util.AttributeKey;  
import org.slf4j.Logger;  
import org.slf4j.LoggerFactory;  
import org.springframework.stereotype.Component;  
  
  
/**  
 * TextWebSocketFrame Type, representing a text frame  
 * @author sixiaojie  
 * @date 2020-03-28-13:47  
 */  
@Component  
@ChannelHandler.Sharable  
public class WebSocketHandler extends SimpleChannelInboundHandler<TextWebSocketFrame> {  
    private static final Logger log = LoggerFactory.getLogger(NettyServer.class);  
  
    /**  
     * Once connected, the first is executed  
     * @param ctx  
     * @throws Exception  
     */  
    @Override  
    public void handlerAdded(ChannelHandlerContext ctx) throws Exception {  
        log.info("handlerAdded Be called"+ctx.channel().id().asLongText());  
        // Add to channelGroup channel group  
        NettyConfig.getChannelGroup().add(ctx.channel());  
    }  
  
    /**  
     * Read data  
     */  
    @Override  
    protected void channelRead0(ChannelHandlerContext ctx, TextWebSocketFrame msg) throws Exception {  
        log.info("Server received message:{}",msg.text());  
  
        // Get user ID and associate with channel  
        JSONObject jsonObject = JSONUtil.parseObj(msg.text());  
        String uid = jsonObject.getStr("uid");  
        NettyConfig.getUserChannelMap().put(uid,ctx.channel());  
  
        // The user ID is added to the channel as a custom attribute, which is convenient to obtain the user ID in the channel at any time  
        AttributeKey<String> key = AttributeKey.valueOf("userId");  
        ctx.channel().attr(key).setIfAbsent(uid);  
  
        // Reply message  
        ctx.channel().writeAndFlush(new TextWebSocketFrame("Server connection succeeded!"));  
    }  
  
    @Override  
    public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {  
        log.info("handlerRemoved Be called"+ctx.channel().id().asLongText());  
        // Delete channel  
        NettyConfig.getChannelGroup().remove(ctx.channel());  
        removeUserId(ctx);  
    }  
  
    @Override  
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {  
        log.info("Exception:{}",cause.getMessage());  
        // Delete channel  
        NettyConfig.getChannelGroup().remove(ctx.channel());  
        removeUserId(ctx);  
        ctx.close();  
    }  
  
    /**  
     * Delete the corresponding relationship between user and channel  
     * @param ctx  
     */  
    private void removeUserId(ChannelHandlerContext ctx){  
        AttributeKey<String> key = AttributeKey.valueOf("userId");  
        String userId = ctx.channel().attr(key).get();  
        NettyConfig.getUserChannelMap().remove(userId);  
    }  
}

**Step 4: * * specific message push interface

public interface PushService {  
    /**  
     * Push to specified user  
     * @param userId  
     * @param msg  
     */  
    void pushMsgToOne(String userId,String msg);  
  
    /**  
     * Push to all users  
     * @param msg  
     */  
    void pushMsgToAll(String msg);  
}

Interface implementation class:

import java.util.concurrent.ConcurrentHashMap;  
  
/**  
 * @author sixiaojie  
 * @date 2020-03-30-20:10  
 */  
@Service  
public class PushServiceImpl implements PushService {  
  
    @Override  
    public void pushMsgToOne(String userId, String msg){  
        ConcurrentHashMap<String, Channel> userChannelMap = NettyConfig.getUserChannelMap();  
        Channel channel = userChannelMap.get(userId);  
        channel.writeAndFlush(new TextWebSocketFrame(msg));  
    }  
    @Override  
    public void pushMsgToAll(String msg){  
        NettyConfig.getChannelGroup().writeAndFlush(new TextWebSocketFrame(msg));  
    }  
}

controller:

package com.sixj.nettypush.controller;  
  
import com.sixj.nettypush.service.PushService;  
import org.springframework.beans.factory.annotation.Autowired;  
import org.springframework.web.bind.annotation.PostMapping;  
import org.springframework.web.bind.annotation.RequestMapping;  
import org.springframework.web.bind.annotation.RequestParam;  
import org.springframework.web.bind.annotation.RestController;  
  
/**  
 * @author sixiaojie  
 * @date 2020-03-30-20:08  
 */  
@RestController  
@RequestMapping("/push")  
public class PushController {  
  
    @Autowired  
    private PushService pushService;  
  
    /**  
     * Push to all users  
     * @param msg  
     */  
    @PostMapping("/pushAll")  
    public void pushToAll(@RequestParam("msg") String msg){  
        pushService.pushMsgToAll(msg);  
    }  
    /**  
     * Push to specified user  
     * @param userId  
     * @param msg  
     */  
    @PostMapping("/pushOne")  
    public void pushMsgToOne(@RequestParam("userId") String userId,@RequestParam("msg") String msg){  
        pushService.pushMsgToOne(userId,msg);  
    }  
  
}

**Step 5: * * front end html page

<!DOCTYPE html>  
<html lang="en">  
<head>  
    <meta charset="UTF-8">  
    <title>Title</title>  
</head>  
<body>  
<script>  
    var socket;  
    // Determine whether the current browser supports webSocket  
    if(window.WebSocket){  
        socket = new WebSocket("ws://192.168.174.25:58080/webSocket")  
        // Equivalent to the read event of the channel, ev receives the message sent back by the server  
        socket.onmessage = function (ev) {  
            var rt = document.getElementById("responseText");  
            rt.value = rt.value + "\n" + ev.data;  
        }  
        // Equivalent to connection on  
        socket.onopen = function (ev) {  
            var rt = document.getElementById("responseText");  
            rt.value =  "Connection turned on..."  
            socket.send(  
                JSON.stringify({  
                    // The connection succeeds. The user ID is passed to the server  
                    uid: "123456"  
                })  
            );  
        }  
        // Equivalent to connection closing  
        socket.onclose = function (ev) {  
            var rt = document.getElementById("responseText");  
            rt.value = rt.value + "\n" + "Connection closed...";  
        }  
    }else{  
        alert("Current browser does not support webSocket")  
    }  
  
  
</script>  
    <form onsubmit="return false">  
        <textarea id="responseText" style="height: 150px; width: 300px;"></textarea>  
        <input type="button" value="Empty content" onclick="document.getElementById('responseText').value=''">  
    </form>  
</body>  
</html>

So far, all the code has been written. Test it

Run the html file first, and you will see the message "server connection succeeded!" returned by the server to the front end , the back-end log will print the message received by the server: {"uid":"123456"}

Then use postman to test the pushed interface

Thank you for your reference- http://bjbsair.com/2020-04-01/tech-info/18392.html

One of the application scenarios of Netty in the project: the message push function can be used to push messages to all users, or to specify a user. The spring boot project is created. The background server uses Netty technology, and the front page uses WebSocket technology.

General realization idea:

When the front-end uses webSocket to create a connection with the server, it will pass the user ID to the server and associate the user ID with the channel for storage. Meanwhile, it will put the channel into the channel group. If it needs to send messages to all users, it will directly execute the writeAndFlush() method of the channel group. If it needs to send messages to the specified users, it will query the corresponding channel according to the user ID, Then execute the writeAndFlush() method to get the message pushed by the server, and display the message content to the text field

The following is the specific code implementation. Basically, each step of operation is provided with notes. It should be easier to understand with notes.

**Step 0: * * introduce the dependency of Netty and a toolkit (only json tools are used, which can be replaced by other json tools)

<dependency>  
  <groupId>io.netty</groupId>  
  <artifactId>netty-all</artifactId>   
  <version>4.1.33.Final</version>  
</dependency>  
  
<dependency>  
  <groupId>cn.hutool</groupId>  
  <artifactId>hutool-all</artifactId>  
  <version>5.2.3</version>  
</dependency>

**Step 1: * * define a channel group in NettyConfig, manage all channels, and define a map to manage the corresponding relationship between users and channels.

package com.sixj.nettypush.config;  
  
import io.netty.channel.Channel;  
import io.netty.channel.group.ChannelGroup;  
import io.netty.channel.group.DefaultChannelGroup;  
import io.netty.util.concurrent.GlobalEventExecutor;  
  
import java.util.concurrent.ConcurrentHashMap;  
  
/**  
 * @author sixiaojie  
 * @date 2020-03-28-15:07  
 */  
public class NettyConfig {  
    /**  
     * Define a channel group and manage all channels  
     * GlobalEventExecutor.INSTANCE Is a global event executor, a single example  
     */  
    private static ChannelGroup channelGroup = new DefaultChannelGroup(GlobalEventExecutor.INSTANCE);  
  
    /**  
     * Store the corresponding information between the user and Chanel to send messages to the specified user  
     */  
    private static ConcurrentHashMap<String,Channel> userChannelMap = new ConcurrentHashMap<>();  
  
    private NettyConfig() {}  
      
    /**  
     * Get channel group  
     * @return  
     */  
    public static ChannelGroup getChannelGroup() {  
        return channelGroup;  
    }  
  
    /**  
     * Get user channel map  
     * @return  
     */  
    public static ConcurrentHashMap<String,Channel> getUserChannelMap(){  
        return userChannelMap;  
    }  

**Step 2: * * create a netty server, define two eventloopgroups, and define the tcp connection requests of the secondary client of bossGroup. workGroup is responsible for the read and write operations with the client before. It should be noted that a new thread needs to be opened to execute the netty server, otherwise the main thread will be blocked, and other controller interfaces of the project will not be called at that time.

package com.sixj.nettypush.websocket;  
  
import io.netty.bootstrap.ServerBootstrap;  
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.NioServerSocketChannel;  
import io.netty.handler.codec.http.HttpObjectAggregator;  
import io.netty.handler.codec.http.HttpServerCodec;  
import io.netty.handler.codec.http.websocketx.WebSocketServerProtocolHandler;  
import io.netty.handler.codec.serialization.ObjectEncoder;  
import io.netty.handler.stream.ChunkedWriteHandler;  
import org.slf4j.Logger;  
import org.slf4j.LoggerFactory;  
import org.springframework.beans.factory.annotation.Autowired;  
import org.springframework.beans.factory.annotation.Value;  
import org.springframework.stereotype.Component;  
  
import javax.annotation.PostConstruct;  
import javax.annotation.PreDestroy;  
import java.net.InetSocketAddress;  
  
/**  
 * @author sixiaojie  
 * @date 2020-03-28-13:44  
 */  
  
@Component  
public class NettyServer{  
    private static final Logger log = LoggerFactory.getLogger(NettyServer.class);  
    /**  
     * webSocket Protocol name  
     */  
    private static final String WEBSOCKET_PROTOCOL = "WebSocket";  
  
    /**  
     * Port number  
     */  
    @Value("${webSocket.netty.port:58080}")  
    private int port;  
  
    /**  
     * webSocket Route  
     */  
    @Value("${webSocket.netty.path:/webSocket}")  
    private String webSocketPath;  
  
    @Autowired  
    private WebSocketHandler webSocketHandler;  
  
    private EventLoopGroup bossGroup;  
    private EventLoopGroup workGroup;  
  
    /**  
     * start-up  
     * @throws InterruptedException  
     */  
    private void start() throws InterruptedException {  
        bossGroup = new NioEventLoopGroup();  
        workGroup = new NioEventLoopGroup();  
        ServerBootstrap bootstrap = new ServerBootstrap();  
        // For the tcp connection request of the secondary client of bossGroup, workGroup is responsible for the read and write operations before the client  
        bootstrap.group(bossGroup,workGroup);  
        // Set channel of NIO type  
        bootstrap.channel(NioServerSocketChannel.class);  
        // Set listening port  
        bootstrap.localAddress(new InetSocketAddress(port));  
        // A channel is created when the connection arrives  
        bootstrap.childHandler(new ChannelInitializer<SocketChannel>() {  
  
            @Override  
            protected void initChannel(SocketChannel ch) throws Exception {  
                // The Handler in the pipeline management channel is used to handle business  
                // The webSocket protocol itself is based on the http protocol, so we also need to use the http codec here  
                ch.pipeline().addLast(new HttpServerCodec());  
                ch.pipeline().addLast(new ObjectEncoder());  
                // A processor written in blocks  
                ch.pipeline().addLast(new ChunkedWriteHandler());  
                /*  
                Explain:  
                1,http Data is segmented during transmission, and HttpObjectAggregator can aggregate multiple segments  
                2,That's why when a browser sends a lot of data, it sends multiple http requests  
                 */  
                ch.pipeline().addLast(new HttpObjectAggregator(8192));  
                /*  
                Explain:  
                1,Corresponding to webSocket, its data is passed in the form of frame  
                2,When the browser requests, ws://localhost:58080/xxx indicates the requested uri  
                3,The core function is to upgrade http protocol to ws protocol and maintain long connection  
                */  
                ch.pipeline().addLast(new WebSocketServerProtocolHandler(webSocketPath, WEBSOCKET_PROTOCOL, true, 65536 * 10));  
                // Custom handler, handling business logic  
                ch.pipeline().addLast(webSocketHandler);  
  
            }  
        });  
        // After the configuration is completed, start binding the server, and block by calling the sync synchronization method until the binding is successful  
        ChannelFuture channelFuture = bootstrap.bind().sync();  
        log.info("Server started and listen on:{}",channelFuture.channel().localAddress());  
        // Monitor the closed channel  
        channelFuture.channel().closeFuture().sync();  
    }  
  
    /**  
     * Release resources  
     * @throws InterruptedException  
     */  
    @PreDestroy  
    public void destroy() throws InterruptedException {  
        if(bossGroup != null){  
            bossGroup.shutdownGracefully().sync();  
        }  
        if(workGroup != null){  
            workGroup.shutdownGracefully().sync();  
        }  
    }  
    @PostConstruct()  
    public void init() {  
        //A new thread needs to be opened to execute the netty server  
        new Thread(() -> {  
            try {  
                start();  
            } catch (InterruptedException e) {  
                e.printStackTrace();  
            }  
        }).start();  
    }  
}

Step 3: implement the WebSocketHandler of the business, and see the notes for the implementation logic

package com.sixj.nettypush.websocket;  
  
import cn.hutool.json.JSONObject;  
import cn.hutool.json.JSONUtil;  
import com.sixj.nettypush.config.NettyConfig;  
import io.netty.channel.ChannelHandler;  
import io.netty.channel.ChannelHandlerContext;  
import io.netty.channel.SimpleChannelInboundHandler;  
import io.netty.handler.codec.http.websocketx.TextWebSocketFrame;  
import io.netty.util.AttributeKey;  
import org.slf4j.Logger;  
import org.slf4j.LoggerFactory;  
import org.springframework.stereotype.Component;  
  
  
/**  
 * TextWebSocketFrame Type, representing a text frame  
 * @author sixiaojie  
 * @date 2020-03-28-13:47  
 */  
@Component  
@ChannelHandler.Sharable  
public class WebSocketHandler extends SimpleChannelInboundHandler<TextWebSocketFrame> {  
    private static final Logger log = LoggerFactory.getLogger(NettyServer.class);  
  
    /**  
     * Once connected, the first is executed  
     * @param ctx  
     * @throws Exception  
     */  
    @Override  
    public void handlerAdded(ChannelHandlerContext ctx) throws Exception {  
        log.info("handlerAdded Be called"+ctx.channel().id().asLongText());  
        // Add to channelGroup channel group  
        NettyConfig.getChannelGroup().add(ctx.channel());  
    }  
  
    /**  
     * Read data  
     */  
    @Override  
    protected void channelRead0(ChannelHandlerContext ctx, TextWebSocketFrame msg) throws Exception {  
        log.info("Server received message:{}",msg.text());  
  
        // Get user ID and associate with channel  
        JSONObject jsonObject = JSONUtil.parseObj(msg.text());  
        String uid = jsonObject.getStr("uid");  
        NettyConfig.getUserChannelMap().put(uid,ctx.channel());  
  
        // The user ID is added to the channel as a custom attribute, which is convenient to obtain the user ID in the channel at any time  
        AttributeKey<String> key = AttributeKey.valueOf("userId");  
        ctx.channel().attr(key).setIfAbsent(uid);  
  
        // Reply message  
        ctx.channel().writeAndFlush(new TextWebSocketFrame("Server connection succeeded!"));  
    }  
  
    @Override  
    public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {  
        log.info("handlerRemoved Be called"+ctx.channel().id().asLongText());  
        // Delete channel  
        NettyConfig.getChannelGroup().remove(ctx.channel());  
        removeUserId(ctx);  
    }  
  
    @Override  
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {  
        log.info("Exception:{}",cause.getMessage());  
        // Delete channel  
        NettyConfig.getChannelGroup().remove(ctx.channel());  
        removeUserId(ctx);  
        ctx.close();  
    }  
  
    /**  
     * Delete the corresponding relationship between user and channel  
     * @param ctx  
     */  
    private void removeUserId(ChannelHandlerContext ctx){  
        AttributeKey<String> key = AttributeKey.valueOf("userId");  
        String userId = ctx.channel().attr(key).get();  
        NettyConfig.getUserChannelMap().remove(userId);  
    }  
}

**Step 4: * * specific message push interface

public interface PushService {  
    /**  
     * Push to specified user  
     * @param userId  
     * @param msg  
     */  
    void pushMsgToOne(String userId,String msg);  
  
    /**  
     * Push to all users  
     * @param msg  
     */  
    void pushMsgToAll(String msg);  
}

Interface implementation class:

import java.util.concurrent.ConcurrentHashMap;  
  
/**  
 * @author sixiaojie  
 * @date 2020-03-30-20:10  
 */  
@Service  
public class PushServiceImpl implements PushService {  
  
    @Override  
    public void pushMsgToOne(String userId, String msg){  
        ConcurrentHashMap<String, Channel> userChannelMap = NettyConfig.getUserChannelMap();  
        Channel channel = userChannelMap.get(userId);  
        channel.writeAndFlush(new TextWebSocketFrame(msg));  
    }  
    @Override  
    public void pushMsgToAll(String msg){  
        NettyConfig.getChannelGroup().writeAndFlush(new TextWebSocketFrame(msg));  
    }  
}

controller:

package com.sixj.nettypush.controller;  
  
import com.sixj.nettypush.service.PushService;  
import org.springframework.beans.factory.annotation.Autowired;  
import org.springframework.web.bind.annotation.PostMapping;  
import org.springframework.web.bind.annotation.RequestMapping;  
import org.springframework.web.bind.annotation.RequestParam;  
import org.springframework.web.bind.annotation.RestController;  
  
/**  
 * @author sixiaojie  
 * @date 2020-03-30-20:08  
 */  
@RestController  
@RequestMapping("/push")  
public class PushController {  
  
    @Autowired  
    private PushService pushService;  
  
    /**  
     * Push to all users  
     * @param msg  
     */  
    @PostMapping("/pushAll")  
    public void pushToAll(@RequestParam("msg") String msg){  
        pushService.pushMsgToAll(msg);  
    }  
    /**  
     * Push to specified user  
     * @param userId  
     * @param msg  
     */  
    @PostMapping("/pushOne")  
    public void pushMsgToOne(@RequestParam("userId") String userId,@RequestParam("msg") String msg){  
        pushService.pushMsgToOne(userId,msg);  
    }  
  
}

**Step 5: * * front end html page

<!DOCTYPE html>  
<html lang="en">  
<head>  
    <meta charset="UTF-8">  
    <title>Title</title>  
</head>  
<body>  
<script>  
    var socket;  
    // Determine whether the current browser supports webSocket  
    if(window.WebSocket){  
        socket = new WebSocket("ws://192.168.174.25:58080/webSocket")  
        // Equivalent to the read event of the channel, ev receives the message sent back by the server  
        socket.onmessage = function (ev) {  
            var rt = document.getElementById("responseText");  
            rt.value = rt.value + "\n" + ev.data;  
        }  
        // Equivalent to connection on  
        socket.onopen = function (ev) {  
            var rt = document.getElementById("responseText");  
            rt.value =  "Connection turned on..."  
            socket.send(  
                JSON.stringify({  
                    // The connection succeeds. The user ID is passed to the server  
                    uid: "123456"  
                })  
            );  
        }  
        // Equivalent to connection closing  
        socket.onclose = function (ev) {  
            var rt = document.getElementById("responseText");  
            rt.value = rt.value + "\n" + "Connection closed...";  
        }  
    }else{  
        alert("Current browser does not support webSocket")  
    }  
  
  
</script>  
    <form onsubmit="return false">  
        <textarea id="responseText" style="height: 150px; width: 300px;"></textarea>  
        <input type="button" value="Empty content" onclick="document.getElementById('responseText').value=''">  
    </form>  
</body>  
</html>

So far, all the code has been written. Test it

Run the html file first, and you will see the message "server connection succeeded!" returned by the server to the front end , the back-end log will print the message received by the server: {"uid":"123456"}

Then use postman to test the pushed interface

Thank you for your reference- http://bjbsair.com/2020-04-01/tech-info/18392.html

One of the application scenarios of Netty in the project: the message push function can be used to push messages to all users, or to specify a user. The spring boot project is created. The background server uses Netty technology, and the front page uses WebSocket technology.

General realization idea:

When the front-end uses webSocket to create a connection with the server, it will pass the user ID to the server and associate the user ID with the channel for storage. Meanwhile, it will put the channel into the channel group. If it needs to send messages to all users, it will directly execute the writeAndFlush() method of the channel group. If it needs to send messages to the specified users, it will query the corresponding channel according to the user ID, Then execute the writeAndFlush() method to get the message pushed by the server, and display the message content to the text field

The following is the specific code implementation. Basically, each step of operation is provided with notes. It should be easier to understand with notes.

**Step 0: * * introduce the dependency of Netty and a toolkit (only json tools are used, which can be replaced by other json tools)

<dependency>  
  <groupId>io.netty</groupId>  
  <artifactId>netty-all</artifactId>   
  <version>4.1.33.Final</version>  
</dependency>  
  
<dependency>  
  <groupId>cn.hutool</groupId>  
  <artifactId>hutool-all</artifactId>  
  <version>5.2.3</version>  
</dependency>

**Step 1: * * define a channel group in NettyConfig, manage all channels, and define a map to manage the corresponding relationship between users and channels.

package com.sixj.nettypush.config;  
  
import io.netty.channel.Channel;  
import io.netty.channel.group.ChannelGroup;  
import io.netty.channel.group.DefaultChannelGroup;  
import io.netty.util.concurrent.GlobalEventExecutor;  
  
import java.util.concurrent.ConcurrentHashMap;  
  
/**  
 * @author sixiaojie  
 * @date 2020-03-28-15:07  
 */  
public class NettyConfig {  
    /**  
     * Define a channel group and manage all channels  
     * GlobalEventExecutor.INSTANCE Is a global event executor, a single example  
     */  
    private static ChannelGroup channelGroup = new DefaultChannelGroup(GlobalEventExecutor.INSTANCE);  
  
    /**  
     * Store the corresponding information between the user and Chanel to send messages to the specified user  
     */  
    private static ConcurrentHashMap<String,Channel> userChannelMap = new ConcurrentHashMap<>();  
  
    private NettyConfig() {}  
      
    /**  
     * Get channel group  
     * @return  
     */  
    public static ChannelGroup getChannelGroup() {  
        return channelGroup;  
    }  
  
    /**  
     * Get user channel map  
     * @return  
     */  
    public static ConcurrentHashMap<String,Channel> getUserChannelMap(){  
        return userChannelMap;  
    }  

**Step 2: * * create a netty server, define two eventloopgroups, and define the tcp connection requests of the secondary client of bossGroup. workGroup is responsible for the read and write operations with the client before. It should be noted that a new thread needs to be opened to execute the netty server, otherwise the main thread will be blocked, and other controller interfaces of the project will not be called at that time.

package com.sixj.nettypush.websocket;  
  
import io.netty.bootstrap.ServerBootstrap;  
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.NioServerSocketChannel;  
import io.netty.handler.codec.http.HttpObjectAggregator;  
import io.netty.handler.codec.http.HttpServerCodec;  
import io.netty.handler.codec.http.websocketx.WebSocketServerProtocolHandler;  
import io.netty.handler.codec.serialization.ObjectEncoder;  
import io.netty.handler.stream.ChunkedWriteHandler;  
import org.slf4j.Logger;  
import org.slf4j.LoggerFactory;  
import org.springframework.beans.factory.annotation.Autowired;  
import org.springframework.beans.factory.annotation.Value;  
import org.springframework.stereotype.Component;  
  
import javax.annotation.PostConstruct;  
import javax.annotation.PreDestroy;  
import java.net.InetSocketAddress;  
  
/**  
 * @author sixiaojie  
 * @date 2020-03-28-13:44  
 */  
  
@Component  
public class NettyServer{  
    private static final Logger log = LoggerFactory.getLogger(NettyServer.class);  
    /**  
     * webSocket Protocol name  
     */  
    private static final String WEBSOCKET_PROTOCOL = "WebSocket";  
  
    /**  
     * Port number  
     */  
    @Value("${webSocket.netty.port:58080}")  
    private int port;  
  
    /**  
     * webSocket Route  
     */  
    @Value("${webSocket.netty.path:/webSocket}")  
    private String webSocketPath;  
  
    @Autowired  
    private WebSocketHandler webSocketHandler;  
  
    private EventLoopGroup bossGroup;  
    private EventLoopGroup workGroup;  
  
    /**  
     * start-up  
     * @throws InterruptedException  
     */  
    private void start() throws InterruptedException {  
        bossGroup = new NioEventLoopGroup();  
        workGroup = new NioEventLoopGroup();  
        ServerBootstrap bootstrap = new ServerBootstrap();  
        // For the tcp connection request of the secondary client of bossGroup, workGroup is responsible for the read and write operations before the client  
        bootstrap.group(bossGroup,workGroup);  
        // Set channel of NIO type  
        bootstrap.channel(NioServerSocketChannel.class);  
        // Set listening port  
        bootstrap.localAddress(new InetSocketAddress(port));  
        // A channel is created when the connection arrives  
        bootstrap.childHandler(new ChannelInitializer<SocketChannel>() {  
  
            @Override  
            protected void initChannel(SocketChannel ch) throws Exception {  
                // The Handler in the pipeline management channel is used to handle business  
                // The webSocket protocol itself is based on the http protocol, so we also need to use the http codec here  
                ch.pipeline().addLast(new HttpServerCodec());  
                ch.pipeline().addLast(new ObjectEncoder());  
                // A processor written in blocks  
                ch.pipeline().addLast(new ChunkedWriteHandler());  
                /*  
                Explain:  
                1,http Data is segmented during transmission, and HttpObjectAggregator can aggregate multiple segments  
                2,That's why when a browser sends a lot of data, it sends multiple http requests  
                 */  
                ch.pipeline().addLast(new HttpObjectAggregator(8192));  
                /*  
                Explain:  
                1,Corresponding to webSocket, its data is passed in the form of frame  
                2,When the browser requests, ws://localhost:58080/xxx indicates the requested uri  
                3,The core function is to upgrade http protocol to ws protocol and maintain long connection  
                */  
                ch.pipeline().addLast(new WebSocketServerProtocolHandler(webSocketPath, WEBSOCKET_PROTOCOL, true, 65536 * 10));  
                // Custom handler, handling business logic  
                ch.pipeline().addLast(webSocketHandler);  
  
            }  
        });  
        // After the configuration is completed, start binding the server, and block by calling the sync synchronization method until the binding is successful  
        ChannelFuture channelFuture = bootstrap.bind().sync();  
        log.info("Server started and listen on:{}",channelFuture.channel().localAddress());  
        // Monitor the closed channel  
        channelFuture.channel().closeFuture().sync();  
    }  
  
    /**  
     * Release resources  
     * @throws InterruptedException  
     */  
    @PreDestroy  
    public void destroy() throws InterruptedException {  
        if(bossGroup != null){  
            bossGroup.shutdownGracefully().sync();  
        }  
        if(workGroup != null){  
            workGroup.shutdownGracefully().sync();  
        }  
    }  
    @PostConstruct()  
    public void init() {  
        //A new thread needs to be opened to execute the netty server  
        new Thread(() -> {  
            try {  
                start();  
            } catch (InterruptedException e) {  
                e.printStackTrace();  
            }  
        }).start();  
    }  
}

Step 3: implement the WebSocketHandler of the business, and see the notes for the implementation logic

package com.sixj.nettypush.websocket;  
  
import cn.hutool.json.JSONObject;  
import cn.hutool.json.JSONUtil;  
import com.sixj.nettypush.config.NettyConfig;  
import io.netty.channel.ChannelHandler;  
import io.netty.channel.ChannelHandlerContext;  
import io.netty.channel.SimpleChannelInboundHandler;  
import io.netty.handler.codec.http.websocketx.TextWebSocketFrame;  
import io.netty.util.AttributeKey;  
import org.slf4j.Logger;  
import org.slf4j.LoggerFactory;  
import org.springframework.stereotype.Component;  
  
  
/**  
 * TextWebSocketFrame Type, representing a text frame  
 * @author sixiaojie  
 * @date 2020-03-28-13:47  
 */  
@Component  
@ChannelHandler.Sharable  
public class WebSocketHandler extends SimpleChannelInboundHandler<TextWebSocketFrame> {  
    private static final Logger log = LoggerFactory.getLogger(NettyServer.class);  
  
    /**  
     * Once connected, the first is executed  
     * @param ctx  
     * @throws Exception  
     */  
    @Override  
    public void handlerAdded(ChannelHandlerContext ctx) throws Exception {  
        log.info("handlerAdded Be called"+ctx.channel().id().asLongText());  
        // Add to channelGroup channel group  
        NettyConfig.getChannelGroup().add(ctx.channel());  
    }  
  
    /**  
     * Read data  
     */  
    @Override  
    protected void channelRead0(ChannelHandlerContext ctx, TextWebSocketFrame msg) throws Exception {  
        log.info("Server received message:{}",msg.text());  
  
        // Get user ID and associate with channel  
        JSONObject jsonObject = JSONUtil.parseObj(msg.text());  
        String uid = jsonObject.getStr("uid");  
        NettyConfig.getUserChannelMap().put(uid,ctx.channel());  
  
        // The user ID is added to the channel as a custom attribute, which is convenient to obtain the user ID in the channel at any time  
        AttributeKey<String> key = AttributeKey.valueOf("userId");  
        ctx.channel().attr(key).setIfAbsent(uid);  
  
        // Reply message  
        ctx.channel().writeAndFlush(new TextWebSocketFrame("Server connection succeeded!"));  
    }  
  
    @Override  
    public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {  
        log.info("handlerRemoved Be called"+ctx.channel().id().asLongText());  
        // Delete channel  
        NettyConfig.getChannelGroup().remove(ctx.channel());  
        removeUserId(ctx);  
    }  
  
    @Override  
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {  
        log.info("Exception:{}",cause.getMessage());  
        // Delete channel  
        NettyConfig.getChannelGroup().remove(ctx.channel());  
        removeUserId(ctx);  
        ctx.close();  
    }  
  
    /**  
     * Delete the corresponding relationship between user and channel  
     * @param ctx  
     */  
    private void removeUserId(ChannelHandlerContext ctx){  
        AttributeKey<String> key = AttributeKey.valueOf("userId");  
        String userId = ctx.channel().attr(key).get();  
        NettyConfig.getUserChannelMap().remove(userId);  
    }  
}

**Step 4: * * specific message push interface

public interface PushService {  
    /**  
     * Push to specified user  
     * @param userId  
     * @param msg  
     */  
    void pushMsgToOne(String userId,String msg);  
  
    /**  
     * Push to all users  
     * @param msg  
     */  
    void pushMsgToAll(String msg);  
}

Interface implementation class:

import java.util.concurrent.ConcurrentHashMap;  
  
/**  
 * @author sixiaojie  
 * @date 2020-03-30-20:10  
 */  
@Service  
public class PushServiceImpl implements PushService {  
  
    @Override  
    public void pushMsgToOne(String userId, String msg){  
        ConcurrentHashMap<String, Channel> userChannelMap = NettyConfig.getUserChannelMap();  
        Channel channel = userChannelMap.get(userId);  
        channel.writeAndFlush(new TextWebSocketFrame(msg));  
    }  
    @Override  
    public void pushMsgToAll(String msg){  
        NettyConfig.getChannelGroup().writeAndFlush(new TextWebSocketFrame(msg));  
    }  
}

controller:

package com.sixj.nettypush.controller;  
  
import com.sixj.nettypush.service.PushService;  
import org.springframework.beans.factory.annotation.Autowired;  
import org.springframework.web.bind.annotation.PostMapping;  
import org.springframework.web.bind.annotation.RequestMapping;  
import org.springframework.web.bind.annotation.RequestParam;  
import org.springframework.web.bind.annotation.RestController;  
  
/**  
 * @author sixiaojie  
 * @date 2020-03-30-20:08  
 */  
@RestController  
@RequestMapping("/push")  
public class PushController {  
  
    @Autowired  
    private PushService pushService;  
  
    /**  
     * Push to all users  
     * @param msg  
     */  
    @PostMapping("/pushAll")  
    public void pushToAll(@RequestParam("msg") String msg){  
        pushService.pushMsgToAll(msg);  
    }  
    /**  
     * Push to specified user  
     * @param userId  
     * @param msg  
     */  
    @PostMapping("/pushOne")  
    public void pushMsgToOne(@RequestParam("userId") String userId,@RequestParam("msg") String msg){  
        pushService.pushMsgToOne(userId,msg);  
    }  
  
}

**Step 5: * * front end html page

<!DOCTYPE html>  
<html lang="en">  
<head>  
    <meta charset="UTF-8">  
    <title>Title</title>  
</head>  
<body>  
<script>  
    var socket;  
    // Determine whether the current browser supports webSocket  
    if(window.WebSocket){  
        socket = new WebSocket("ws://192.168.174.25:58080/webSocket")  
        // Equivalent to the read event of the channel, ev receives the message sent back by the server  
        socket.onmessage = function (ev) {  
            var rt = document.getElementById("responseText");  
            rt.value = rt.value + "\n" + ev.data;  
        }  
        // Equivalent to connection on  
        socket.onopen = function (ev) {  
            var rt = document.getElementById("responseText");  
            rt.value =  "Connection turned on..."  
            socket.send(  
                JSON.stringify({  
                    // The connection succeeds. The user ID is passed to the server  
                    uid: "123456"  
                })  
            );  
        }  
        // Equivalent to connection closing  
        socket.onclose = function (ev) {  
            var rt = document.getElementById("responseText");  
            rt.value = rt.value + "\n" + "Connection closed...";  
        }  
    }else{  
        alert("Current browser does not support webSocket")  
    }  
  
  
</script>  
    <form onsubmit="return false">  
        <textarea id="responseText" style="height: 150px; width: 300px;"></textarea>  
        <input type="button" value="Empty content" onclick="document.getElementById('responseText').value=''">  
    </form>  
</body>  
</html>

So far, all the code has been written. Test it

Run the html file first, and you will see the message "server connection succeeded!" returned by the server to the front end , the back-end log will print the message received by the server: {"uid":"123456"}

Then use postman to test the pushed interface

Thank you for your reference- http://bjbsair.com/2020-04-01/tech-info/18392.html

One of the application scenarios of Netty in the project: the message push function can be used to push messages to all users, or to specify a user. The spring boot project is created. The background server uses Netty technology, and the front page uses WebSocket technology.

General realization idea:

When the front-end uses webSocket to create a connection with the server, it will pass the user ID to the server and associate the user ID with the channel for storage. Meanwhile, it will put the channel into the channel group. If it needs to send messages to all users, it will directly execute the writeAndFlush() method of the channel group. If it needs to send messages to the specified users, it will query the corresponding channel according to the user ID, Then execute the writeAndFlush() method to get the message pushed by the server, and display the message content to the text field

The following is the specific code implementation. Basically, each step of operation is provided with notes. It should be easier to understand with notes.

**Step 0: * * introduce the dependency of Netty and a toolkit (only json tools are used, which can be replaced by other json tools)

<dependency>  
  <groupId>io.netty</groupId>  
  <artifactId>netty-all</artifactId>   
  <version>4.1.33.Final</version>  
</dependency>  
  
<dependency>  
  <groupId>cn.hutool</groupId>  
  <artifactId>hutool-all</artifactId>  
  <version>5.2.3</version>  
</dependency>

**Step 1: * * define a channel group in NettyConfig, manage all channels, and define a map to manage the corresponding relationship between users and channels.

package com.sixj.nettypush.config;  
  
import io.netty.channel.Channel;  
import io.netty.channel.group.ChannelGroup;  
import io.netty.channel.group.DefaultChannelGroup;  
import io.netty.util.concurrent.GlobalEventExecutor;  
  
import java.util.concurrent.ConcurrentHashMap;  
  
/**  
 * @author sixiaojie  
 * @date 2020-03-28-15:07  
 */  
public class NettyConfig {  
    /**  
     * Define a channel group and manage all channels  
     * GlobalEventExecutor.INSTANCE Is a global event executor, a single example  
     */  
    private static ChannelGroup channelGroup = new DefaultChannelGroup(GlobalEventExecutor.INSTANCE);  
  
    /**  
     * Store the corresponding information between the user and Chanel to send messages to the specified user  
     */  
    private static ConcurrentHashMap<String,Channel> userChannelMap = new ConcurrentHashMap<>();  
  
    private NettyConfig() {}  
      
    /**  
     * Get channel group  
     * @return  
     */  
    public static ChannelGroup getChannelGroup() {  
        return channelGroup;  
    }  
  
    /**  
     * Get user channel map  
     * @return  
     */  
    public static ConcurrentHashMap<String,Channel> getUserChannelMap(){  
        return userChannelMap;  
    }  

**Step 2: * * create a netty server, define two eventloopgroups, and define the tcp connection requests of the secondary client of bossGroup. workGroup is responsible for the read and write operations with the client before. It should be noted that a new thread needs to be opened to execute the netty server, otherwise the main thread will be blocked, and other controller interfaces of the project will not be called at that time.

package com.sixj.nettypush.websocket;  
  
import io.netty.bootstrap.ServerBootstrap;  
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.NioServerSocketChannel;  
import io.netty.handler.codec.http.HttpObjectAggregator;  
import io.netty.handler.codec.http.HttpServerCodec;  
import io.netty.handler.codec.http.websocketx.WebSocketServerProtocolHandler;  
import io.netty.handler.codec.serialization.ObjectEncoder;  
import io.netty.handler.stream.ChunkedWriteHandler;  
import org.slf4j.Logger;  
import org.slf4j.LoggerFactory;  
import org.springframework.beans.factory.annotation.Autowired;  
import org.springframework.beans.factory.annotation.Value;  
import org.springframework.stereotype.Component;  
  
import javax.annotation.PostConstruct;  
import javax.annotation.PreDestroy;  
import java.net.InetSocketAddress;  
  
/**  
 * @author sixiaojie  
 * @date 2020-03-28-13:44  
 */  
  
@Component  
public class NettyServer{  
    private static final Logger log = LoggerFactory.getLogger(NettyServer.class);  
    /**  
     * webSocket Protocol name  
     */  
    private static final String WEBSOCKET_PROTOCOL = "WebSocket";  
  
    /**  
     * Port number  
     */  
    @Value("${webSocket.netty.port:58080}")  
    private int port;  
  
    /**  
     * webSocket Route  
     */  
    @Value("${webSocket.netty.path:/webSocket}")  
    private String webSocketPath;  
  
    @Autowired  
    private WebSocketHandler webSocketHandler;  
  
    private EventLoopGroup bossGroup;  
    private EventLoopGroup workGroup;  
  
    /**  
     * start-up  
     * @throws InterruptedException  
     */  
    private void start() throws InterruptedException {  
        bossGroup = new NioEventLoopGroup();  
        workGroup = new NioEventLoopGroup();  
        ServerBootstrap bootstrap = new ServerBootstrap();  
        // For the tcp connection request of the secondary client of bossGroup, workGroup is responsible for the read and write operations before the client  
        bootstrap.group(bossGroup,workGroup);  
        // Set channel of NIO type  
        bootstrap.channel(NioServerSocketChannel.class);  
        // Set listening port  
        bootstrap.localAddress(new InetSocketAddress(port));  
        // A channel is created when the connection arrives  
        bootstrap.childHandler(new ChannelInitializer<SocketChannel>() {  
  
            @Override  
            protected void initChannel(SocketChannel ch) throws Exception {  
                // The Handler in the pipeline management channel is used to handle business  
                // The webSocket protocol itself is based on the http protocol, so we also need to use the http codec here  
                ch.pipeline().addLast(new HttpServerCodec());  
                ch.pipeline().addLast(new ObjectEncoder());  
                // A processor written in blocks  
                ch.pipeline().addLast(new ChunkedWriteHandler());  
                /*  
                Explain:  
                1,http Data is segmented during transmission, and HttpObjectAggregator can aggregate multiple segments  
                2,That's why when a browser sends a lot of data, it sends multiple http requests  
                 */  
                ch.pipeline().addLast(new HttpObjectAggregator(8192));  
                /*  
                Explain:  
                1,Corresponding to webSocket, its data is passed in the form of frame  
                2,When the browser requests, ws://localhost:58080/xxx indicates the requested uri  
                3,The core function is to upgrade http protocol to ws protocol and maintain long connection  
                */  
                ch.pipeline().addLast(new WebSocketServerProtocolHandler(webSocketPath, WEBSOCKET_PROTOCOL, true, 65536 * 10));  
                // Custom handler, handling business logic  
                ch.pipeline().addLast(webSocketHandler);  
  
            }  
        });  
        // After the configuration is completed, start binding the server, and block by calling the sync synchronization method until the binding is successful  
        ChannelFuture channelFuture = bootstrap.bind().sync();  
        log.info("Server started and listen on:{}",channelFuture.channel().localAddress());  
        // Monitor the closed channel  
        channelFuture.channel().closeFuture().sync();  
    }  
  
    /**  
     * Release resources  
     * @throws InterruptedException  
     */  
    @PreDestroy  
    public void destroy() throws InterruptedException {  
        if(bossGroup != null){  
            bossGroup.shutdownGracefully().sync();  
        }  
        if(workGroup != null){  
            workGroup.shutdownGracefully().sync();  
        }  
    }  
    @PostConstruct()  
    public void init() {  
        //A new thread needs to be opened to execute the netty server  
        new Thread(() -> {  
            try {  
                start();  
            } catch (InterruptedException e) {  
                e.printStackTrace();  
            }  
        }).start();  
    }  
}

Step 3: implement the WebSocketHandler of the business, and see the notes for the implementation logic

package com.sixj.nettypush.websocket;  
  
import cn.hutool.json.JSONObject;  
import cn.hutool.json.JSONUtil;  
import com.sixj.nettypush.config.NettyConfig;  
import io.netty.channel.ChannelHandler;  
import io.netty.channel.ChannelHandlerContext;  
import io.netty.channel.SimpleChannelInboundHandler;  
import io.netty.handler.codec.http.websocketx.TextWebSocketFrame;  
import io.netty.util.AttributeKey;  
import org.slf4j.Logger;  
import org.slf4j.LoggerFactory;  
import org.springframework.stereotype.Component;  
  
  
/**  
 * TextWebSocketFrame Type, representing a text frame  
 * @author sixiaojie  
 * @date 2020-03-28-13:47  
 */  
@Component  
@ChannelHandler.Sharable  
public class WebSocketHandler extends SimpleChannelInboundHandler<TextWebSocketFrame> {  
    private static final Logger log = LoggerFactory.getLogger(NettyServer.class);  
  
    /**  
     * Once connected, the first is executed  
     * @param ctx  
     * @throws Exception  
     */  
    @Override  
    public void handlerAdded(ChannelHandlerContext ctx) throws Exception {  
        log.info("handlerAdded Be called"+ctx.channel().id().asLongText());  
        // Add to channelGroup channel group  
        NettyConfig.getChannelGroup().add(ctx.channel());  
    }  
  
    /**  
     * Read data  
     */  
    @Override  
    protected void channelRead0(ChannelHandlerContext ctx, TextWebSocketFrame msg) throws Exception {  
        log.info("Server received message:{}",msg.text());  
  
        // Get user ID and associate with channel  
        JSONObject jsonObject = JSONUtil.parseObj(msg.text());  
        String uid = jsonObject.getStr("uid");  
        NettyConfig.getUserChannelMap().put(uid,ctx.channel());  
  
        // The user ID is added to the channel as a custom attribute, which is convenient to obtain the user ID in the channel at any time  
        AttributeKey<String> key = AttributeKey.valueOf("userId");  
        ctx.channel().attr(key).setIfAbsent(uid);  
  
        // Reply message  
        ctx.channel().writeAndFlush(new TextWebSocketFrame("Server connection succeeded!"));  
    }  
  
    @Override  
    public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {  
        log.info("handlerRemoved Be called"+ctx.channel().id().asLongText());  
        // Delete channel  
        NettyConfig.getChannelGroup().remove(ctx.channel());  
        removeUserId(ctx);  
    }  
  
    @Override  
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {  
        log.info("Exception:{}",cause.getMessage());  
        // Delete channel  
        NettyConfig.getChannelGroup().remove(ctx.channel());  
        removeUserId(ctx);  
        ctx.close();  
    }  
  
    /**  
     * Delete the corresponding relationship between user and channel  
     * @param ctx  
     */  
    private void removeUserId(ChannelHandlerContext ctx){  
        AttributeKey<String> key = AttributeKey.valueOf("userId");  
        String userId = ctx.channel().attr(key).get();  
        NettyConfig.getUserChannelMap().remove(userId);  
    }  
}

**Step 4: * * specific message push interface

public interface PushService {  
    /**  
     * Push to specified user  
     * @param userId  
     * @param msg  
     */  
    void pushMsgToOne(String userId,String msg);  
  
    /**  
     * Push to all users  
     * @param msg  
     */  
    void pushMsgToAll(String msg);  
}

Interface implementation class:

import java.util.concurrent.ConcurrentHashMap;  
  
/**  
 * @author sixiaojie  
 * @date 2020-03-30-20:10  
 */  
@Service  
public class PushServiceImpl implements PushService {  
  
    @Override  
    public void pushMsgToOne(String userId, String msg){  
        ConcurrentHashMap<String, Channel> userChannelMap = NettyConfig.getUserChannelMap();  
        Channel channel = userChannelMap.get(userId);  
        channel.writeAndFlush(new TextWebSocketFrame(msg));  
    }  
    @Override  
    public void pushMsgToAll(String msg){  
        NettyConfig.getChannelGroup().writeAndFlush(new TextWebSocketFrame(msg));  
    }  
}

controller:

package com.sixj.nettypush.controller;  
  
import com.sixj.nettypush.service.PushService;  
import org.springframework.beans.factory.annotation.Autowired;  
import org.springframework.web.bind.annotation.PostMapping;  
import org.springframework.web.bind.annotation.RequestMapping;  
import org.springframework.web.bind.annotation.RequestParam;  
import org.springframework.web.bind.annotation.RestController;  
  
/**  
 * @author sixiaojie  
 * @date 2020-03-30-20:08  
 */  
@RestController  
@RequestMapping("/push")  
public class PushController {  
  
    @Autowired  
    private PushService pushService;  
  
    /**  
     * Push to all users  
     * @param msg  
     */  
    @PostMapping("/pushAll")  
    public void pushToAll(@RequestParam("msg") String msg){  
        pushService.pushMsgToAll(msg);  
    }  
    /**  
     * Push to specified user  
     * @param userId  
     * @param msg  
     */  
    @PostMapping("/pushOne")  
    public void pushMsgToOne(@RequestParam("userId") String userId,@RequestParam("msg") String msg){  
        pushService.pushMsgToOne(userId,msg);  
    }  
  
}

**Step 5: * * front end html page

<!DOCTYPE html>  
<html lang="en">  
<head>  
    <meta charset="UTF-8">  
    <title>Title</title>  
</head>  
<body>  
<script>  
    var socket;  
    // Determine whether the current browser supports webSocket  
    if(window.WebSocket){  
        socket = new WebSocket("ws://192.168.174.25:58080/webSocket")  
        // Equivalent to the read event of the channel, ev receives the message sent back by the server  
        socket.onmessage = function (ev) {  
            var rt = document.getElementById("responseText");  
            rt.value = rt.value + "\n" + ev.data;  
        }  
        // Equivalent to connection on  
        socket.onopen = function (ev) {  
            var rt = document.getElementById("responseText");  
            rt.value =  "Connection turned on..."  
            socket.send(  
                JSON.stringify({  
                    // The connection succeeds. The user ID is passed to the server  
                    uid: "123456"  
                })  
            );  
        }  
        // Equivalent to connection closing  
        socket.onclose = function (ev) {  
            var rt = document.getElementById("responseText");  
            rt.value = rt.value + "\n" + "Connection closed...";  
        }  
    }else{  
        alert("Current browser does not support webSocket")  
    }  
  
  
</script>  
    <form onsubmit="return false">  
        <textarea id="responseText" style="height: 150px; width: 300px;"></textarea>  
        <input type="button" value="Empty content" onclick="document.getElementById('responseText').value=''">  
    </form>  
</body>  
</html>

So far, all the code has been written. Test it

Run the html file first, and you will see the message "server connection succeeded!" returned by the server to the front end , the back-end log will print the message received by the server: {"uid":"123456"}

Then use postman to test the pushed interface

Thank you for your reference- http://bjbsair.com/2020-04-01/tech-info/18392.html

One of the application scenarios of Netty in the project: the message push function can be used to push messages to all users, or to specify a user. The spring boot project is created. The background server uses Netty technology, and the front page uses WebSocket technology.

General realization idea:

When the front-end uses webSocket to create a connection with the server, it will pass the user ID to the server and associate the user ID with the channel for storage. Meanwhile, it will put the channel into the channel group. If it needs to send messages to all users, it will directly execute the writeAndFlush() method of the channel group. If it needs to send messages to the specified users, it will query the corresponding channel according to the user ID, Then execute the writeAndFlush() method to get the message pushed by the server, and display the message content to the text field

The following is the specific code implementation. Basically, each step of operation is provided with notes. It should be easier to understand with notes.

**Step 0: * * introduce the dependency of Netty and a toolkit (only json tools are used, which can be replaced by other json tools)

<dependency>  
  <groupId>io.netty</groupId>  
  <artifactId>netty-all</artifactId>   
  <version>4.1.33.Final</version>  
</dependency>  
  
<dependency>  
  <groupId>cn.hutool</groupId>  
  <artifactId>hutool-all</artifactId>  
  <version>5.2.3</version>  
</dependency>

**Step 1: * * define a channel group in NettyConfig, manage all channels, and define a map to manage the corresponding relationship between users and channels.

package com.sixj.nettypush.config;  
  
import io.netty.channel.Channel;  
import io.netty.channel.group.ChannelGroup;  
import io.netty.channel.group.DefaultChannelGroup;  
import io.netty.util.concurrent.GlobalEventExecutor;  
  
import java.util.concurrent.ConcurrentHashMap;  
  
/**  
 * @author sixiaojie  
 * @date 2020-03-28-15:07  
 */  
public class NettyConfig {  
    /**  
     * Define a channel group and manage all channels  
     * GlobalEventExecutor.INSTANCE Is a global event executor, a single example  
     */  
    private static ChannelGroup channelGroup = new DefaultChannelGroup(GlobalEventExecutor.INSTANCE);  
  
    /**  
     * Store the corresponding information between the user and Chanel to send messages to the specified user  
     */  
    private static ConcurrentHashMap<String,Channel> userChannelMap = new ConcurrentHashMap<>();  
  
    private NettyConfig() {}  
      
    /**  
     * Get channel group  
     * @return  
     */  
    public static ChannelGroup getChannelGroup() {  
        return channelGroup;  
    }  
  
    /**  
     * Get user channel map  
     * @return  
     */  
    public static ConcurrentHashMap<String,Channel> getUserChannelMap(){  
        return userChannelMap;  
    }  

**Step 2: * * create a netty server, define two eventloopgroups, and define the tcp connection requests of the secondary client of bossGroup. workGroup is responsible for the read and write operations with the client before. It should be noted that a new thread needs to be opened to execute the netty server, otherwise the main thread will be blocked, and other controller interfaces of the project will not be called at that time.

package com.sixj.nettypush.websocket;  
  
import io.netty.bootstrap.ServerBootstrap;  
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.NioServerSocketChannel;  
import io.netty.handler.codec.http.HttpObjectAggregator;  
import io.netty.handler.codec.http.HttpServerCodec;  
import io.netty.handler.codec.http.websocketx.WebSocketServerProtocolHandler;  
import io.netty.handler.codec.serialization.ObjectEncoder;  
import io.netty.handler.stream.ChunkedWriteHandler;  
import org.slf4j.Logger;  
import org.slf4j.LoggerFactory;  
import org.springframework.beans.factory.annotation.Autowired;  
import org.springframework.beans.factory.annotation.Value;  
import org.springframework.stereotype.Component;  
  
import javax.annotation.PostConstruct;  
import javax.annotation.PreDestroy;  
import java.net.InetSocketAddress;  
  
/**  
 * @author sixiaojie  
 * @date 2020-03-28-13:44  
 */  
  
@Component  
public class NettyServer{  
    private static final Logger log = LoggerFactory.getLogger(NettyServer.class);  
    /**  
     * webSocket Protocol name  
     */  
    private static final String WEBSOCKET_PROTOCOL = "WebSocket";  
  
    /**  
     * Port number  
     */  
    @Value("${webSocket.netty.port:58080}")  
    private int port;  
  
    /**  
     * webSocket Route  
     */  
    @Value("${webSocket.netty.path:/webSocket}")  
    private String webSocketPath;  
  
    @Autowired  
    private WebSocketHandler webSocketHandler;  
  
    private EventLoopGroup bossGroup;  
    private EventLoopGroup workGroup;  
  
    /**  
     * start-up  
     * @throws InterruptedException  
     */  
    private void start() throws InterruptedException {  
        bossGroup = new NioEventLoopGroup();  
        workGroup = new NioEventLoopGroup();  
        ServerBootstrap bootstrap = new ServerBootstrap();  
        // For the tcp connection request of the secondary client of bossGroup, workGroup is responsible for the read and write operations before the client  
        bootstrap.group(bossGroup,workGroup);  
        // Set channel of NIO type  
        bootstrap.channel(NioServerSocketChannel.class);  
        // Set listening port  
        bootstrap.localAddress(new InetSocketAddress(port));  
        // A channel is created when the connection arrives  
        bootstrap.childHandler(new ChannelInitializer<SocketChannel>() {  
  
            @Override  
            protected void initChannel(SocketChannel ch) throws Exception {  
                // The Handler in the pipeline management channel is used to handle business  
                // The webSocket protocol itself is based on the http protocol, so we also need to use the http codec here  
                ch.pipeline().addLast(new HttpServerCodec());  
                ch.pipeline().addLast(new ObjectEncoder());  
                // A processor written in blocks  
                ch.pipeline().addLast(new ChunkedWriteHandler());  
                /*  
                Explain:  
                1,http Data is segmented during transmission, and HttpObjectAggregator can aggregate multiple segments  
                2,That's why when a browser sends a lot of data, it sends multiple http requests  
                 */  
                ch.pipeline().addLast(new HttpObjectAggregator(8192));  
                /*  
                Explain:  
                1,Corresponding to webSocket, its data is passed in the form of frame  
                2,When the browser requests, ws://localhost:58080/xxx indicates the requested uri  
                3,The core function is to upgrade http protocol to ws protocol and maintain long connection  
                */  
                ch.pipeline().addLast(new WebSocketServerProtocolHandler(webSocketPath, WEBSOCKET_PROTOCOL, true, 65536 * 10));  
                // Custom handler, handling business logic  
                ch.pipeline().addLast(webSocketHandler);  
  
            }  
        });  
        // After the configuration is completed, start binding the server, and block by calling the sync synchronization method until the binding is successful  
        ChannelFuture channelFuture = bootstrap.bind().sync();  
        log.info("Server started and listen on:{}",channelFuture.channel().localAddress());  
        // Monitor the closed channel  
        channelFuture.channel().closeFuture().sync();  
    }  
  
    /**  
     * Release resources  
     * @throws InterruptedException  
     */  
    @PreDestroy  
    public void destroy() throws InterruptedException {  
        if(bossGroup != null){  
            bossGroup.shutdownGracefully().sync();  
        }  
        if(workGroup != null){  
            workGroup.shutdownGracefully().sync();  
        }  
    }  
    @PostConstruct()  
    public void init() {  
        //A new thread needs to be opened to execute the netty server  
        new Thread(() -> {  
            try {  
                start();  
            } catch (InterruptedException e) {  
                e.printStackTrace();  
            }  
        }).start();  
    }  
}

Step 3: implement the WebSocketHandler of the business, and see the notes for the implementation logic

package com.sixj.nettypush.websocket;  
  
import cn.hutool.json.JSONObject;  
import cn.hutool.json.JSONUtil;  
import com.sixj.nettypush.config.NettyConfig;  
import io.netty.channel.ChannelHandler;  
import io.netty.channel.ChannelHandlerContext;  
import io.netty.channel.SimpleChannelInboundHandler;  
import io.netty.handler.codec.http.websocketx.TextWebSocketFrame;  
import io.netty.util.AttributeKey;  
import org.slf4j.Logger;  
import org.slf4j.LoggerFactory;  
import org.springframework.stereotype.Component;  
  
  
/**  
 * TextWebSocketFrame Type, representing a text frame  
 * @author sixiaojie  
 * @date 2020-03-28-13:47  
 */  
@Component  
@ChannelHandler.Sharable  
public class WebSocketHandler extends SimpleChannelInboundHandler<TextWebSocketFrame> {  
    private static final Logger log = LoggerFactory.getLogger(NettyServer.class);  
  
    /**  
     * Once connected, the first is executed  
     * @param ctx  
     * @throws Exception  
     */  
    @Override  
    public void handlerAdded(ChannelHandlerContext ctx) throws Exception {  
        log.info("handlerAdded Be called"+ctx.channel().id().asLongText());  
        // Add to channelGroup channel group  
        NettyConfig.getChannelGroup().add(ctx.channel());  
    }  
  
    /**  
     * Read data  
     */  
    @Override  
    protected void channelRead0(ChannelHandlerContext ctx, TextWebSocketFrame msg) throws Exception {  
        log.info("Server received message:{}",msg.text());  
  
        // Get user ID and associate with channel  
        JSONObject jsonObject = JSONUtil.parseObj(msg.text());  
        String uid = jsonObject.getStr("uid");  
        NettyConfig.getUserChannelMap().put(uid,ctx.channel());  
  
        // The user ID is added to the channel as a custom attribute, which is convenient to obtain the user ID in the channel at any time  
        AttributeKey<String> key = AttributeKey.valueOf("userId");  
        ctx.channel().attr(key).setIfAbsent(uid);  
  
        // Reply message  
        ctx.channel().writeAndFlush(new TextWebSocketFrame("Server connection succeeded!"));  
    }  
  
    @Override  
    public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {  
        log.info("handlerRemoved Be called"+ctx.channel().id().asLongText());  
        // Delete channel  
        NettyConfig.getChannelGroup().remove(ctx.channel());  
        removeUserId(ctx);  
    }  
  
    @Override  
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {  
        log.info("Exception:{}",cause.getMessage());  
        // Delete channel  
        NettyConfig.getChannelGroup().remove(ctx.channel());  
        removeUserId(ctx);  
        ctx.close();  
    }  
  
    /**  
     * Delete the corresponding relationship between user and channel  
     * @param ctx  
     */  
    private void removeUserId(ChannelHandlerContext ctx){  
        AttributeKey<String> key = AttributeKey.valueOf("userId");  
        String userId = ctx.channel().attr(key).get();  
        NettyConfig.getUserChannelMap().remove(userId);  
    }  
}

**Step 4: * * specific message push interface

public interface PushService {  
    /**  
     * Push to specified user  
     * @param userId  
     * @param msg  
     */  
    void pushMsgToOne(String userId,String msg);  
  
    /**  
     * Push to all users  
     * @param msg  
     */  
    void pushMsgToAll(String msg);  
}

Interface implementation class:

import java.util.concurrent.ConcurrentHashMap;  
  
/**  
 * @author sixiaojie  
 * @date 2020-03-30-20:10  
 */  
@Service  
public class PushServiceImpl implements PushService {  
  
    @Override  
    public void pushMsgToOne(String userId, String msg){  
        ConcurrentHashMap<String, Channel> userChannelMap = NettyConfig.getUserChannelMap();  
        Channel channel = userChannelMap.get(userId);  
        channel.writeAndFlush(new TextWebSocketFrame(msg));  
    }  
    @Override  
    public void pushMsgToAll(String msg){  
        NettyConfig.getChannelGroup().writeAndFlush(new TextWebSocketFrame(msg));  
    }  
}

controller:

package com.sixj.nettypush.controller;  
  
import com.sixj.nettypush.service.PushService;  
import org.springframework.beans.factory.annotation.Autowired;  
import org.springframework.web.bind.annotation.PostMapping;  
import org.springframework.web.bind.annotation.RequestMapping;  
import org.springframework.web.bind.annotation.RequestParam;  
import org.springframework.web.bind.annotation.RestController;  
  
/**  
 * @author sixiaojie  
 * @date 2020-03-30-20:08  
 */  
@RestController  
@RequestMapping("/push")  
public class PushController {  
  
    @Autowired  
    private PushService pushService;  
  
    /**  
     * Push to all users  
     * @param msg  
     */  
    @PostMapping("/pushAll")  
    public void pushToAll(@RequestParam("msg") String msg){  
        pushService.pushMsgToAll(msg);  
    }  
    /**  
     * Push to specified user  
     * @param userId  
     * @param msg  
     */  
    @PostMapping("/pushOne")  
    public void pushMsgToOne(@RequestParam("userId") String userId,@RequestParam("msg") String msg){  
        pushService.pushMsgToOne(userId,msg);  
    }  
  
}

**Step 5: * * front end html page

<!DOCTYPE html>  
<html lang="en">  
<head>  
    <meta charset="UTF-8">  
    <title>Title</title>  
</head>  
<body>  
<script>  
    var socket;  
    // Determine whether the current browser supports webSocket  
    if(window.WebSocket){  
        socket = new WebSocket("ws://192.168.174.25:58080/webSocket")  
        // Equivalent to the read event of the channel, ev receives the message sent back by the server  
        socket.onmessage = function (ev) {  
            var rt = document.getElementById("responseText");  
            rt.value = rt.value + "\n" + ev.data;  
        }  
        // Equivalent to connection on  
        socket.onopen = function (ev) {  
            var rt = document.getElementById("responseText");  
            rt.value =  "Connection turned on..."  
            socket.send(  
                JSON.stringify({  
                    // The connection succeeds. The user ID is passed to the server  
                    uid: "123456"  
                })  
            );  
        }  
        // Equivalent to connection closing  
        socket.onclose = function (ev) {  
            var rt = document.getElementById("responseText");  
            rt.value = rt.value + "\n" + "Connection closed...";  
        }  
    }else{  
        alert("Current browser does not support webSocket")  
    }  
  
  
</script>  
    <form onsubmit="return false">  
        <textarea id="responseText" style="height: 150px; width: 300px;"></textarea>  
        <input type="button" value="Empty content" onclick="document.getElementById('responseText').value=''">  
    </form>  
</body>  
</html>

So far, all the code has been written. Test it

Run the html file first, and you will see the message "server connection succeeded!" returned by the server to the front end , the back-end log will print the message received by the server: {"uid":"123456"}

Then use postman to test the pushed interface

Thank you for your reference- http://bjbsair.com/2020-04-01/tech-info/18392.html

One of the application scenarios of Netty in the project: the message push function can be used to push messages to all users, or to specify a user. The spring boot project is created. The background server uses Netty technology, and the front page uses WebSocket technology.

General realization idea:

When the front-end uses webSocket to create a connection with the server, it will pass the user ID to the server and associate the user ID with the channel for storage. Meanwhile, it will put the channel into the channel group. If it needs to send messages to all users, it will directly execute the writeAndFlush() method of the channel group. If it needs to send messages to the specified users, it will query the corresponding channel according to the user ID, Then execute the writeAndFlush() method to get the message pushed by the server, and display the message content to the text field

The following is the specific code implementation. Basically, each step of operation is provided with notes. It should be easier to understand with notes.

**Step 0: * * introduce the dependency of Netty and a toolkit (only json tools are used, which can be replaced by other json tools)

<dependency>  
  <groupId>io.netty</groupId>  
  <artifactId>netty-all</artifactId>   
  <version>4.1.33.Final</version>  
</dependency>  
  
<dependency>  
  <groupId>cn.hutool</groupId>  
  <artifactId>hutool-all</artifactId>  
  <version>5.2.3</version>  
</dependency>

**Step 1: * * define a channel group in NettyConfig, manage all channels, and define a map to manage the corresponding relationship between users and channels.

package com.sixj.nettypush.config;  
  
import io.netty.channel.Channel;  
import io.netty.channel.group.ChannelGroup;  
import io.netty.channel.group.DefaultChannelGroup;  
import io.netty.util.concurrent.GlobalEventExecutor;  
  
import java.util.concurrent.ConcurrentHashMap;  
  
/**  
 * @author sixiaojie  
 * @date 2020-03-28-15:07  
 */  
public class NettyConfig {  
    /**  
     * Define a channel group and manage all channels  
     * GlobalEventExecutor.INSTANCE Is a global event executor, a single example  
     */  
    private static ChannelGroup channelGroup = new DefaultChannelGroup(GlobalEventExecutor.INSTANCE);  
  
    /**  
     * Store the corresponding information between the user and Chanel to send messages to the specified user  
     */  
    private static ConcurrentHashMap<String,Channel> userChannelMap = new ConcurrentHashMap<>();  
  
    private NettyConfig() {}  
      
    /**  
     * Get channel group  
     * @return  
     */  
    public static ChannelGroup getChannelGroup() {  
        return channelGroup;  
    }  
  
    /**  
     * Get user channel map  
     * @return  
     */  
    public static ConcurrentHashMap<String,Channel> getUserChannelMap(){  
        return userChannelMap;  
    }  

**Step 2: * * create a netty server, define two eventloopgroups, and define the tcp connection requests of the secondary client of bossGroup. workGroup is responsible for the read and write operations with the client before. It should be noted that a new thread needs to be opened to execute the netty server, otherwise the main thread will be blocked, and other controller interfaces of the project will not be called at that time.

package com.sixj.nettypush.websocket;  
  
import io.netty.bootstrap.ServerBootstrap;  
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.NioServerSocketChannel;  
import io.netty.handler.codec.http.HttpObjectAggregator;  
import io.netty.handler.codec.http.HttpServerCodec;  
import io.netty.handler.codec.http.websocketx.WebSocketServerProtocolHandler;  
import io.netty.handler.codec.serialization.ObjectEncoder;  
import io.netty.handler.stream.ChunkedWriteHandler;  
import org.slf4j.Logger;  
import org.slf4j.LoggerFactory;  
import org.springframework.beans.factory.annotation.Autowired;  
import org.springframework.beans.factory.annotation.Value;  
import org.springframework.stereotype.Component;  
  
import javax.annotation.PostConstruct;  
import javax.annotation.PreDestroy;  
import java.net.InetSocketAddress;  
  
/**  
 * @author sixiaojie  
 * @date 2020-03-28-13:44  
 */  
  
@Component  
public class NettyServer{  
    private static final Logger log = LoggerFactory.getLogger(NettyServer.class);  
    /**  
     * webSocket Protocol name  
     */  
    private static final String WEBSOCKET_PROTOCOL = "WebSocket";  
  
    /**  
     * Port number  
     */  
    @Value("${webSocket.netty.port:58080}")  
    private int port;  
  
    /**  
     * webSocket Route  
     */  
    @Value("${webSocket.netty.path:/webSocket}")  
    private String webSocketPath;  
  
    @Autowired  
    private WebSocketHandler webSocketHandler;  
  
    private EventLoopGroup bossGroup;  
    private EventLoopGroup workGroup;  
  
    /**  
     * start-up  
     * @throws InterruptedException  
     */  
    private void start() throws InterruptedException {  
        bossGroup = new NioEventLoopGroup();  
        workGroup = new NioEventLoopGroup();  
        ServerBootstrap bootstrap = new ServerBootstrap();  
        // For the tcp connection request of the secondary client of bossGroup, workGroup is responsible for the read and write operations before the client  
        bootstrap.group(bossGroup,workGroup);  
        // Set channel of NIO type  
        bootstrap.channel(NioServerSocketChannel.class);  
        // Set listening port  
        bootstrap.localAddress(new InetSocketAddress(port));  
        // A channel is created when the connection arrives  
        bootstrap.childHandler(new ChannelInitializer<SocketChannel>() {  
  
            @Override  
            protected void initChannel(SocketChannel ch) throws Exception {  
                // The Handler in the pipeline management channel is used to handle business  
                // The webSocket protocol itself is based on the http protocol, so we also need to use the http codec here  
                ch.pipeline().addLast(new HttpServerCodec());  
                ch.pipeline().addLast(new ObjectEncoder());  
                // A processor written in blocks  
                ch.pipeline().addLast(new ChunkedWriteHandler());  
                /*  
                Explain:  
                1,http Data is segmented during transmission, and HttpObjectAggregator can aggregate multiple segments  
                2,That's why when a browser sends a lot of data, it sends multiple http requests  
                 */  
                ch.pipeline().addLast(new HttpObjectAggregator(8192));  
                /*  
                Explain:  
                1,Corresponding to webSocket, its data is passed in the form of frame  
                2,When the browser requests, ws://localhost:58080/xxx indicates the requested uri  
                3,The core function is to upgrade http protocol to ws protocol and maintain long connection  
                */  
                ch.pipeline().addLast(new WebSocketServerProtocolHandler(webSocketPath, WEBSOCKET_PROTOCOL, true, 65536 * 10));  
                // Custom handler, handling business logic  
                ch.pipeline().addLast(webSocketHandler);  
  
            }  
        });  
        // After the configuration is completed, start binding the server, and block by calling the sync synchronization method until the binding is successful  
        ChannelFuture channelFuture = bootstrap.bind().sync();  
        log.info("Server started and listen on:{}",channelFuture.channel().localAddress());  
        // Monitor the closed channel  
        channelFuture.channel().closeFuture().sync();  
    }  
  
    /**  
     * Release resources  
     * @throws InterruptedException  
     */  
    @PreDestroy  
    public void destroy() throws InterruptedException {  
        if(bossGroup != null){  
            bossGroup.shutdownGracefully().sync();  
        }  
        if(workGroup != null){  
            workGroup.shutdownGracefully().sync();  
        }  
    }  
    @PostConstruct()  
    public void init() {  
        //A new thread needs to be opened to execute the netty server  
        new Thread(() -> {  
            try {  
                start();  
            } catch (InterruptedException e) {  
                e.printStackTrace();  
            }  
        }).start();  
    }  
}

Step 3: implement the WebSocketHandler of the business, and see the notes for the implementation logic

package com.sixj.nettypush.websocket;  
  
import cn.hutool.json.JSONObject;  
import cn.hutool.json.JSONUtil;  
import com.sixj.nettypush.config.NettyConfig;  
import io.netty.channel.ChannelHandler;  
import io.netty.channel.ChannelHandlerContext;  
import io.netty.channel.SimpleChannelInboundHandler;  
import io.netty.handler.codec.http.websocketx.TextWebSocketFrame;  
import io.netty.util.AttributeKey;  
import org.slf4j.Logger;  
import org.slf4j.LoggerFactory;  
import org.springframework.stereotype.Component;  
  
  
/**  
 * TextWebSocketFrame Type, representing a text frame  
 * @author sixiaojie  
 * @date 2020-03-28-13:47  
 */  
@Component  
@ChannelHandler.Sharable  
public class WebSocketHandler extends SimpleChannelInboundHandler<TextWebSocketFrame> {  
    private static final Logger log = LoggerFactory.getLogger(NettyServer.class);  
  
    /**  
     * Once connected, the first is executed  
     * @param ctx  
     * @throws Exception  
     */  
    @Override  
    public void handlerAdded(ChannelHandlerContext ctx) throws Exception {  
        log.info("handlerAdded Be called"+ctx.channel().id().asLongText());  
        // Add to channelGroup channel group  
        NettyConfig.getChannelGroup().add(ctx.channel());  
    }  
  
    /**  
     * Read data  
     */  
    @Override  
    protected void channelRead0(ChannelHandlerContext ctx, TextWebSocketFrame msg) throws Exception {  
        log.info("Server received message:{}",msg.text());  
  
        // Get user ID and associate with channel  
        JSONObject jsonObject = JSONUtil.parseObj(msg.text());  
        String uid = jsonObject.getStr("uid");  
        NettyConfig.getUserChannelMap().put(uid,ctx.channel());  
  
        // The user ID is added to the channel as a custom attribute, which is convenient to obtain the user ID in the channel at any time  
        AttributeKey<String> key = AttributeKey.valueOf("userId");  
        ctx.channel().attr(key).setIfAbsent(uid);  
  
        // Reply message  
        ctx.channel().writeAndFlush(new TextWebSocketFrame("Server connection succeeded!"));  
    }  
  
    @Override  
    public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {  
        log.info("handlerRemoved Be called"+ctx.channel().id().asLongText());  
        // Delete channel  
        NettyConfig.getChannelGroup().remove(ctx.channel());  
        removeUserId(ctx);  
    }  
  
    @Override  
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {  
        log.info("Exception:{}",cause.getMessage());  
        // Delete channel  
        NettyConfig.getChannelGroup().remove(ctx.channel());  
        removeUserId(ctx);  
        ctx.close();  
    }  
  
    /**  
     * Delete the corresponding relationship between user and channel  
     * @param ctx  
     */  
    private void removeUserId(ChannelHandlerContext ctx){  
        AttributeKey<String> key = AttributeKey.valueOf("userId");  
        String userId = ctx.channel().attr(key).get();  
        NettyConfig.getUserChannelMap().remove(userId);  
    }  
}

**Step 4: * * specific message push interface

public interface PushService {  
    /**  
     * Push to specified user  
     * @param userId  
     * @param msg  
     */  
    void pushMsgToOne(String userId,String msg);  
  
    /**  
     * Push to all users  
     * @param msg  
     */  
    void pushMsgToAll(String msg);  
}

Interface implementation class:

import java.util.concurrent.ConcurrentHashMap;  
  
/**  
 * @author sixiaojie  
 * @date 2020-03-30-20:10  
 */  
@Service  
public class PushServiceImpl implements PushService {  
  
    @Override  
    public void pushMsgToOne(String userId, String msg){  
        ConcurrentHashMap<String, Channel> userChannelMap = NettyConfig.getUserChannelMap();  
        Channel channel = userChannelMap.get(userId);  
        channel.writeAndFlush(new TextWebSocketFrame(msg));  
    }  
    @Override  
    public void pushMsgToAll(String msg){  
        NettyConfig.getChannelGroup().writeAndFlush(new TextWebSocketFrame(msg));  
    }  
}

controller:

package com.sixj.nettypush.controller;  
  
import com.sixj.nettypush.service.PushService;  
import org.springframework.beans.factory.annotation.Autowired;  
import org.springframework.web.bind.annotation.PostMapping;  
import org.springframework.web.bind.annotation.RequestMapping;  
import org.springframework.web.bind.annotation.RequestParam;  
import org.springframework.web.bind.annotation.RestController;  
  
/**  
 * @author sixiaojie  
 * @date 2020-03-30-20:08  
 */  
@RestController  
@RequestMapping("/push")  
public class PushController {  
  
    @Autowired  
    private PushService pushService;  
  
    /**  
     * Push to all users  
     * @param msg  
     */  
    @PostMapping("/pushAll")  
    public void pushToAll(@RequestParam("msg") String msg){  
        pushService.pushMsgToAll(msg);  
    }  
    /**  
     * Push to specified user  
     * @param userId  
     * @param msg  
     */  
    @PostMapping("/pushOne")  
    public void pushMsgToOne(@RequestParam("userId") String userId,@RequestParam("msg") String msg){  
        pushService.pushMsgToOne(userId,msg);  
    }  
  
}

**Step 5: * * front end html page

<!DOCTYPE html>  
<html lang="en">  
<head>  
    <meta charset="UTF-8">  
    <title>Title</title>  
</head>  
<body>  
<script>  
    var socket;  
    // Determine whether the current browser supports webSocket  
    if(window.WebSocket){  
        socket = new WebSocket("ws://192.168.174.25:58080/webSocket")  
        // Equivalent to the read event of the channel, ev receives the message sent back by the server  
        socket.onmessage = function (ev) {  
            var rt = document.getElementById("responseText");  
            rt.value = rt.value + "\n" + ev.data;  
        }  
        // Equivalent to connection on  
        socket.onopen = function (ev) {  
            var rt = document.getElementById("responseText");  
            rt.value =  "Connection turned on..."  
            socket.send(  
                JSON.stringify({  
                    // The connection succeeds. The user ID is passed to the server  
                    uid: "123456"  
                })  
            );  
        }  
        // Equivalent to connection closing  
        socket.onclose = function (ev) {  
            var rt = document.getElementById("responseText");  
            rt.value = rt.value + "\n" + "Connection closed...";  
        }  
    }else{  
        alert("Current browser does not support webSocket")  
    }  
  
  
</script>  
    <form onsubmit="return false">  
        <textarea id="responseText" style="height: 150px; width: 300px;"></textarea>  
        <input type="button" value="Empty content" onclick="document.getElementById('responseText').value=''">  
    </form>  
</body>  
</html>

So far, all the code has been written. Test it

Run the html file first, and you will see the message "server connection succeeded!" returned by the server to the front end , the back-end log will print the message received by the server: {"uid":"123456"}

Then use postman to test the pushed interface

Thank you for your reference- http://bjbsair.com/2020-04-01/tech-info/18392.html

One of the application scenarios of Netty in the project: the message push function can be used to push messages to all users, or to specify a user. The spring boot project is created. The background server uses Netty technology, and the front page uses WebSocket technology.

General realization idea:

When the front-end uses webSocket to create a connection with the server, it will pass the user ID to the server and associate the user ID with the channel for storage. Meanwhile, it will put the channel into the channel group. If it needs to send messages to all users, it will directly execute the writeAndFlush() method of the channel group. If it needs to send messages to the specified users, it will query the corresponding channel according to the user ID, Then execute the writeAndFlush() method to get the message pushed by the server, and display the message content to the text field

The following is the specific code implementation. Basically, each step of operation is provided with notes. It should be easier to understand with notes.

**Step 0: * * introduce the dependency of Netty and a toolkit (only json tools are used, which can be replaced by other json tools)

<dependency>  
  <groupId>io.netty</groupId>  
  <artifactId>netty-all</artifactId>   
  <version>4.1.33.Final</version>  
</dependency>  
  
<dependency>  
  <groupId>cn.hutool</groupId>  
  <artifactId>hutool-all</artifactId>  
  <version>5.2.3</version>  
</dependency>

**Step 1: * * define a channel group in NettyConfig, manage all channels, and define a map to manage the corresponding relationship between users and channels.

package com.sixj.nettypush.config;  
  
import io.netty.channel.Channel;  
import io.netty.channel.group.ChannelGroup;  
import io.netty.channel.group.DefaultChannelGroup;  
import io.netty.util.concurrent.GlobalEventExecutor;  
  
import java.util.concurrent.ConcurrentHashMap;  
  
/**  
 * @author sixiaojie  
 * @date 2020-03-28-15:07  
 */  
public class NettyConfig {  
    /**  
     * Define a channel group and manage all channels  
     * GlobalEventExecutor.INSTANCE Is a global event executor, a single example  
     */  
    private static ChannelGroup channelGroup = new DefaultChannelGroup(GlobalEventExecutor.INSTANCE);  
  
    /**  
     * Store the corresponding information between the user and Chanel to send messages to the specified user  
     */  
    private static ConcurrentHashMap<String,Channel> userChannelMap = new ConcurrentHashMap<>();  
  
    private NettyConfig() {}  
      
    /**  
     * Get channel group  
     * @return  
     */  
    public static ChannelGroup getChannelGroup() {  
        return channelGroup;  
    }  
  
    /**  
     * Get user channel map  
     * @return  
     */  
    public static ConcurrentHashMap<String,Channel> getUserChannelMap(){  
        return userChannelMap;  
    }  

**Step 2: * * create a netty server, define two eventloopgroups, and define the tcp connection requests of the secondary client of bossGroup. workGroup is responsible for the read and write operations with the client before. It should be noted that a new thread needs to be opened to execute the netty server, otherwise the main thread will be blocked, and other controller interfaces of the project will not be called at that time.

package com.sixj.nettypush.websocket;  
  
import io.netty.bootstrap.ServerBootstrap;  
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.NioServerSocketChannel;  
import io.netty.handler.codec.http.HttpObjectAggregator;  
import io.netty.handler.codec.http.HttpServerCodec;  
import io.netty.handler.codec.http.websocketx.WebSocketServerProtocolHandler;  
import io.netty.handler.codec.serialization.ObjectEncoder;  
import io.netty.handler.stream.ChunkedWriteHandler;  
import org.slf4j.Logger;  
import org.slf4j.LoggerFactory;  
import org.springframework.beans.factory.annotation.Autowired;  
import org.springframework.beans.factory.annotation.Value;  
import org.springframework.stereotype.Component;  
  
import javax.annotation.PostConstruct;  
import javax.annotation.PreDestroy;  
import java.net.InetSocketAddress;  
  
/**  
 * @author sixiaojie  
 * @date 2020-03-28-13:44  
 */  
  
@Component  
public class NettyServer{  
    private static final Logger log = LoggerFactory.getLogger(NettyServer.class);  
    /**  
     * webSocket Protocol name  
     */  
    private static final String WEBSOCKET_PROTOCOL = "WebSocket";  
  
    /**  
     * Port number  
     */  
    @Value("${webSocket.netty.port:58080}")  
    private int port;  
  
    /**  
     * webSocket Route  
     */  
    @Value("${webSocket.netty.path:/webSocket}")  
    private String webSocketPath;  
  
    @Autowired  
    private WebSocketHandler webSocketHandler;  
  
    private EventLoopGroup bossGroup;  
    private EventLoopGroup workGroup;  
  
    /**  
     * start-up  
     * @throws InterruptedException  
     */  
    private void start() throws InterruptedException {  
        bossGroup = new NioEventLoopGroup();  
        workGroup = new NioEventLoopGroup();  
        ServerBootstrap bootstrap = new ServerBootstrap();  
        // For the tcp connection request of the secondary client of bossGroup, workGroup is responsible for the read and write operations before the client  
        bootstrap.group(bossGroup,workGroup);  
        // Set channel of NIO type  
        bootstrap.channel(NioServerSocketChannel.class);  
        // Set listening port  
        bootstrap.localAddress(new InetSocketAddress(port));  
        // A channel is created when the connection arrives  
        bootstrap.childHandler(new ChannelInitializer<SocketChannel>() {  
  
            @Override  
            protected void initChannel(SocketChannel ch) throws Exception {  
                // The Handler in the pipeline management channel is used to handle business  
                // The webSocket protocol itself is based on the http protocol, so we also need to use the http codec here  
                ch.pipeline().addLast(new HttpServerCodec());  
                ch.pipeline().addLast(new ObjectEncoder());  
                // A processor written in blocks  
                ch.pipeline().addLast(new ChunkedWriteHandler());  
                /*  
                Explain:  
                1,http Data is segmented during transmission, and HttpObjectAggregator can aggregate multiple segments  
                2,That's why when a browser sends a lot of data, it sends multiple http requests  
                 */  
                ch.pipeline().addLast(new HttpObjectAggregator(8192));  
                /*  
                Explain:  
                1,Corresponding to webSocket, its data is passed in the form of frame  
                2,When the browser requests, ws://localhost:58080/xxx indicates the requested uri  
                3,The core function is to upgrade http protocol to ws protocol and maintain long connection  
                */  
                ch.pipeline().addLast(new WebSocketServerProtocolHandler(webSocketPath, WEBSOCKET_PROTOCOL, true, 65536 * 10));  
                // Custom handler, handling business logic  
                ch.pipeline().addLast(webSocketHandler);  
  
            }  
        });  
        // After the configuration is completed, start binding the server, and block by calling the sync synchronization method until the binding is successful  
        ChannelFuture channelFuture = bootstrap.bind().sync();  
        log.info("Server started and listen on:{}",channelFuture.channel().localAddress());  
        // Monitor the closed channel  
        channelFuture.channel().closeFuture().sync();  
    }  
  
    /**  
     * Release resources  
     * @throws InterruptedException  
     */  
    @PreDestroy  
    public void destroy() throws InterruptedException {  
        if(bossGroup != null){  
            bossGroup.shutdownGracefully().sync();  
        }  
        if(workGroup != null){  
            workGroup.shutdownGracefully().sync();  
        }  
    }  
    @PostConstruct()  
    public void init() {  
        //A new thread needs to be opened to execute the netty server  
        new Thread(() -> {  
            try {  
                start();  
            } catch (InterruptedException e) {  
                e.printStackTrace();  
            }  
        }).start();  
    }  
}

Step 3: implement the WebSocketHandler of the business, and see the notes for the implementation logic

package com.sixj.nettypush.websocket;  
  
import cn.hutool.json.JSONObject;  
import cn.hutool.json.JSONUtil;  
import com.sixj.nettypush.config.NettyConfig;  
import io.netty.channel.ChannelHandler;  
import io.netty.channel.ChannelHandlerContext;  
import io.netty.channel.SimpleChannelInboundHandler;  
import io.netty.handler.codec.http.websocketx.TextWebSocketFrame;  
import io.netty.util.AttributeKey;  
import org.slf4j.Logger;  
import org.slf4j.LoggerFactory;  
import org.springframework.stereotype.Component;  
  
  
/**  
 * TextWebSocketFrame Type, representing a text frame  
 * @author sixiaojie  
 * @date 2020-03-28-13:47  
 */  
@Component  
@ChannelHandler.Sharable  
public class WebSocketHandler extends SimpleChannelInboundHandler<TextWebSocketFrame> {  
    private static final Logger log = LoggerFactory.getLogger(NettyServer.class);  
  
    /**  
     * Once connected, the first is executed  
     * @param ctx  
     * @throws Exception  
     */  
    @Override  
    public void handlerAdded(ChannelHandlerContext ctx) throws Exception {  
        log.info("handlerAdded Be called"+ctx.channel().id().asLongText());  
        // Add to channelGroup channel group  
        NettyConfig.getChannelGroup().add(ctx.channel());  
    }  
  
    /**  
     * Read data  
     */  
    @Override  
    protected void channelRead0(ChannelHandlerContext ctx, TextWebSocketFrame msg) throws Exception {  
        log.info("Server received message:{}",msg.text());  
  
        // Get user ID and associate with channel  
        JSONObject jsonObject = JSONUtil.parseObj(msg.text());  
        String uid = jsonObject.getStr("uid");  
        NettyConfig.getUserChannelMap().put(uid,ctx.channel());  
  
        // The user ID is added to the channel as a custom attribute, which is convenient to obtain the user ID in the channel at any time  
        AttributeKey<String> key = AttributeKey.valueOf("userId");  
        ctx.channel().attr(key).setIfAbsent(uid);  
  
        // Reply message  
        ctx.channel().writeAndFlush(new TextWebSocketFrame("Server connection succeeded!"));  
    }  
  
    @Override  
    public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {  
        log.info("handlerRemoved Be called"+ctx.channel().id().asLongText());  
        // Delete channel  
        NettyConfig.getChannelGroup().remove(ctx.channel());  
        removeUserId(ctx);  
    }  
  
    @Override  
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {  
        log.info("Exception:{}",cause.getMessage());  
        // Delete channel  
        NettyConfig.getChannelGroup().remove(ctx.channel());  
        removeUserId(ctx);  
        ctx.close();  
    }  
  
    /**  
     * Delete the corresponding relationship between user and channel  
     * @param ctx  
     */  
    private void removeUserId(ChannelHandlerContext ctx){  
        AttributeKey<String> key = AttributeKey.valueOf("userId");  
        String userId = ctx.channel().attr(key).get();  
        NettyConfig.getUserChannelMap().remove(userId);  
    }  
}

**Step 4: * * specific message push interface

public interface PushService {  
    /**  
     * Push to specified user  
     * @param userId  
     * @param msg  
     */  
    void pushMsgToOne(String userId,String msg);  
  
    /**  
     * Push to all users  
     * @param msg  
     */  
    void pushMsgToAll(String msg);  
}

Interface implementation class:

import java.util.concurrent.ConcurrentHashMap;  
  
/**  
 * @author sixiaojie  
 * @date 2020-03-30-20:10  
 */  
@Service  
public class PushServiceImpl implements PushService {  
  
    @Override  
    public void pushMsgToOne(String userId, String msg){  
        ConcurrentHashMap<String, Channel> userChannelMap = NettyConfig.getUserChannelMap();  
        Channel channel = userChannelMap.get(userId);  
        channel.writeAndFlush(new TextWebSocketFrame(msg));  
    }  
    @Override  
    public void pushMsgToAll(String msg){  
        NettyConfig.getChannelGroup().writeAndFlush(new TextWebSocketFrame(msg));  
    }  
}

controller:

package com.sixj.nettypush.controller;  
  
import com.sixj.nettypush.service.PushService;  
import org.springframework.beans.factory.annotation.Autowired;  
import org.springframework.web.bind.annotation.PostMapping;  
import org.springframework.web.bind.annotation.RequestMapping;  
import org.springframework.web.bind.annotation.RequestParam;  
import org.springframework.web.bind.annotation.RestController;  
  
/**  
 * @author sixiaojie  
 * @date 2020-03-30-20:08  
 */  
@RestController  
@RequestMapping("/push")  
public class PushController {  
  
    @Autowired  
    private PushService pushService;  
  
    /**  
     * Push to all users  
     * @param msg  
     */  
    @PostMapping("/pushAll")  
    public void pushToAll(@RequestParam("msg") String msg){  
        pushService.pushMsgToAll(msg);  
    }  
    /**  
     * Push to specified user  
     * @param userId  
     * @param msg  
     */  
    @PostMapping("/pushOne")  
    public void pushMsgToOne(@RequestParam("userId") String userId,@RequestParam("msg") String msg){  
        pushService.pushMsgToOne(userId,msg);  
    }  
  
}

**Step 5: * * front end html page

<!DOCTYPE html>  
<html lang="en">  
<head>  
    <meta charset="UTF-8">  
    <title>Title</title>  
</head>  
<body>  
<script>  
    var socket;  
    // Determine whether the current browser supports webSocket  
    if(window.WebSocket){  
        socket = new WebSocket("ws://192.168.174.25:58080/webSocket")  
        // Equivalent to the read event of the channel, ev receives the message sent back by the server  
        socket.onmessage = function (ev) {  
            var rt = document.getElementById("responseText");  
            rt.value = rt.value + "\n" + ev.data;  
        }  
        // Equivalent to connection on  
        socket.onopen = function (ev) {  
            var rt = document.getElementById("responseText");  
            rt.value =  "Connection turned on..."  
            socket.send(  
                JSON.stringify({  
                    // The connection succeeds. The user ID is passed to the server  
                    uid: "123456"  
                })  
            );  
        }  
        // Equivalent to connection closing  
        socket.onclose = function (ev) {  
            var rt = document.getElementById("responseText");  
            rt.value = rt.value + "\n" + "Connection closed...";  
        }  
    }else{  
        alert("Current browser does not support webSocket")  
    }  
  
  
</script>  
    <form onsubmit="return false">  
        <textarea id="responseText" style="height: 150px; width: 300px;"></textarea>  
        <input type="button" value="Empty content" onclick="document.getElementById('responseText').value=''">  
    </form>  
</body>  
</html>

So far, all the code has been written. Test it

Run the html file first, and you will see the message "server connection succeeded!" returned by the server to the front end , the back-end log will print the message received by the server: {"uid":"123456"}

Then use postman to test the pushed interface

Thank you for your reference- http://bjbsair.com/2020-04-01/tech-info/18392.html

One of the application scenarios of Netty in the project: the message push function can be used to push messages to all users, or to specify a user. The spring boot project is created. The background server uses Netty technology, and the front page uses WebSocket technology.

General realization idea:

When the front-end uses webSocket to create a connection with the server, it will pass the user ID to the server and associate the user ID with the channel for storage. Meanwhile, it will put the channel into the channel group. If it needs to send messages to all users, it will directly execute the writeAndFlush() method of the channel group. If it needs to send messages to the specified users, it will query the corresponding channel according to the user ID, Then execute the writeAndFlush() method to get the message pushed by the server, and display the message content to the text field

The following is the specific code implementation. Basically, each step of operation is provided with notes. It should be easier to understand with notes.

**Step 0: * * introduce the dependency of Netty and a toolkit (only json tools are used, which can be replaced by other json tools)

<dependency>  
  <groupId>io.netty</groupId>  
  <artifactId>netty-all</artifactId>   
  <version>4.1.33.Final</version>  
</dependency>  
  
<dependency>  
  <groupId>cn.hutool</groupId>  
  <artifactId>hutool-all</artifactId>  
  <version>5.2.3</version>  
</dependency>

**Step 1: * * define a channel group in NettyConfig, manage all channels, and define a map to manage the corresponding relationship between users and channels.

package com.sixj.nettypush.config;  
  
import io.netty.channel.Channel;  
import io.netty.channel.group.ChannelGroup;  
import io.netty.channel.group.DefaultChannelGroup;  
import io.netty.util.concurrent.GlobalEventExecutor;  
  
import java.util.concurrent.ConcurrentHashMap;  
  
/**  
 * @author sixiaojie  
 * @date 2020-03-28-15:07  
 */  
public class NettyConfig {  
    /**  
     * Define a channel group and manage all channels  
     * GlobalEventExecutor.INSTANCE Is a global event executor, a single example  
     */  
    private static ChannelGroup channelGroup = new DefaultChannelGroup(GlobalEventExecutor.INSTANCE);  
  
    /**  
     * Store the corresponding information between the user and Chanel to send messages to the specified user  
     */  
    private static ConcurrentHashMap<String,Channel> userChannelMap = new ConcurrentHashMap<>();  
  
    private NettyConfig() {}  
      
    /**  
     * Get channel group  
     * @return  
     */  
    public static ChannelGroup getChannelGroup() {  
        return channelGroup;  
    }  
  
    /**  
     * Get user channel map  
     * @return  
     */  
    public static ConcurrentHashMap<String,Channel> getUserChannelMap(){  
        return userChannelMap;  
    }  

**Step 2: * * create a netty server, define two eventloopgroups, and define the tcp connection requests of the secondary client of bossGroup. workGroup is responsible for the read and write operations with the client before. It should be noted that a new thread needs to be opened to execute the netty server, otherwise the main thread will be blocked, and other controller interfaces of the project will not be called at that time.

package com.sixj.nettypush.websocket;  
  
import io.netty.bootstrap.ServerBootstrap;  
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.NioServerSocketChannel;  
import io.netty.handler.codec.http.HttpObjectAggregator;  
import io.netty.handler.codec.http.HttpServerCodec;  
import io.netty.handler.codec.http.websocketx.WebSocketServerProtocolHandler;  
import io.netty.handler.codec.serialization.ObjectEncoder;  
import io.netty.handler.stream.ChunkedWriteHandler;  
import org.slf4j.Logger;  
import org.slf4j.LoggerFactory;  
import org.springframework.beans.factory.annotation.Autowired;  
import org.springframework.beans.factory.annotation.Value;  
import org.springframework.stereotype.Component;  
  
import javax.annotation.PostConstruct;  
import javax.annotation.PreDestroy;  
import java.net.InetSocketAddress;  
  
/**  
 * @author sixiaojie  
 * @date 2020-03-28-13:44  
 */  
  
@Component  
public class NettyServer{  
    private static final Logger log = LoggerFactory.getLogger(NettyServer.class);  
    /**  
     * webSocket Protocol name  
     */  
    private static final String WEBSOCKET_PROTOCOL = "WebSocket";  
  
    /**  
     * Port number  
     */  
    @Value("${webSocket.netty.port:58080}")  
    private int port;  
  
    /**  
     * webSocket Route  
     */  
    @Value("${webSocket.netty.path:/webSocket}")  
    private String webSocketPath;  
  
    @Autowired  
    private WebSocketHandler webSocketHandler;  
  
    private EventLoopGroup bossGroup;  
    private EventLoopGroup workGroup;  
  
    /**  
     * start-up  
     * @throws InterruptedException  
     */  
    private void start() throws InterruptedException {  
        bossGroup = new NioEventLoopGroup();  
        workGroup = new NioEventLoopGroup();  
        ServerBootstrap bootstrap = new ServerBootstrap();  
        // For the tcp connection request of the secondary client of bossGroup, workGroup is responsible for the read and write operations before the client  
        bootstrap.group(bossGroup,workGroup);  
        // Set channel of NIO type  
        bootstrap.channel(NioServerSocketChannel.class);  
        // Set listening port  
        bootstrap.localAddress(new InetSocketAddress(port));  
        // A channel is created when the connection arrives  
        bootstrap.childHandler(new ChannelInitializer<SocketChannel>() {  
  
            @Override  
            protected void initChannel(SocketChannel ch) throws Exception {  
                // The Handler in the pipeline management channel is used to handle business  
                // The webSocket protocol itself is based on the http protocol, so we also need to use the http codec here  
                ch.pipeline().addLast(new HttpServerCodec());  
                ch.pipeline().addLast(new ObjectEncoder());  
                // A processor written in blocks  
                ch.pipeline().addLast(new ChunkedWriteHandler());  
                /*  
                Explain:  
                1,http Data is segmented during transmission, and HttpObjectAggregator can aggregate multiple segments  
                2,That's why when a browser sends a lot of data, it sends multiple http requests  
                 */  
                ch.pipeline().addLast(new HttpObjectAggregator(8192));  
                /*  
                Explain:  
                1,Corresponding to webSocket, its data is passed in the form of frame  
                2,When the browser requests, ws://localhost:58080/xxx indicates the requested uri  
                3,The core function is to upgrade http protocol to ws protocol and maintain long connection  
                */  
                ch.pipeline().addLast(new WebSocketServerProtocolHandler(webSocketPath, WEBSOCKET_PROTOCOL, true, 65536 * 10));  
                // Custom handler, handling business logic  
                ch.pipeline().addLast(webSocketHandler);  
  
            }  
        });  
        // After the configuration is completed, start binding the server, and block by calling the sync synchronization method until the binding is successful  
        ChannelFuture channelFuture = bootstrap.bind().sync();  
        log.info("Server started and listen on:{}",channelFuture.channel().localAddress());  
        // Monitor the closed channel  
        channelFuture.channel().closeFuture().sync();  
    }  
  
    /**  
     * Release resources  
     * @throws InterruptedException  
     */  
    @PreDestroy  
    public void destroy() throws InterruptedException {  
        if(bossGroup != null){  
            bossGroup.shutdownGracefully().sync();  
        }  
        if(workGroup != null){  
            workGroup.shutdownGracefully().sync();  
        }  
    }  
    @PostConstruct()  
    public void init() {  
        //A new thread needs to be opened to execute the netty server  
        new Thread(() -> {  
            try {  
                start();  
            } catch (InterruptedException e) {  
                e.printStackTrace();  
            }  
        }).start();  
    }  
}

Step 3: implement the WebSocketHandler of the business, and see the notes for the implementation logic

package com.sixj.nettypush.websocket;  
  
import cn.hutool.json.JSONObject;  
import cn.hutool.json.JSONUtil;  
import com.sixj.nettypush.config.NettyConfig;  
import io.netty.channel.ChannelHandler;  
import io.netty.channel.ChannelHandlerContext;  
import io.netty.channel.SimpleChannelInboundHandler;  
import io.netty.handler.codec.http.websocketx.TextWebSocketFrame;  
import io.netty.util.AttributeKey;  
import org.slf4j.Logger;  
import org.slf4j.LoggerFactory;  
import org.springframework.stereotype.Component;  
  
  
/**  
 * TextWebSocketFrame Type, representing a text frame  
 * @author sixiaojie  
 * @date 2020-03-28-13:47  
 */  
@Component  
@ChannelHandler.Sharable  
public class WebSocketHandler extends SimpleChannelInboundHandler<TextWebSocketFrame> {  
    private static final Logger log = LoggerFactory.getLogger(NettyServer.class);  
  
    /**  
     * Once connected, the first is executed  
     * @param ctx  
     * @throws Exception  
     */  
    @Override  
    public void handlerAdded(ChannelHandlerContext ctx) throws Exception {  
        log.info("handlerAdded Be called"+ctx.channel().id().asLongText());  
        // Add to channelGroup channel group  
        NettyConfig.getChannelGroup().add(ctx.channel());  
    }  
  
    /**  
     * Read data  
     */  
    @Override  
    protected void channelRead0(ChannelHandlerContext ctx, TextWebSocketFrame msg) throws Exception {  
        log.info("Server received message:{}",msg.text());  
  
        // Get user ID and associate with channel  
        JSONObject jsonObject = JSONUtil.parseObj(msg.text());  
        String uid = jsonObject.getStr("uid");  
        NettyConfig.getUserChannelMap().put(uid,ctx.channel());  
  
        // The user ID is added to the channel as a custom attribute, which is convenient to obtain the user ID in the channel at any time  
        AttributeKey<String> key = AttributeKey.valueOf("userId");  
        ctx.channel().attr(key).setIfAbsent(uid);  
  
        // Reply message  
        ctx.channel().writeAndFlush(new TextWebSocketFrame("Server connection succeeded!"));  
    }  
  
    @Override  
    public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {  
        log.info("handlerRemoved Be called"+ctx.channel().id().asLongText());  
        // Delete channel  
        NettyConfig.getChannelGroup().remove(ctx.channel());  
        removeUserId(ctx);  
    }  
  
    @Override  
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {  
        log.info("Exception:{}",cause.getMessage());  
        // Delete channel  
        NettyConfig.getChannelGroup().remove(ctx.channel());  
        removeUserId(ctx);  
        ctx.close();  
    }  
  
    /**  
     * Delete the corresponding relationship between user and channel  
     * @param ctx  
     */  
    private void removeUserId(ChannelHandlerContext ctx){  
        AttributeKey<String> key = AttributeKey.valueOf("userId");  
        String userId = ctx.channel().attr(key).get();  
        NettyConfig.getUserChannelMap().remove(userId);  
    }  
}

**Step 4: * * specific message push interface

public interface PushService {  
    /**  
     * Push to specified user  
     * @param userId  
     * @param msg  
     */  
    void pushMsgToOne(String userId,String msg);  
  
    /**  
     * Push to all users  
     * @param msg  
     */  
    void pushMsgToAll(String msg);  
}

Interface implementation class:

import java.util.concurrent.ConcurrentHashMap;  
  
/**  
 * @author sixiaojie  
 * @date 2020-03-30-20:10  
 */  
@Service  
public class PushServiceImpl implements PushService {  
  
    @Override  
    public void pushMsgToOne(String userId, String msg){  
        ConcurrentHashMap<String, Channel> userChannelMap = NettyConfig.getUserChannelMap();  
        Channel channel = userChannelMap.get(userId);  
        channel.writeAndFlush(new TextWebSocketFrame(msg));  
    }  
    @Override  
    public void pushMsgToAll(String msg){  
        NettyConfig.getChannelGroup().writeAndFlush(new TextWebSocketFrame(msg));  
    }  
}

controller:

package com.sixj.nettypush.controller;  
  
import com.sixj.nettypush.service.PushService;  
import org.springframework.beans.factory.annotation.Autowired;  
import org.springframework.web.bind.annotation.PostMapping;  
import org.springframework.web.bind.annotation.RequestMapping;  
import org.springframework.web.bind.annotation.RequestParam;  
import org.springframework.web.bind.annotation.RestController;  
  
/**  
 * @author sixiaojie  
 * @date 2020-03-30-20:08  
 */  
@RestController  
@RequestMapping("/push")  
public class PushController {  
  
    @Autowired  
    private PushService pushService;  
  
    /**  
     * Push to all users  
     * @param msg  
     */  
    @PostMapping("/pushAll")  
    public void pushToAll(@RequestParam("msg") String msg){  
        pushService.pushMsgToAll(msg);  
    }  
    /**  
     * Push to specified user  
     * @param userId  
     * @param msg  
     */  
    @PostMapping("/pushOne")  
    public void pushMsgToOne(@RequestParam("userId") String userId,@RequestParam("msg") String msg){  
        pushService.pushMsgToOne(userId,msg);  
    }  
  
}

**Step 5: * * front end html page

<!DOCTYPE html>  
<html lang="en">  
<head>  
    <meta charset="UTF-8">  
    <title>Title</title>  
</head>  
<body>  
<script>  
    var socket;  
    // Determine whether the current browser supports webSocket  
    if(window.WebSocket){  
        socket = new WebSocket("ws://192.168.174.25:58080/webSocket")  
        // Equivalent to the read event of the channel, ev receives the message sent back by the server  
        socket.onmessage = function (ev) {  
            var rt = document.getElementById("responseText");  
            rt.value = rt.value + "\n" + ev.data;  
        }  
        // Equivalent to connection on  
        socket.onopen = function (ev) {  
            var rt = document.getElementById("responseText");  
            rt.value =  "Connection turned on..."  
            socket.send(  
                JSON.stringify({  
                    // The connection succeeds. The user ID is passed to the server  
                    uid: "123456"  
                })  
            );  
        }  
        // Equivalent to connection closing  
        socket.onclose = function (ev) {  
            var rt = document.getElementById("responseText");  
            rt.value = rt.value + "\n" + "Connection closed...";  
        }  
    }else{  
        alert("Current browser does not support webSocket")  
    }  
  
  
</script>  
    <form onsubmit="return false">  
        <textarea id="responseText" style="height: 150px; width: 300px;"></textarea>  
        <input type="button" value="Empty content" onclick="document.getElementById('responseText').value=''">  
    </form>  
</body>  
</html>

So far, all the code has been written. Test it

Run the html file first, and you will see the message "server connection succeeded!" returned by the server to the front end , the back-end log will print the message received by the server: {"uid":"123456"}

Then use postman to test the pushed interface

Posted by jpadie on Thu, 02 Apr 2020 08:24:30 -0700