Implementing a Netty WebSocket Server with URL Parameter Support in Spring Boot
This article demonstrates how to build a Netty‑based WebSocket server integrated with Spring Boot, covering Maven dependencies, server initialization, channel handlers, a simple HTML client, and extending the handler to parse URL query parameters for custom communication.
Netty is a Java client/server framework that abstracts low‑level network operations behind an easy‑to‑use API. The article starts by adding the io.netty:netty-all:4.1.36.Final dependency to a Maven project.
SpringBootApplication
The Spring Boot entry point creates a NettyServer instance on port 12345 and starts it, printing the server URL for testing.
@SpringBootApplication
public class SpringCloudStudyDemoApplication {
public static void main(String[] args) {
SpringApplication.run(SpringCloudStudyDemoApplication.class, args);
try {
new NettyServer(12345).start();
System.out.println("https://blog.csdn.net/moshowgame");
System.out.println("http://127.0.0.1:6688/netty-websocket/index");
} catch (Exception e) {
System.out.println("NettyServerError:" + e.getMessage());
}
}
}NettyServer
The server configures boss and worker EventLoopGroup s, sets up a ServerBootstrap with TCP backlog, binds to the specified port, and installs a pipeline consisting of HTTP codec, chunked writer, HTTP aggregator, WebSocket protocol handler, and a custom MyWebSocketHandler. It logs the listening address and shuts down gracefully.
public class NettyServer {
private final int port;
public NettyServer(int port) { this.port = port; }
public void start() throws Exception {
EventLoopGroup bossGroup = new NioEventLoopGroup();
EventLoopGroup group = new NioEventLoopGroup();
try {
ServerBootstrap sb = new ServerBootstrap();
sb.option(ChannelOption.SO_BACKLOG, 1024);
sb.group(group, bossGroup)
.channel(NioServerSocketChannel.class)
.localAddress(this.port)
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
System.out.println("收到新连接");
ch.pipeline().addLast(new HttpServerCodec());
ch.pipeline().addLast(new ChunkedWriteHandler());
ch.pipeline().addLast(new HttpObjectAggregator(8192));
ch.pipeline().addLast(new WebSocketServerProtocolHandler("/ws", null, true, 65536 * 10));
ch.pipeline().addLast(new MyWebSocketHandler());
}
});
ChannelFuture cf = sb.bind().sync();
System.out.println(NettyServer.class + " 启动正在监听: " + cf.channel().localAddress());
cf.channel().closeFuture().sync();
} finally {
group.shutdownGracefully().sync();
bossGroup.shutdownGracefully().sync();
}
}
}Channel Management
A static MyChannelHandlerPool holds a ChannelGroup that tracks all active WebSocket connections, allowing broadcast of messages.
public class MyChannelHandlerPool {
public static ChannelGroup channelGroup = new DefaultChannelGroup(GlobalEventExecutor.INSTANCE);
public MyChannelHandlerPool() {}
}MyWebSocketHandler
The handler overrides channelActive, channelInactive, and channelRead. On the first HTTP handshake it extracts the request URI, parses query parameters, and removes them from the URI before delegating to the WebSocket protocol handler. Subsequent TextWebSocketFrame messages are logged and broadcast to all channels.
public class MyWebSocketHandler extends SimpleChannelInboundHandler<TextWebSocketFrame> {
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
System.out.println("与客户端建立连接,通道开启!");
MyChannelHandlerPool.channelGroup.add(ctx.channel());
}
@Override
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
System.out.println("与客户端断开连接,通道关闭!");
MyChannelHandlerPool.channelGroup.remove(ctx.channel());
}
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
if (msg instanceof FullHttpRequest) {
FullHttpRequest request = (FullHttpRequest) msg;
String uri = request.uri();
Map<String, String> paramMap = getUrlParams(uri);
System.out.println("接收到的参数是:" + JSON.toJSONString(paramMap));
if (uri.contains("?")) {
String newUri = uri.substring(0, uri.indexOf("?"));
request.setUri(newUri);
}
} else if (msg instanceof TextWebSocketFrame) {
TextWebSocketFrame frame = (TextWebSocketFrame) msg;
System.out.println("客户端收到服务器数据:" + frame.text());
sendAllMessage(frame.text());
}
super.channelRead(ctx, msg);
}
private void sendAllMessage(String message) {
MyChannelHandlerPool.channelGroup.writeAndFlush(new TextWebSocketFrame(message));
}
private static Map<String, String> getUrlParams(String url) {
Map<String, String> map = new HashMap<>();
url = url.replace("?", ";");
if (!url.contains(";")) return map;
String[] parts = url.split(";");
if (parts.length > 1) {
String[] pairs = parts[1].split("&");
for (String s : pairs) {
String[] kv = s.split("=");
if (kv.length == 2) map.put(kv[0], kv[1]);
}
}
return map;
}
}HTML Client (socket.html)
A simple HTML page creates a WebSocket connection to ws://127.0.0.1:12345/ws, displays received messages in a textarea, and provides a form to send messages with a user‑defined UID.
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
<title>Netty-Websocket</title>
<script type="text/javascript">
var socket;
if (!window.WebSocket) window.WebSocket = window.MozWebSocket;
if (window.WebSocket) {
socket = new WebSocket("ws://127.0.0.1:12345/ws");
socket.onmessage = function(event) {
var ta = document.getElementById('responseText');
ta.value += event.data + "
";
};
socket.onopen = function(event) {
var ta = document.getElementById('responseText');
ta.value = "Netty-WebSocket服务器。。。。。。连接
";
};
socket.onclose = function(event) {
var ta = document.getElementById('responseText');
ta.value = "Netty-WebSocket服务器。。。。。。关闭
";
};
} else {
alert("您的浏览器不支持WebSocket协议!");
}
function send(message) {
if (!window.WebSocket) return;
if (socket.readyState == WebSocket.OPEN) {
socket.send(message);
} else {
alert("WebSocket 连接没有建立成功!");
}
}
</script>
</head>
<body>
<form onSubmit="return false;">
<label>ID</label>
<input type="text" name="uid" value="${uid!!}"/><br/>
<label>TEXT</label>
<input type="text" name="message" value="这里输入消息"/><br/>
<input type="button" value="发送ws消息" onClick="send(this.form.uid.value+':'+this.form.message.value)"/>
<hr color="black"/>
<h3>服务端返回的应答消息</h3>
<textarea id="responseText" style="width:1024px;height:300px;"></textarea>
</form>
</body>
</html>Spring MVC Controller
The controller maps /index to the HTML page, injecting a random six‑digit UID for the client.
@RestController
public class IndexController {
@GetMapping("/index")
public ModelAndView index() {
ModelAndView mav = new ModelAndView("socket");
mav.addObject("uid", RandomUtil.randomNumbers(6));
return mav;
}
}Adding URL Parameter Support
To handle query strings, the article reorders the pipeline so MyWebSocketHandler runs before WebSocketServerProtocolHandler. The handler extracts parameters from the initial FullHttpRequest, logs them, and strips them from the URI so the WebSocket handshake proceeds normally. The client can now connect with a URL such as ws://127.0.0.1:12345/ws?uid=666&gid=777.
Console output after the change shows the connection establishment, parameter parsing, and broadcast of messages.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
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.
