Simple chat system

websocket server startup class

Based on the characteristics of Netty, let's take a look at the simple chat system? First, for the backend, we need to create a Websocket Server. Here, we need to have a pair of thread groups EventLoopGroup. After defining, we need to define a Server:

public static void main(String[] args) throws Exception {
        
        EventLoopGroup mainGroup = new NioEventLoopGroup();
        EventLoopGroup subGroup = new NioEventLoopGroup();
        
        try {
            ServerBootstrap server = new ServerBootstrap();
            server.group(mainGroup, subGroup)
                .channel(NioServerSocketChannel.class)
                .childHandler(new WSServerInitialzer());
            
            ChannelFuture future = server.bind(8088).sync();
            
            future.channel().closeFuture().sync();
        } finally {
            mainGroup.shutdownGracefully();
            subGroup.shutdownGracefully();
        }
}

Add the thread group to the Server. Next, you need to set a channel: NioServerSocketChannel and an initializer: wsserverinitializer.

Step 2: bind the port version of the Server:

ChannelFuture future = server.bind(8088).sync()

Finally, you need to monitor the future. After listening, the thread resource needs to be closed:

mainGroup.shutdownGracefully();
subGroup.shutdownGracefully();

websocket sub processor initializer

The WebSocket Server is mentioned above. For sockets, there is an initialization processor. Here we define one:

public class WSServerInitialzer 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(1024*64));

        pipeline.addLast(new WebSocketServerProtocolHandler("/ws"));
        
        pipeline.addLast(new ChatHandler());
    }

}

Because websocket is based on http protocol, http codec HttpServerCodec is required. At the same time, on some http, there are some data stream processing. Moreover, if the data stream is large or small, you can add a large data stream processing: ChunkedWriteHandler.

Usually, httpmessages are aggregated into FullHttpRequest or FullHttpResponse, and this handler is used in almost all programming in netty.

In addition, the protocol handled by the websocket server is used to specify the route for client connection access: "/ ws". This handler will help you handle some heavy and complex things. For example, it will help you handle handshaking: handshaking (close, ping, pong) ping + pong = heartbeat. For WebSockets, they are all transmitted in frames, and frames corresponding to different data types are also different.

Finally, we customize a message handler: ChatHandler.

Processing of messages by chatHandler

In Netty, there is an object TextWebSocketFrame that is used to process text for websocket. The frame is the message carrier.

public class ChatHandler extends SimpleChannelInboundHandler<TextWebSocketFrame> {
    private static ChannelGroup clients = new DefaultChannelGroup(GlobalEventExecutor.INSTANCE);
    
    @Override
    protected void channelRead0(ChannelHandlerContext ctx, TextWebSocketFrame msg) 
            throws Exception {
        String content = msg.text();
        System.out.println("Received data:" + content);
        
        clients.writeAndFlush(new TextWebSocketFrame("Server time at " + LocalDateTime.now() + " Message received, The message is:" + content));

    }

    @Override
    public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
        clients.add(ctx.channel());
        System.out.println("Client connection, channle Corresponding length id Is:" + ctx.channel().id().asLongText());
        System.out.println("Client connection, channle Corresponding short id Is:" + ctx.channel().id().asShortText());
    }

    @Override
    public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {
        System.out.println("Client disconnected, channle Corresponding length id Is:" + ctx.channel().id().asLongText());
        System.out.println("Client disconnected, channle Corresponding short id Is:" + ctx.channel().id().asShortText());
    }
}

At first, the message is in the carrier TextWebSocketFrame. At this time, you can directly get the content and print it. And the message can be sent to the client corresponding to the request. Of course, you can also forward messages to all clients, which involves the channel in Netty. At this time, users in the channel need to be managed so that messages can be forwarded to users in all channels. That is, the handlerAdded function above opens the connection after the client connects to the server, obtains the channel of the client, and puts it into the ChannelGroup for management. Meanwhile, after the client and the server are disconnected or closed, the handlerRemoved function will be triggered, and the ChannelGroup will automatically remove the channel of the corresponding client.

Next, you need to refresh the data to all clients after obtaining it:

for (Channel channel : clients) {
            channel.writeAndFlush(new TextWebSocketFrame("[Server in]" + LocalDateTime.now() + "Message received, The message is:" + content));
}

Note: you need to Flush the information with the help of the carrier, because the writeAndFlush function needs to be passed to the object carrier instead of the direct string. In fact, as ChannelGroup clients, the writeAndFlush function is provided, which can be directly output to all clients:

clients.writeAndFlush(new TextWebSocketFrame("Server time at " + LocalDateTime.now() + " Message received, The message is:" + content));

Introduction of related APIs of websocket based on js

First, you need a connection between the client and the server. This connection bridge is a socket in js:

var socket = new WebSocket("ws://192.168.174.145:8088/ws");

Let's look at its life cycle. On the back end, the channel has its life cycle, while in the front-end socket:

  • onopen(), an onopen event will be triggered when the client establishes a connection with the server
  • onmessage(), an onmessage event is triggered when the client receives a message
  • When an exception occurs in oneror(), the front end will trigger an oneror event
  • onclose(), the onclose event will be triggered after the connection between the client and the server is closed

Next, look at two proactive approaches:

  • Socket.send(), after the front end actively obtains the content, send the message through send
  • Socket.close(), when the user triggers a button, it will disconnect the client from the server

The above is the api corresponding to the front-end websocket js.

Implement front-end websocket

The above describes the back-end message processing, encoding and decoding, as well as the related functions of websocket js. Next, let's look at how the front end implements websocket. First, let's write a text input, click and other functions:

<html>
    <head>
        <meta charset="utf-8" />
        <title></title>
    </head>
    <body>
        
        <div>send msg:</div>
        <input type="text" id="msgContent"/>
        <input type="button" value="send" onclick="CHAT.chat()"/>
        
        <div>receive msg:</div>
        <div id="receiveMsg" style="background-color: gainsboro;"></div>
    </body>
</html>

Access connection: C: \ users \ Damon \ desktop \ netty \ webchat \ index HTML, we can see the effect:

Next, we need to write websocket js:

<script type="application/javascript">
            
            window.CHAT = {
                socket: null,
                init: function() {
                    if (window.WebSocket) {
                        CHAT.socket = new WebSocket("ws://192.168.174.145:8088/ws");
                        CHAT.socket.onopen = function() {
                            console.log("Connection established successfully...");
                        },
                        CHAT.socket.onclose = function() {
                            console.log("Connection closed...");
                        },
                        CHAT.socket.onerror = function() {
                            console.log("An error occurred...");
                        },
                        CHAT.socket.onmessage = function(e) {
                            console.log("Received message:" + e.data);
                            var receiveMsg = document.getElementById("receiveMsg");
                            var html = receiveMsg.innerHTML;
                            receiveMsg.innerHTML = html + "<br/>" + e.data;
                        }
                    } else {
                        alert("Browser does not support websocket agreement...");
                    }
                },
                chat: function() {
                    var msg = document.getElementById("msgContent");
                    CHAT.socket.send(msg.value);
                }
            };
            
            CHAT.init();
            
        </script>

In this way, a simple websocket js is finished. When you enter information in the text box and click send, the back end can receive the information and reply.

Tags: Front-end Javascript api

Posted by mikeweb on Thu, 13 Jan 2022 06:27:08 +1030