지대한
7 months ago
13 changed files with 345 additions and 12 deletions
@ -0,0 +1,8 @@
|
||||
dependencies { |
||||
|
||||
implementation "$boot:spring-boot-starter" |
||||
implementation "io.netty:netty-all:4.1.68.Final" |
||||
implementation project(":common:util") |
||||
|
||||
} |
||||
|
@ -0,0 +1,15 @@
|
||||
package kr.co.palnet.kac.socket; |
||||
|
||||
|
||||
import lombok.extern.slf4j.Slf4j; |
||||
import org.springframework.boot.SpringApplication; |
||||
import org.springframework.boot.autoconfigure.SpringBootApplication; |
||||
|
||||
@Slf4j |
||||
@SpringBootApplication |
||||
public class KacSocketApplication { |
||||
public static void main(String[] args) { |
||||
log.info(">>>>> main <<<<<"); |
||||
SpringApplication.run(KacSocketApplication.class, args); |
||||
} |
||||
} |
@ -0,0 +1,22 @@
|
||||
package kr.co.palnet.kac.socket.core.codec; |
||||
|
||||
import lombok.AllArgsConstructor; |
||||
import lombok.Builder; |
||||
import lombok.Data; |
||||
import lombok.NoArgsConstructor; |
||||
|
||||
@Data |
||||
@NoArgsConstructor |
||||
@AllArgsConstructor |
||||
@Builder |
||||
public class DronPayload { |
||||
|
||||
private String authKey; |
||||
|
||||
private String terminalId; |
||||
|
||||
private String command; |
||||
|
||||
private Object body; |
||||
|
||||
} |
@ -0,0 +1,31 @@
|
||||
package kr.co.palnet.kac.socket.core.codec; |
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper; |
||||
import io.netty.channel.ChannelHandlerContext; |
||||
import io.netty.handler.codec.MessageToMessageDecoder; |
||||
import kr.co.palnet.kac.util.ObjectMapperUtils; |
||||
import lombok.RequiredArgsConstructor; |
||||
import lombok.extern.slf4j.Slf4j; |
||||
|
||||
import java.util.List; |
||||
|
||||
|
||||
@Slf4j |
||||
@RequiredArgsConstructor |
||||
public class DronPayloadDecorder extends MessageToMessageDecoder<String> { |
||||
// private int DATA_LENGTH = 100;
|
||||
private final ObjectMapper objectMapper = ObjectMapperUtils.getObjectMapper(); |
||||
|
||||
@Override |
||||
protected void decode(ChannelHandlerContext ctx, String in, List<Object> out) throws Exception { |
||||
log.info(">>>>> decode <<<<<"); |
||||
try { |
||||
DronPayload payload = objectMapper.readValue(in, DronPayload.class); |
||||
if (payload != null) { |
||||
out.add(payload); |
||||
} |
||||
} catch (Exception e) { |
||||
log.warn("decode parsing error : {} :: {}", e.getMessage(), in); |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,28 @@
|
||||
package kr.co.palnet.kac.socket.core.codec; |
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper; |
||||
import io.netty.channel.ChannelHandlerContext; |
||||
import io.netty.handler.codec.MessageToMessageEncoder; |
||||
import kr.co.palnet.kac.util.ObjectMapperUtils; |
||||
import lombok.RequiredArgsConstructor; |
||||
import lombok.extern.slf4j.Slf4j; |
||||
|
||||
import java.util.List; |
||||
|
||||
@Slf4j |
||||
@RequiredArgsConstructor |
||||
public class DronPayloadEncorder extends MessageToMessageEncoder<DronPayload> { |
||||
|
||||
private final ObjectMapper objectMapper = ObjectMapperUtils.getObjectMapper(); |
||||
|
||||
@Override |
||||
protected void encode(ChannelHandlerContext ctx, DronPayload msg, List<Object> out) throws Exception { |
||||
log.info(">>>>> encode <<<"); |
||||
try { |
||||
String json = objectMapper.writeValueAsString(msg); |
||||
out.add(json); |
||||
} catch (Exception e) { |
||||
log.warn("json parsing error : {} :: {}", e.getMessage(), msg); |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,22 @@
|
||||
package kr.co.palnet.kac.socket.core.config; |
||||
|
||||
import kr.co.palnet.kac.socket.core.socket.DronServerSocket; |
||||
import lombok.RequiredArgsConstructor; |
||||
import lombok.extern.slf4j.Slf4j; |
||||
import org.springframework.boot.context.event.ApplicationReadyEvent; |
||||
import org.springframework.context.ApplicationListener; |
||||
import org.springframework.stereotype.Component; |
||||
|
||||
@Slf4j |
||||
@RequiredArgsConstructor |
||||
@Component |
||||
public class AppReadyEvent implements ApplicationListener<ApplicationReadyEvent> { |
||||
|
||||
private final DronServerSocket socketServer; |
||||
|
||||
@Override |
||||
public void onApplicationEvent(ApplicationReadyEvent event) { |
||||
log.info(">>>> onApplicationEvent <<<<<"); |
||||
socketServer.start(); |
||||
} |
||||
} |
@ -0,0 +1,74 @@
|
||||
package kr.co.palnet.kac.socket.core.config; |
||||
|
||||
import io.netty.bootstrap.ServerBootstrap; |
||||
import io.netty.channel.ChannelOption; |
||||
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 kr.co.palnet.kac.socket.core.socket.DronChannelInitializer; |
||||
import lombok.RequiredArgsConstructor; |
||||
import lombok.extern.slf4j.Slf4j; |
||||
import org.springframework.beans.factory.annotation.Value; |
||||
import org.springframework.context.annotation.Bean; |
||||
import org.springframework.context.annotation.Configuration; |
||||
|
||||
import java.net.InetSocketAddress; |
||||
|
||||
@Slf4j |
||||
@RequiredArgsConstructor |
||||
@Configuration |
||||
public class NettyConfig { |
||||
@Value("${netty.socket.tcp-port}") |
||||
private int port; |
||||
@Value("${netty.socket.boss-count}") |
||||
private int bossCount; |
||||
@Value("${netty.socket.worker-count}") |
||||
private int workerCount; |
||||
@Value("${netty.socket.keep-alive}") |
||||
private boolean keepAlive; |
||||
@Value("${netty.socket.backlog}") |
||||
private int backlog; |
||||
|
||||
@Bean |
||||
public ServerBootstrap serverBootstrap(DronChannelInitializer nettyChannelInitializer) { |
||||
log.info(">>>>> serverBootstrap <<<<<"); |
||||
// ServerBootstrap: 서버 설정을 도와주는 class
|
||||
ServerBootstrap b = new ServerBootstrap(); |
||||
b.group(bossGroup(), workerGroup()) |
||||
// NioServerSocketChannel: incoming connections를 수락하기 위해 새로운 Channel을 객체화할 때 사용
|
||||
.channel(NioServerSocketChannel.class) |
||||
.handler(new LoggingHandler(LogLevel.DEBUG)) |
||||
// ChannelInitializer: 새로운 Channel을 구성할 때 사용되는 특별한 handler. 주로 ChannelPipeline으로 구성
|
||||
.childHandler(nettyChannelInitializer); |
||||
|
||||
// ServerBootstarp에 다양한 Option 추가 가능
|
||||
// SO_BACKLOG: 동시에 수용 가능한 최대 incoming connections 개수
|
||||
// 이 외에도 SO_KEEPALIVE, TCP_NODELAY 등 옵션 제공
|
||||
b.option(ChannelOption.SO_BACKLOG, backlog); |
||||
|
||||
return b; |
||||
} |
||||
|
||||
// boss: incoming connection을 수락하고, 수락한 connection을 worker에게 등록(register)
|
||||
@Bean(destroyMethod = "shutdownGracefully") |
||||
public NioEventLoopGroup bossGroup() { |
||||
log.info(">>>>> bossGroup <<<<<"); |
||||
return new NioEventLoopGroup(bossCount); |
||||
} |
||||
|
||||
// worker: boss가 수락한 연결의 트래픽 관리
|
||||
@Bean(destroyMethod = "shutdownGracefully") |
||||
public NioEventLoopGroup workerGroup() { |
||||
log.info(">>>>> workerGroup <<<<<"); |
||||
return new NioEventLoopGroup(workerCount); |
||||
} |
||||
|
||||
// IP 소켓 주소(IP 주소, Port 번호)를 구현
|
||||
// 도메인 이름으로 객체 생성 가능
|
||||
@Bean |
||||
public InetSocketAddress inetSocketAddress() { |
||||
log.info(">>>>> inetSocketAddress <<<<<"); |
||||
return new InetSocketAddress(port); |
||||
} |
||||
} |
@ -0,0 +1,44 @@
|
||||
package kr.co.palnet.kac.socket.core.handler; |
||||
|
||||
import io.netty.buffer.ByteBuf; |
||||
import io.netty.channel.*; |
||||
import kr.co.palnet.kac.socket.core.codec.DronPayload; |
||||
import lombok.RequiredArgsConstructor; |
||||
import lombok.extern.slf4j.Slf4j; |
||||
import org.springframework.stereotype.Component; |
||||
|
||||
@Slf4j |
||||
@RequiredArgsConstructor |
||||
@ChannelHandler.Sharable |
||||
@Component |
||||
public class DronSocketHandler extends SimpleChannelInboundHandler<DronPayload> { |
||||
|
||||
@Override |
||||
protected void channelRead0(ChannelHandlerContext ctx, DronPayload msg) throws Exception { |
||||
log.info(">>>> channelRead0 <<<<<"); |
||||
log.info("Received0 JSON: " + msg.toString()); |
||||
ctx.writeAndFlush(msg); |
||||
} |
||||
|
||||
// 클라이언트와 연결되어 트래픽을 생성할 준비가 되었을 때 호출되는 메소드
|
||||
@Override |
||||
public void channelActive(ChannelHandlerContext ctx) { |
||||
log.info(">>>> channelActive <<<<<"); |
||||
String remoteAddress = ctx.channel().remoteAddress().toString(); |
||||
log.info("Remote Address: " + remoteAddress); |
||||
// ctx.writeAndFlush("success");
|
||||
} |
||||
|
||||
|
||||
@Override |
||||
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) { |
||||
log.info(">>>> exceptionCaught <<<<<"); |
||||
// Close the connection when an exception is raised.
|
||||
ctx.close(); |
||||
cause.printStackTrace(); |
||||
} |
||||
|
||||
|
||||
|
||||
|
||||
} |
@ -0,0 +1,40 @@
|
||||
package kr.co.palnet.kac.socket.core.socket; |
||||
|
||||
import io.netty.channel.ChannelInitializer; |
||||
import io.netty.channel.ChannelPipeline; |
||||
import io.netty.channel.socket.SocketChannel; |
||||
import io.netty.handler.codec.json.JsonObjectDecoder; |
||||
import io.netty.handler.codec.string.StringDecoder; |
||||
import io.netty.handler.codec.string.StringEncoder; |
||||
import io.netty.util.CharsetUtil; |
||||
import kr.co.palnet.kac.socket.core.codec.DronPayloadDecorder; |
||||
import kr.co.palnet.kac.socket.core.codec.DronPayloadEncorder; |
||||
import kr.co.palnet.kac.socket.core.handler.DronSocketHandler; |
||||
import lombok.RequiredArgsConstructor; |
||||
import lombok.extern.slf4j.Slf4j; |
||||
import org.springframework.stereotype.Component; |
||||
|
||||
@Slf4j |
||||
@RequiredArgsConstructor |
||||
@Component |
||||
public class DronChannelInitializer extends ChannelInitializer<SocketChannel> { |
||||
|
||||
// private final DronSocketHandler dronSocketHandler;
|
||||
|
||||
// 클라이언트 소켓 채널이 생성될 때 호출
|
||||
@Override |
||||
protected void initChannel(SocketChannel ch) { |
||||
log.info(">>>>> initChannel <<<<<"); |
||||
ChannelPipeline pipeline = ch.pipeline(); |
||||
|
||||
// decoder는 @Sharable이 안 됨, Bean 객체 주입이 안 되고, 매번 새로운 객체 생성해야 함
|
||||
DronPayloadDecorder sampleDecoder = new DronPayloadDecorder(); |
||||
|
||||
// 뒤이어 처리할 디코더 및 핸들러 추가
|
||||
pipeline.addLast(new JsonObjectDecoder()); |
||||
pipeline.addLast(new StringDecoder(CharsetUtil.UTF_8), new StringEncoder(CharsetUtil.UTF_8)); |
||||
pipeline.addLast(new DronPayloadDecorder(), new DronPayloadEncorder()); |
||||
pipeline.addLast(new DronSocketHandler()); |
||||
// pipeline.addLast(dronSocketHandler);
|
||||
} |
||||
} |
@ -0,0 +1,45 @@
|
||||
package kr.co.palnet.kac.socket.core.socket; |
||||
|
||||
import io.netty.bootstrap.ServerBootstrap; |
||||
import io.netty.channel.Channel; |
||||
import io.netty.channel.ChannelFuture; |
||||
import jakarta.annotation.PreDestroy; |
||||
import lombok.RequiredArgsConstructor; |
||||
import lombok.extern.slf4j.Slf4j; |
||||
import org.springframework.stereotype.Component; |
||||
|
||||
import java.net.InetSocketAddress; |
||||
|
||||
@Slf4j |
||||
@RequiredArgsConstructor |
||||
@Component |
||||
public class DronServerSocket { |
||||
private final ServerBootstrap serverBootstrap; |
||||
private final InetSocketAddress tcpPort; |
||||
private Channel serverChannel; |
||||
|
||||
public void start() { |
||||
log.info(">>>>> start <<<<<"); |
||||
try { |
||||
// ChannelFuture: I/O operation의 결과나 상태를 제공하는 객체
|
||||
// 지정한 host, port로 소켓을 바인딩하고 incoming connections을 받도록 준비함
|
||||
ChannelFuture serverChannelFuture = serverBootstrap.bind(tcpPort).sync(); |
||||
|
||||
// 서버 소켓이 닫힐 때까지 기다림
|
||||
serverChannel = serverChannelFuture.channel().closeFuture().sync().channel(); |
||||
} |
||||
catch (InterruptedException e) { |
||||
e.printStackTrace(); |
||||
} |
||||
} |
||||
|
||||
// Bean을 제거하기 전에 해야할 작업이 있을 때 설정
|
||||
@PreDestroy |
||||
public void stop() { |
||||
log.info(">>>>> stop <<<<<"); |
||||
if (serverChannel != null) { |
||||
serverChannel.close(); |
||||
serverChannel.parent().closeFuture(); |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,15 @@
|
||||
netty: |
||||
socket: |
||||
tcp-port: 8090 |
||||
boss-count: 1 |
||||
worker-count: 10 |
||||
keep-alive: false |
||||
backlog: 100 |
||||
|
||||
|
||||
--- |
||||
|
||||
spring: |
||||
config: |
||||
activate: |
||||
on-profile: local |
@ -1,5 +1,5 @@
|
||||
|
||||
|
||||
dependencies { |
||||
implementation "$boot:spring-boot-starter-json" |
||||
api "$boot:spring-boot-starter-json" |
||||
} |
||||
|
Loading…
Reference in new issue