본문 바로가기
📚 Kotlin

[TCP Socket 통신] Okio 라이브러리를 이용한 kotlin socket 통신

by GroovyArea 2023. 6. 24.
io stream 과 socket 을 이용한 tcp 통신은 java 를 처음 공부할 때, 간단하게 멀티 스레드 채팅 정도의 예제로 이해하며 넘어갔다..
웬만한 클라이언트 - 서버 간의 통신은 http 로 이루어지므로 이를 더 깊게 파며 공부했었다.

이번에 이직한 회사에서 본격적으로 맡게 된 첫 과제는 TCP 전문 통신을 구축하는 것이다.
소켓 통신을 구현하는데, java socket api 를 이용하여 순수 구현할 수 있지만, kotlin 에서는 okio 라는 통신 전용 라이브러리가 있다고 해서 이를 한번 사용해서 전문 통신을 위한 소켓 클라이언트 초석을 구현해봤다.

잘 해보지 않은 socket 통신을 이해하고 구현하며 이틀간 많이 고통 받았고, 그 과정에서 좀 더 편리한 kotlin okio 라이브러리를 사용하여 간단하게 소켓 통신을 할 수 있는 예시 코드를 보자.
혹시나 okio 라이브러리를 사용하여 byte 배열 socket 통신을 하시려는 분들에게 도움이 되길 바란다.

 

참고 [Okio 라이브러리]

https://github.com/square/okio

 

GitHub - square/okio: A modern I/O library for Android, Java, and Kotlin Multiplatform.

A modern I/O library for Android, Java, and Kotlin Multiplatform. - GitHub - square/okio: A modern I/O library for Android, Java, and Kotlin Multiplatform.

github.com

https://square.github.io/okio/

 

Okio

Okio Okio is a library that complements java.io and java.nio to make it much easier to access, store, and process your data. It started as a component of OkHttp, the capable HTTP client included in Android. It’s well-exercised and ready to solve new prob

square.github.io

 

Server 코드

import okio.buffer
import okio.sink
import okio.source
import java.net.ServerSocket
import java.util.concurrent.TimeUnit

fun main() {
    Server().server()
}

class Server {
    fun server() {
        val serverSocket = ServerSocket(9999)
        
        while (true) {
            val client = serverSocket.accept()

            val bufferedSource = client.source().buffer() // 내부적으로 getInputStream() 을 호출한다.
            bufferedSource.timeout().timeout(READ_TIME_OUT, TimeUnit.MILLISECONDS)
            val readByteArray = bufferedSource.readByteArray() // byte 배열로 읽기
            println(String(readByteArray))

            val bufferedSink = client.sink().buffer() // 내부적으로 getOutputStream() 을 호출한다.
            bufferedSink.timeout().timeout(WRITE_TIME_OUT, TimeUnit.MILLISECONDS)
            bufferedSink.write(readByteArray) // 에코 서버 마냥 받은 byte 배열을 클라이언트로 보내기
            bufferedSink.flush() // flush 를 호출해서 buffer 를 비우자.
            client.shutdownOutput() // eof 마냥, write 이 끝났다는 것을 socket 의 shutdownOutput() 을 호출해야 한다.
        }
    }
}
  • 자원의 close() 함수를 명시하진 않았다.
    • 실제 코드에서는 호출해야.
  • okio 에서는 input, output stream 같은 것을 한번 래핑하여 추상화했다.
    • source, sink 라는 객체로 추상화

source : socket 에서 inputstream 가져옴.
sink : socket 에서 outputstream 가져옴.

 

Client 코드

import okio.buffer
import okio.sink
import okio.source
import java.io.IOException
import java.net.InetSocketAddress
import java.net.Socket
import java.util.concurrent.TimeUnit

const val SOCKET_CONNECT_TIME_OUT = 5000
const val WRITE_TIME_OUT: Long = 12000
const val READ_TIME_OUT: Long = 12000

fun main() {
    print(
        String(
            requestToTrsServer(
                niceVanIp = "127.0.0.1",
                niceVanPort = 9999,
                buffer = "Hello Server!".toByteArray()
            )
        )
    )
}

fun requestToTrsServer(niceVanIp: String, niceVanPort: Int, buffer: ByteArray): ByteArray {
    Socket().use { socket ->
        try {
            socket.connect(InetSocketAddress(niceVanIp, niceVanPort), SOCKET_CONNECT_TIME_OUT)

            val bufferedSink = socket.sink().buffer()
            with(bufferedSink) {
                timeout().timeout(WRITE_TIME_OUT, TimeUnit.MILLISECONDS)
                write(buffer)
                flush()
                socket.shutdownOutput()
            }
			// 서버로 bytearray 보내기

            val bufferedSource = socket.source().buffer()
            val responseBytes =
                with(bufferedSource) {
                    timeout().timeout(READ_TIME_OUT, TimeUnit.MILLISECONDS)
                    readByteArray()
                }
			// 서버에서 bytearray 받기
            
            bufferedSink.close()
            bufferedSource.close()

            return responseBytes
        } catch (e: IOException) {
            println("소켓 서버와 TCP 통신 중 IOException 발생")
            return ByteArray(1)
        }
    }
}
  • 클라이언트도 마찬가지
  • bytearray 를 보내고, 받는 코드이다.
  • 위의 개념과 동일하게, source, sink 를 Socket 객체에서 얻을 수 있다.

서버와 클라이언트 콘솔에 각각 찍힌 값

 

이렇게, java 코드보다 훨씬 간결하게 작성할 수 있다.

소켓 통신을 제대로 처음 구현해봤는데, 역시 공부할 건 끝이 없고, 필요 없는 것은 어디에도 없다.

 

"이것만 하면 되지~"

라는 마인드는 개발자로서는 부적합한 마인드 같다.

필요한 모든 것은 상황에 따라 학습하고 적용할 수 있어야 한다.

 

반응형