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