이번 글에서는 Android 앱에서 WebSocket 통신을 구현하는 방법에 대해서 작성해보려고 합니다.
WebSocket은 웹 브라우저와 서버 간에 양방향 실시간 통신을 가능하게 하는 프로토콜입니다. 일반적인 Http 요청과 달리, WebSocket은 연결이 지속적으로 유지되어 서버와 클라이언트 간에 즉시 메시지를 전달할 수 있습니다. 이는 실시간 채팅, 게임, 주식 시세 애플리케이션 등에 사용되는 기술입니다.
우선 Android에서 WebSocket 통신을 하려고 한다면 OkHttp 라이브러리를 사용해야합니다. OkHttp 라이브러리를 사용하면 쉽게 웹소켓 통신을 구현할 수 있기 때문입니다.
dependencies {
implementation 'com.squareup.okhttp3:okhttp:4.9.3'
}
WebSocket 연결을 시작하려면 OkHttpClient를 생성하고, 웹소켓 서버 URL을 사용하여 Request를 생성한 다음, WebSocketListener를 사용하여 웹소켓 이벤트를 처리합니다.
val request = Request.Builder()
.url("wss://your-websocket-server-url")
.build()
val listener = object : WebSocketListener() {
override fun onOpen(webSocket: WebSocket, response: Response) {
// 웹소켓 연결이 성공적으로 열림
}
}
val client = OkHttpClient()
val websocket = client.newWebSocket(request, listener)
WebSocket을 사용하여 메시지를 보내려면 send() 메서드를 사용하며, 메시지를 받으려면 WebSocketListener의 onMessage() 메서드를 재정의합니다.
// 메시지 전송
websocket.send("send Message")
// 메시지 수신 처리
override fun onMessage(webSocket: WebSocket, text: String) {
// 웹소켓으로부터 문자열 메시지를 받음
}
웹소켓 연결을 종료하려면 Close() 메서드를 호출하고, 정상적인 종료 코드와 종료 메시지를 전달할 수 있습니다.
// 웹소켓 연결을 종료
websocket.close(1000, "Goodbye, WebSocket!")
만약 WebSocketListener에서 연결 종료 이벤트를 처리하려면 onClosing() 또는 onClosed() 메서드를 재정의합니다.
val listener = object : WebSocketListener() {
// ...
override fun onClosing(webSocket: WebSocket, code: Int, reason: String) {
// 연결이 종료되기 전에 호출됩니다.
println("WebSocket is closing. Code: $code, Reason: $reason")
}
override fun onClosed(webSocket: WebSocket, code: Int, reason: String) {
// 연결이 완전히 종료된 후에 호출됩니다.
println("WebSocket closed. Code: $code, Reason: $reason")
}
// ...
}
재연결을 구현하려면, 연결이 종료되거나 실패할 때 새로운 연결을 생성하는 로직을 추가합니다. 예를 들어 onFailure() 또는 onClosed() 메서드에서 새로운 연결을 생성할 수 있습니다.
OkHttp의 WebSocket 통신을 조금 더 효율적으로 사용하기 위한 방법을 작성해보려고 합니다.
OkHttpClient 인스턴스를 애플리케이션 전체에서 공유하는 싱글턴으로 사용하면, 연결 풀링 및 스레드 풀 관리 등의 이점을 얻을 수 있습니다.
// 코드 예시
object OkHttpClientSingleton {
val instance: OkHttpClient = OkHttpClient.Builder()
.readTimeout(30, TimeUnit.SECONDS)
.writeTimeout(30, TimeUnit.SECONDS)
.build()
}
그리고 웹소켓 연결을 생성할 때 싱글턴을 사용하는 것입니다.
val client = OkHttpClientSingleton.instance
// Hilt를 사용한다면
@Module
@IntallIn(SingletonComponent::class)
object NetworkModule {
@Provides
@Singleton
fun provideOkHttpClient(): OkHttpClient =
OkHttpClient.Builder()
.readTimeOut(30, TimeUnit.SECONDS)
.writeTimeOut(30, TimeUnit.SECONDS)
.build()
}
Repository.kt
class NetWorkingRepository @Inject constructor(
private val okHttpClient: OkHttpClient
) {
// 클라이언트 사용
}
웹소켓에서 메시지 압축을 사용하면, 통신 효율을 높일 수 있습니다. OkHttp에서는 메시지 압축을 지원하기 때문에, 이를 활용하려면 OkHttpClient.Builder에서 다음과 같이 Interceptor를 넣어주면 됩니다.
val client = OkHttpClient.Builder()
.addInterceptor(WebSocketCompressionInterceptor())
.build()
웹 소켓 연결이 끊기지 않도록 주기적으로 핑(ping) 메시지를 보내는 것이 좋습니다.
val client = OkHttpClient.Builder()
.pingInterval(30, TimeUnit.SECONDS)
.build()
애플리케이션이 백그라운드로 이동할 때, 웹소켓 연결을 닫거나 연결 상태를 관리하는 로직을 구현하세요. 이렇게 하면, 불필요한 연결이 존재하지 않으며, 자원을 효율적으로 사용할 수 있습니다.
OkHttp의 로깅 인터셉터를 사용하여 웹소켓 연결 및 메시지에 대한 디버깅 정보를 얻을 수 있습니다. 하지만, 로그 레벨을 적절히 설정하여 불필요한 로그가 기록되지 않도록 주의하세요.
다음과 같은 방식등을 통해 WebSocket 통신을 수행할 수 있습니다. 꼭 OkHttp3 라이브러리를 사용해야한 하는 것은 아닙니다. Scarlet 라이브러리를 사용하면 웹소켓 연결 관리 및 재연결 전략, 스레드 관리 등을 자동화하여 관리해주기 때문에 좀 더 효율적으로 웹소켓을 사용할 수 있습니다. 추후에 기회가 된다면 Scarlet 라이브러리에 대한 글을 작성해보도록 하겠습니다.