Implementing Long‑Lived Server‑Client Messaging with WebSocket in Netty

This article walks through building a multi‑client chatroom using Netty and HTML5 WebSocket, explaining why WebSocket outperforms Ajax polling, detailing server‑side handlers, client‑side JavaScript, and demonstrating connection lifecycle and message exchange with full code examples.

The Dominant Programmer
The Dominant Programmer
The Dominant Programmer
Implementing Long‑Lived Server‑Client Messaging with WebSocket in Netty

Scenario

Demonstrates how to build a multi‑client chatroom using Netty and the HTML5 WebSocket protocol, replacing traditional Ajax polling with a persistent full‑duplex TCP connection.

Why WebSocket

Ajax polling repeatedly sends HTTP requests, wasting bandwidth because each request carries large headers while only a small payload is needed. WebSocket provides a single handshake and then a persistent connection, allowing the server to push data instantly and reducing resource consumption.

WebSocket basics

WebSocket is a full‑duplex protocol over a single TCP connection. The client creates a WebSocket object, uses send() to transmit data, and receives messages via the onmessage event. Important properties include readyState (0‑3) and bufferedAmount. Core events are open, message, error, and close. Methods are send() and close().

Server implementation

1. Create a Netty project in IDEA and add the required dependencies.

2. Add a WebSocketServer class that starts two NioEventLoopGroup instances, configures ServerBootstrap, binds to port 70, and adds a LoggingHandler and a custom initializer.

package com.badao.NettyWebSocket;

import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.logging.LogLevel;
import io.netty.handler.logging.LoggingHandler;

public class WebSocketServer {
    public static void main(String[] args) throws Exception {
        EventLoopGroup bossGroup = new NioEventLoopGroup();
        EventLoopGroup workerGroup = new NioEventLoopGroup();
        try {
            ServerBootstrap serverBootstrap = new ServerBootstrap();
            serverBootstrap.group(bossGroup, workerGroup)
                .channel(NioServerSocketChannel.class)
                .handler(new LoggingHandler(LogLevel.INFO))
                .childHandler(new WebSocketInitializer());
            ChannelFuture channelFuture = serverBootstrap.bind(70).sync();
            channelFuture.channel().closeFuture().sync();
        } finally {
            bossGroup.shutdownGracefully();
            workerGroup.shutdownGracefully();
        }
    }
}

3. Define WebSocketInitializer extending ChannelInitializer<SocketChannel> to install the HTTP codec, aggregator, chunked writer, the WebSocketServerProtocolHandler with path “/badao”, and a custom WebSocketHandler.

package com.badao.NettyWebSocket;

import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.socket.SocketChannel;
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.stream.ChunkedWriteHandler;

public class WebSocketInitializer extends ChannelInitializer<SocketChannel> {
    @Override
    protected void initChannel(SocketChannel ch) throws Exception {
        ChannelPipeline pipeline = ch.pipeline();
        pipeline.addLast(new HttpServerCodec());
        pipeline.addLast(new ChunkedWriteHandler());
        pipeline.addLast(new HttpObjectAggregator(8192));
        pipeline.addLast(new WebSocketServerProtocolHandler("/badao"));
        pipeline.addLast(new WebSocketHandler());
    }
}

4. Implement WebSocketHandler extending SimpleChannelInboundHandler<TextWebSocketFrame>. Override channelRead0 to log received text and echo a message containing the current time. Override handlerAdded, handlerRemoved, and exceptionCaught to log connection IDs and close on error.

package com.badao.NettyWebSocket;

import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.handler.codec.http.websocketx.TextWebSocketFrame;

import java.time.LocalDateTime;

public class WebSocketHandler extends SimpleChannelInboundHandler<TextWebSocketFrame> {
    @Override
    protected void channelRead0(ChannelHandlerContext ctx, TextWebSocketFrame msg) throws Exception {
        System.out.println("收到消息:" + msg.text());
        ctx.channel().writeAndFlush(new TextWebSocketFrame(
            "WebSocket服务端在" + LocalDateTime.now() + "发送消息(公众号:霸道的程序猿)"));
    }

    @Override
    public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
        System.out.println("handlerAdded:" + ctx.channel().id().asLongText());
    }

    @Override
    public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {
        System.out.println("handlerRemoved:" + ctx.channel().id().asLongText());
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        System.out.println("异常发生");
        ctx.close();
    }
}

Client implementation

Create badao.html under src/main/webapp. The page loads a script that checks window.WebSocket, opens a connection to ws://localhost:70/badao, and defines callbacks for onopen, onmessage, and onclose. A textarea shows server messages, and a button sends the content of another textarea using socket.send().

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>WebSocket Demo</title>
</head>
<body>
<script type="text/javascript">
    var socket;
    if (window.WebSocket) {
        socket = new WebSocket("ws://localhost:70/badao");
        socket.onmessage = function (ev) {
            var ta = document.getElementById("responseText");
            ta.value = ta.value + "
" + ev.data;
        };
        socket.onopen = function () {
            document.getElementById("responseText").value = "连接开启";
        };
        socket.onclose = function () {
            var ta = document.getElementById("responseText");
            ta.value = ta.value + "
连接关闭";
        };
    } else {
        alert("当前浏览器不支持WebSocket");
    }

    function send(message) {
        if (!window.WebSocket) return;
        if (socket.readyState === WebSocket.OPEN) {
            socket.send(message);
        } else {
            alert("连接尚未开启");
        }
    }
</script>
<form>
    <textarea name="message" style="width:400px;height:200px"></textarea>
    <input type="button" value="发送数据" onclick="send(this.form.message.value)">
    <h3>服务端输出:</h3>
    <textarea id="responseText" style="width:400px;height:200px"></textarea>
</form>
</body>
</html>

Running the demo

Execute the WebSocketServer main method, then open badao.html in a browser and click “运行”. The server prints the channel ID when a client connects; the client shows “连接开启”. Refreshing the page triggers a disconnect and a new connection, both logged on the server. Stopping the server causes the client to display “连接关闭”. Sending text from the client results in the server echoing a timestamped message back, which appears in the client textarea.

Download

Full source code can be downloaded from https://download.csdn.net/download/BADAO_LIUMANG_QIZHI/12853829.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

JavaNettyWebSocketChatroomLong-lived connection
The Dominant Programmer
Written by

The Dominant Programmer

Resources and tutorials for programmers' advanced learning journey. Advanced tracks in Java, Python, and C#. Blog: https://blog.csdn.net/badao_liumang_qizhi

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.