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:
- Set the connection address, set the connection callback, set the message receive listening.
- The connection starts, and the connection succeeds or fails.
- 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
- 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:
- 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
- Callback, an operation Callback that tells whether the operation succeeded or failed.
Interface Callback {/** * successful */ funonsuccess () /** * failed */ funonfail ()}Copy the code
- 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