# 前言
如果底层TCP Socket库是非挂起的, 那么**不要**将IO读写操作使用`withContext`包裹,
这会导致在网络IO中频繁的切换线程导致程序性能**下降**.
# 基于Netty的TCP Socket实现
```kotlin
dependencies {
implementation("io.netty:netty-transport:4.2.17.Final")
implementation("io.netty:netty-buffer:4.2.17.Final")
}
```
点击展开代码
```kotlin
import cn.rtast.libmc.network.RawSocket
import cn.rtast.libmc.network.ReadChannel
import cn.rtast.libmc.network.SocketEngine
import cn.rtast.libmc.network.WriteChannel
import io.netty.bootstrap.Bootstrap
import io.netty.buffer.ByteBuf
import io.netty.buffer.Unpooled
import io.netty.channel.*
import io.netty.channel.nio.NioIoHandler
import io.netty.channel.socket.SocketChannel
import io.netty.channel.socket.nio.NioSocketChannel
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.suspendCancellableCoroutine
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
import kotlinx.coroutines.withContext
import kotlin.coroutines.resume
import kotlin.coroutines.resumeWithException
import io.netty.channel.Channel as NettyChannel
import kotlinx.coroutines.channels.Channel as KotlinChannel
public class NettyNetworkSocketEngine : SocketEngine {
override fun create(host: String, port: Int): RawSocket = NettyNetworkSocket(host, port)
private class NettyNetworkSocket(private val host: String, private val port: Int) : RawSocket {
private val workerGroup: EventLoopGroup = MultiThreadIoEventLoopGroup(0, NioIoHandler.newFactory())
private lateinit var channel: NettyChannel
private val inboundQueue = KotlinChannel(KotlinChannel.UNLIMITED)
private lateinit var readChannel: NettyReadChannel
private lateinit var writeChannel: NettyWriteChannel
override suspend fun connect() {
val bootstrap = Bootstrap()
.group(workerGroup)
.channel(NioSocketChannel::class.java)
.option(ChannelOption.TCP_NODELAY, true)
.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 10000)
.handler(object : ChannelInitializer() {
override fun initChannel(ch: SocketChannel) {
ch.pipeline().addLast(object : ChannelInboundHandlerAdapter() {
override fun channelRead(ctx: ChannelHandlerContext, msg: Any) {
if (msg is ByteBuf) {
try {
val bytes = ByteArray(msg.readableBytes())
msg.readBytes(bytes)
inboundQueue.trySend(bytes)
} finally {
msg.release()
}
}
}
override fun exceptionCaught(ctx: ChannelHandlerContext, cause: Throwable) {
inboundQueue.close(cause)
ctx.close()
}
override fun channelInactive(ctx: ChannelHandlerContext) {
inboundQueue.close()
super.channelInactive(ctx)
}
})
}
})
channel = withContext(Dispatchers.IO) {
bootstrap.connect(host, port).suspendAwait()
}
readChannel = NettyReadChannel(inboundQueue)
writeChannel = NettyWriteChannel(channel)
}
override fun openReadChannel(): ReadChannel = readChannel
override fun openWriteChannel(): WriteChannel = writeChannel
override fun close() {
if (::channel.isInitialized && channel.isOpen) {
channel.close()
}
inboundQueue.close()
workerGroup.shutdownGracefully()
}
}
private class NettyReadChannel(private val inboundQueue: KotlinChannel) : ReadChannel {
private var currentChunk: ByteArray? = null
private var chunkOffset = 0
private suspend fun fetchNextChunk() {
val next = inboundQueue.receiveCatching().getOrNull()
?: throw IllegalStateException("Socket/Channel closed while reading")
currentChunk = next
chunkOffset = 0
}
override suspend fun readByte(): Byte {
while (currentChunk == null || chunkOffset >= currentChunk!!.size) {
fetchNextChunk()
}
val chunk = currentChunk!!
return chunk[chunkOffset++]
}
override suspend fun readBytes(length: Int): ByteArray {
val result = ByteArray(length)
readFully(result, 0, length)
return result
}
override suspend fun readFully(out: ByteArray, start: Int, end: Int) {
var written = start
while (written < end) {
while (currentChunk == null || chunkOffset >= currentChunk!!.size) {
fetchNextChunk()
}
val chunk = currentChunk!!
val available = chunk.size - chunkOffset
val toCopy = minOf(available, end - written)
chunk.copyInto(
out,
destinationOffset = written,
startIndex = chunkOffset,
endIndex = chunkOffset + toCopy
)
chunkOffset += toCopy
written += toCopy
}
}
}
private class NettyWriteChannel(private val channel: NettyChannel) : WriteChannel {
private val writeMutex = Mutex()
override suspend fun writeFully(value: ByteArray, startIndex: Int, endIndex: Int) {
val length = endIndex - startIndex
if (length <= 0) return
val nettyBuf = Unpooled.copiedBuffer(value, startIndex, length)
writeMutex.withLock {
withContext(Dispatchers.IO) {
channel.writeAndFlush(nettyBuf).suspendAwait()
}
}
}
override suspend fun flush() {
writeMutex.withLock {
withContext(Dispatchers.IO) {
channel.flush()
}
}
}
}
}
private suspend inline fun ChannelFuture.suspendAwait(): NettyChannel = suspendCancellableCoroutine { cont ->
if (isDone) {
if (isSuccess) cont.resume(channel())
else cont.resumeWithException(cause() ?: RuntimeException("Netty operation failed"))
return@suspendCancellableCoroutine
}
addListener { future ->
if (future.isSuccess) {
cont.resume(channel())
} else {
cont.resumeWithException(future.cause() ?: RuntimeException("Netty operation failed"))
}
}
cont.invokeOnCancellation { cancel(false) }
}
```
> 请一并复制import部分的代码
# 基于ktor-network的TCP Socket实现
```kotlin
dependencies {
implementation("io.ktor:ktor-network:3.5.2")
}
```
点击展开代码
```kotlin
public class KtorNetworkEngine : SocketEngine {
override fun create(host: String, port: Int): RawSocket = KtorNetworkSocket(host, port)
}
public class KtorNetworkSocket(private val host: String, private val port: Int) : RawSocket {
private val sm = SelectorManager(Dispatchers.IO)
private lateinit var socket: Socket
override suspend fun connect(): Unit = run { socket = aSocket(sm).tcp().connect(host, port) }
override fun openReadChannel(): ReadChannel = KtorReadChannel(socket.openReadChannel())
override fun openWriteChannel(): WriteChannel = KtorWriteChannel(socket.openWriteChannel())
override fun close() {
socket.close()
sm.close()
}
}
public class KtorReadChannel(private val readChannel: ByteReadChannel) : ReadChannel {
override suspend fun readByte(): Byte = readChannel.readByte()
override suspend fun readBytes(length: Int): ByteArray = readChannel.readByteArray(length)
override suspend fun readFully(out: ByteArray, start: Int, end: Int): Unit =
readChannel.readFully(out, start, end)
}
public class KtorWriteChannel(private val writeChannel: ByteWriteChannel) : WriteChannel {
override suspend fun writeFully(value: ByteArray, startIndex: Int, endIndex: Int) {
writeChannel.writeFully(value, startIndex, endIndex)
}
override suspend fun flush(): Unit = writeChannel.flush()
}
```
# 基于Java内置Socket的TCP Socket实现
点击展开代码
```kotlin
class JavaSocketEngine : SocketEngine {
override fun create(host: String, port: Int): RawSocket = JavaNetworkSocket(host, port)
}
class JavaNetworkSocket(
private val host: String,
private val port: Int,
private val connectTimeoutMs: Int = 10000,
) : RawSocket {
private lateinit var socket: Socket
private lateinit var readChannel: JavaReadChannel
private lateinit var writeChannel: JavaWriteChannel
override suspend fun connect() {
val s = Socket()
s.tcpNoDelay = true
s.connect(InetSocketAddress(host, port), connectTimeoutMs)
socket = s
readChannel = JavaReadChannel(s.getInputStream())
writeChannel = JavaWriteChannel(s.getOutputStream())
}
override fun openReadChannel(): ReadChannel = readChannel
override fun openWriteChannel(): WriteChannel = writeChannel
override fun close() {
if (::socket.isInitialized && !socket.isClosed) {
runCatching { socket.close() }
}
}
}
class JavaReadChannel(private val inputStream: InputStream) : ReadChannel {
override suspend fun readByte(): Byte {
val b = inputStream.read()
if (b == -1) throw IllegalStateException("Socket stream reached EOF while reading byte")
return b.toByte()
}
override suspend fun readBytes(length: Int): ByteArray {
val buffer = ByteArray(length)
readFullyInternal(buffer, 0, length)
return buffer
}
override suspend fun readFully(out: ByteArray, start: Int, end: Int) {
readFullyInternal(out, start, end - start)
}
private fun readFullyInternal(out: ByteArray, offset: Int, length: Int) {
var bytesRead = 0
while (bytesRead < length) {
val count = inputStream.read(out, offset + bytesRead, length - bytesRead)
if (count == -1) {
throw IllegalStateException("Socket stream closed unexpectedly (read $bytesRead of $length bytes)")
}
bytesRead += count
}
}
}
class JavaWriteChannel(private val outputStream: OutputStream) : WriteChannel {
override suspend fun writeFully(value: ByteArray, startIndex: Int, endIndex: Int) {
outputStream.write(value, startIndex, endIndex - startIndex)
}
override suspend fun flush() {
outputStream.flush()
}
}
```