Browse Source

feat: socket 기본 틀 구성

feature/socket
지대한 7 months ago
parent
commit
7fbed6288a
  1. 8
      app/kac-socket-app/build.gradle
  2. 15
      app/kac-socket-app/src/main/java/kr/co/palnet/kac/socket/KacSocketApplication.java
  3. 22
      app/kac-socket-app/src/main/java/kr/co/palnet/kac/socket/core/codec/DronPayload.java
  4. 31
      app/kac-socket-app/src/main/java/kr/co/palnet/kac/socket/core/codec/DronPayloadDecorder.java
  5. 28
      app/kac-socket-app/src/main/java/kr/co/palnet/kac/socket/core/codec/DronPayloadEncorder.java
  6. 22
      app/kac-socket-app/src/main/java/kr/co/palnet/kac/socket/core/config/AppReadyEvent.java
  7. 74
      app/kac-socket-app/src/main/java/kr/co/palnet/kac/socket/core/config/NettyConfig.java
  8. 44
      app/kac-socket-app/src/main/java/kr/co/palnet/kac/socket/core/handler/DronSocketHandler.java
  9. 40
      app/kac-socket-app/src/main/java/kr/co/palnet/kac/socket/core/socket/DronChannelInitializer.java
  10. 45
      app/kac-socket-app/src/main/java/kr/co/palnet/kac/socket/core/socket/DronServerSocket.java
  11. 15
      app/kac-socket-app/src/main/resources/application.yml
  12. 2
      common/util/build.gradle
  13. 11
      common/util/src/main/java/kr/co/palnet/kac/util/ObjectMapperUtils.java

8
app/kac-socket-app/build.gradle

@ -0,0 +1,8 @@
dependencies {
implementation "$boot:spring-boot-starter"
implementation "io.netty:netty-all:4.1.68.Final"
implementation project(":common:util")
}

15
app/kac-socket-app/src/main/java/kr/co/palnet/kac/socket/KacSocketApplication.java

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

22
app/kac-socket-app/src/main/java/kr/co/palnet/kac/socket/core/codec/DronPayload.java

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

31
app/kac-socket-app/src/main/java/kr/co/palnet/kac/socket/core/codec/DronPayloadDecorder.java

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

28
app/kac-socket-app/src/main/java/kr/co/palnet/kac/socket/core/codec/DronPayloadEncorder.java

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

22
app/kac-socket-app/src/main/java/kr/co/palnet/kac/socket/core/config/AppReadyEvent.java

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

74
app/kac-socket-app/src/main/java/kr/co/palnet/kac/socket/core/config/NettyConfig.java

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

44
app/kac-socket-app/src/main/java/kr/co/palnet/kac/socket/core/handler/DronSocketHandler.java

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

40
app/kac-socket-app/src/main/java/kr/co/palnet/kac/socket/core/socket/DronChannelInitializer.java

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

45
app/kac-socket-app/src/main/java/kr/co/palnet/kac/socket/core/socket/DronServerSocket.java

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

15
app/kac-socket-app/src/main/resources/application.yml

@ -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

2
common/util/build.gradle

@ -1,5 +1,5 @@
dependencies { dependencies {
implementation "$boot:spring-boot-starter-json" api "$boot:spring-boot-starter-json"
} }

11
common/util/src/main/java/kr/co/palnet/kac/util/ObjectMapperUtils.java

@ -15,17 +15,6 @@ import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder;
import java.time.format.DateTimeFormatter; import java.time.format.DateTimeFormatter;
import java.util.TimeZone; import java.util.TimeZone;
/**
* packageName : com.sumair.common.core.utils
* fileName : ObjectMapperUtils
* author : dhji
* date : 2023-05-17(017)
* description :
* ===========================================================
* DATE AUTHOR NOTE
* -----------------------------------------------------------
* 2023-05-17(017) dhji 최초 생성
*/
public class ObjectMapperUtils { public class ObjectMapperUtils {
public static ObjectMapper getObjectMapper() { public static ObjectMapper getObjectMapper() {
return getObjectMapperBuilder().build(); return getObjectMapperBuilder().build();

Loading…
Cancel
Save