[웹소켓] 웹소켓을 이용한 1대1 채팅 구현

2024. 6. 18. 11:22웹소켓

많은 사람들이 SNS에 집착한다. 사용자가 집착한다는 건 그만큼 개발자의 노력과 정성이 들어가야 한다는 뜻이다. 사람들은 왜 그렇게 SNS에 집착할까? 우리가 연결되어있다는 사실을 실체를 가진 메시지로 보여주기 때문이라고 생각한다. 웹소켓을 이용해서 채팅을 구현해보고 싶은 이유다. 

 

웹소켓을 이용해 채팅을 구현하기 위해서 ws 혹은 wss라는 프로토콜을 이용해야 한다. http https와 유사하지만 실시간 양방향 통신을 지원하는 웹소켓을 위해 사용된다. 

처음에 테이블을 설계할 때 애를 좀 먹었다. 1대1이든, 1대 N이든 간에 채팅을 위해선 채팅방(동시에 접속할 수 있는)이 필요할 테고, 채팅방에 따라 메시지를 저장해야 한다. 관리자와의 채팅이기에 유저 한 명당 딱 하나의 고유 채팅방만 있으면 된다고 생각했다. 다만 관리자는 모든 유저의 채팅방에 들어갈 수 있어야 하겠지. 때문에 굳이 유저 혹은 관리자가 서로간의 채팅을 위해 채팅방을 따로 개설한다는 과정이 필요치 않다고 느꼈고, 이내 유저가 회원가입을 완료했을 때 채팅방 고유 넘버를 할당하는 방식을 선택했다. 

 

public class MemberVO {
	
	@Id
	private String member_id;
	
	@OneToOne(cascade=CascadeType.ALL, fetch=FetchType.EAGER)
	@JoinColumn(name="chat_no")//여기서 지정하는 것이 외래키 이름.
    private ChatVO chatVO;
    
}

 

public class ChatVO {//유저별로 채팅방을 구분하기 위해 만든 테이블
	
	@Id
    @GeneratedValue(
            strategy=GenerationType.SEQUENCE,
            generator="chat_no_seq_gename"
            )
    private long chatNo;
    
    @OneToOne(mappedBy="chatVO")
    private MemberVO memberVO;
    
    
	@OneToMany(mappedBy = "chatVO",cascade=CascadeType.ALL, fetch=FetchType.EAGER)
	private List<MessageVO> messages = new ArrayList<>();

}

 

public class MessageVO {

	@Id
    @GeneratedValue(
            strategy=GenerationType.SEQUENCE,
            generator="message_no_seq_gename"
            )
    private long messageNo;
	
	private String messageText;

	@ManyToOne
    @JoinColumn(name = "chat_no")
    private ChatVO chatVO;
	
	@CreationTimestamp
	private Timestamp message_time;
	
}

 

유저는 하나의 채팅방을 가지며, 하나의 채팅방은 다수의 메시지를 저장할 수 있게 설계했다. 이제 관리자는 유저의 아이디에 할당된 채팅방 번호를 기준으로 메시지를 받을 수 있게 된다. 흔히 카카오톡처럼, 대부분의 개발 블로거들이 구현하려고 시도한 채팅방 개설을 통한 채팅 기능이었다면, 테이블 설계가 달라질 것이다. 

 

@Configuration
@EnableWebSocket
public class WebSocketConfig implements WebSocketConfigurer{

	@Autowired
	SocketHandler socketHandler;
	
	@Override
	public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
		registry.addHandler(socketHandler, "/chating/{chatNumber}");
	}
}

 

	function wsOpen(){
		
		ws = new WebSocket("ws://" + location.host + "/chating/"+$("#chatNumber").val());
		wsEvt();
	}

 

이제 ws 프로토콜을 사용하는 주소로 연결되어 채팅에 대한 추가적인 기능을 구현할 수 있게 된다. 

 

채팅을 보내면서 동시에 그 값을 메시지 테이블에 저장한다. 여기서 하나의 문제가 생겼는데, 흔히 채팅을 할 때 '내'가 보낸 메시지와 '상대'가 보낸 메시지를 구별할 수 있어야 한다. 메시지를 둘러싼 색이 될 수도 있고 왼쪽과 오른쪽으로 나눌 수도 있다. 그렇다면 채팅방에 접속한 유저마다 메시지를 따로 보관해야 하나? 처음엔 따로 보관한 다음 그 안에서 각각 시간 순서로 메시지를 뽑은 다음, 유저 입장에서 보낸 '나'와 관리자인 '상대'를 다시 한 번 더 시간 순서로 정렬하는 알고리즘을 생각했다. 너무 복잡하지 않은가. 따로 보관한다는 사실은 중요하지 않다. 누가 보낸 건지 구별만 할 수 있으면 된다. 가끔은 처음에 떠오른 생각이 나머지 사고를 전부 장악할 가능성이 있기에, 생각의 시작점은 매우 중요하다. 이내 메시지를 DB에 저장할 때 메시지 앞에 '관리자'와 '유저의 아이디'를 작성해 보내는 로직을 작성했다. 메시지 앞에 '관리자'가 붙어 있으면 채팅방의 왼쪽에, 아닐 경우 오른쪽에 표시하면 된다. 

 

곧이어 또 다른 문제가 발생하는데...

당연히 저장된 메시지가 순서대로 나올 거라고 예상했다. 실제로도 초반엔 의도에 맞게 메시지가 저장됐다. 하지만 일정량 이상의 메시지를 저장하자 위치가 뒤섞이는 상황이 일어났다. 

 

public class MessageComparator implements Comparator<MessageVO> {
	    @Override
	    public int compare(MessageVO message1, MessageVO message2) {
	        return Long.compare(message1.getMessageNo(), message2.getMessageNo());
	    }
	}
    
    Collections.sort(allMessage, new MessageComparator());

 

추후에 갑자기 떠오른 건, 굳이 이러한 방법을 사용하지 않고 order by 정렬문을 사용했더라면 더 간단하게 처리할 수 있지 않았을까 생각이 들었다. (order by가 더 효율적인 방법이지 않을까...?  애초에 쿼리문을 작성할 때 너무 소홀했다.)

 

웹소켓을 통해 채팅을 구현하면서 개발에 흥미를 많이 느꼈다. 웹소켓에 대해 더 공부하고 싶은 마음이 들어, 웹소켓을 이용한 또 다른 기능을 구현해봐야겠다.