Kotlin + Netty 在 Android 上实现 Socket 的服务端

本文介绍了如何使用Kotlin和Netty在Android上构建一个Socket服务端,该服务端能同时监听TCP和WebSocket协议。通过NettyServer和NettyServerInitializer设置各种Handler来处理连接、协议选择、消息编码解码。服务端和客户端的启动、关闭及消息发送都有详细阐述,提供了一个简单的Demo实现,并提到了在实际生产中可能采用JSON作为消息格式的情况。

一. 背景

最近的一个项目:需要使用 Android App 作为 Socket 的服务端,并且一个端口能够同时监听 TCP/Web Socket 协议。

自然而然,项目决定采用 Netty 框架。Netty 服务端在收到客户端发来的消息后,能够做出相应的业务处理。在某些场景下,服务端也需要给客户端 App/网页发送消息。

二. Netty 的使用

2.1 Netty 服务端

首先,定义好 NettyServer,它使用 object声明表示是一个单例。用于 Netty 服务端的启动、关闭以及发送消息。

  1. object NettyServer {

  2. private val TAG = "NettyServer"

  3. private var channel: Channel?=null

  4. private lateinit var listener: NettyServerListener<String>

  5. private lateinit var bossGroup: EventLoopGroup

  6. private lateinit var workerGroup: EventLoopGroup

  7. var port = 8888

  8. set(value) {

  9. field = value

  10. }

  11. var webSocketPath = "/ws"

  12. set(value) {

  13. field = value

  14. }

  15. var isServerStart: Boolean = false

  16. private set

  17. fun start() {

  18. object : Thread() {

  19. override fun run() {

  20. super.run()

  21. bossGroup = NioEventLoopGroup(1)

  22. workerGroup = NioEventLoopGroup()

  23. try {

  24. val b = ServerBootstrap()

  25. b.group(bossGroup, workerGroup)

  26. .channel(NioServerSocketChannel::class.java)

  27. .localAddress(InetSocketAddress(port))

  28. .childOption(ChannelOption.SO_KEEPALIVE, true)

  29. .childOption(ChannelOption.SO_REUSEADDR, true)

  30. .childOption(ChannelOption.TCP_NODELAY, true)

  31. .childHandler(NettyServerInitializer(listener,webSocketPath))

  32. // Bind and start to accept incoming connections.

  33. val f = b.bind().sync()

  34. Log.i(TAG, NettyServer::class.java.name + " started and listen on " + f.channel().localAddress())

  35. isServerStart = true

  36. listener.onStartServer()

  37. f.channel().closeFuture().sync()

  38. } catch (e: Exception) {

  39. Log.e(TAG, e.localizedMessage)

  40. e.printStackTrace()

  41. } finally {

  42. isServerStart = false

  43. listener.onStopServer()

  44. disconnect()

  45. }

  46. }

  47. }.start()

  48. }

  49. fun disconnect() {

  50. workerGroup.shutdownGracefully()

  51. bossGroup.shutdownGracefully()

  52. }

  53. fun setListener(listener: NettyServerListener<String>) {

  54. this.listener = listener

  55. }

  56. // 异步发送TCP消息

  57. fun sendMsgToClient(data: String, listener: ChannelFutureListener) = channel?.run {

  58. val flag = this.isActive

  59. if (flag) {

  60. this.writeAndFlush(data + System.getProperty("line.separator")).addListener(listener)

  61. }

  62. flag

  63. } ?: false

  64. // 同步发送TCP消息

  65. fun sendMsgToClient(data: String) = channel?.run {

  66. if (this.isActive) {

  67. return this.writeAndFlush(data + System.getProperty("line.separator")).awaitUninterruptibly().isSuccess

  68. }

  69. false

  70. } ?: false

  71. // 异步发送WebSocket消息

  72. fun sendMsgToWS(data: String,listener: ChannelFutureListener) = channel?.run {

  73. val flag = this.isActive

  74. if (flag) {

  75. this.writeAndFlush(TextWebSocketFrame(data)).addListener(listener)

  76. }

  77. flag

  78. } ?: false

  79. // 同步发送TCP消息

  80. fun sendMsgToWS(data: String) = channel?.run {

  81. if (this.isActive) {

  82. return this.writeAndFlush(TextWebSocketFrame(data)).awaitUninterruptibly().isSuccess

  83. }

  84. false

  85. } ?: false

  86. /**

  87. * 切换通道

  88. * 设置服务端,与哪个客户端通信

  89. * @param channel

  90. */

  91. fun selectorChannel(channel: Channel?) {

  92. this.channel = channel

  93. }

  94. }

NettyServerInitializer 是服务端跟客户端连接之后使用的 childHandler:

  1. class NettyServerInitializer(private val mListener: NettyServerListener<String>,private val webSocketPath:String) : ChannelInitializer<SocketChannel>() {

  2. @Throws(Exception::class)

  3. public override fun initChannel(ch: SocketChannel) {

  4. val pipeline = ch.pipeline()

  5. pipeline.addLast("active",ChannelActiveHandler(mListener))

  6. pipeline.addLast("socketChoose", SocketChooseHandler(webSocketPath))

  7. pipeline.addLast("string_encoder",StringEncoder(CharsetUtil.UTF_8))

  8. pipeline.addLast("linebased",LineBasedFrameDecoder(1024))

  9. pipeline.addLast("string_decoder",StringDecoder(CharsetUtil.UTF_8))

  10. pipeline.addLast("commonhandler", CustomerServerHandler(mListener))

  11. }

  12. }

NettyServerInitializer 包含了多个 Handler:连接使用的ChannelActiveHandler,协议选择使用的 SocketChooseHandler,TCP 消息使用的 StringEncoder、LineBasedFrameDecoder、StringDecoder,以及最终处理消息的 CustomerServerHandler。

ChannelActiveHandler:

  1. @ChannelHandler.Sharable

  2. class ChannelActiveHandler(var mListener: NettyServerListener<String>) : ChannelInboundHandlerAdapter() {

  3. @Throws(Exception::class)

  4. override fun channelActive(ctx: ChannelHandlerContext) {

  5. val insocket = ctx.channel().remoteAddress() as InetSocketAddress

  6. val clientIP = insocket.address.hostAddress

  7. val clientPort = insocket.port

  8. Log.i("ChannelActiveHandler","新的连接:$clientIP : $clientPort")

  9. mListener.onChannelConnect(ctx.channel())

  10. }

  11. }

SocketChooseHandler 通过读取消息来区分是 WebSocket 还是 Socket。如果是 WebSocket 的话,去掉 Socket 使用的相关 Handler。

  1. class SocketChooseHandler(val webSocketPath:String) : ByteToMessageDecoder() {

  2. @Throws(Exception::class)

  3. override fun decode(ctx: ChannelHandlerContext, `in`: ByteBuf, out: List<Any>) {

  4. val protocol = getBufStart(`in`)

  5. if (protocol.startsWith(WEBSOCKET_PREFIX)) {

  6. PipelineAdd.websocketAdd(ctx,webSocketPath)

  7. ctx.pipeline().remove("string_encoder")

  8. ctx.pipeline().remove("linebased")

  9. ctx.pipeline().remove("string_decoder")

  10. }

  11. `in`.resetReaderIndex()

  12. ctx.pipeline().remove(this.javaClass)

  13. }

  14. private fun getBufStart(`in`: ByteBuf): String {

  15. var length = `in`.readableBytes()

  16. if (length > MAX_LENGTH) {

  17. length = MAX_LENGTH

  18. }

  19. // 标记读位置

  20. `in`.markReaderIndex()

  21. val content = ByteArray(length)

  22. `in`.readBytes(content)

  23. return String(content)

  24. }

  25. companion object {

  26. /** 默认暗号长度为23 */

  27. private val MAX_LENGTH = 23

  28. /** WebSocket握手的协议前缀 */

  29. private val WEBSOCKET_PREFIX = "GET /"

  30. }

  31. }

StringEncoder、LineBasedFrameDecoder、StringDecoder 都是 Netty 内置的编、解码器。其中,LineBasedFrameDecoder 用于解决 TCP粘包/拆包的问题。

CustomerServerHandler:

  1. @ChannelHandler.Sharable

  2. class CustomerServerHandler(private val mListener: NettyServerListener<String>) : SimpleChannelInboundHandler<Any>() {

  3. @Throws(Exception::class)

  4. override fun channelReadComplete(ctx: ChannelHandlerContext) {

  5. }

  6. override fun exceptionCaught(ctx: ChannelHandlerContext,

  7. cause: Throwable) {

  8. cause.printStackTrace()

  9. ctx.close()

  10. }

  11. @Throws(Exception::class)

  12. override fun channelRead0(ctx: ChannelHandlerContext, msg: Any) {

  13. val buff = msg as ByteBuf

  14. val info = buff.toString(CharsetUtil.UTF_8)

  15. Log.d(TAG,"收到消息内容:$info")

  16. }

  17. @Throws(Exception::class)

  18. override fun channelRead(ctx: ChannelHandlerContext, msg: Any) {

  19. if (msg is WebSocketFrame) { // 处理 WebSocket 消息

  20. val webSocketInfo = (msg as TextWebSocketFrame).text().trim { it <= ' ' }

  21. Log.d(TAG, "收到WebSocketSocket消息:$webSocketInfo")

  22. mListener.onMessageResponseServer(webSocketInfo , ctx.channel().id().asShortText())

  23. } else if (msg is String){ // 处理 Socket 消息

  24. Log.d(TAG, "收到socket消息:$msg")

  25. mListener.onMessageResponseServer(msg, ctx.channel().id().asShortText())

  26. }

  27. }

  28. // 断开连接

  29. @Throws(Exception::class)

  30. override fun channelInactive(ctx: ChannelHandlerContext) {

  31. super.channelInactive(ctx)

  32. Log.d(TAG, "channelInactive")

  33. val reAddr = ctx.channel().remoteAddress() as InetSocketAddress

  34. val clientIP = reAddr.address.hostAddress

  35. val clientPort = reAddr.port

  36. Log.d(TAG,"连接断开:$clientIP : $clientPort")

  37. mListener.onChannelDisConnect(ctx.channel())

  38. }

  39. companion object {

  40. private val TAG = "CustomerServerHandler"

  41. }

  42. }

2.2 Netty 客户端

客户端也需要一个启动、关闭、发送消息的 NettyTcpClient,并且 NettyTcpClient 的创建采用 Builder 模式。

  1. class NettyTcpClient private constructor(val host: String, val tcp_port: Int, val index: Int) {

  2. private lateinit var group: EventLoopGroup

  3. private lateinit var listener: NettyClientListener<String>

  4. private var channel: Channel? = null

  5. /**

  6. * 获取TCP连接状态

  7. *

  8. * @return 获取TCP连接状态

  9. */

  10. var connectStatus = false

  11. /**

  12. * 最大重连次数

  13. */

  14. var maxConnectTimes = Integer.MAX_VALUE

  15. private set

  16. private var reconnectNum = maxConnectTimes

  17. private var isNeedReconnect = true

  18. var isConnecting = false

  19. private set

  20. var reconnectIntervalTime: Long = 5000

  21. private set

  22. /**

  23. * 心跳间隔时间

  24. */

  25. var heartBeatInterval: Long = 5

  26. private set//单位秒

  27. /**

  28. * 是否发送心跳

  29. */

  30. var isSendheartBeat = false

  31. private set

  32. /**

  33. * 心跳数据,可以是String类型,也可以是byte[].

  34. */

  35. private var heartBeatData: Any? = null

  36. fun connect() {

  37. if (isConnecting) {

  38. return

  39. }

  40. val clientThread = object : Thread("Netty-Client") {

  41. override fun run() {

  42. super.run()

  43. isNeedReconnect = true

  44. reconnectNum = maxConnectTimes

  45. connectServer()

  46. }

  47. }

  48. clientThread.start()

  49. }

  50. private fun connectServer() {

  51. synchronized(this@NettyTcpClient) {

  52. var channelFuture: ChannelFuture?=null

  53. if (!connectStatus) {

  54. isConnecting = true

  55. group = NioEventLoopGroup()

  56. val bootstrap = Bootstrap().group(group)

  57. .option(ChannelOption.TCP_NODELAY, true)//屏蔽Nagle算法试图

  58. .option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 5000)

  59. .channel(NioSocketChannel::class.java as Class<out Channel>?)

  60. .handler(object : ChannelInitializer<SocketChannel>() {

  61. @Throws(Exception::class)

  62. public override fun initChannel(ch: SocketChannel) {

  63. if (isSendheartBeat) {

  64. ch.pipeline().addLast("ping", IdleStateHandler(0, heartBeatInterval, 0, TimeUnit.SECONDS)) //5s未发送数据,回调userEventTriggered

  65. }

  66. ch.pipeline().addLast(StringEncoder(CharsetUtil.UTF_8))

  67. ch.pipeline().addLast(StringDecoder(CharsetUtil.UTF_8))

  68. ch.pipeline().addLast(LineBasedFrameDecoder(1024))//黏包处理,需要客户端、服务端配合

  69. ch.pipeline().addLast(NettyClientHandler(listener, index, isSendheartBeat, heartBeatData))

  70. }

  71. })

  72. try {

  73. channelFuture = bootstrap.connect(host, tcp_port).addListener {

  74. if (it.isSuccess) {

  75. Log.d(TAG, "连接成功")

  76. reconnectNum = maxConnectTimes

  77. connectStatus = true

  78. channel = channelFuture?.channel()

  79. } else {

  80. Log.d(TAG, "连接失败")

  81. connectStatus = false

  82. }

  83. isConnecting = false

  84. }.sync()

  85. // Wait until the connection is closed.

  86. channelFuture.channel().closeFuture().sync()

  87. Log.d(TAG, " 断开连接")

  88. } catch (e: Exception) {

  89. e.printStackTrace()

  90. } finally {

  91. connectStatus = false

  92. listener.onClientStatusConnectChanged(ConnectState.STATUS_CONNECT_CLOSED, index)

  93. if (channelFuture != null) {

  94. if (channelFuture.channel() != null && channelFuture.channel().isOpen) {

  95. channelFuture.channel().close()

  96. }

  97. }

  98. group.shutdownGracefully()

  99. reconnect()

  100. }

  101. }

  102. }

  103. }

  104. fun disconnect() {

  105. Log.d(TAG, "disconnect")

  106. isNeedReconnect = false

  107. group.shutdownGracefully()

  108. }

  109. fun reconnect() {

  110. Log.d(TAG, "reconnect")

  111. if (isNeedReconnect && reconnectNum > 0 && !connectStatus) {

  112. reconnectNum--

  113. SystemClock.sleep(reconnectIntervalTime)

  114. if (isNeedReconnect && reconnectNum > 0 && !connectStatus) {

  115. Log.e(TAG, "重新连接")

  116. connectServer()

  117. }

  118. }

  119. }

  120. /**

  121. * 异步发送

  122. *

  123. * @param data 要发送的数据

  124. * @param listener 发送结果回调

  125. * @return 方法执行结果

  126. */

  127. fun sendMsgToServer(data: String, listener: MessageStateListener) = channel?.run {

  128. val flag = this != null && connectStatus

  129. if (flag) {

  130. this.writeAndFlush(data + System.getProperty("line.separator")).addListener { channelFuture -> listener.isSendSuccss(channelFuture.isSuccess) }

  131. }

  132. flag

  133. } ?: false

  134. /**

  135. * 同步发送

  136. *

  137. * @param data 要发送的数据

  138. * @return 方法执行结果

  139. */

  140. fun sendMsgToServer(data: String) = channel?.run {

  141. val flag = this != null && connectStatus

  142. if (flag) {

  143. val channelFuture = this.writeAndFlush(data + System.getProperty("line.separator")).awaitUninterruptibly()

  144. return channelFuture.isSuccess

  145. }

  146. false

  147. }?:false

  148. fun setListener(listener: NettyClientListener<String>) {

  149. this.listener = listener

  150. }

  151. /**

  152. * Builder 模式创建NettyTcpClient

  153. */

  154. class Builder {

  155. /**

  156. * 最大重连次数

  157. */

  158. private var MAX_CONNECT_TIMES = Integer.MAX_VALUE

  159. /**

  160. * 重连间隔

  161. */

  162. private var reconnectIntervalTime: Long = 5000

  163. /**

  164. * 服务器地址

  165. */

  166. private var host: String? = null

  167. /**

  168. * 服务器端口

  169. */

  170. private var tcp_port: Int = 0

  171. /**

  172. * 客户端标识,(因为可能存在多个连接)

  173. */

  174. private var mIndex: Int = 0

  175. /**

  176. * 是否发送心跳

  177. */

  178. private var isSendheartBeat: Boolean = false

  179. /**

  180. * 心跳时间间隔

  181. */

  182. private var heartBeatInterval: Long = 5

  183. /**

  184. * 心跳数据,可以是String类型,也可以是byte[].

  185. */

  186. private var heartBeatData: Any? = null

  187. fun setMaxReconnectTimes(reConnectTimes: Int): Builder {

  188. this.MAX_CONNECT_TIMES = reConnectTimes

  189. return this

  190. }

  191. fun setReconnectIntervalTime(reconnectIntervalTime: Long): Builder {

  192. this.reconnectIntervalTime = reconnectIntervalTime

  193. return this

  194. }

  195. fun setHost(host: String): Builder {

  196. this.host = host

  197. return this

  198. }

  199. fun setTcpPort(tcp_port: Int): Builder {

  200. this.tcp_port = tcp_port

  201. return this

  202. }

  203. fun setIndex(mIndex: Int): Builder {

  204. this.mIndex = mIndex

  205. return this

  206. }

  207. fun setHeartBeatInterval(intervalTime: Long): Builder {

  208. this.heartBeatInterval = intervalTime

  209. return this

  210. }

  211. fun setSendheartBeat(isSendheartBeat: Boolean): Builder {

  212. this.isSendheartBeat = isSendheartBeat

  213. return this

  214. }

  215. fun setHeartBeatData(heartBeatData: Any): Builder {

  216. this.heartBeatData = heartBeatData

  217. return this

  218. }

  219. fun build(): NettyTcpClient {

  220. val nettyTcpClient = NettyTcpClient(host!!, tcp_port, mIndex)

  221. nettyTcpClient.maxConnectTimes = this.MAX_CONNECT_TIMES

  222. nettyTcpClient.reconnectIntervalTime = this.reconnectIntervalTime

  223. nettyTcpClient.heartBeatInterval = this.heartBeatInterval

  224. nettyTcpClient.isSendheartBeat = this.isSendheartBeat

  225. nettyTcpClient.heartBeatData = this.heartBeatData

  226. return nettyTcpClient

  227. }

  228. }

  229. companion object {

  230. private val TAG = "NettyTcpClient"

  231. private val CONNECT_TIMEOUT_MILLIS = 5000

  232. }

  233. }

Android 的客户端相对而言比较简单,需要的 Handler 包括:支持心跳的 IdleStateHandler, TCP 消息需要使用的 Handler (跟服务端一样分别是StringEncoder、StringDecoder、LineBasedFrameDecoder),以及对收到 TCP 消息进行处理的 NettyClientHandler。

NettyClientHandler:

  1. class NettyClientHandler(private val listener: NettyClientListener<String>, private val index: Int, private val isSendheartBeat: Boolean, private val heartBeatData: Any?) : SimpleChannelInboundHandler<String>() {

  2. /**

  3. *

  4. * 设定IdleStateHandler心跳检测每x秒进行一次读检测,

  5. * 如果x秒内ChannelRead()方法未被调用则触发一次userEventTrigger()方法

  6. *

  7. * @param ctx ChannelHandlerContext

  8. * @param evt IdleStateEvent

  9. */

  10. override fun userEventTriggered(ctx: ChannelHandlerContext, evt: Any) {

  11. if (evt is IdleStateEvent) {

  12. if (evt.state() == IdleState.WRITER_IDLE) { //发送心跳

  13. if (isSendheartBeat) {

  14. if (heartBeatData == null) {

  15. ctx.channel().writeAndFlush("Heartbeat" + System.getProperty("line.separator")!!)

  16. } else {

  17. if (heartBeatData is String) {

  18. Log.d(TAG, "userEventTriggered: String")

  19. ctx.channel().writeAndFlush(heartBeatData + System.getProperty("line.separator")!!)

  20. } else if (heartBeatData is ByteArray) {

  21. Log.d(TAG, "userEventTriggered: byte")

  22. val buf = Unpooled.copiedBuffer((heartBeatData as ByteArray?)!!)

  23. ctx.channel().writeAndFlush(buf)

  24. } else {

  25. Log.d(TAG, "userEventTriggered: heartBeatData type error")

  26. }

  27. }

  28. } else {

  29. Log.d(TAG, "不发送心跳")

  30. }

  31. }

  32. }

  33. }

  34. /**

  35. *

  36. * 客户端上线

  37. *

  38. * @param ctx ChannelHandlerContext

  39. */

  40. override fun channelActive(ctx: ChannelHandlerContext) {

  41. Log.d(TAG, "channelActive")

  42. listener.onClientStatusConnectChanged(ConnectState.STATUS_CONNECT_SUCCESS, index)

  43. }

  44. /**

  45. *

  46. * 客户端下线

  47. *

  48. * @param ctx ChannelHandlerContext

  49. */

  50. override fun channelInactive(ctx: ChannelHandlerContext) {

  51. Log.d(TAG, "channelInactive")

  52. }

  53. /**

  54. * 客户端收到消息

  55. *

  56. * @param channelHandlerContext ChannelHandlerContext

  57. * @param msg 消息

  58. */

  59. override fun channelRead0(channelHandlerContext: ChannelHandlerContext, msg: String) {

  60. Log.d(TAG, "channelRead0:")

  61. listener.onMessageResponseClient(msg, index)

  62. }

  63. /**

  64. * @param ctx ChannelHandlerContext

  65. * @param cause 异常

  66. */

  67. override fun exceptionCaught(ctx: ChannelHandlerContext, cause: Throwable) {

  68. Log.e(TAG, "exceptionCaught")

  69. listener.onClientStatusConnectChanged(ConnectState.STATUS_CONNECT_ERROR, index)

  70. cause.printStackTrace()

  71. ctx.close()

  72. }

  73. companion object {

  74. private val TAG = "NettyClientHandler"

  75. }

  76. }

三. Demo 的实现

3.1 Socket 服务端

启动 NettyServer:

  1. private fun startServer() {

  2. if (!NettyServer.isServerStart) {

  3. NettyServer.setListener(this@MainActivity)

  4. NettyServer.port = port

  5. NettyServer.webSocketPath = webSocketPath

  6. NettyServer.start()

  7. } else {

  8. NettyServer.disconnect()

  9. }

  10. }

NettyServer 异步发送 TCP 消息:

  1. NettyServer.sendMsgToClient(msg, ChannelFutureListener { channelFuture ->

  2. if (channelFuture.isSuccess) {

  3. msgSend(msg)

  4. }

  5. })

NettyServer 异步发送 WebSocket 消息:

  1. NettyServer.sendMsgToWS(msg, ChannelFutureListener { channelFuture ->

  2. if (channelFuture.isSuccess) {

  3. msgSend(msg)

  4. }

  5. })

Demo 可以通过 startServer 来启动 Socket 服务端,也可以在启动之前点击 configServer 来修改服务端的端口以及 WebSocket 的 Endpoint。

3.2 Socket 客户端

NettyTcpClient 通过 Builder 模式创建:

  1. mNettyTcpClient = NettyTcpClient.Builder()

  2. .setHost(ip) //设置服务端地址

  3. .setTcpPort(port) //设置服务端端口号

  4. .setMaxReconnectTimes(5) //设置最大重连次数

  5. .setReconnectIntervalTime(5) //设置重连间隔时间。单位:秒

  6. .setSendheartBeat(false) //设置发送心跳

  7. .setHeartBeatInterval(5) //设置心跳间隔时间。单位:秒

  8. .setHeartBeatData("I'm is HeartBeatData") //设置心跳数据,可以是String类型,也可以是byte[],以后设置的为准

  9. .setIndex(0) //设置客户端标识.(因为可能存在多个tcp连接)

  10. .build()

  11. mNettyTcpClient.setListener(this@MainActivity) //设置TCP监听

启动、关闭客户端连接:

  1. private fun connect() {

  2. Log.d(TAG, "connect")

  3. if (!mNettyTcpClient.connectStatus) {

  4. mNettyTcpClient.connect()//连接服务器

  5. } else {

  6. mNettyTcpClient.disconnect()

  7. }

  8. }

NettyTcpClient 异步发送 TCP 消息到服务端:

  1. mNettyTcpClient.sendMsgToServer(msg, object : MessageStateListener {

  2. override fun isSendSuccss(isSuccess: Boolean) {

  3. if (isSuccess) {

  4. msgSend(msg)

  5. }

  6. }

  7. })

Demo 的客户端 App 也可以在启动之前点击 configClient 来修改要连接的服务端 IP 、端口。

WebSocket 的测试可以通过:http://www.websocket-test.com/

Netty Server 端跟网页通信:

WebSocket在线测试:

四. 总结

借助 Kotlin 的特性以及 Netty 框架,我们在 Android 上也实现了一个 Socket 服务端。

本文 demo github 地址:https://github.com/fengzhizi715/Netty4Android

本文的例子很简单,只是发送简单的消息。在实际生产环境中,我们采用的消息格式可能是 json ,因为 json 更加灵活,通过解析 json 获取消息的内容。

参考资料:

  1. https://github.com/aLittleGreens/NettyDemo

  2. Netty 实现 一个端口同时接收 socket 和 webSocket 连接(https://blog.youkuaiyun.com/NRlovestudy/article/details/93325965)

关注【Java与Android技术栈】

更多精彩内容请关注扫码

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值