모든 소스는 깃헙에 있습니다.
해당 소스의 목적은 Netty 기초 튜토리얼에 목적이 있습니다.
Netty에 대해서 이미 기본 지식이 있는 상태에서 보는 것을 추천드립니다.
이 글은 이전 글들과 채팅 기능에 있어서는 기능적으로 크게 달라진 점은 없습니다.
마찬가지로 WebSocket 채팅 방식으로 이루어지며 내부적으로 Netty를 통해 한번 더 전달하는 방식으로 만들어져 있어서 Netty를 모른다면 익히기에 편리 합니다.
Netty는 TCP 기반의 양방향 통신과 비동기 구현이 가능합니다.
WebSocket도 TCP 통신 방식을 이용하고 Netty를 이용해서 TCP 통신을 개발할 수 있기 때문에 적절한 예제가 될 것 같아서 만들어 봤습니다.
양방향 통신을 위해 Netty는 Server와 Client 두개 모두 만들어 Netty 흐름을 파악하고자 구현하였습니다.
사용법
http://localhost:8080/master
http://localhost:8080/guest
소스 설명
Netty Server 부터 오픈합니다.
ServerBootstrap bs = new ServerBootstrap();
bs.group(bossGroup, workerGroup)
.channel(NioServerSocketChannel.class)
.option(ChannelOption.SO_KEEPALIVE, true)
// .handler(new LoggingHandler(LogLevel.DEBUG))
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
public void initChannel(SocketChannel ch) {
ChannelPipeline p = ch.pipeline();
p.addLast(new NettyChattingServerHandler());
}
});
ChannelFuture f = bs.bind(8888).sync();
그 후에 Netty Client를 만들어서 Netty Server에 TCP 연결을 합니다.
SocketAddress address = new InetSocketAddress("127.0.0.1", 8888);
try {
Bootstrap bs = new Bootstrap();
bs.group(workerGroup)
.channel(NioSocketChannel.class)
.option(ChannelOption.SO_KEEPALIVE, true)
.handler(new ChannelInitializer<SocketChannel>() {
@Override
public void initChannel(SocketChannel ch) throws Exception {
ChannelPipeline p = ch.pipeline();
p.addLast(nettyChattingClientHandler);
}
});
channel = bs.connect(address).sync().channel();
WebSocket을 통해 들어온 채팅 내용을 Netty Client를 통해 Netty Server로 데이터를 요청합니다. (들어온 Channel을 구분하면 원하는 사람에게만 전달할 수도 있습니다.)
Netty Server로 전달된 데이터를 다시 Netty Client로 전달합니다.
Netty Client는 WebSocket을 통해 메시지를 발행합니다.
그 외 팁이 될만한 TCP 연결 수립 확인 방법
SocketTest 프로그램
위처럼 Netty Server 포트를 연결해서 테스트 해볼 수 있는 프로그램을 이용할 수도 있습니다.
Telnet
Telnet으로 직접 연결을 해볼 수도 있습니다.
netstat
netstat -ano | find "8888"
netstat 명령어를 이용해서 TCP 서버의 연결 수립을 확인해볼 수 있다.
그리고 localhost:8080/master로 들어가보면 netstat을 통해 웹소켓이 연결된 것도 확인해볼 수 있다.
트러블 슈팅
만드는 과정에서 Spring Bean 순환 참조가 발생했습니다.
평소에 만나보지 못했던 에러라 이럴 수도 있구나 하고 조금 당황했습니다.
WebSocket Config Class와 Netty Client Class는 모두 스프링 Bean으로 설정되어있습니다.
메시지를 서버로 전달하기 위해서 WebSocket에서 Netty Client에 전달합니다. Netty Client는 서버로 메시지를 전송하고 다시 서버에서 받아온 메시지를 Netty Client가 WebSocket을 통해 전송하는 방식을 이용하면 결과적으로 소스에서 서로의 Bean을 참조하도록 만들어야 하므로 Bean들이 서로가 서로를 참조하는 순환 참조가 발생합니다.
평소에 순환 참조가 발생하기 힘든 이유는 스프링 기본구조에서는 Controller - Service - Repository와 같은 방식을 이용하면 단방향을 통해 Return을 받는 방식이라 순환 참조가 발생하기 어렵습니다.
또 이전글인 Kafka를 이용하면 Producer와 Consumer가 완벽히 분리되어 있어서 서로를 참조할 여지가 없습니다.
위와 같은 상황이 존재한다면 기본적으로 설계 관점의 문제이고 다시 한번 고려해서 만들어야 되는데요.
생각한 아이디어로는 2가지로 압축되었습니다.
실패 - WebSocket에서 Netty Client로 넘겨 준 후 다시 Netty Server로부터 받은 메시지를 Netty Client가 WebSocket을 통해서 메시지를 발행하지 않고 Netty Client가 직접 발행하는건 어떨까 생각했습니다. 그러면 Netty Client가 WebSocket에 대한 정보를 매개변수로 넘겨받아야하겠죠. 다만 이 방식은 WebSocket이 가지고 있는 Session 정보, Message 정보 등 필요한 정보를 전부 전달해야하는데요. Netty로 전송할 Message Buffer를 세심하게 가공해줘야 할 필요가 있어서 코드가 복잡해질 우려가 있었습니다.
성공 - 두번째로는 WebSocket에서 Netty Client를 참조하면서 동시에 특정 Bean을 하나 더 만드어서 참조하도록 했습니다.
새로 만들어진 Bean에는 WebSocket에서 만들어진 정보와 메시지 발행에 대한 기능을 넘겨줍니다.
그리고 Netty Client는 WebSocket Config Bean을 참조하지 않고 새로 만들어진 Bean을 통해 메시지 발행을 한다면 결과적으로 순환 참조 해결을 할 수 있었습니다.
새로 만들어진 Bean은 어떠한 Bean도 참조하지 않는다는 특징을 이용한 해결 방법입니다.(흥미롭네요)
정리
Kafka든 Netty든 비동기, TCP 양방향 통신 구현이 가능하다는 공통점이 존재합니다.
여러개의 채팅방을 만들기 위해서도 Kafka의 토픽 방식 처럼 Netty는 채팅방에 관한 객체를 만들면 됩니다.
Kafka는 분산형 메시지 큐 시스템으로, 대용량의 데이터를 처리하는 데 특화되어 있습니다. 따라서, Kafka를 사용하여 여러 개의 채팅방을 구현하려면, 각 채팅방을 Kafka의 토픽으로 표현하고, 클라이언트가 해당 토픽을 구독(subscribe)하여 메시지를 주고받도록 구현해야 합니다.
반면에 Netty는 TCP 기반의 네트워크 프레임워크로, 네트워크 프로그래밍을 지원합니다. 따라서, Netty를 사용하여 여러 개의 채팅방을 구현하려면, 서버 측에서 해당 기능을 구현해야 합니다. 채팅방을 관리하는 객체를 만들고, 해당 객체가 채팅방의 입장, 퇴장, 메시지 전송 등을 처리하도록 구현하는 것이 일반적입니다.
만약 내가 만드는 것이 채팅이라면 Netty와 Kafka 중에서는 Kafka를 선택하는 것이 분산형 메시지 큐 시스템이라는 목적이 알맞기 때문에 Kafka를 선택하는 것이 좋습니다. 각각의 시스템의 목적에 맞게 선택하는 것이 개발이 더 수월할 것 입니다.
동기 방식은 요청과 응답이 짝을 이루어야 하는 방식으로, 요청을 보내면 해당 요청에 대한 응답이 올 때까지 대기하는 것을 의미한다. 이를 Websocket에 적용한다면, 클라이언트가 서버로 메시지를 보내면, 해당 메시지에 대한 응답이 올 때까지 클라이언트는 다른 작업을 수행할 수 없게 된다.
'🍃 Spring' 카테고리의 다른 글
어노테이션의 요소는 항상 상수여야 하는가?(컴파일 타임 상수) (0) | 2022.12.31 |
---|---|
스프링 로그인 권한 검사에 대한 설계 방법 (0) | 2022.12.26 |
Spring Boot로 만드는 WebSocket & Kafka - 채팅(2) (0) | 2022.12.06 |
Spring Boot로 만드는 WebSocket Tutorial - 채팅(1) (4) | 2022.12.06 |
스프링부트 로그 찍는 법, 로그 파일 만드는 방법 (0) | 2022.12.01 |