You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

Sockets.kt 4.1KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131
  1. package com.dmdirc.ktirc.io
  2. import kotlinx.coroutines.CancellableContinuation
  3. import kotlinx.coroutines.CoroutineScope
  4. import kotlinx.coroutines.io.ByteChannel
  5. import kotlinx.coroutines.io.ByteWriteChannel
  6. import kotlinx.coroutines.io.close
  7. import kotlinx.coroutines.launch
  8. import kotlinx.coroutines.suspendCancellableCoroutine
  9. import kotlinx.io.pool.useInstance
  10. import java.net.SocketAddress
  11. import java.nio.ByteBuffer
  12. import java.nio.channels.*
  13. import kotlin.coroutines.resume
  14. import kotlin.coroutines.resumeWithException
  15. internal interface Socket {
  16. fun bind(socketAddress: SocketAddress)
  17. suspend fun connect(socketAddress: SocketAddress)
  18. suspend fun read(): ByteBuffer?
  19. fun close()
  20. val write: ByteWriteChannel
  21. val isOpen: Boolean
  22. }
  23. internal class PlainTextSocket(private val scope: CoroutineScope) : Socket {
  24. private val client = AsynchronousSocketChannel.open()
  25. private var writeChannel = ByteChannel(autoFlush = true)
  26. override val write: ByteWriteChannel
  27. get() = writeChannel
  28. override val isOpen: Boolean
  29. get() = client.isOpen
  30. override fun bind(socketAddress: SocketAddress) {
  31. client.bind(socketAddress)
  32. }
  33. override suspend fun connect(socketAddress: SocketAddress) {
  34. writeChannel = ByteChannel(autoFlush = true)
  35. suspendCancellableCoroutine<Unit> { continuation ->
  36. client.closeOnCancel(continuation)
  37. client.connect(socketAddress, continuation, AsyncVoidIOHandler)
  38. }
  39. scope.launch { writeLoop() }
  40. }
  41. override fun close() {
  42. writeChannel.close()
  43. client.close()
  44. }
  45. override suspend fun read() = try {
  46. val buffer = byteBufferPool.borrow()
  47. val bytes = suspendCancellableCoroutine<Int> { continuation ->
  48. client.closeOnCancel(continuation)
  49. client.read(buffer, continuation, asyncIOHandler())
  50. }
  51. if (bytes == -1) {
  52. close()
  53. }
  54. buffer.flip()
  55. buffer
  56. } catch (_: ClosedChannelException) {
  57. // Ignore
  58. null
  59. }
  60. private suspend fun writeLoop() {
  61. while (client.isOpen) {
  62. byteBufferPool.useInstance { buffer ->
  63. writeChannel.readAvailable(buffer)
  64. buffer.flip()
  65. try {
  66. suspendCancellableCoroutine<Int> { continuation ->
  67. client.closeOnCancel(continuation)
  68. client.write(buffer, continuation, asyncIOHandler())
  69. }
  70. } catch (_: ClosedChannelException) {
  71. // Ignore
  72. }
  73. }
  74. }
  75. }
  76. }
  77. private fun Channel.closeOnCancel(cont: CancellableContinuation<*>) {
  78. cont.invokeOnCancellation {
  79. try {
  80. close()
  81. } catch (ex: Throwable) {
  82. // Specification says that it is Ok to call it any time, but reality is different,
  83. // so we have just to ignore exception
  84. }
  85. }
  86. }
  87. @Suppress("UNCHECKED_CAST")
  88. private fun <T> asyncIOHandler(): CompletionHandler<T, CancellableContinuation<T>> =
  89. AsyncIOHandlerAny as CompletionHandler<T, CancellableContinuation<T>>
  90. private object AsyncIOHandlerAny : CompletionHandler<Any, CancellableContinuation<Any>> {
  91. override fun completed(result: Any, cont: CancellableContinuation<Any>) {
  92. cont.resume(result)
  93. }
  94. override fun failed(ex: Throwable, cont: CancellableContinuation<Any>) {
  95. // just return if already cancelled and got an expected exception for that case
  96. if (ex is AsynchronousCloseException && cont.isCancelled) return
  97. cont.resumeWithException(ex)
  98. }
  99. }
  100. private object AsyncVoidIOHandler : CompletionHandler<Void?, CancellableContinuation<Unit>> {
  101. override fun completed(result: Void?, cont: CancellableContinuation<Unit>) {
  102. cont.resume(Unit)
  103. }
  104. override fun failed(ex: Throwable, cont: CancellableContinuation<Unit>) {
  105. // just return if already cancelled and got an expected exception for that case
  106. if (ex is AsynchronousCloseException && cont.isCancelled) return
  107. cont.resumeWithException(ex)
  108. }
  109. }