Integrating Spring Boot with Netty‑SocketIO for Real‑Time Messaging (Backend and Frontend Example)
This tutorial shows how to combine Spring Boot and Netty‑SocketIO to build a simple real‑time push system with broadcast, group and single‑user messaging, providing Maven dependencies, configuration files, Java classes, a REST controller and two HTML client pages for demonstration.
In this tutorial a top‑level architect demonstrates how to integrate Spring Boot with Netty‑SocketIO to build a simple real‑time push system that supports broadcast, group and single‑user messaging.
First the required Maven dependencies (fastjson, netty‑socketio, spring‑boot‑starter‑web, spring‑boot‑starter‑test) are added to the pom.xml file.
<dependencies>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.75</version>
</dependency>
<dependency>
<groupId>com.corundumstudio.socketio</groupId>
<artifactId>netty-socketio</artifactId>
<version>1.7.7</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>The application.yml configures the HTTP server port and the socket‑io host, port and performance parameters.
server:
port: 8089
socketio:
host: localhost
port: 8503
maxFramePayloadLength: 1048576
maxHttpContentLength: 1048576
bossCount: 1
workCount: 100
allowCustomRequests: true
upgradeTimeout: 10000
pingTimeout: 60000
pingInterval: 25000A configuration class MySocketConfig reads the yml values, creates a SocketIOServer and registers the Spring annotation scanner.
@Configuration
public class MySocketConfig {
@Value("${socketio.host}") private String host;
@Value("${socketio.port}") private Integer port;
@Value("${socketio.bossCount}") private int bossCount;
@Value("${socketio.workCount}") private int workCount;
@Value("${socketio.allowCustomRequests}") private boolean allowCustomRequests;
@Value("${socketio.upgradeTimeout}") private int upgradeTimeout;
@Value("${socketio.pingTimeout}") private int pingTimeout;
@Value("${socketio.pingInterval}") private int pingInterval;
@Bean
public SocketIOServer socketIOServer() {
SocketConfig socketConfig = new SocketConfig();
socketConfig.setTcpNoDelay(true);
socketConfig.setSoLinger(0);
com.corundumstudio.socketio.Configuration config = new com.corundumstudio.socketio.Configuration();
buildSocketConfig(socketConfig, config);
return new SocketIOServer(config);
}
@Bean
public SpringAnnotationScanner springAnnotationScanner() {
return new SpringAnnotationScanner(socketIOServer());
}
private void buildSocketConfig(SocketConfig socketConfig, com.corundumstudio.socketio.Configuration config) {
config.setSocketConfig(socketConfig);
config.setHostname(host);
config.setPort(port);
config.setBossThreads(bossCount);
config.setWorkerThreads(workCount);
config.setAllowCustomRequests(allowCustomRequests);
config.setUpgradeTimeout(upgradeTimeout);
config.setPingTimeout(pingTimeout);
config.setPingInterval(pingInterval);
}
}The message DTO MyMessage contains fields type, content, from, to and channel with getters and setters.
public class MyMessage {
private String type;
private String content;
private String from;
private String to;
private String channel;
// getters and setters omitted for brevity
}The handler MySocketHandler logs client connections and disconnections and stores the mapping between a user flag and the SocketIOClient .
@Component
public class MySocketHandler {
private final Logger log = LoggerFactory.getLogger(this.getClass());
@Autowired private SocketIOServer socketIoServer;
@PostConstruct private void start() { socketIoServer.start(); }
@PreDestroy private void destroy() { socketIoServer.stop(); }
@OnConnect public void connect(SocketIOClient client) {
String userFlag = client.getHandshakeData().getSingleUrlParam("userFlag");
SocketUtil.connectMap.put(userFlag, client);
log.info("客户端userFlag: " + userFlag + "已连接");
}
@OnDisconnect public void onDisconnect(SocketIOClient client) {
String userFlag = client.getHandshakeData().getSingleUrlParam("userFlag");
log.info("客户端userFlag:" + userFlag + "断开连接");
SocketUtil.connectMap.remove(userFlag, client);
}
}The utility SocketUtil keeps a concurrent map of online clients and provides sendToAll , sendToOne and a listener for the system channel.
@Component
public class SocketUtil {
public static ConcurrentMap<String, SocketIOClient> connectMap = new ConcurrentHashMap<>();
@OnEvent(value = "CHANNEL_SYSTEM")
public void systemDataListener(String receiveMsg) {
if (!StringUtils.hasLength(receiveMsg)) return;
JSONObject msgObject = (JSONObject) JSON.parse(receiveMsg);
String userFlag = String.valueOf(msgObject.get("from"));
String content = String.valueOf(msgObject.get("content"));
log.info("收到用户 : {} 推送到系统频道的一条消息 :{}", userFlag, content);
}
public void sendToAll(Map<String, Object> msg, String sendChannel) {
if (connectMap.isEmpty()) return;
for (Map.Entry<String, SocketIOClient> entry : connectMap.entrySet()) {
entry.getValue().sendEvent(sendChannel, msg);
}
}
public void sendToOne(String userFlag, Map<String, Object> msg, String sendChannel) {
SocketIOClient socketClient = getSocketClient(userFlag);
if (Objects.nonNull(socketClient)) {
socketClient.sendEvent(sendChannel, msg);
}
}
public SocketIOClient getSocketClient(String userFlag) {
if (StringUtils.hasLength(userFlag) && !connectMap.isEmpty()) {
return connectMap.get(userFlag);
}
return null;
}
}A REST controller TestController receives a MyMessage payload and forwards it either to all clients or to a specific client according to the type field.
@RestController
public class TestController {
public static final String SEND_TYPE_ALL = "ALL";
public static final String SEND_TYPE_ALONE = "ALONE";
@Autowired SocketUtil socketUtil;
@PostMapping("/testSendMsg")
public String testSendMsg(@RequestBody MyMessage myMessage) {
Map<String, Object> map = new HashMap<>();
map.put("msg", myMessage.getContent());
if (SEND_TYPE_ALL.equals(myMessage.getType())) {
socketUtil.sendToAll(map, myMessage.getChannel());
return "success";
}
if (SEND_TYPE_ALONE.equals(myMessage.getType())) {
socketUtil.sendToOne(myMessage.getTo(), map, myMessage.getChannel());
return "success";
}
return "fail";
}
}Two simple HTML pages ( TestClientStudentJC.html and TestClientStudentPU.html ) use jQuery and the Socket.IO client library to connect to the server, display received messages and send messages to the system channel.
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
<title>我要连SOCKET</title>
<script src="https://cdn.bootcss.com/jquery/3.4.0/jquery.min.js"></script>
<script src="https://cdn.bootcss.com/socket.io/2.2.0/socket.io.js"></script>
<style>/* styles omitted */</style>
</head>
<body>
<div id="console" class="well"></div>
<div id="conversationDiv">
<label>给系统推消息</label>
<input type="text" id="content"/>
<button id="btnSendToSystem" onclick="sendSys();">发送</button>
</div>
<script type="text/javascript">
var socket;
function connect() {
var userFlag = 'user_JC';
var opts = { query: 'userFlag=' + userFlag };
socket = io.connect('http://localhost:8503', opts);
socket.on('connect', function() { console.log('连接成功'); output('当前用户是:' + userFlag); });
socket.on('CHANNEL_SYSTEM', function(data) { output('收到系统全局消息了:' + JSON.stringify(data)); });
}
function sendSys() {
var content = document.getElementById('content').value;
socket.emit('CHANNEL_SYSTEM', JSON.stringify({ content: content, from: 'user_JC' }));
}
function output(message) { $('#console').prepend($('<div>' + message + '</div>')); }
connect();
</script>
</body>
</html>Running the project and opening the HTML pages shows connection logs, broadcast messages, group messages and one‑to‑one messages as described in the functional scenarios, proving that Spring Boot and Netty‑SocketIO can be combined to build a lightweight real‑time push service.
Top Architect
Top Architect focuses on sharing practical architecture knowledge, covering enterprise, system, website, large‑scale distributed, and high‑availability architectures, plus architecture adjustments using internet technologies. We welcome idea‑driven, sharing‑oriented architects to exchange and learn together.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.