implementation 'io.netty:netty-all:4.1.90.Final'
plugins {
id 'java'
id 'org.springframework.boot' version '2.7.5'
id 'io.spring.dependency-management' version '1.1.0'
}
group = 'com.netty'
version = '0.0.1-SNAPSHOT'
java {
sourceCompatibility = '11'
}
configurations {
compileOnly {
extendsFrom annotationProcessor
}
}
repositories {
mavenCentral()
}
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-web'
compileOnly 'org.projectlombok:lombok'
developmentOnly 'org.springframework.boot:spring-boot-devtools'
annotationProcessor 'org.springframework.boot:spring-boot-configuration-processor'
annotationProcessor 'org.projectlombok:lombok'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
implementation 'io.netty:netty-all:4.1.90.Final'
}
tasks.named('test') {
useJUnitPlatform()
}
server:
port:
host:
netty:
worker-count:
boss-count:
keep-alive:
backlog:
package com.netty.echoserver.config;
import com.netty.echoserver.socket.NettyServerSocket;
import lombok.RequiredArgsConstructor;
import org.springframework.boot.context.event.ApplicationReadyEvent;
import org.springframework.context.ApplicationListener;
import org.springframework.stereotype.Component;
@Component
@RequiredArgsConstructor
public class ApplicationStartupTask implements ApplicationListener<ApplicationReadyEvent> {
private final NettyServerSocket nettyServerSocket;
@Override
public void onApplicationEvent(ApplicationReadyEvent event) {
nettyServerSocket.start();
}
}
🚩 ServerBootstrap 메서드
group(EventLoopGroup parentGroup, EventLoopGroup childGroup)
첫번째 인자는 스레드들로 스레드 그룹을 초기화 해준다.
두번째 group 역시 같은 역할을 하지만 인자로 parentGroup과 childGroup 두가지를 받는다.
package com.netty.echoserver.config;
import com.netty.echoserver.socket.NettyChannelInitializer;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.logging.LogLevel;
import io.netty.handler.logging.LoggingHandler;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.net.InetSocketAddress;
@Configuration
@RequiredArgsConstructor
public class NettyConfiguration {
@Value("${server.host}")
private String host;
@Value("${server.port}")
private int port;
@Value("${server.netty.boss-count}")
private int bossCount;
@Value("${server.netty.worker-count}")
private int workerCount;
@Value("${server.netty.keep-alive}")
private boolean keepAlive;
@Value("${server.netty.backlog}")
private int backlog;
@Bean
public ServerBootstrap serverBootstrap(NettyChannelInitializer nettyChannelInitializer){
// server bootstrap : 서버 설정을 도와주는 class
ServerBootstrap bootstrap = new ServerBootstrap();
bootstrap.group(bossGroup(), workerGroup())
.channel(NioServerSocketChannel.class) //NioServerSocketChannel: incoming connections를 수락하기 위해 새로운 Channel을 객체화.
.handler(new LoggingHandler(LogLevel.DEBUG))
.childHandler(nettyChannelInitializer);
//SO_BACKLOG: 동시에 수용 가능한 최대 incoming connections 개수
bootstrap.option(ChannelOption.SO_BACKLOG, backlog);
return bootstrap;
}
@Bean(destroyMethod = "shutdownGracefully")
public EventLoopGroup workerGroup() {
return new NioEventLoopGroup(workerCount);
}
@Bean(destroyMethod = "shutdownGracefully")
public EventLoopGroup bossGroup() {
return new NioEventLoopGroup(bossCount);
}
@Bean
public InetSocketAddress inetSocketAddress(){
return new InetSocketAddress(host, port);
}
}
package com.netty.echoserver.decoder;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.ByteToMessageDecoder;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Component;
import java.util.List;
@Component
@RequiredArgsConstructor
public class TestDecoder extends ByteToMessageDecoder {
private int DATA_LENGTH = 2048;
@Override
protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
if (in.readableBytes() < DATA_LENGTH) {
return;
}
out.add(in.readBytes(DATA_LENGTH));//정해진 길이만큼 데이터가 들어올 때까지 기다린다.
}
}
package com.netty.echoserver.handler;
import io.netty.buffer.ByteBuf;
import io.netty.channel.*;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
@Slf4j
@Component
@ChannelHandler.Sharable
@RequiredArgsConstructor
public class TestHandler extends ChannelInboundHandlerAdapter {
private int DATA_LENGTH = 2048;
private ByteBuf buff;
@Override
public void handlerAdded(ChannelHandlerContext ctx){
buff = ctx.alloc().buffer(DATA_LENGTH);
}
@Override
public void handlerRemoved(ChannelHandlerContext ctx){
buff = null;
}
@Override
public void channelActive(ChannelHandlerContext ctx){
String remoteAddress = ctx.channel().remoteAddress().toString();
log.info("Remote Address: " + remoteAddress);
}
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) {
ByteBuf mBuf = (ByteBuf) msg;
buff.writeBytes(mBuf);
mBuf.release();
final ChannelFuture f = ctx.writeAndFlush(buff);
f.addListener(ChannelFutureListener.CLOSE);
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
ctx.close();
cause.printStackTrace();
}
}
package com.netty.echoserver.socket;
import com.netty.echoserver.decoder.TestDecoder;
import com.netty.echoserver.handler.TestHandler;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.socket.SocketChannel;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Component;
@Component
@RequiredArgsConstructor
public class NettyChannelInitializer extends ChannelInitializer<SocketChannel> {
private final TestHandler testHandler;
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ChannelPipeline pipeline = ch.pipeline();
TestDecoder testDecoder = new TestDecoder();
pipeline.addLast(testDecoder);
pipeline.addLast(testHandler);
}
}
package com.netty.echoserver.socket;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import javax.annotation.PreDestroy;
import java.net.InetSocketAddress;
@Slf4j
@RequiredArgsConstructor
@Component
public class NettyServerSocket {
private final ServerBootstrap serverBootstrap;//서버를 위한 bootstrap
private final InetSocketAddress tcpPort;
private Channel serverChannel;
public void start() {
try {
ChannelFuture serverChannelFuture = serverBootstrap.bind(tcpPort).sync();//1
serverChannel = serverChannelFuture.channel().closeFuture().sync().channel();//2
} catch (InterruptedException e) {
e.printStackTrace();
}
}
@PreDestroy
public void stop(){
if (serverChannel != null){
serverChannel.close();
serverChannel.parent().closeFuture();
}
}
}
java.net.ConnectException: Connection refused (Connection refused)
참고