[Netty] 기본설정 + 서버

Welcome to Seoyun Dev Log·2023년 6월 25일
0

[네트워크] Netty

목록 보기
2/2

1. 환경 설정

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: 

ApplicationStartupTask

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();
    }
}

NettyConfiguration

  • ServerBootstrap
    • ServerBootstrap: 서버를 위한 부트스트랩
    • Bootstrap: 클라이언트를 위한 부트스트랩

🚩 ServerBootstrap 메서드

  • group: EventLoopGroup을 설정하는 역할
group(EventLoopGroup parentGroup, EventLoopGroup childGroup)

첫번째 인자는 스레드들로 스레드 그룹을 초기화 해준다.
두번째 group 역시 같은 역할을 하지만 인자로 parentGroup과 childGroup 두가지를 받는다.

  • parentGroup: 부모 스레드로써 클라이언트 요청을 수락하는 역할
  • childGroup: 자식 스레드로써 IO와 이벤트 처리 담당
  • channel: 소켓의 입출력 모드를 설정하는 역할을 한다.
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);
    }
}

TestDecoder

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));//정해진 길이만큼 데이터가 들어올 때까지 기다린다.
    }
}

TestHandler

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();
    }
}

NettyChannelInitializer

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);
    }
}

NettyServerSocket

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)

참고

profile
하루 일지 보단 행동 고찰 과정에 대한 개발 블로그

0개의 댓글

관련 채용 정보