본문 바로가기
Android/Project

[MBTree] 기록#1: 웹소켓으로 안드로이드 채팅 구현

by YOONAYEON 2022. 7. 13.

안드로이드에서 실시간 채팅 기능을 구현하고자 할 때, 웹서버에 저장하는 방법Firebase를 이용하는 방법이 있다.

서버에 저장하는 경우(Websocket이용), 추후 채팅에 대한 추가 기능 개발같은 것을 하게 된다면 보다 용이하지만 서버와 클라이언트 모두 웹소켓에 대한 이해가 필요하다.

Firebase의 경우에는 웹소켓보다는 쉽게 학습이 가능하므로 빠른 시간 내에 구현이 가능하지만 반대로 복잡한 기능은 추가하기 어려운 경우를 만날 수도 있다고 한다.

나는 이번 프로젝트에서 단순 채팅 기능보다 랜덤 매칭 등 부가적인 기능을 더할 것이므로 웹 소켓으로 구현하고자 한다.

 

 

WebSocket

- 서버와 클라이언트 간에 socket connection을 유지하여 실시간 양방향 통신과 데이터 전송을 가능하도록 하는 기술

- 기존 client와 server의 관계는 클라이언트의 요청이 먼저 있어야 서버가 그에 응답하는 방식이었지만, 웹소켓은 새로운 데이터가 들어오면 서버가 먼저 클라이언트에게 데이터를 전송하는 기술

1. 연결시작 [Handshake]

최초 연결 시에 클라이언트에서 HTTP를 통해 웹서버에 웹소켓 프로토콜 요청 / 서버에서 웹소켓 프로토콜 전환 응답

2. 양방향 통신

3. 연결 종료 [한쪽에서 통신 종료 요청]

 

Stomp라이브러리 사용

stomp라이브러리는 웹소켓 위에서 동작하는 프로토콜로, 클라이언트와 서버가 전송할 메세지의 유형, 형식, 내용들을 정의하는 메커니즘이다. 서버 쪽에서 이미 Stomp로 구현해놓아서 Stomp라이브러리를 사용했고 레퍼런스가 많이 없어서 힘들었던 것 같다.

 

 

1. build.gradle 추가 (안드는 개인이 구현해놓은 라이브러리를 사용해야 함)

// stomp
implementation 'com.github.NaikSoftware:StompProtocolAndroid:1.6.6'

 

 

2. 필요한 곳에서 Stomp구현

    fun runStomp(roomId: Int, userId: Int){
        stompClient.connect()

        stompClient.topic("/topic/chat/${roomId}").subscribe{ topicMessage ->
            Log.d("message Receive", topicMessage.payload)
            val sender = JSONObject(topicMessage.payload).getString("userId").toInt()
            if(sender != userId){
                val content = JSONObject(topicMessage.payload).getString("message")
                multiAdapter.addItem(Chat(content, "12:00", 1))

                runOnUiThread{
                    multiAdapter.notifyDataSetChanged()
                }
            }
        }

        stompClient.lifecycle().subscribe{ lifecycleEvent ->
            when(lifecycleEvent.type){
                LifecycleEvent.Type.OPENED -> {
                    Log.d("OPENED", "opened")
                }
                LifecycleEvent.Type.CLOSED -> {
                    Log.d("CLOSED", "closed")
                }
                LifecycleEvent.Type.ERROR -> {
                    Log.d("ERROR", "error")
                    Log.i("CONNECT ERROR", lifecycleEvent.exception.toString())
                }
                else -> {
                    Log.d("else", lifecycleEvent.message)
                }
            }
        }

        binding.etChatSend.setOnTouchListener(object : View.OnTouchListener{
            override fun onTouch(v: View?, event: MotionEvent?): Boolean {
                if(event?.action == MotionEvent.ACTION_UP){
                //action here
//                  Toast.makeText(applicationContext, "누름", Toast.LENGTH_SHORT).show()
                    sendStomp(binding.etChatSend.text.toString(), roomId, userId)
                    binding.etChatSend.text = null
                    return true
                }
                return false
            }
        })
    }

 

fun sendStomp(msg: String, roomId: Int, userId: Int){
    val data = JSONObject()
    data.put("messageType", "CHAT")
    data.put("userId", userId.toString())
    data.put("message", msg)

    stompClient.send("/app/chat.message/${roomId}", data.toString()).subscribe()
    Log.d("Message Send", "내가 보낸거: " + msg)

    multiAdapter.addItem(Chat(msg, "12:00", 2))
    runOnUiThread{
        multiAdapter.notifyDataSetChanged()
    }
}