“Zefengengchen” wechat technology account is now open, in order to get the first-hand technical articles push, welcome to search attention!

preface

As the technical points discussed in this series continue, the official test server mentioned above is no longer sufficient for our demo, so it’s worth trying to build your own WebSocket server locally, and today’s article is about that.

Since it is not part of the original writing plan, and in order to maintain the continuity of the series of articles, this article is specially named “Passion”.

A brief introduction to Netty

Remember the previous article, “Android Instant Messaging (part 2) Network Protocol Selection: What criteria should you use to select the right network protocol for your application?” Is that what we’ve been talking about? WebSocket itself is only an application-layer protocol. In principle, any client/server that complies with this protocol can use it. On the client side, we have explicitly implemented the OkHttp framework, and on the server side, we plan to implement the Netty framework.

What is Netty? Netty is an asynchronous, event-based network application framework that supports the rapid development of maintainable, high-performance, protocol-oriented servers and clients.

Netty encapsulates the power of the Java NIO API, hiding cumbersome and error-prone I/O operations under heavy loads in a simple, easy-to-use API. This is certainly very friendly to client-side developers who lack server-side programming experience, and once you’ve figured out a few core components of Netty, you can quickly build a WebSocket server that meets the needs of this project’s demo.

Netty core components

Channel

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

The callback

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

Future

A Future provides a way to notify an application when an operation is complete, and can be viewed as a placeholder for the result of an asynchronous operation, which will complete at some point in the Future, and provides access to its result.

Netty provides its own implementation, ChannelFuture. The notification mechanism provided by ChannelFutureListener eliminates the need to manually check whether the corresponding operation is complete.

Events and ChannelHandler

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

Each event can be distributed to the ChannelHandler class, which provides custom business logic that architecturally helps to keep the business logic separate from the network processing code.

Using IntelliJ IDEA to run Netty WebSocket demo code

As you know, Android Studio is based on IntelliJ IDEA, so there are almost no barriers for Android developers who are used to developing with Android Studio. The purpose of this article is to quickly set up a WebSocket server, so I chose to pull Netty’s WebSocket demo code down and run it. Step by step analyze the demo code after ensuring that the project is running successfully.

This demo code shows that the interaction effect is very simple, as in the previous official Test server, when the client sends a message to the server, the server sends the message back to the client (yes, again Echo Test…). . While it may not seem very useful, it is a good example of the typical request-response interaction pattern in a client/server system.

Next, we will work on both ends separately:

Work on the server:

  • New-Project-Maven create a New Project
  • Pull Netty’s WebSocket demo code into the SRC directory
  • Press Alt+Enter to automatically import Netty dependencies
  • Run the main() function of the WebSocketServer class

When the console prints an output statement, it indicates that the WebSocket server is running successfully on the local machine:

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

Client work:

  • Ensure that the mobile network and server are in the same LAN
  • Change the WebSocket server address to: ws://{server IP address}:8080/ WebSocket
  • Sending a message normally

As you can see from the console, the client successfully established a connection with the WebSocket server and successfully received a return message from the server after sending the message:

WebSocket demo code analysis

In general, Netty WebSocket demo code contains two parts of the core work, the meaning of their respective and the corresponding class is shown in the following table:

Core work meaning The corresponding class
Provides ChannelHandler interface implementation The business logic processing of the data received by the server from the client WebSocketServerHandler
ServerBootstrap Instance creation Configure the startup of the server by binding the server to the port on which it will listen for connection requests WebSocketServer

Let’s first look at the main code that works at the core of the WebSocketServerHandler class:

public class WebSocketServerHandler extends SimpleChannelInboundHandler<Object> { private WebSocketServerHandshaker handshaker; / /... @override public void channelRead0(ChannelHandlerContext CTX, @override public void ChannelHandlerContext CTX, @override public void 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) { // ... Save other code / / 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 code // 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()); }} / /... Leave out other codeCopy the code

As you can see, to handle all the data we receive, we override the channelRead() method of the WebSocketServerHandler class, which handles both Http requests and WebSocket frames.

Http request type data is used to handle the handshake process of the client. For details, see the previous article “Android Instant Messaging Series (2) Network Communication Protocol Selection: What criteria should be used to select the right network communication protocol for your application?” I won’t expand on that.

The WebSocket frame type is used to process messages from the client. As we know, after the WebSocket connection is established, all subsequent data is sent in the form of frames. Contains the following types of frames:

  • The text frame
  • Binary frame
  • Ping frame
  • Pong frame
  • Close the frame

Among them, text frame and binary frame belong to message frame, Ping frame and Ping frame are mainly used to keep the connection alive, and close frame is used to close the connection. We are mainly concerned about the processing of message frame here, as can be seen, we just send data back to the remote node, so as to realize Echo Test.

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

ublic final class WebSocketServer { // ... Static final int PORT = integer.parseint (system.getProperty (" PORT ", SSL? "8443", "8080")); public static void main(String[] args) throws Exception { // ... EventLoopGroup bossGroup = new NioEventLoopGroup(1); EventLoopGroup workerGroup = new NioEventLoopGroup(); try { ServerBootstrap b = new ServerBootstrap(); b.group(bossGroup, WorkerGroup). Channel (NioServerSocketChannel. Class) the NIO transmission channel. / / specified use childHandler (new WebSocketServerInitializer(sslCtx)); // Bind the server asynchronously using the specified port; Channel ch = b.ind (PORT).sync().channel(); System.out.println("Open your web browser and navigate to " + (SSL? "HTTPS" : "HTTP") + ": / / 127.0.0.1:" + PORT + '/'); // Get the Channel's CloseFuture and block the current thread until it completes ch.closeFuture().sync(); } finally {/ / close EventLoopGroup, releasing all resources bossGroup. ShutdownGracefully (); workerGroup.shutdownGracefully(); }}}Copy the code

We use the ServerBootstrap class to configure the network layer of the Websocket server. Then we call the bind(int inetPort) method to bind the process to a specified port. This process is called the boot server.

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

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

public class WebSocketServerInitializer extends ChannelInitializer<SocketChannel> { // ... Save other code public WebSocketServerInitializer (SslContext sslCtx) {/ /... } @override public void initChannel(SocketChannel CH) throws Exception {ChannelPipeline = ch.pipeline(); if (sslCtx ! = null) { pipeline.addLast(sslCtx.newHandler(ch.alloc())); } pipeline.addLast(new HttpServerCodec()); pipeline.addLast(new HttpObjectAggregator(65536)); pipeline.addLast(new WebSocketServerHandler()); }}Copy the code

Change the Echo format to the Broadcast format

As we have said before, today’s mainstream IM applications almost all use the server transfer mode to carry out message transmission. In order to better practice this design, we will further transform the WebSocket server by changing the Echo form to the Broadcast form, namely:

When a message is received from a client, the message is forwarded to other client connections maintained by the server besides the sender.

To do this we need to use the ChannelGroup class, which tracks all active WebSocket connections. When a new client successfully establishes a connection through the handshake, we add the new Channel to the ChannelGroup.

When a WebSocket frame is received, the writeAndFlush() method of ChannelGroup is called to transmit the message to all connected WebSocket channels.

ChannelGroup also allows you to pass filter parameters that filter out the sender’s Channel.

public class WebSocketServerHandler extends SimpleChannelInboundHandler<Object> { // ... Private final ChannelGroup group; public WebSocketServerHandler(ChannelGroup group) { this.group = group; } private void handleWebSocketFrame(ChannelHandlerContext ctx, WebSocketFrame frame) { // ... 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 the new WebSocket Channel to the ChannelGroup so that it can receive all messages group.add(ctx.channel()); } else { super.userEventTriggered(ctx, evt); }}}Copy the code

When running, have multiple clients connect to the server. When one client sends a message, the other clients receive the message broadcast by the server:

The relevant source code has been uploaded to Github

conclusion

To illustrate more scenarios, we used the Netty framework to quickly build a native WebSocket server.

We based on Netty WebSocket demo code for transformation, the core work includes the following two parts:

  • Configure the startup of the server by binding the server to the port on which it will listen for connection requests
  • The business logic processing of the data received by the server from the client

We first implemented the typical request/response interaction pattern in the client/server system in the form of a simple Echo, and then moved to the broadcast form to realize the communication between multiple users.

There are other, richer parts to the Netty framework that we need to explore… If you are also interested, please keep an eye out for the follow-up update of the technical number “Zifeng Trap Chen”!