Android instant messaging series articles use the Netty framework to quickly set up a WebSocket server

The wechat technology account of "Wei Feng Chen" has been opened. In order to get the first-hand push of technical articles, you are welcome to search and pay attention!

preface

With the gradual deepening of the technical points discussed in this series, the official test server mentioned earlier can no longer meet the needs of our demonstration. Therefore, it is necessary for us to try to build our own WebSocket server locally. Today's article introduces this aspect.

Because it does not belong to the original writing plan, and in order to maintain the consistency of the series of articles, this article is specially named "external articles".

Introduction to Netty

Remember the previous article "
Android instant messaging series articles (2) network communication protocol selection: what criteria should be used to select the network communication protocol suitable for your application?
"What we mentioned in the book? WebSocket itself is only an application layer protocol. In principle, it can be used by any client / server that follows this protocol. For the client, we have explicitly adopted the OkHttp framework, while for the server, we plan to adopt the Netty framework.

What is netty? Netty is an asynchronous, event driven network application framework, which supports the rapid development of maintainable, high-performance, protocol oriented server and client.

Netty encapsulates the capability of Java NIO API and hides the tedious and error prone I/O operations under high load under a simple and easy-to-use API. This is undoubtedly very friendly to the client developers who lack server programming experience. As long as they understand several core components of netty, there is basically no problem in quickly setting up a WebSocket server to meet the demonstration needs of this project.

Netty core components

Channel

Channel is the core of Netty transport API and is used for all I/O operations. The API provided by channel interface greatly reduces the complexity of using Socket class directly in Java.

Callback

Netty internally uses callbacks to handle events. When a callback is triggered, related events can be handled by the implementation of a ChannelHandler.

Future

Future provides a way to notify the application when the operation is completed. It can be regarded as a placeholder for the result of an asynchronous operation. It will complete at some time in the future and provide access to its result.

Netty provides its own implementation - ChannelFuture. The notification mechanism provided by ChannelFutureListener eliminates the step of manually checking whether the corresponding operation is completed.

Events and ChannelHandler

Netty uses different events to notify us of changes in state, which enables us to trigger appropriate actions based on events that have occurred.

Each event can be distributed to the ChannelHandler class. The ChannelHandler class provides custom business logic, which is architecturally helpful to keep the business logic separated from the network processing code.

Run Netty's WebSocket demo code with IntelliJ IDEA

As we all know, Android Studio is developed based on IntelliJ IDEA. Therefore, for Android developers who are used to developing with Android Studio, there are almost no obstacles to using IntelliJ IDEA. The purpose of this article is to quickly set up a WebSocket server, so we choose to directly pull down Netty's WebSocket demonstration code and run it. On the basis of ensuring the successful operation of the project, analyze the demonstration code step by step.

The interactive effect shown by the demo code is very simple. Like the previous official test server, when the client sends a message to the server, the server will send the message back to the client (yes, Echo Test...). Although it seems to be of little use, it fully reflects the typical request response interaction mode in the client / server system.

Next, we will work on both ends:

Work of the server:

  • Create new project Maven in the upper left corner of IntelliJ IDEA
  • Pull Netsocket demo code of Netty To src directory
  • Press Alt+Enter to automatically import Netty dependencies
  • Run the main() function of the WebSocketServer class

When the console outputs a statement, it means that the WebSocket server has successfully run on the machine:

Open your web browser and navigate to http://127.0.0.1:8080/

Work of the client:

  • Ensure that the mobile network and the server are under the same LAN
  • Change the WebSocket server address to be connected to: ws: / / {server IP address}: 8080/websocket
  • Send message normally

It can be seen from the console that the client has successfully established a connection with the WebSocket server and successfully received the server's return message after sending the message:

WebSocket demo code analysis

In general, Netty's WebSocket demo code contains two parts of core work. Their respective meanings and corresponding classes are shown in the following table:

Core worksignificanceCorresponding class
Provide ChannelHandler interface implementationBusiness logic processing of data received from the client by the serverWebSocketServerHandler
ServerBootstrap instance creationConfigure the startup of the server and bind the server to the port where it wants to listen for connection requestsWebSocketServer

Let's take a look at the main code of the core work of the WebSocketServerHandler class:

public class WebSocketServerHandler extends SimpleChannelInboundHandler<Object> {

    private WebSocketServerHandshaker handshaker;

    // ... Omit other codes
    
    /**
     * When a new message comes in, it will be called back
     *
     * @param ctx
     * @param msg
     */
    @Override
    public void channelRead0(ChannelHandlerContext ctx, Object msg) {
        if (msg instanceof FullHttpRequest) {
            handleHttpRequest(ctx, (FullHttpRequest) msg);
        } else if (msg instanceof WebSocketFrame) {
            handleWebSocketFrame(ctx, (WebSocketFrame) msg);
        }
    }

    private void handleHttpRequest(ChannelHandlerContext ctx, FullHttpRequest req) {
        // ... Omit other codes
        
        // handshake
        WebSocketServerHandshakerFactory wsFactory = new WebSocketServerHandshakerFactory(
                getWebSocketLocation(req), null, true, 5 * 1024 * 1024);
        handshaker = wsFactory.newHandshaker(req);
        if (handshaker == null) {
            WebSocketServerHandshakerFactory.sendUnsupportedVersionResponse(ctx.channel());
        } else {
            handshaker.handshake(ctx.channel(), req);
        }
    }

    private void handleWebSocketFrame(ChannelHandlerContext ctx, WebSocketFrame frame) {
        // ... Omit other codes
        
        // For text frames and binary data frames, the data is simply sent back to the remote node.
        if (frame instanceof TextWebSocketFrame) {
            // Echo the frame
            ctx.write(frame.retain());
            return;
        }
        if (frame instanceof BinaryWebSocketFrame) {
            // Echo the frame
            ctx.write(frame.retain());
        }
    }

    // ... Omit other codes
    
}

As you can see, in order to process all received data, we have rewritten the channelRead() method of WebSocketServerHandler class. The rewritten method mainly processes two types of data: Http request and WebSocket frame.

The data of Http request type is mainly used to handle the handshake and connection establishment process of the client. For details, please refer to the previous article
Android instant messaging series articles (2) network communication protocol selection: what criteria should be used to select the network communication protocol suitable for your application?
", I won't start here.

The WebSocket frame type data is mainly used to process messages actively sent from the client. We know that after the WebSocket connection is established, subsequent data is sent in the form of frames. It mainly includes the following types of frames:

  • Text frame
  • Binary frame
  • Ping frame
  • Pong frame
  • Close frame

Among them, text frames and binary frames belong to message frames. Ping frames and Ping frames are mainly used to keep the connection alive, and closing frames are used to close the connection. We are mainly concerned with the processing of message frames. We can see that we simply return the data to the remote node to realize Echo Test.

Then, let's go back to the main code of the core work of the WebSocketServer class:

ublic final class WebSocketServer {

    // ... Omit other codes
    static final int PORT = Integer.parseInt(System.getProperty("port", SSL? "8443" : "8080"));

    public static void main(String[] args) throws Exception {
        // ... Omit other codes

        EventLoopGroup bossGroup = new NioEventLoopGroup(1);
        EventLoopGroup workerGroup = new NioEventLoopGroup();
        try {
            ServerBootstrap b = new ServerBootstrap();
            b.group(bossGroup, workerGroup)
             .channel(NioServerSocketChannel.class) // Specifies the NIO transport Channel used
             .childHandler(new WebSocketServerInitializer(sslCtx));

            // Use the specified port to bind the server asynchronously; Call the sync() method to block and wait until the binding is complete
            Channel ch = b.bind(PORT).sync().channel();

            System.out.println("Open your web browser and navigate to " +
                    (SSL? "https" : "http") + "://127.0.0.1:" + PORT + '/');

            // Gets the CloseFuture of the Channel and blocks the current thread until it completes
            ch.closeFuture().sync();
        } finally {
            // Close EventLoopGroup and free all resources
            bossGroup.shutdownGracefully();
            workerGroup.shutdownGracefully();
        }
    }
}

We use the ServerBootstrap boot class to complete the network layer configuration of the Websocket server, and then call the bind(int inetPort) method to bind the process to a specified port, which is called the boot server.

How do we associate the WebSocketServerHandler defined earlier with ServerBootstrap? The key is the childHandler(ChannelHandler childHandler) method.

Each Channel has a ChannelPipeline associated with it, which holds an instance chain of ChannelHandler. We need to provide an implementation of ChannelInitializer and install a group of custom channelhandlers including WebSocketServerHandler into ChannelPipeline in its initChannel() callback method:

public class WebSocketServerInitializer extends ChannelInitializer<SocketChannel> {

    // ... Omit other codes

    public WebSocketServerInitializer(SslContext sslCtx) {
        // ... Omit other codes
    }

    @Override
    public void initChannel(SocketChannel ch) throws Exception {
        ChannelPipeline pipeline = ch.pipeline();
        if (sslCtx != null) {
            pipeline.addLast(sslCtx.newHandler(ch.alloc()));
        }
        pipeline.addLast(new HttpServerCodec());
        pipeline.addLast(new HttpObjectAggregator(65536));
        pipeline.addLast(new WebSocketServerHandler());
    }
}

Change Echo form to Broadcast form

As we said before, most of today's mainstream IM applications use server transfer for message transmission. In order to better practice this design, we further transform the WebSocket server and change the Echo form to Broadcast form, that is:

After receiving a message from a client, the message is forwarded to other client connections maintained by the server except the sender.

To achieve this function, we need to use the ChannelGroup class. The ChannelGroup is responsible for tracking all active WebSocket connections. When a new client successfully establishes a connection through handshake, we need to add this new Channel to the ChannelGroup.

After receiving the WebSocket message frame data, call the writeAndFlush() method of ChannelGroup to transmit the message to all connected WebSocket channels.

ChannelGroup also allows you to pass filter parameters, so that we can filter out the sender's Channel.

public class WebSocketServerHandler extends SimpleChannelInboundHandler<Object> {

    // ... Omit other codes
    private final ChannelGroup group;

    public WebSocketServerHandler(ChannelGroup group) {
        this.group = group;
    }
    
    private void handleWebSocketFrame(ChannelHandlerContext ctx, WebSocketFrame frame) {
        // ... Omit other codes
        if (frame instanceof TextWebSocketFrame) {
//            ctx.write(frame.retain());
            group.writeAndFlush(frame.retain(), ChannelMatchers.isNot(ctx.channel()));
            return;
        }
        if (frame instanceof BinaryWebSocketFrame) {
//            ctx.write(frame.retain());
            group.writeAndFlush(frame.retain(), ChannelMatchers.isNot(ctx.channel()));
        }
    }
    
    @Override
    public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
        if (evt == WebSocketServerProtocolHandler.ServerHandshakeStateEvent.HANDSHAKE_COMPLETE) {
            // Add a new WebSocket Channel to the ChannelGroup so that it can receive all messages
            group.add(ctx.channel());
        } else {
            super.userEventTriggered(ctx, evt);
        }
    }
}


After running, connect multiple clients to the server. When one of the clients sends a message, the other connected clients will receive the message broadcast by the server:


Relevant source code has been uploaded to Github

summary

In order to meet the demonstration needs of more scenarios, we use the Netty framework to quickly build the local WebSocket server.

We modified the WebSocket demo code based on Netty. The core work includes the following two parts:

  • Configure the startup of the server and bind the server to the port where it wants to listen for connection requests
  • Business logic processing of data received from the client by the server

Firstly, we implement the typical request / response interaction mode in the client / server system in the form of simple Echo, and further use the form of broadcast to realize the communication between multiple users.

Netty framework has other richer contents, waiting for us to explore them one by one... If you are also interested, please pay attention to the follow-up update of the technology number "pitfalls"!

Keywords: Java Android websocket

Added by Bijan on Fri, 21 Jan 2022 10:36:21 +0200