
간단한 채팅 기능을 구현해야 하는 상황이 발생하였다. 서버측에서는 STOMP 프로토콜 기반으로 구현했기 때문에, 필자도 이 기회에 STOMP 프로토콜을 학습하여 채팅을 구현하였다.
STOMP는 WebSocket 위에서 동작하는 메시지 프로토콜로, WebSocket의 특징을 모두 지니고 있으면서 기존의 WebSocket과 달리 클라이언트와 서버가 전송할 메세지의 내용, 형식을 정의하여 규격을 갖춘 메세지 전송이 가능하다. 또한 pub/sub(Publish/Subscribe) 방식으로 동작하여 메세지의 송/수신에 대한 처리를 명확하게 정의할 수 있다.
아래는 간단한 STOMP의 구조이다.

채팅의 경우, 단반향이 아닌 서로 메세지를 주고 받아야 하기 때문에, 발행자와 구독자가 각각 따로 존재하는 것이 아닌, 발행자가 구독자가 되고, 구독자가 발행자가 된다.
안드로이드는 공식적으로 STOMP를 지원하는 라이브러리가 존재하지 않기 때문에, 오픈소스 라이브러리를 이용하였는데, Kotlin 언어로 제작된 라이브러리를 사용하였다.
https://github.com/bishoybasily/stomp
private val intervalMillis = 5000L
private lateinit var stomp: StompClient
private lateinit var stompConnection: Disposable
private lateinit var topic: Disposable
StompClient의 내부 코드는 RxJava로 작성 되어있다.
http 로 시작한다면 ws 로, https 로 시작 시 wss를 사용하면 된다.
val url = "ws://your_URL"
stomp = StompClient(client, intervalMillis).apply {
this@apply.url = url
}
stompClient를 생성했으면 웹소켓에 연결해보자. stomp.connect.subscribe()로 소켓에 연결할 수 있다.
stompConnection = stomp.connect().subscribe { event ->
when (event.type) {
Event.Type.OPENED -> handleWebSocketOpened()
Event.Type.CLOSED -> handleWebSocketClosed()
Event.Type.ERROR -> handleWebSocketError()
else -> {}
}
}
Event Class의 Type는 enum class로 정의되어있다.
enum class Type {
OPENED,
CLOSED,
ERROR
}
다시 돌아와서 소켓이 연결되었을 경우 채팅방을 구독하여 메세지를 받아올 준비를 하면 된다.
private fun handleWebSocketOpened() {
_connectState.update { UiState.Success(true) }
// 채팅방 구독
topic = stomp.join("/sub/chats/${roomId}").subscribe { message ->
viewModelScope.launch(Dispatchers.Main) {
handleMessage(message)
}
}
}
소켓이 연결된 경우 stomp.join.subscribe로 구독하면 된다. 구독한 채팅방에 메세지가 전송되면, Message Broker는 모든 구독자에게 메세지를 전달해준다. 전달받은 메세지는 서버와 클라이언트가 미리 지정한 형식에 맞게 파싱하여 처리하면 된다. (RecyclerView에 추가 등등...)
메세지 전달은 stomp.send("api/destination", "MESSAGE")로 보내면 된다.
fun sendMessage(message: String) {
val jsonObject = JSONObject().apply {
put("roomId", roomId)
put("content", message)
put("sender", member.name)
}
val body = jsonObject.toString()
viewModelScope.launch (Dispatchers.IO) {
stomp.send("/pub/hello", body).subscribe {}
}
}
MESSAGE는 String 타입이기 때문에, 일반적으로 메세지를 보낼 때 사용하는 Json Type으로 보내려면 위 코드처럼 보내면 된다.
예를 들어, 서버와 클라이언트가 다음과 같이 메세지 형식을 정의하였을 때,
{
roomId : Long
content : String
sender : String
}
클라이언트가 보낼 메세지는 다음과 같다.
{"roomId": 11, "content": "hello", "sender": "UserName"}
stompConection.dispose()
참고 자료:
https://brunch.co.kr/@springboot/695
https://github.com/bishoybasily/stomp