ソースを参照

refactoring traceConfig

shjung 1 週間 前
コミット
fbd53dc5b5
19 ファイル変更796 行追加234 行削除
  1. 1 0
      conf/tsi-comm-server-trace.cfg
  2. 1 0
      conf/tsi-comm-server.pid
  3. 152 15
      tsi-comm-server/src/main/java/com/tsi/comm/server/config/TraceConfig.java
  4. 70 45
      tsi-comm-server/src/main/java/com/tsi/comm/server/controller/TsiCommServerRestController.java
  5. 2 0
      tsi-comm-server/src/main/java/com/tsi/comm/server/kafka/KafkaProducerService.java
  6. 177 112
      tsi-comm-server/src/main/java/com/tsi/comm/server/process/packet/TsiCvimPacketWorker.java
  7. 3 6
      tsi-comm-server/src/main/java/com/tsi/comm/server/protocol/TsiCpuPacket.java
  8. 6 0
      tsi-comm-server/src/main/java/com/tsi/comm/server/repository/TsiNodeManager.java
  9. 1 0
      tsi-comm-server/src/main/java/com/tsi/comm/server/repository/TsiSessionManager.java
  10. 4 5
      tsi-comm-server/src/main/java/com/tsi/comm/server/service/TsiCommServerService.java
  11. 11 9
      tsi-comm-server/src/main/java/com/tsi/comm/server/tcp/handler/CvimServerPacketProcessHandler.java
  12. 196 0
      tsi-comm-server/src/main/java/com/tsi/comm/server/tcp/handler/MdcLoggingHandler.java
  13. 15 0
      tsi-comm-server/src/main/java/com/tsi/comm/server/tcp/initializer/CvimServerInitializer.java
  14. 9 7
      tsi-comm-server/src/main/java/com/tsi/comm/server/tcp/service/ConnectionLifecycleService.java
  15. 7 0
      tsi-comm-server/src/main/java/com/tsi/comm/server/vo/TsiNodeVo.java
  16. 4 15
      tsi-comm-server/src/main/resources/logback-spring.xml
  17. 44 17
      tsi-common/build.gradle
  18. 64 0
      tsi-common/build.gradle.old
  19. 29 3
      tsi-common/src/main/java/com/tsi/common/utils/StringUtils.java

+ 1 - 0
conf/tsi-comm-server-trace.cfg

@@ -2,3 +2,4 @@
 #session-report=false
 #DUMP=1610121022,...
 DUMP=1610121022
+#TCP-DUMP=10.30.0.40 , 10.30.0.45

+ 1 - 0
conf/tsi-comm-server.pid

@@ -0,0 +1 @@
+20930

+ 152 - 15
tsi-comm-server/src/main/java/com/tsi/comm/server/config/TraceConfig.java

@@ -2,8 +2,13 @@ package com.tsi.comm.server.config;
 
 import com.tsi.comm.server.TsiCommServerApplication;
 import com.tsi.comm.server.repository.TsiNodeManager;
+import com.tsi.comm.server.repository.TsiSessionManager;
+import com.tsi.comm.server.tcp.handler.MdcLoggingHandler;
 import com.tsi.comm.server.vo.TsiNodeVo;
 import com.tsi.common.utils.StringUtils;
+import io.netty.channel.Channel;
+import io.netty.channel.ChannelPipeline;
+import io.netty.handler.logging.LogLevel;
 import lombok.Getter;
 import lombok.RequiredArgsConstructor;
 import lombok.Setter;
@@ -11,10 +16,14 @@ import lombok.ToString;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.stereotype.Component;
 
+import javax.annotation.PostConstruct;
 import java.io.File;
 import java.io.FileInputStream;
-import java.util.List;
+import java.io.FileNotFoundException;
+import java.util.HashSet;
 import java.util.Properties;
+import java.util.Set;
+import java.util.stream.Collectors;
 
 @Slf4j
 @Getter
@@ -26,10 +35,18 @@ public class TraceConfig {
 
     private final TsiNodeManager nodeManager;
 
+    private Set<Long> currentDumpNodeIds = new HashSet<>();
+    private Set<String> currentTcpDumpIps = new HashSet<>();
+
     private boolean queueReport = false;
     private boolean sessionReport = false;
     private boolean nodeLogging = false;
 
+    @PostConstruct
+    void init() {
+        loadTraceInfo();
+    }
+
     private Properties getProperties() {
         String traceFileName = System.getProperty("user.dir") + File.separator + "conf" + File.separator + TsiCommServerApplication.APPLICATION_NAME + "-trace.cfg";
         Properties props = new Properties();
@@ -37,8 +54,12 @@ public class TraceConfig {
             FileInputStream in = new FileInputStream(traceFileName);
             props.load(in);
             in.close();
-        } catch(Exception e) {
-//            log.error("{}.getTraceFileInputStream: Exception1: {}", this.getClass().getSimpleName(), e.toString());
+        }
+        catch (FileNotFoundException ef) {
+            // no logging
+        }
+        catch(Exception e) {
+//            log.error("getProperties: Exception1: {}", e.toString());
         }
         return props;
     }
@@ -52,30 +73,146 @@ public class TraceConfig {
         }
     }
 
+    /**
+     * 설정 파일을 다시 로드하여, '변경된' 노드의 덤프 상태만 선택적으로 업데이트합니다.
+     */
     public void loadTraceInfo() {
         try {
             Properties props = getProperties();
 
-            this.nodeManager.initDump();
-
-            String dumps  = props.getProperty("DUMP",  "").trim();
-            if (!dumps.isEmpty()) {
-                List<String> regionCds = StringUtils.split(dumps, ",");
-                regionCds.forEach(id -> {
-                    TsiNodeVo node = this.nodeManager.get(getNodeId(id.trim()));
-                    if (node != null) {
-                        node.setDump(true);
-                    }
-                });
+            // --- DUMP
+            Set<Long> newDumpNodeIds = StringUtils.splitToStream(props.getProperty("DUMP", ""), ",")
+                    .map(this::getNodeId)
+                    .filter(id -> id > 0)
+                    .collect(Collectors.toSet());
+
+            // 비활성화할 노드 찾기 (이전 Set에서 새로운 Set에는 없는 노드를 제거)
+            Set<Long> nodesToDisableDump = new HashSet<>(this.currentDumpNodeIds);
+            nodesToDisableDump.removeAll(newDumpNodeIds);
+            nodesToDisableDump.forEach(nodeId -> updateDumpStatus(nodeId, false));
+
+            // 활성화할 노드 찾기 (새로운 Set에는 있지만, 이전 Set에는 없었던 노드) ==> 이전과 신규 모두 설정된것은 변경작업을 하지 않음
+            Set<Long> nodesToEnableDump = new HashSet<>(newDumpNodeIds);
+            nodesToEnableDump.removeAll(this.currentDumpNodeIds);
+            nodesToEnableDump.forEach(nodeId -> updateDumpStatus(nodeId, true));
+
+            // 다음 비교를 위해 현재 상태를 업데이트
+            this.currentDumpNodeIds = newDumpNodeIds;
+
+
+            // --- TCP-DUMP
+            Set<String> newTcpDumpIps = StringUtils.splitToStream(props.getProperty("TCP-DUMP", ""), ",")
+                    .collect(Collectors.toSet());
+
+            Set<String> ipsToDisableTcpDump = new HashSet<>(this.currentTcpDumpIps);
+            ipsToDisableTcpDump.removeAll(newTcpDumpIps);
+            ipsToDisableTcpDump.forEach(ip -> updateTcpDumpStatus(ip, false));
+
+            Set<String> ipsToEnableTcpDump = new HashSet<>(newTcpDumpIps);
+            ipsToEnableTcpDump.removeAll(this.currentTcpDumpIps);
+            ipsToEnableTcpDump.forEach(ip -> updateTcpDumpStatus(ip, true));
+
+            this.currentTcpDumpIps = newTcpDumpIps;
+
+            // --- 기타 설정 로드 ---
+            this.sessionReport = props.getProperty("session-report", "false").trim().equalsIgnoreCase("true");
+            this.queueReport = props.getProperty("queue-report", "false").trim().equalsIgnoreCase("true");
+            this.nodeLogging = props.getProperty("node-logging", "false").trim().equalsIgnoreCase("true");
+
+        } catch (Exception e) {
+            log.error("loadTraceInfo: Exception: {}", e.toString(), e);
+        }
+    }
+
+    private void updateTcpDumpStatus(String ipAddr, boolean shouldTcpDump) {
+        TsiNodeVo nodeVo = this.nodeManager.getIpAddr(ipAddr);
+        if (nodeVo != null && nodeVo.isTcpDump() != shouldTcpDump) {
+            log.info("TCP dump status changed for node {}: {} -> {}", nodeVo.getNodeId(), nodeVo.isTcpDump(), shouldTcpDump);
+            updateTcpDumpForChannel(nodeVo, shouldTcpDump);
+        }
+    }
+
+    private void updateDumpStatus(long nodeId, boolean shouldDump) {
+        TsiNodeVo nodeVo = this.nodeManager.get(nodeId);
+        if (nodeVo != null && nodeVo.isDump() != shouldDump) {
+            log.info("Dump status changed for node {}: {} -> {}", nodeId, nodeVo.isDump(), shouldDump);
+            nodeVo.setDump(shouldDump);
+        }
+    }
+
+    /**
+     * '특정 노드 하나'의 TCP 덤프 상태를 변경하고, 채널 파이프라인을 수정합니다.
+     */
+    private void updateTcpDumpForChannel(TsiNodeVo nodeVo, boolean addHandler) {
+
+        Channel channel = nodeVo.getChannel();
+        if (channel == null || !channel.isActive()) {
+            return;
+        }
+
+        final String handlerName = TsiSessionManager.MDC_LOGGING_HANDLER_NAME;
+        try {
+            channel.eventLoop().execute(() -> {
+                ChannelPipeline pipeline = channel.pipeline();
+                boolean hasHandler = pipeline.get(handlerName) != null;
+                if (addHandler && !hasHandler) {
+                    log.info("Adding TCP dump handler to channel for center: {}, {}", nodeVo.getNodeId(), nodeVo.getIpAddr());
+                    pipeline.addFirst(handlerName, new MdcLoggingHandler(LogLevel.INFO));
+                }
+                else if (!addHandler && hasHandler) {
+                    log.info("Removing TCP dump handler from channel for center: {}, {}", nodeVo.getNodeId(), nodeVo.getIpAddr());
+                    pipeline.remove(handlerName);
+                }
+            });
+        }
+        catch(Exception e) {
+            log.error("updateTcpDumpForChannel Exception: {}", e.getMessage());
+        }
+    }
+
+    public void loadTraceInfo2() {
+        try {
+            Properties props = getProperties();
+
+//            this.nodeManager.initDump();
+
+            // 1. DUMP 설정
+            Set<Long> dumpNodeIds = StringUtils.splitToStream(props.getProperty("DUMP", ""), ",")
+                    .map(this::getNodeId)
+                    .filter(id -> id > 0)
+                    .collect(Collectors.toSet());
+
+            // 2. TCP-DUMP 설정
+            Set<String> tcpDumpIps = StringUtils.splitToStream(props.getProperty("TCP-DUMP", ""), ",")
+                    .collect(Collectors.toSet());
+
+            // 3. 한 번의 순회로 모든 노드의 상태를 확인하고 업데이트
+            for (TsiNodeVo nodeVo : this.nodeManager.getAllNodes()) {
+                // 3-1. DUMP 상태 업데이트
+                boolean shouldDump = dumpNodeIds.contains(nodeVo.getNodeId());
+                if (nodeVo.isDump() != shouldDump) {
+                    log.info("Dump status changed for node {}: {} -> {}", nodeVo.getNodeId(), nodeVo.isDump(), shouldDump);
+                    nodeVo.setDump(shouldDump);
+                }
+
+                // 3-2. TCP-DUMP 상태 업데이트
+                boolean shouldTcpDump = tcpDumpIps.contains(nodeVo.getIpAddr());
+                if (nodeVo.isTcpDump() != shouldTcpDump) {
+                    log.info("TCP dump status changed for node {}: {} -> {}", nodeVo.getNodeId(), nodeVo.isTcpDump(), shouldTcpDump);
+                    nodeVo.setTcpDump(shouldTcpDump);
+                    updateTcpDumpForChannel(nodeVo, shouldTcpDump);
+                }
             }
 
+            // 4. 기타 설정 로드
             this.sessionReport = props.getProperty("session-report",  "false").trim().equalsIgnoreCase("true");
             this.queueReport = props.getProperty("queue-report",  "false").trim().equalsIgnoreCase("true");
             this.nodeLogging = props.getProperty("node-logging",  "false").trim().equalsIgnoreCase("true");
         }
         catch(Exception e) {
-            log.error("{}.loadDebugInfo: Exception2: {}", this.getClass().getSimpleName(), e.toString());
+            log.error("loadTraceInfo: Exception: {}", e.toString());
         }
+
     }
 
 }

+ 70 - 45
tsi-comm-server/src/main/java/com/tsi/comm/server/controller/TsiCommServerRestController.java

@@ -1,6 +1,7 @@
 package com.tsi.comm.server.controller;
 
 import com.tsi.comm.server.config.ApplicationConfig;
+import com.tsi.comm.server.config.TraceConfig;
 import com.tsi.comm.server.config.TsiCvimServerConfig;
 import com.tsi.comm.server.process.AbstractTsiCvimWorker;
 import com.tsi.comm.server.process.dbms.TsiCvimDbmsProcess;
@@ -18,13 +19,11 @@ import com.tsi.common.utils.TimeUtils;
 import lombok.RequiredArgsConstructor;
 import org.springframework.web.bind.annotation.GetMapping;
 import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
 import org.springframework.web.bind.annotation.RestController;
 
 import java.text.SimpleDateFormat;
-import java.util.Date;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
+import java.util.*;
 import java.util.stream.Collectors;
 
 @RestController
@@ -34,6 +33,7 @@ public class TsiCommServerRestController {
 
     private final ApplicationConfig config;
     private final TsiCvimServerConfig serverConfig;
+    private final TraceConfig traceConfig;
     private final TsiNodeManager nodeManager;
     private final TsiSessionManager sessionManager;
     private final TsiAlarmManager alarmManager;
@@ -60,25 +60,63 @@ public class TsiCommServerRestController {
 
     @GetMapping(value = "/info", produces = {"application/json; charset=utf8"})
     public String info() {
-        StringBuilder sb = getCommonHead();
+        int nodeCount = this.nodeManager.getTsiNodeVoMap().size();
+        return infoNode(0, nodeCount, true);
+    }
+
+    // http://localhost:9871/info-page?page=0&size=100
+    @GetMapping(value = "/info-page", produces = {"application/json; charset=utf8"})
+    public String info(
+            @RequestParam(value = "page", defaultValue = "0") int page,
+            @RequestParam(value = "size", defaultValue = "100") int size) {
+        return infoNode(page, size, false);
+
+    }
 
+    private String infoNode(int page, int size, boolean all) {
+
+        StringBuilder sb = getCommonHead();
         int nodeCount = this.nodeManager.getTsiNodeVoMap().size();
-        sb.append(String.format(" Report Node Sessions: %d Nodes. INTC(Install/SendNode/SendTest/SendCvim)", nodeCount)).append(sep);
 
+        List<TsiNodeVo> allNodes = this.nodeManager.getTsiNodeVoMap().values().stream()
+                .sorted(Comparator.comparing(TsiNodeVo::getNodeId)) // 일관된 순서를 위해 정렬
+                .collect(Collectors.toList());
+
+        int totalNodes = allNodes.size();
+        int totalPages = (int) Math.ceil((double) totalNodes / size);
+
+        if (!all) {
+            sb.append(String.format(" Report Node Sessions: %d Nodes. (Page %d / %d)", totalNodes, page + 1, totalPages)).append(sep);
+        }
+        sb.append(String.format(" Report Node Sessions: %d Nodes. A-INTCA(AddNode-Install/SendNode/SendTest/SendCvim)", nodeCount)).append(sep);
+
+        List<TsiNodeVo> pagedNodes = allNodes.stream()
+                .skip((long) page * size)
+                .limit(size)
+                .collect(Collectors.toList());
+
+        int seqStart = page * size + 1;
+        //  CRC/PACKET
+        //false/false
         int registered = 0;
         int unknown = 0;
         int connected = 0;
         SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
         sdf.setTimeZone(java.util.TimeZone.getTimeZone("GMT+9"));
 
-        sb.append(String.format("   SEQ[U] %10s %4s %7s %19s %19s %9s %6s %19s  IP Address      Remote-Address   Signal AvgPrcTm(us)  Dropped/Overlapping Queue(P/L/D)",
-                "Node ID", "INTC", "Connect", "Connect Time", "Disconnect Time", "Connected", "Closed", "Last-Recv-Time")).append(sep);
+        sb.append(String.format("   SEQ[U] %10s %4s %7s %19s %19s %9s %6s %19s  IP Address          CRC/PACKET Signal AvgPrcTm(us)  Dropped/Overlapping Queue(P/L/D)",
+                "Node ID", "A-INTC", "Connect", "Connect Time", "Disconnect Time", "Connected", "Closed", "Last-Recv-Time")).append(sep);
         sb.append(heading).append(sep);
 
-        int ii = 1;
-        for (Map.Entry<Long, TsiNodeVo> obj : this.nodeManager.getTsiNodeVoMap().entrySet()) {
-            final TsiNodeVo node = obj.getValue();
-            String check = (node.isInstalled() ? "Y" : "N");
+//        List<TsiNodeVo> nodesSnapshot = new ArrayList<>(this.nodeManager.getTsiNodeVoMap().values());
+//        for (TsiNodeVo node : nodesSnapshot) {
+//        }
+
+        int crcErrorCnt = 0;
+        int packetErrorCnt = 0;
+        for (TsiNodeVo node : pagedNodes) {
+            String check = (node.isAddNodeEnabled() ? "Y-" : "N-");
+            check = check + (node.isInstalled() ? "Y" : "N");
             check = check + (node.isSendNode() ? "Y" : "N");
             check = check + (node.isSendTest() ? "Y" : "N");
             check = check + (node.isSendCvim() ? "Y" : "N");
@@ -101,18 +139,30 @@ public class TsiCommServerRestController {
             }
             else {
                 connect = "N";
-                info = "---.---.---.---";
+                //info = "---.---.---.---";
+                info = node.getIpAddr();
                 avgTime = 0;
             }
 
+            String crcErr = "false";
+            if (node.isCrcError()) {
+                crcErr = "true";
+                crcErrorCnt++;
+            }
+            String packetErr = "false";
+            if (node.isPacketError()) {
+                packetErr = "true";
+                packetErrorCnt++;
+            }
+
             String connectTm = sdf.format(new Date(node.getConnectTm()));
             String disconnectTm = sdf.format(new Date(node.getDisconnectTm()));
             String lastCommTm = sdf.format(new Date(node.getLastCommTm()));
 
-            sb.append(String.format(" %5d%3s %10s %4s %7s %19s %19s %9d %6d %19s  %-15.15s %-15.15s  %6d %12d  %-19s %d/%d/%d",
-                    ii++, unknownNode, node.getKey(), check, connect, connectTm, disconnectTm, node.getConnectCount().get(),
-                    node.getDisconnectCount().get(), lastCommTm, node.getIpAddr(), info,
-                    node.getSigCount(), avgTime,
+            sb.append(String.format(" %5d%3s %10s %4s %7s %19s %19s %9d %6d %19s  %-15.15s   %5s/%5s  %6d %12d  %-19s %d/%d/%d",
+                    seqStart++, unknownNode, node.getKey(), check, connect, connectTm, disconnectTm, node.getConnectCount().get(),
+                    node.getDisconnectCount().get(), lastCommTm, info, //node.getIpAddr(), //info,
+                    crcErr, packetErr, node.getSigCount(), avgTime,
                     String.format("%d/%d", node.getDroppedPacketCount(), node.getOverlappingPacketCount()),
                     node.getPktQIdx(), node.getLogQIdx(), node.getDbmsQIdx())).append(sep);
         }
@@ -123,36 +173,11 @@ public class TsiCommServerRestController {
                 this.alarmManager.getUnknownIpAddrCount(), this.alarmManager.getDupConnectCount())).append(sep);
 //        sb.append(heading).append(sep);
         sb.append(String.format(" Channel: %d EA, Session: %d EA", this.sessionManager.getChannelCount(), this.sessionManager.getCount())).append(sep);
+        sb.append(String.format(" CRC Error: %d EA, Packet Error: %d EA", crcErrorCnt, packetErrorCnt)).append(sep);
+        sb.append(String.format(" DUMP: %s", this.traceConfig.getCurrentDumpNodeIds().toString())).append(sep);
+        sb.append(String.format(" TCP DUMP: %s", this.traceConfig.getCurrentTcpDumpIps().toString())).append(sep);
         sb.append(heading).append(sep);
 
-//        Set<Long> nodeIdSet = new HashSet<>();
-//        ConcurrentHashMap<Channel, TsiNodeVo> channelNodeMap = this.sessionManager.getChannelNodeMap();
-//        for (Map.Entry<Channel, TsiNodeVo> entry : channelNodeMap.entrySet()) {
-//            Channel channel = entry.getKey();
-//            TsiNodeVo nodeVo = entry.getValue();
-//            Long nodeId = nodeVo.getNodeId();
-//
-//            TsiNodeVo dbNode = this.nodeManager.getTsiNodeVoMap().get(nodeId);
-//            String ip = NettyUtils.getRemoteIpAddress(channel);
-//            String dbIp = dbNode == null ? "" : dbNode.getIpAddr();
-//
-//            if (dbNode == null) {
-//                sb.append(String.format("Not Found Node: %d, %s/%s", nodeId, ip, dbIp)).append(sep);
-//            }
-//            else {
-//                if (!dbNode.isConnect()) {
-//                    sb.append(String.format("Network state error: %d, %s/%s", nodeId, ip, dbIp)).append(sep);
-//                }
-//            }
-//            if (nodeIdSet.contains(nodeId)) {
-//                 sb.append(String.format("Dup Channel: %d %s/%s", nodeId, ip, dbIp)).append(sep);
-//            } else {
-//                nodeIdSet.add(nodeId);
-//            }
-//            if (!ip.equals(dbIp)) {
-//                sb.append(String.format("IP Not Match: %d,  %s/%s", nodeId, ip, dbIp)).append(sep);
-//            }
-//        }
         return sb.toString();
     }
 

+ 2 - 0
tsi-comm-server/src/main/java/com/tsi/comm/server/kafka/KafkaProducerService.java

@@ -10,6 +10,7 @@ import com.tsi.comm.server.repository.TsiAlarmManager;
 import com.tsi.comm.server.repository.TsiTpmsManager;
 import com.tsi.comm.server.vo.TsiAlarmConfigVo;
 import com.tsi.common.utils.TimeUtils;
+import io.netty.buffer.ByteBufUtil;
 import lombok.RequiredArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
 import org.apache.kafka.clients.producer.ProducerConfig;
@@ -198,6 +199,7 @@ public class KafkaProducerService {
     public void sendNode(String key, byte[] data) {
         if (this.nodeProducer != null) {
             try {
+                ByteBufUtil.getBytes()
                 this.nodeProducer.send(key, key, data);
             }
             catch (Exception e) {

+ 177 - 112
tsi-comm-server/src/main/java/com/tsi/comm/server/process/packet/TsiCvimPacketWorker.java

@@ -1,18 +1,19 @@
 package com.tsi.comm.server.process.packet;
 
-import com.tsi.comm.server.protocol.enums.eOpCode;
 import com.tsi.comm.server.config.TraceConfig;
 import com.tsi.comm.server.config.TsiCvimServerConfig;
 import com.tsi.comm.server.kafka.KafkaProducerService;
 import com.tsi.comm.server.process.AbstractTsiCvimWorker;
 import com.tsi.comm.server.process.logging.TsiCvimLoggingProcess;
 import com.tsi.comm.server.protocol.AbstractTsiPacket;
+import com.tsi.comm.server.protocol.TsiCpuAddPacket;
 import com.tsi.comm.server.protocol.TsiCpuDisconnected;
 import com.tsi.comm.server.protocol.TsiCpuPacket;
+import com.tsi.comm.server.protocol.enums.eOpCode;
 import com.tsi.comm.server.vo.TsiNodeVo;
 import com.tsi.common.spring.SpringUtils;
-import com.tsi.common.utils.HexString;
 import com.tsi.common.utils.TimeUtils;
+import io.netty.buffer.ByteBufUtil;
 import lombok.Getter;
 import lombok.extern.slf4j.Slf4j;
 import org.slf4j.MDC;
@@ -31,7 +32,7 @@ public class TsiCvimPacketWorker extends AbstractTsiCvimWorker implements Runnab
     private final KafkaProducerService kafkaProducer;
     private final TsiCvimLoggingProcess loggingProcess;
     private final TraceConfig traceConfig;
-    private final TsiCvimServerConfig cvimServerConfig;
+    private final TsiCvimServerConfig serverConfig;
 
     public TsiCvimPacketWorker(int idx, int qSize, KafkaProducerService kafkaProducer) {
         this.idx = idx;
@@ -40,7 +41,7 @@ public class TsiCvimPacketWorker extends AbstractTsiCvimWorker implements Runnab
         this.DATA_QUEUE = new LinkedBlockingQueue<>(qSize);
         this.loggingProcess = SpringUtils.getBean(TsiCvimLoggingProcess.class);
         this.traceConfig = SpringUtils.getBean(TraceConfig.class);
-        this.cvimServerConfig = SpringUtils.getBean(TsiCvimServerConfig.class);
+        this.serverConfig = SpringUtils.getBean(TsiCvimServerConfig.class);
     }
 
     @Override
@@ -48,7 +49,7 @@ public class TsiCvimPacketWorker extends AbstractTsiCvimWorker implements Runnab
         log.info("PacketWorker({}): {} Start. QSIZE: {}", this.idx, Thread.currentThread().getName(), this.qSize);
         try {
             while (!Thread.currentThread().isInterrupted()) {
-                Object packet = null;
+                AbstractTsiPacket packet = null;
                 try {
                     packet = this.DATA_QUEUE.take();
                     if (packet == SHUTDOWN_PACKET) {
@@ -72,7 +73,7 @@ public class TsiCvimPacketWorker extends AbstractTsiCvimWorker implements Runnab
                         log.error("PacketWorker({}): {} Exception: {}", this.idx, Thread.currentThread().getName(), e.getMessage());
                     }
                 } finally {
-                    if (packet != null) {
+                    if (packet != null && packet != SHUTDOWN_PACKET) {
                         TsiCpuPacket cpuPacket = (TsiCpuPacket)packet;
                         TsiNodeVo nodeVo = (TsiNodeVo)cpuPacket.getObj();
                         if (nodeVo != null) {
@@ -123,140 +124,204 @@ public class TsiCvimPacketWorker extends AbstractTsiCvimWorker implements Runnab
             return;
         }
 
-        boolean isLogging = this.traceConfig.isNodeLogging() || nodeVo.isDump();
-        String logKey = nodeVo.getKey();
-        int loggingIdx = nodeVo.getLogQIdx();   // 로깅인덱스큐로 데이터 전송
+        if (!nodeVo.isRegistered()) {
+            // 등록되지 않은 노드인 경우 다른 처리를 하지 않는다.
+            return;
+        }
+
+        long curr = System.nanoTime();
+        packet.setPop(curr);
 
-        MDC.put("id", logKey);
+        MDC.put("id", nodeVo.getKey());
         try {
-            long curr = System.nanoTime();
-            if (TimeUnit.MILLISECONDS.convert(curr - packet.getRcv(), TimeUnit.NANOSECONDS) > 3000) {
-                log.warn("Packet skip::: {}, {} ms.", nodeId, TimeUnit.MILLISECONDS.convert(curr - packet.getRcv(), TimeUnit.NANOSECONDS));
+            if (isPacketDelayed(cpuPacket, curr)) {
                 return;
             }
-            packet.setPop(curr);
 
-            if (packet.getOpCode() == (byte) eOpCode.TSI_CPU_DISCONNECTED.getValue()) {
-                try {
-                    TsiCpuDisconnected disconnected = (TsiCpuDisconnected) packet;
-                    disconnected.parsing(nodeVo);
-                    if (nodeVo.isSendCvim() && disconnected.getCvimData() != null) {
-                        this.kafkaProducer.sendCvim(disconnected.getNodeId(), disconnected.getCvimData());
-                    }
-
-                    if (disconnected.getAddNodes() != null) {
-                        // 연등지 인 경우
-                        for (int ii = 0; ii < disconnected.getAddNodes().size(); ii++) {
-                            this.kafkaProducer.sendCvim(disconnected.getAddNodes().get(ii).getNodeId(), disconnected.getAddNodes().get(ii).getCvimData());
-                        }
-                    }
-                    if (isLogging) {
-                        this.loggingProcess.add(packet, loggingIdx);
-                    }
-                } catch (Exception e) {
-                    if (nodeVo.isConnect()) {
-                        log.warn("Node: {}, Disconnect parsing error: {}, connect: {}", nodeId, Thread.currentThread().getName(), nodeVo.isConnect());
-                    }
-                }
-                return;
+            if (isDisconnectedPacket(cpuPacket)) {
+                handleDisconnectedPacket(cpuPacket, nodeVo);
+            }
+            else {
+                handleDataPacket(cpuPacket, nodeVo);
             }
+        } finally {
+            MDC.clear();
+        }
+    }
 
-            // 20250425: packet parsing 의 로그를 노드별 로그파일에 저장되도록 MDC 위치 변경
-            // 20250425: parsing 함수에 packet-check 여부를 같이 넘겨줘서 CRC 체크여부 확인
-            try {
-                int result = cpuPacket.parsing(nodeVo, this.cvimServerConfig.isCheckPacket());
-                if (0 != result) {
-                    if (-4 == result) {
-                        return; // 패킷 버퍼가 null 이거나 너무 짧은 경우
-                    }
-                    if (!nodeVo.isConnect()) {
-                        return; // 연결이 끊어진 경우
-                    }
+    /**
+     * 일반 데이터 패킷에 대한 처리
+     */
+    private void handleDataPacket(TsiCpuPacket cpuPacket, TsiNodeVo nodeVo) {
+        // 20250425: packet parsing 의 로그를 노드별 로그파일에 저장되도록 MDC 위치 변경
+        // 20250425: parsing 함수에 packet-check 여부를 같이 넘겨줘서 CRC 체크여부 확인
 
-                    byte[] buf = cpuPacket.getBuf();
-                    log.error("Node: {}, CPU Packet parsing failed. error: {}.", nodeId, result);
-                    log.error("{}", HexString.fromBytes(buf));
-                    byte stx1 = 0x00, stx2 = 0x00, version = 0x00;
-                    if (buf.length > TsiCpuPacket.INDEX_VERSION) {
-                        stx1 = buf[TsiCpuPacket.INDEX_STX1];
-                        stx2 = buf[TsiCpuPacket.INDEX_STX2];
-                        version = buf[TsiCpuPacket.INDEX_VERSION];
-                    }
+        // 1. 파싱 및 유효성 검사
+        if (!parseAndValidate(cpuPacket, nodeVo)) {
+            return; // 파싱 실패 시 처리 중단
+        }
+        nodeVo.setPacketError(false);
+        cpuPacket.setPar(System.nanoTime());
 
-                    int reqLength = TsiCpuPacket.SIZE_PACKET_DATA + (TsiCpuPacket.SIZE_STATUS_DATA * cpuPacket.getCount());
-                    switch (result) {
-                        case -1:
-                            log.error("Node: {}, STX Error: {}, {}", nodeId, stx1, stx2);
-                            break;
-                        case -2:
-                            log.error("Node: {}, Length Error: {}, Version: {}, status count: {}, {}", nodeId, cpuPacket.getLength(), version,
-                                    cpuPacket.getCount(), reqLength);
-                            break;
-                        case -3:
-                            log.error("Node: {}, Check Sum Error: Version: {}, recv: {}, calc: {}", nodeId, version, cpuPacket.getCheckSum(),
-                                    cpuPacket.getCalcCheckSum());
-                            break;
-                        default:
-                            log.error("Node: {}, Packet parsing error: {}", nodeId, result);
-                            break;
-                    }
-                    return;
+        // 2. Kafka로 데이터 전송
+        sendToKafka(cpuPacket, nodeVo);
+        cpuPacket.setEnd(System.nanoTime());
+
+        // 3. 통계 및 로깅
+        cpuPacket.setAvg(calcProcessTime(cpuPacket.getRcv()));
+
+        logPacketIfNeeded(cpuPacket, nodeVo);
+    }
+
+    private void sendToKafka(TsiCpuPacket cpuPacket, TsiNodeVo nodeVo) {
+        if (nodeVo.isSendNode() && cpuPacket.getNodeData() != null) {
+            this.kafkaProducer.sendNode(Long.toString(nodeVo.getNodeId()), cpuPacket.getNodeData());
+        }
+        if (nodeVo.isSendTest() && cpuPacket.getTestData() != null) {
+            this.kafkaProducer.sendTest(nodeVo.getNodeId(), cpuPacket.getTestData());
+        }
+        if (nodeVo.isSendCvim() && cpuPacket.getCvimData() != null) {
+            this.kafkaProducer.sendCvim(nodeVo.getNodeId(), cpuPacket.getCvimData());
+        }
+
+        if (cpuPacket.getAddNodes() != null) {
+            for (TsiCpuAddPacket addNodePacket : cpuPacket.getAddNodes()) {
+                // 연등지 노드 패킷 카프카 전송
+                if (nodeVo.isSendNode()) {
+                    this.kafkaProducer.sendNode(Long.toString(addNodePacket.getNodeId()), addNodePacket.getNodeData());
+                }
+                if (nodeVo.isSendCvim()) {
+                    this.kafkaProducer.sendCvim(addNodePacket.getNodeId(), addNodePacket.getCvimData());
                 }
-            } catch (Exception e) {
-                log.warn("Node: {}, CPU Packet parsing error: {}, connect: {}", nodeId, Thread.currentThread().getName(), nodeVo.isConnect());
+            }
+        }
+    }
+
+    private boolean parseAndValidate(TsiCpuPacket cpuPacket, TsiNodeVo nodeVo) {
+        try {
+            nodeVo.setCrcError(false);
+            nodeVo.setPacketError(false);
+
+            int result = cpuPacket.parsing(nodeVo, this.serverConfig.isCheckPacket());
+            if (result == 0) {
+                return true; // 파싱 성공
+            }
+            if (!nodeVo.isConnect()) {
+                return false;   // 네트워크 연결이 끊어진 경우
             }
 
-            packet.setPar(System.nanoTime());
+            nodeVo.setPacketError(true);
 
-            // 카프카 전송
-            if (nodeVo.isSendNode() && packet.getNodeData() != null) {
-                this.kafkaProducer.sendNode(Long.toString(nodeId), packet.getNodeData());
+            if (-4 == result) {
+                return false; // 패킷 버퍼가 null 이거나 너무 짧은 경우
             }
-            if (nodeVo.isSendTest()) {
-                this.kafkaProducer.sendTest(nodeId, packet.getTestData());
+
+            long nodeId = nodeVo.getNodeId();
+            int reqLength = TsiCpuPacket.SIZE_PACKET_DATA + (TsiCpuPacket.SIZE_STATUS_DATA * cpuPacket.getCount());
+            byte stx1 = 0x00, stx2 = 0x00, version = 0x00;
+            if (cpuPacket.getBuf() != null) {
+                byte[] buf = cpuPacket.getBuf();
+                log.error("packet parse error: {}", ByteBufUtil.hexDump(buf));
+                if (buf.length > TsiCpuPacket.INDEX_VERSION) {
+                    stx1 = buf[TsiCpuPacket.INDEX_STX1];
+                    stx2 = buf[TsiCpuPacket.INDEX_STX2];
+                    version = buf[TsiCpuPacket.INDEX_VERSION];
+                }
             }
-            if (nodeVo.isSendCvim() && packet.getCvimData() != null) {
-                this.kafkaProducer.sendCvim(nodeId, packet.getCvimData());
+
+            switch (result) {
+                case -1:
+                    log.error("Node: {}, STX Error: {}, {}", nodeId, stx1, stx2);
+                    break;
+                case -2:
+                    log.error("Node: {}, Length Error: {}, Version: {}, status count: {}, {}", nodeId, cpuPacket.getLength(), version,
+                            cpuPacket.getCount(), reqLength);
+                    break;
+                case -3:
+                    log.error("Node: {}, Check Sum Error: Version: {}, recv: {}, calc: {}", nodeId, version, cpuPacket.getCheckSum(),
+                            cpuPacket.getCalcCheckSum());
+                    break;
+                default:
+                    log.error("Node: {}, Packet parsing error: {}", nodeId, result);
+                    break;
+            }
+            return false;
+        } catch (Exception e) {
+            log.warn("Node: {}, CPU Packet parsing error: {}, connect: {}", nodeVo.getNodeId(), Thread.currentThread().getName(), nodeVo.isConnect(), e);
+            return false;
+        }
+    }
+
+    /**
+     * 연결 종료 패킷에 대한 처리
+     */
+    private void handleDisconnectedPacket(TsiCpuPacket packet, TsiNodeVo nodeVo) {
+        TsiCpuDisconnected disconnected = (TsiCpuDisconnected) packet;
+        try {
+            disconnected.parsing(nodeVo);
+
+            if (nodeVo.isSendCvim() && disconnected.getCvimData() != null) {
+                this.kafkaProducer.sendCvim(disconnected.getNodeId(), disconnected.getCvimData());
             }
 
-            if (cpuPacket.getAddNodes() != null) {
+            if (nodeVo.isSendCvim() && disconnected.getAddNodes() != null) {
                 // 연등지 인 경우
-                for (int ii = 0; ii < cpuPacket.getAddNodes().size(); ii++) {
-                    if (nodeVo.isSendNode()) {
-                        this.kafkaProducer.sendNode(Long.toString(cpuPacket.getAddNodes().get(ii).getNodeId()), cpuPacket.getAddNodes().get(ii).getNodeData());
-                    }
-                    if (nodeVo.isSendCvim()) {
-                        this.kafkaProducer.sendCvim(cpuPacket.getAddNodes().get(ii).getNodeId(), cpuPacket.getAddNodes().get(ii).getCvimData());
-                    }
+                for (TsiCpuPacket addNodePacket : disconnected.getAddNodes()) {
+                    this.kafkaProducer.sendCvim(addNodePacket.getNodeId(), addNodePacket.getCvimData());
                 }
             }
 
-            packet.setEnd(System.nanoTime());
+            logPacketIfNeeded(packet, nodeVo);
+        } catch (Exception e) {
+            if (nodeVo.isConnect()) {
+                log.warn("Node: {}, Disconnect parsing error: {}, connect: {}", nodeVo.getNodeId(), Thread.currentThread().getName(), nodeVo.isConnect(), e);
+            }
+        }
+    }
 
-            packet.setAvg(calcProcessTime(packet.getRcv()));
+    /**
+     * 연결 종료 패킷 여부 확인
+     */
+    private boolean isDisconnectedPacket(TsiCpuPacket packet) {
+        return packet.getOpCode() == (byte) eOpCode.TSI_CPU_DISCONNECTED.getValue();
+    }
 
-            // 로그큐로 전송한다.
-            if (isLogging) {
-                this.loggingProcess.add(packet, loggingIdx);
-            }
+    /**
+     * 수신한 패킷의 시간초과 여부 체크
+     * 큐에 수신한 시각과 큐에서 읽어 낸 시간의 차이를 계산
+     */
+    private boolean isPacketDelayed(TsiCpuPacket packet, long curr) {
+        final long delayMillis = TimeUnit.NANOSECONDS.toMillis(curr - packet.getRcv());
+        if (delayMillis > 3000) {
+            log.warn("Packet skip::: {}, {} ms.", packet.getNodeId(), delayMillis);
+            return true;
+        }
+        return false;
+    }
+
+    /**
+     * 로깅 여부에 따라 로길 프로세스로 데이터 전송
+     */
+    private void logPacketIfNeeded(AbstractTsiPacket packet, TsiNodeVo nodeVo) {
+        if (this.traceConfig.isNodeLogging() || nodeVo.isDump()) {
 
-            // 연등지 인 경우
-            if (cpuPacket.getAddNodes() != null) {
-                for (int ii = 0; ii < cpuPacket.getAddNodes().size(); ii++) {
-                    cpuPacket.getAddNodes().get(ii).setAdd(packet.getAdd());
-                    cpuPacket.getAddNodes().get(ii).setPop(packet.getPop());
-                    cpuPacket.getAddNodes().get(ii).setPar(packet.getPar());
-                    cpuPacket.getAddNodes().get(ii).setEnd(packet.getEnd());
-                    cpuPacket.getAddNodes().get(ii).setAvg(packet.getAvg());
-                    if (isLogging) {
-                        this.loggingProcess.add(cpuPacket.getAddNodes().get(ii), loggingIdx);
+            this.loggingProcess.add(packet, nodeVo.getLogQIdx());
+
+            if (packet instanceof TsiCpuPacket) {
+                TsiCpuPacket cpuPacket = (TsiCpuPacket) packet;
+                if (cpuPacket.getAddNodes() != null) {
+                    // 연등지 인 경우 연등지 패킷 로깅
+                    for (TsiCpuAddPacket addNodePacket : cpuPacket.getAddNodes()) {
+                        addNodePacket.setAdd(packet.getAdd());
+                        addNodePacket.setPop(packet.getPop());
+                        addNodePacket.setPar(packet.getPar());
+                        addNodePacket.setEnd(packet.getEnd());
+                        addNodePacket.setAvg(packet.getAvg());
+                        this.loggingProcess.add(addNodePacket, nodeVo.getLogQIdx());
                     }
                 }
             }
-        } finally {
-            MDC.clear();
         }
-
     }
 
     public void report() {

+ 3 - 6
tsi-comm-server/src/main/java/com/tsi/comm/server/protocol/TsiCpuPacket.java

@@ -202,15 +202,9 @@ public class TsiCpuPacket extends AbstractTsiPacket {
         return 0x00;
     }
 
-    public byte[] getCvimData() {
-        return this.cvimData;
-    }
     public byte[] getTestData() {
         return this.buf;
     }
-    public byte[] getNodeData() {
-        return this.nodeData;
-    }
 
     /*
      *  Make cvim-raw packet
@@ -320,6 +314,9 @@ public class TsiCpuPacket extends AbstractTsiPacket {
 
         int result = checkPacket();
         if (0 != result) {
+            if (-3 == result) {
+                obj.setCrcError(true);
+            }
             if (isCheckPacket) {
                 // 20250425: CRC 체크여부에 따라 바로 리턴(기본값은 체크여부가 true 임)
                 return result;

+ 6 - 0
tsi-comm-server/src/main/java/com/tsi/comm/server/repository/TsiNodeManager.java

@@ -6,6 +6,7 @@ import lombok.RequiredArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.stereotype.Component;
 
+import java.util.Collection;
 import java.util.Map;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.function.Function;
@@ -41,6 +42,10 @@ public class TsiNodeManager {
         return this.tsiNodeVoMap.size();
     }
 
+    public Collection<TsiNodeVo> getAllNodes() {
+        return this.tsiNodeVoMap.values();
+    }
+
     public TsiNodeVo getIpAddr(String ipAddr) {
         if (ipAddr == null || ipAddr.isEmpty()) {
             return null;
@@ -86,6 +91,7 @@ public class TsiNodeManager {
         for (Map.Entry<Long, TsiNodeVo> obj : this.tsiNodeVoMap.entrySet()) {
             TsiNodeVo node = obj.getValue();
             node.setDump(false);
+            node.setTcpDump(false);
         }
     }
 

+ 1 - 0
tsi-comm-server/src/main/java/com/tsi/comm/server/repository/TsiSessionManager.java

@@ -22,6 +22,7 @@ public class TsiSessionManager {
     public static final AttributeKey<Integer> PKT_Q_IDX_KEY = AttributeKey.valueOf("pktQIdx");
     public static final AttributeKey<Integer> DBMS_Q_IDX_KEY = AttributeKey.valueOf("dbmsQIdx");
     public static final AttributeKey<Integer> LOG_Q_IDX_KEY = AttributeKey.valueOf("logQIdx");
+    public static final String MDC_LOGGING_HANDLER_NAME = "dumpLogger";
 
     // nodeId가 0이거나 유효하지 않아 인증되지 않은 채널임을 표시하는 키. 이 키가 채널에 존재하면, 해당 채널에서 오는 패킷은 무시
     public static final AttributeKey<Boolean> UNAUTHENTICATED_KEY = AttributeKey.valueOf("unauthenticated");

+ 4 - 5
tsi-comm-server/src/main/java/com/tsi/comm/server/service/TsiCommServerService.java

@@ -18,7 +18,6 @@ import lombok.extern.slf4j.Slf4j;
 import org.springframework.stereotype.Service;
 
 import java.util.List;
-import java.util.Map;
 
 @Slf4j
 @Getter
@@ -85,13 +84,13 @@ public class TsiCommServerService {
         int idx = 0;
         List<NodeVo> objLists = this.databaseMapper.getNodeInfoList();
         for (NodeVo obj : objLists) {
-            installed = obj.getUseYn().equals("Y");
+            installed = "Y".equals(obj.getUseYn());
             node = this.nodeManager.get(obj.getNodeId());
             if (node != null) {
                 node.setCheckInstalled(installed);
-                node.setSendTest(obj.getTestYn().equals("Y"));
-                node.setSendNode(obj.getNodeYn().equals("Y"));
-                node.setSendCvim(obj.getCvimYn().equals("Y"));
+                node.setSendTest("Y".equals(obj.getTestYn()));
+                node.setSendNode("Y".equals(obj.getNodeYn()));
+                node.setSendCvim("Y".equals(obj.getCvimYn()));
                 node.setRegistered(true);
             }
             else {

+ 11 - 9
tsi-comm-server/src/main/java/com/tsi/comm/server/tcp/handler/CvimServerPacketProcessHandler.java

@@ -111,14 +111,17 @@ public class CvimServerPacketProcessHandler extends SimpleChannelInboundHandler<
                 this.dbmsProcess.add(nodeIpAddrVo, nodeVo.getDbmsQIdx());
             }
 
-            NodeStatusVo status = new NodeStatusVo(AbstractDbmsVo.DBMS_NODE_STATUS);
-            status.setServerId(this.config.getServerId());
-            status.setNodeId(nodeId);
-            status.setStatus(1);
-            status.setIpAddr(NettyUtils.getRemoteIpAddress(ctx.channel()));
-
-            // 신호제어기 통신상태 업데이트
-            this.dbmsProcess.add(status, nodeVo.getDbmsQIdx());
+            if (nodeVo.isRegistered()) {
+                // DB에 등록된 교차로에 대해서 상태정보 업데이트
+                NodeStatusVo status = new NodeStatusVo(AbstractDbmsVo.DBMS_NODE_STATUS);
+                status.setServerId(this.config.getServerId());
+                status.setNodeId(nodeId);
+                status.setStatus(1);
+                status.setIpAddr(NettyUtils.getRemoteIpAddress(ctx.channel()));
+
+                // 신호제어기 통신상태 업데이트
+                this.dbmsProcess.add(status, nodeVo.getDbmsQIdx());
+            }
         }
 
         final long DUPLICATE_PACKET_INTERVAL_MS = 600;
@@ -199,7 +202,6 @@ public class CvimServerPacketProcessHandler extends SimpleChannelInboundHandler<
             log.error("Exception in pipeline for channel {}:", NettyUtils.getAddress(ctx.channel()), cause);
         }
 
-        // 문제가 발생한 채널은 닫아서 추가적인 문제를 방지합니다.
         ctx.close();
     }
 }

+ 196 - 0
tsi-comm-server/src/main/java/com/tsi/comm/server/tcp/handler/MdcLoggingHandler.java

@@ -0,0 +1,196 @@
+package com.tsi.comm.server.tcp.handler;
+
+import com.tsi.comm.server.xnet.NettyUtils;
+import io.netty.channel.ChannelHandlerContext;
+import io.netty.channel.ChannelPromise;
+import io.netty.handler.logging.LogLevel;
+import io.netty.handler.logging.LoggingHandler;
+import org.slf4j.MDC;
+
+import java.net.SocketAddress;
+
+public class MdcLoggingHandler extends LoggingHandler {
+
+    public MdcLoggingHandler(LogLevel level) {
+        super(level);
+    }
+
+    private void setMdc(ChannelHandlerContext ctx) {
+        final String remoteIpAddress = NettyUtils.getRemoteIpAddress(ctx.channel());
+        MDC.put("id", remoteIpAddress);
+    }
+
+    private void clearMdc() {
+        MDC.remove("id");
+    }
+
+    @Override
+    public void channelRegistered(ChannelHandlerContext ctx) throws Exception {
+        setMdc(ctx);
+        try {
+            super.channelRegistered(ctx);
+        } finally {
+            clearMdc();
+        }
+    }
+
+    @Override
+    public void channelUnregistered(ChannelHandlerContext ctx) throws Exception {
+        setMdc(ctx);
+        try {
+            super.channelUnregistered(ctx);
+        } finally {
+            clearMdc();
+        }
+    }
+
+    @Override
+    public void channelActive(ChannelHandlerContext ctx) throws Exception {
+        setMdc(ctx);
+        try {
+            super.channelActive(ctx);
+        } finally {
+            clearMdc();
+        }
+    }
+
+    @Override
+    public void channelInactive(ChannelHandlerContext ctx) throws Exception {
+        setMdc(ctx);
+        try {
+            super.channelInactive(ctx);
+        } finally {
+            clearMdc();
+        }
+    }
+
+    @Override
+    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
+        setMdc(ctx);
+        try {
+            super.channelRead(ctx, msg);
+        } finally {
+            clearMdc();
+        }
+    }
+
+    @Override
+    public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
+        setMdc(ctx);
+        try {
+            super.channelReadComplete(ctx);
+        } finally {
+            clearMdc();
+        }
+    }
+
+    @Override
+    public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
+        setMdc(ctx);
+        try {
+            super.userEventTriggered(ctx, evt);
+        } finally {
+            clearMdc();
+        }
+    }
+
+    @Override
+    public void channelWritabilityChanged(ChannelHandlerContext ctx) throws Exception {
+        setMdc(ctx);
+        try {
+            super.channelWritabilityChanged(ctx);
+        } finally {
+            clearMdc();
+        }
+    }
+
+    @Override
+    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
+        setMdc(ctx);
+        try {
+            super.exceptionCaught(ctx, cause);
+        } finally {
+            clearMdc();
+        }
+    }
+
+    @Override
+    public void bind(ChannelHandlerContext ctx, SocketAddress localAddress, ChannelPromise promise) throws Exception {
+        setMdc(ctx);
+        try {
+            super.bind(ctx, localAddress, promise);
+        } finally {
+            clearMdc();
+        }
+    }
+
+    @Override
+    public void connect(ChannelHandlerContext ctx, SocketAddress remoteAddress, SocketAddress localAddress, ChannelPromise promise) throws Exception {
+        setMdc(ctx);
+        try {
+            super.connect(ctx, remoteAddress, localAddress, promise);
+        } finally {
+            clearMdc();
+        }
+    }
+
+    @Override
+    public void disconnect(ChannelHandlerContext ctx, ChannelPromise promise) throws Exception {
+        setMdc(ctx);
+        try {
+            super.disconnect(ctx, promise);
+        } finally {
+            clearMdc();
+        }
+    }
+
+    @Override
+    public void close(ChannelHandlerContext ctx, ChannelPromise promise) throws Exception {
+        setMdc(ctx);
+        try {
+            super.close(ctx, promise);
+        } finally {
+            clearMdc();
+        }
+    }
+
+    @Override
+    public void deregister(ChannelHandlerContext ctx, ChannelPromise promise) throws Exception {
+        setMdc(ctx);
+        try {
+            super.deregister(ctx, promise);
+        } finally {
+            clearMdc();
+        }
+    }
+
+    @Override
+    public void read(ChannelHandlerContext ctx) throws Exception {
+        setMdc(ctx);
+        try {
+            super.read(ctx);
+        } finally {
+            clearMdc();
+        }
+    }
+
+    @Override
+    public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {
+        setMdc(ctx);
+        try {
+            super.write(ctx, msg, promise);
+        } finally {
+            clearMdc();
+        }
+    }
+
+    @Override
+    public void flush(ChannelHandlerContext ctx) throws Exception {
+        setMdc(ctx);
+        try {
+            super.flush(ctx);
+        } finally {
+            clearMdc();
+        }
+    }
+}

+ 15 - 0
tsi-comm-server/src/main/java/com/tsi/comm/server/tcp/initializer/CvimServerInitializer.java

@@ -3,17 +3,22 @@ package com.tsi.comm.server.tcp.initializer;
 import com.tsi.comm.server.config.TsiCvimServerConfig;
 import com.tsi.comm.server.protocol.TsiCpuPacket;
 import com.tsi.comm.server.repository.TsiAlarmManager;
+import com.tsi.comm.server.repository.TsiNodeManager;
+import com.tsi.comm.server.repository.TsiSessionManager;
 import com.tsi.comm.server.tcp.codec.CvimServerByteBufMessageDecoder;
 import com.tsi.comm.server.tcp.codec.CvimServerEncoder;
 import com.tsi.comm.server.tcp.handler.CvimServerInboundMessageHandler;
 import com.tsi.comm.server.tcp.handler.CvimServerPacketProcessHandler;
+import com.tsi.comm.server.tcp.handler.MdcLoggingHandler;
 import com.tsi.comm.server.vo.TsiAlarmConfigVo;
+import com.tsi.comm.server.vo.TsiNodeVo;
 import com.tsi.comm.server.xnet.NettyUtils;
 import com.tsi.common.spring.SpringUtils;
 import io.netty.channel.Channel;
 import io.netty.channel.ChannelInitializer;
 import io.netty.channel.ChannelPipeline;
 import io.netty.handler.codec.LengthFieldBasedFrameDecoder;
+import io.netty.handler.logging.LogLevel;
 import io.netty.handler.timeout.IdleStateHandler;
 
 public class CvimServerInitializer extends ChannelInitializer<Channel> {
@@ -23,6 +28,7 @@ public class CvimServerInitializer extends ChannelInitializer<Channel> {
     private final CvimServerPacketProcessHandler cvimServerPacketProcessHandler;
     private final CvimServerInboundMessageHandler cvimServerInboundMessageHandler;
     private final CvimServerEncoder cvimServerEncoder;
+    private final TsiNodeManager nodeManager;
     private int readerIdleTimeSeconds;
 
     public CvimServerInitializer(TsiCvimServerConfig config) {
@@ -31,6 +37,7 @@ public class CvimServerInitializer extends ChannelInitializer<Channel> {
         this.cvimServerPacketProcessHandler = SpringUtils.getBean(CvimServerPacketProcessHandler.class);
         this.cvimServerInboundMessageHandler = SpringUtils.getBean(CvimServerInboundMessageHandler.class);
         this.cvimServerEncoder = SpringUtils.getBean(CvimServerEncoder.class);
+        this.nodeManager = SpringUtils.getBean(TsiNodeManager.class);
         TsiAlarmManager alarmManager = SpringUtils.getBean(TsiAlarmManager.class);
         this.readerIdleTimeSeconds = config.getReaderIdleTimeSeconds();
         TsiAlarmConfigVo vo = alarmManager.get(TsiAlarmConfigVo.COMM_02);
@@ -52,11 +59,19 @@ public class CvimServerInitializer extends ChannelInitializer<Channel> {
             return;
         }
 
+
+        TsiNodeVo nodeVo = this.nodeManager.getIpAddr(remoteIpAddress);
+
         // 교차로 제어기는 통신 연결시 IP로 체크하지 않고 처음 데이터를 수신했을 경우에 노드 ID를 확인 할 수 있다.
         // 공단에서 교차로 제어기의 IP 주소를 관리하지 않는다.
         IdleStateHandler idleStateHandler = new IdleStateHandler(this.readerIdleTimeSeconds, 0, 0);
         ChannelPipeline pipeline = channel.pipeline();
 
+        if (nodeVo != null && nodeVo.isTcpDump()) {
+            //pipeline.addLast(new LoggingHandler(LogLevel.INFO));
+            pipeline.addLast(TsiSessionManager.MDC_LOGGING_HANDLER_NAME, new MdcLoggingHandler(LogLevel.INFO));
+        }
+
         // 유휴 상태 감지(read timeout 처리)
         pipeline.addLast("idleStateHandler", idleStateHandler);
 

+ 9 - 7
tsi-comm-server/src/main/java/com/tsi/comm/server/tcp/service/ConnectionLifecycleService.java

@@ -67,13 +67,15 @@ public class ConnectionLifecycleService {
         }
 
         if (nodeVo != null) {
-            // DB 상태 업데이트 큐잉
-            NodeStatusVo status = new NodeStatusVo(AbstractDbmsVo.DBMS_NODE_STATUS);
-            status.setServerId(this.config.getServerId());
-            status.setNodeId(nodeVo.getNodeId());
-            status.setStatus(0);
-            status.setIpAddr(remoteIpAddr);
-            this.dbmsProcess.add(status, nodeVo.getDbmsQIdx());
+            if (nodeVo.isRegistered()) {
+                // DB에 등록된 교차로에 대해서 상태 업데이트 큐잉
+                NodeStatusVo status = new NodeStatusVo(AbstractDbmsVo.DBMS_NODE_STATUS);
+                status.setServerId(this.config.getServerId());
+                status.setNodeId(nodeVo.getNodeId());
+                status.setStatus(0);
+                status.setIpAddr(remoteIpAddr);
+                this.dbmsProcess.add(status, nodeVo.getDbmsQIdx());
+            }
 
             // Disconnected 패킷 큐잉
             TsiCpuDisconnected packet = new TsiCpuDisconnected(nodeVo.getNodeId(), ctx.channel());

+ 7 - 0
tsi-comm-server/src/main/java/com/tsi/comm/server/vo/TsiNodeVo.java

@@ -52,6 +52,7 @@ public class TsiNodeVo {
     private byte[] nodePacket = new byte[1];
 
     private boolean dump;
+    private boolean tcpDump;
     private int idx;
 
     private int pktQIdx;
@@ -59,6 +60,8 @@ public class TsiNodeVo {
     private int logQIdx;
 
     private int sigCount;
+    private boolean crcError;
+    private boolean packetError;
 
     // 빈번한 패킷 수신 처리를 위한 변수
     private final AtomicLong lastProcessTime = new AtomicLong(0);
@@ -87,10 +90,14 @@ public class TsiNodeVo {
         this.disconnectTm = 0;
         this.lastCommTm = 0;
         this.dump = false;
+        this.tcpDump = false;
         this.idx = 0;
         this.pktQIdx = 0;
         this.dbmsQIdx = 0;
         this.logQIdx = 0;
+        this.sigCount = 0;
+        this.crcError = false;
+        this.packetError = false;
     }
 
     public void initPacket() {

+ 4 - 15
tsi-comm-server/src/main/resources/logback-spring.xml

@@ -97,12 +97,14 @@
 
     <logger name="com.tsi.comm.server.process.packet" level="INFO" additivity="false">
         <appender-ref ref="FILE_PROCESS"/>
-<!--        <appender-ref ref="FILE_LOG"/>-->
         <appender-ref ref="FILE_ERROR"/>
     </logger>
     <logger name="com.tsi.comm.server.process.logging" level="INFO" additivity="false">
         <appender-ref ref="FILE_PROCESS"/>
-<!--        <appender-ref ref="FILE_LOG"/>-->
+        <appender-ref ref="FILE_ERROR"/>
+    </logger>
+    <logger name="com.tsi.comm.server.tcp.handler.MdcLoggingHandler" level="INFO" additivity="false">
+        <appender-ref ref="FILE_PROCESS"/>
         <appender-ref ref="FILE_ERROR"/>
     </logger>
 
@@ -188,19 +190,6 @@
         <appender-ref ref="FILE_ERROR"/>
     </logger>
 
-<!--    <appender name="FILE_REPORT" class="ch.qos.logback.core.rolling.RollingFileAppender">-->
-<!--        <file>${LOG_PATH}${LOG_FILE_NAME_REPORT}</file>-->
-<!--        <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">-->
-<!--            <charset>${LOG_CHARSET}</charset>-->
-<!--            <pattern>${LOG_PATTERN_FILE}</pattern>-->
-<!--        </encoder>-->
-<!--        <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">-->
-<!--            <fileNamePattern>${LOG_BACKUP_PATH}Schedule/${LOG_FILE_NAME_REPORT}.${LOG_FILE_NAME_PATTERN}</fileNamePattern>-->
-<!--            <maxFileSize>${MAX_FILESIZE}</maxFileSize>-->
-<!--            <maxHistory>${MAX_HISTORY}</maxHistory>-->
-<!--        </rollingPolicy>-->
-<!--    </appender>-->
-
     <appender name="FILE_REPORT" class="ch.qos.logback.classic.sift.SiftingAppender">
         <discriminator>
             <key>filename</key>

+ 44 - 17
tsi-common/build.gradle

@@ -1,10 +1,14 @@
 plugins {
     id 'java'
+    id 'maven-publish'
 }
 
 group = 'com.tsi'
 version = '0.0.1'
 
+sourceCompatibility = '1.8'
+targetCompatibility = '1.8'
+
 repositories {
     mavenCentral()
 }
@@ -26,35 +30,58 @@ dependencies {
     testImplementation 'org.junit.jupiter:junit-jupiter'
 }
 
-processResources {
-    enabled = false
-}
+//processResources {
+//    enabled = false
+//}
 
 bootJar {
     enabled = false
 }
 
-jar {
-    enabled = true
-}
+//jar {
+//    enabled = true
+//}
 
 test {
     useJUnitPlatform()
 }
-
-tasks.register('runInstallJarLibrary', Exec) {
-    doFirst {
-        println "tsi-common library install mvn repository..."
-        def os = org.gradle.internal.os.OperatingSystem.current()
-        workingDir = file('.')
-        if (os.isWindows()) {
-            commandLine 'cmd', '/C', 'start', 'install.bat'
-        } else {
-            commandLine 'sh', './install.sh'
+publishing {
+    publications {
+        mavenJava(MavenPublication) {
+            from components.java
         }
     }
+    repositories {
+        maven {
+            name = "customLocalRepo"
+            println "its-cluster library install mvn repository..."
+            def repoPath = org.gradle.internal.os.OperatingSystem.current().isWindows() ?
+                    "C:/java/repository" :
+                    "/Users/openvalue/Projects/java/repository"
+            url = uri(repoPath)
+        }
+
+        /*
+         * 참고: 더 표준적인 방법은 모든 프로젝트가 공유하는 기본 로컬 Maven 저장소(~/.m2/repository)를 사용하는 것입니다.
+         * 그렇게 하려면 위 'maven' 블록 대신 아래 'mavenLocal()'을 사용하면 됩니다.
+         * mavenLocal()
+         */
+    }
 }
-jar.finalizedBy runInstallJarLibrary
+
+//tasks.register('runInstallJarLibrary', Exec) {
+//    doFirst {
+//        println "tsi-common library install mvn repository..."
+//        def os = org.gradle.internal.os.OperatingSystem.current()
+//        workingDir = file('.')
+//        if (os.isWindows()) {
+//            commandLine 'cmd', '/C', 'start', 'install.bat'
+//        } else {
+//            commandLine 'sh', './install.sh'
+//        }
+//    }
+//}
+//jar.finalizedBy runInstallJarLibrary
 
 compileJava.options.encoding = 'UTF-8'
 tasks.withType(JavaCompile).configureEach {

+ 64 - 0
tsi-common/build.gradle.old

@@ -0,0 +1,64 @@
+plugins {
+    id 'java'
+}
+
+group = 'com.tsi'
+version = '0.0.1'
+
+repositories {
+    mavenCentral()
+}
+
+dependencies {
+    // lombok 라이브러리 추가 시작
+    compileOnly 'org.projectlombok:lombok'
+    annotationProcessor 'org.projectlombok:lombok'
+    implementation 'org.springframework.boot:spring-boot-starter-web'
+
+    testImplementation 'org.springframework.boot:spring-boot-starter-test'
+    testCompileOnly 'org.projectlombok:lombok'
+    testAnnotationProcessor 'org.projectlombok:lombok'
+    // lombok 라이브러리 추가 끝
+
+    implementation 'org.springframework.boot:spring-boot-starter-aop'
+
+    testImplementation platform('org.junit:junit-bom:5.10.0')
+    testImplementation 'org.junit.jupiter:junit-jupiter'
+}
+
+processResources {
+    enabled = false
+}
+
+bootJar {
+    enabled = false
+}
+
+jar {
+    enabled = true
+}
+
+test {
+    useJUnitPlatform()
+}
+
+tasks.register('runInstallJarLibrary', Exec) {
+    doFirst {
+        println "tsi-common library install mvn repository..."
+        def os = org.gradle.internal.os.OperatingSystem.current()
+        workingDir = file('.')
+        if (os.isWindows()) {
+            commandLine 'cmd', '/C', 'start', 'install.bat'
+        } else {
+            commandLine 'sh', './install.sh'
+        }
+    }
+}
+jar.finalizedBy runInstallJarLibrary
+
+compileJava.options.encoding = 'UTF-8'
+tasks.withType(JavaCompile).configureEach {
+    options.compilerArgs << '-Xlint:unchecked'
+    options.deprecation = true
+    options.encoding = 'UTF-8'
+}

+ 29 - 3
tsi-common/src/main/java/com/tsi/common/utils/StringUtils.java

@@ -1,9 +1,9 @@
 package com.tsi.common.utils;
 
 import java.nio.charset.StandardCharsets;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.StringTokenizer;
+import java.util.*;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
 
 public class StringUtils {
 
@@ -60,4 +60,30 @@ public class StringUtils {
         return input.substring(0, charIndex);
     }
 
+    /**
+     * 주어진 문자열을 구분자로 분리하고, 각 요소의 앞뒤 공백을 제거한 후,
+     * 비어있지 않은 요소들만 스트림으로 반환합니다.
+     *
+     * @param str       분리할 문자열
+     * @param delimiter 구분자
+     * @return 처리된 문자열의 스트림
+     */
+    public static Stream<String> splitToStream(String str, String delimiter) {
+        // 입력이 null이거나 비어있으면, 빈 스트림을 반환하여 NullPointerException을 방지합니다.
+        if (str == null || str.trim().isEmpty()) {
+            return Stream.empty();
+        }
+
+        return Arrays.stream(str.split(delimiter)) // 1. 쉼표로 문자열 배열을 생성하고 스트림으로 변환
+                .map(String::trim)            // 2. 각 요소의 앞뒤 공백 제거
+                .filter(s -> !s.isEmpty());   // 3. 빈 문자열 요소는 필터링
+    }
+
+    /**
+     * splitToStream을 사용하여 바로 Set으로 수집하는 편의 메서드입니다.
+     */
+    public static Set<String> splitToSet(String str, String delimiter) {
+        return splitToStream(str, delimiter).collect(Collectors.toSet());
+    }
+
 }