Netty, JBoss provides a high-performance, asynchronous event-driven NIO framework, in the Java backend is very common, can better use Socket communication, micro service module communication, etc.. Since I do Android development, so this article is used to do a simple IM text chat.

There are three general steps for IM communication:

  1. Set the connection address, set the connection callback, set the message receive listening.
  2. The connection starts, and the connection succeeds or fails.
  3. Receive the message, parse the message, do other UI processing, etc.

The code is divided into server side and client side, below I posted the server side code, I found a SpringBoot combined with Netty demo to change it.

The service side

The back-end code

Changed: The channelRead() method of the NettyServerHandler class returns the following message:

@ Slf4j public class NettyServerHandler extends ChannelInboundHandlerAdapter {/ * * * client connection will trigger * / @ Override public void channelActive(ChannelHandlerContext ctx) throws Exception { log.info("Channel active......"); } /** * Override public void channelRead(ChannelHandlerContext CTX, Object MSG) throws the Exception {/ / -- -- -- -- -- -- -- -- -- -- - only changed here -- -- -- -- -- -- -- -- -- -- - the info ("Server received message: {}", msg.toString());
        String result;
        result = String.valueOf(msg)
                .replace("吗"."")
                .replace("?"."!")
                .replace("?"."!");
        ctx.write("Artificial Intelligence:+ result); ctx.flush(); Override public void exceptionCaught(ChannelHandlerContext CTX, Throwable cause) throws Exception { cause.printStackTrace(); ctx.close(); }}Copy the code

The client

  1. Add Netty dependencies
dependencies {
    //...
    
    //Netty
    implementation Ty: 'io.net netty - all: 4.1.9. Final'
}
Copy the code

I mainly encapsulate Netty on Android, mainly including the following classes:

  1. NettyClient, an API used to encapsulate connections and send messages.
class NettyClient private constructor() {/** * Socket channel */ private var mSocketChannel: SocketChannel? = NULL /** * Message listener */ private val mMsgReceiveMsgCallbacks by lazy {CopyOnWriteArrayList<ReceiveMsgCallback>()} /** * */ private var isConnect: Boolean =false/** * private val mMainHandler by lazy {Handler(looper.getMainLooper ())} companion object {private class SingleHolder { companion object { @JvmStatic val INSTANCE = NettyClient() } } @JvmStatic fun getInstance(): NettyClient {returnSingleholder. INSTANCE}} /** * connect to @param host * @param port */ fun connect(host: String, port: Int, callback: Callback) { val group = NioEventLoopGroup() Bootstrap() .group(group) .option(ChannelOption.TCP_NODELAY,true) .channel(NioSocketChannel::class.java) .handler(object : ChannelInitializer<SocketChannel>() { override fun initChannel(socketChannel: SocketChannel?) { socketChannel? .pipeline()? .apply { addLast("decoder", StringDecoder(CharsetUtil.UTF_8))
                        addLast("encoder", StringEncoder(CharsetUtil.UTF_8))
                        addLast(NettyClientHandler(object : Callback {
                            override fun onSuccess() {
                                isConnect = true
                            }

                            override fun onFail() {
                                isConnect = false
                                close()
                            }
                        }, object : ReceiveMsgCallback {
                            override fun onReceiveMsg(msg: String) {
                                mMsgReceiveMsgCallbacks.forEach {
                                    it.onReceiveMsg(msg)
                                }
                            }
                        }))
                    }
                }
            })
            .connect(InetSocketAddress(host, port))
            .addListener(ChannelFutureListener { future: ChannelFuture ->
                if (future.isSuccess) {
                    isConnect = true
                    mSocketChannel = future.channel() as SocketChannel
                    mMainHandler.post {
                        callback.onSuccess()
                    }
                } else {
                    isConnect = falseClose () // This must be closed, Cause: OOM Future.channel ().close() group.shutdownhandler.post {callback.onfail ()}})} /** * Disconnect */ fundisconnect() {close()} /** * sendMsg(String, callback: callback) {if(! isConnected()) { callback.onFail()return} mSocketChannel? .run { writeAndFlush(msg).addListener { future -> mMainHandler.post {if (future.isSuccess) {
                        callback.onSuccess()
                    } else{callback.onfail ()}}}}} /** * If connected */ fun isConnected(): Boolean {returnIsConnect} / callback registration news * * * * / fun registerReceiveMsgCallback (receiveMsgCallback: receiveMsgCallback) {if(! MMsgReceiveMsgCallbacks. The contains (receiveMsgCallback)) {mMsgReceiveMsgCallbacks. Add (receiveMsgCallback)}} / news * * * logout the callback  */ fun unregisterReceiveMsgCallback(receiveMsgCallback: ReceiveMsgCallback) {mMsgReceiveMsgCallbacks. Remove (ReceiveMsgCallback)} / close the connection * * * * / private funclose() { mSocketChannel? .close()} / / inner class NettyClientHandler(private val activeCallback: Callback, private val receiveMsgCallback: ReceiveMsgCallback ) : SimpleChannelInboundHandler<String>() { override fun channelActive(ctx: ChannelHandlerContext?) Mmainhandler. post {activecallback.onSuccess ()}} Override fun channelInactive(CTX: ChannelHandlerContext?) {super.channelinactive (CTX) // Lost connection mmainHandler. post {activecallback.onfail ()}} Override fun userEventTriggered(CTX: triggered)  ChannelHandlerContext? , evt: Any?) { super.userEventTriggered(ctx, evt)if (evt is IdleStateEvent) {
                if(evt.state() == idlestate.writer_idle) {// Send heartbeat // CTX!! .writeAndFlush(message.toJson()) } } } override fun channelRead0(ctx: ChannelHandlerContext? , msg: String?) Mmainhandler. post {MSG? .let { message -> receiveMsgCallback.onReceiveMsg(message) ReferenceCountUtil.release(message) } } } override fun exceptionCaught(ctx: ChannelHandlerContext? , cause: Throwable?) { super.exceptionCaught(ctx, cause) ctx? .close() } } }Copy the code
  1. Callback, an operation Callback that tells whether the operation succeeded or failed.
Interface Callback {/** * successful */ funonsuccess () /** * failed */ funonfail ()}Copy the code
  1. ReceiveMsgCallback: the message type is String.
Interface ReceiveMsgCallback {/** * Receive message callback */ fun onReceiveMsg(MSG: String)}Copy the code

Interface layout

The layout is simple: one text to show connection status, one input box, one send button, and one text to show past messages.

<? xml version="1.0" encoding="utf-8"? > <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context=".MainActivity">

    <TextView
        android:id="@+id/connect_status"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:padding="13dp"
        android:text="Connection Status: Not connected"
        android:textColor="@android:color/black"
        android:textSize="17sp" />

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal"
        android:padding="13dp">

        <EditText
            android:id="@+id/msg_input"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_marginEnd="13dp"
            android:layout_weight="1"
            android:hint="Let's have a chat."
            android:textColor="@android:color/black"
            android:textColorHint="@android:color/darker_gray"
            android:textSize="15sp" />

        <Button
            android:id="@+id/send"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="Send" />
    </LinearLayout>

    <ScrollView
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="1"
        android:fillViewport="true">

        <TextView
            android:id="@+id/chat_content"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:lineSpacingMultiplier="1.4"
            android:padding="13dp"
            android:textColor="@android:color/black"
            android:textSize="16sp"
            tools:text="Client: Hello \n Server: Hello" />
    </ScrollView>
</LinearLayout>
Copy the code

Java code

The interface code is simple, handling input boxes, send buttons, and displaying past messages after receiving them.

class MainActivity : AppCompatActivity(), ReceiveMsgCallback {
    private lateinit var vConnectStatus: TextView
    private lateinit var vMsgInput: EditText
    private lateinit var vSend: Button
    private lateinit var vChatContent: TextView

    companion object {
        private const val TAG = "MainActivity"
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        val layout: View = findViewById(android.R.id.content)
        findView(layout)
        bindView()
        setData()
    }

    override fun onDestroy() {
        super.onDestroy()
        NettyClient.getInstance().apply {
            disconnect()
            unregisterReceiveMsgCallback(this@MainActivity)
        }
    }

    private fun findView(view: View) {
        vConnectStatus = view.findViewById(R.id.connect_status)
        vMsgInput = view.findViewById(R.id.msg_input)
        vSend = view.findViewById(R.id.send)
        vChatContent = view.findViewById(R.id.chat_content)
    }

    private fun bindView() {
        vSend.setOnClickListener {
            val inputText = vMsgInput.text.toString().trim()
            if (inputText.isBlank()) {
                Toast.makeText(this@MainActivity, "Please enter the message you want to send.", Toast.LENGTH_SHORT).show()
                return@setSendMsg (inputText, object: Callback {override funonSuccess() {
                    Log.d(TAG, "Sent successfully, contents:$inputText")
                    appendClientMsg("I:$inputText")
                    vMsgInput.setText("")
                }

                override fun onFail() {
                    Log.d(TAG, "Failed to send, content:$inputText")
                    Toast.makeText(this@MainActivity, "Failed to send. Please try again.", Toast.LENGTH_SHORT).show()
                }
            })
        }
    }

    private fun setData() {// connect to nettyClient.getInstance ().connect()"192.168.101.244", 8090, object : Callback {
            override fun onSuccess() {
                vConnectStatus.text = "Connection Status: Connected"
                Toast.makeText(this@MainActivity, "Connection successful", Toast.LENGTH_SHORT).show()
            }

            override fun onFail() {
                vConnectStatus.text = "Connection status: failed to connect"
                Toast.makeText(this@MainActivity, "Connection failed". Toast.LENGTH_SHORT).show() } }) NettyClient.getInstance().registerReceiveMsgCallback(this) } override fun OnReceiveMsg (MSG: String) {// appendClientMsg(MSG)} /** * Add client message */ @suppresslint ("SetTextI18n")
    private fun appendClientMsg(msg: String) {
        val beforeText = vChatContent.text
        vChatContent.text = "$beforeText\n $msg"}}Copy the code

Github address of Android project

Making the address