Netty 프레임워크를 활용해 TCP 서버, 클라이언트를 구성하고 간단한 메시지를 교환하는 테스트 코드를 작성합니다. GitHub
@Slf4j
@TestInstance(TestInstance.Lifecycle.PER_METHOD)
@DisplayName("TCP 서버-클라이언트 간단한 통신 테스트")
public class SimpleTcpTest {
// 서버
ServerBootstrap serverBootstrap = new ServerBootstrap();
NioEventLoopGroup serverAcceptGroup = new NioEventLoopGroup();
NioEventLoopGroup serverServiceGroup = new NioEventLoopGroup();
ConcurrentHashMap<SocketAddress, Channel> activeServerChannelMap = new ConcurrentHashMap<>();
// 클라이언트
Bootstrap clientBootstrap = new Bootstrap();
NioEventLoopGroup clientEventLoopGroup = new NioEventLoopGroup();
BlockingQueue<String> clientResponseQueue = new LinkedBlockingQueue<>();
Channel clientChannel;
// 테스트 데이터
String fixedResponse = "RESP";
@BeforeEach
@SneakyThrows
public void beforeEach() {
serverSetupAndStart();
clientSetupAndConnect();
waitForServerServiceActive();
}
private void waitForServerServiceActive() throws InterruptedException {
Thread.sleep(100);
}
@AfterEach
@SneakyThrows
public void afterEach() {
clientEventLoopGroup.shutdownGracefully();
serverServiceGroup.shutdownGracefully();
serverAcceptGroup.shutdownGracefully();
}
@SneakyThrows
private void serverSetupAndStart() {
serverBootstrap.group(serverAcceptGroup, serverServiceGroup)
.channel(NioServerSocketChannel.class)
.localAddress("0.0.0.0", 12345)
.option(ChannelOption.SO_REUSEADDR, true)
.childHandler(new ChannelInitializer<>() {
@Override
protected void initChannel(Channel ch) {
ch.pipeline()
// Inbound
.addLast(new ActiveServerChannelUpdater(activeServerChannelMap))
.addLast(new FixedLengthFrameDecoder(4))
.addLast(new StringDecoder())
.addLast(new ServerResponseHandler(fixedResponse))
// Outbound
.addLast(new StringEncoder())
// Duplex
.addLast(new Logger("Server", true));
}
});
serverBootstrap.bind().sync();
}
@SneakyThrows
private void clientSetupAndConnect() {
clientBootstrap.group(clientEventLoopGroup)
.channel(NioSocketChannel.class)
.remoteAddress("127.0.0.1", 12345)
.handler(new ChannelInitializer<>() {
@Override
protected void initChannel(Channel ch) {
ch.pipeline()
// Inbound
.addLast(new FixedLengthFrameDecoder(4))
.addLast(new StringDecoder())
.addLast(new ReceiveDataUpdater(clientResponseQueue))
// Outbound
.addLast(new StringEncoder())
// Duplex
.addLast(new Logger("Client", true));
}
});
clientChannel = clientBootstrap.connect().sync().channel();
}
@Test
@SneakyThrows
@DisplayName("메시지 전송-응답 테스트")
void simpleResponseTest() {
// Given : 연결된 서버, 클라이언트
// When : 클라이언트에서 메시지 전송
clientChannel.writeAndFlush("ABCD");
// Then : 서버로부터 응답 수신
String response = clientResponseQueue.poll(100, TimeUnit.MILLISECONDS);
Assertions.assertEquals(fixedResponse, response);
}
@Test
@SneakyThrows
@DisplayName("메시지 N개 수신 테스트")
void multipleReceiveTest() {
// Given : 연결된 서버, 클라이언트
// When : 서버에서 10개 메시지 전송
Channel serverServiceChannel = activeServerChannelMap.get(clientChannel.localAddress());
for (int i = 0; i < 10; i++) {
serverServiceChannel.writeAndFlush(String.format("RES%d", i));
}
// Then : 클라이언트에서 10개 메시지 수신
for (int i = 0; i < 10; i++) {
String response = clientResponseQueue.poll(100, TimeUnit.MILLISECONDS);
Assertions.assertEquals(String.format("RES%d", i), response);
}
}
}