shjung 2 years ago
parent
commit
2ebe4f7d42
24 changed files with 1072 additions and 45 deletions
  1. 12 8
      src/main/java/com/its/cctv/CctvCommServerApplication.java
  2. 65 1
      src/main/java/com/its/cctv/config/RunningConfig.java
  3. 18 6
      src/main/java/com/its/cctv/dao/mapper/batch/CctvCtlrDao.java
  4. 1 0
      src/main/java/com/its/cctv/entity/TbCctvCtlr.java
  5. 1 1
      src/main/java/com/its/cctv/service/CctvCtlrService.java
  6. 8 8
      src/main/java/com/its/cctv/ui/CtlrSttsTableModel.java
  7. 8 1
      src/main/java/com/its/cctv/ui/MainUI.java
  8. 3 3
      src/main/java/com/its/cctv/xnettcp/cctv/CctvTcpClient.java
  9. 1 1
      src/main/java/com/its/cctv/xnettcp/cctv/CctvTcpClientCommService.java
  10. 3 3
      src/main/java/com/its/cctv/xnettcp/cctv/codec/CctvTcpClientDecoder.java
  11. 5 5
      src/main/java/com/its/cctv/xnettcp/cctv/process/CctvDataProcess.java
  12. 6 6
      src/main/java/com/its/cctv/xnettcp/cctv/process/Job_StateRes.java
  13. 1 1
      src/main/java/com/its/cctv/xnettcp/cctv/protocol/CctvResFramePacket.java
  14. 56 0
      src/main/java/com/its/cctv/xnettcp/cctvserver/CctvTcpServerInitializer.java
  15. 114 0
      src/main/java/com/its/cctv/xnettcp/cctvserver/CctvTcpServerService.java
  16. 131 0
      src/main/java/com/its/cctv/xnettcp/cctvserver/codec/CctvTcpServerDecoder.java
  17. 53 0
      src/main/java/com/its/cctv/xnettcp/cctvserver/codec/CctvTcpServerEncoder.java
  18. 198 0
      src/main/java/com/its/cctv/xnettcp/cctvserver/handler/CctvServerIdleStateConnectionHandler.java
  19. 156 0
      src/main/java/com/its/cctv/xnettcp/cctvserver/handler/CctvServerIdleStatePacketHandler.java
  20. 28 0
      src/main/java/com/its/cctv/xnettcp/cctvserver/handler/CctvServerPacketInboundHandler.java
  21. 24 0
      src/main/java/com/its/cctv/xnettcp/cctvserver/process/CctvServerData.java
  22. 161 0
      src/main/java/com/its/cctv/xnettcp/cctvserver/process/CctvServerDataProcess.java
  23. 17 0
      src/main/java/com/its/cctv/xnettcp/cctvserver/process/CctvServerDataTask.java
  24. 2 1
      src/main/resources/application.yml

+ 12 - 8
src/main/java/com/its/cctv/CctvCommServerApplication.java

@@ -12,6 +12,7 @@ import com.its.cctv.ui.JTextAreaOutputStream;
 import com.its.cctv.ui.MainUI;
 import com.its.cctv.xnettcp.cctv.CctvTcpClientCommService;
 import com.its.cctv.xnettcp.cctv.process.CctvDataProcess;
+import com.its.cctv.xnettcp.cctvserver.CctvTcpServerService;
 import com.its.cctv.xnettcp.center.CenterTcpServerService;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.beans.factory.DisposableBean;
@@ -125,7 +126,7 @@ public class CctvCommServerApplication implements CommandLineRunner, Application
                     }
                 });
                 frame.pack();
-                frame.setBounds(100, 100, 1000, 600);
+                frame.setBounds(100, 100, 1100, 600);
                 frame.setLocationRelativeTo(null);
                 frame.setVisible(true);
 
@@ -169,21 +170,24 @@ public class CctvCommServerApplication implements CommandLineRunner, Application
         ctlrService.loadDb();
         ctlrService.updateCtlrStts(true);
 
+        if (OS.isWindows()) {
+            MainUI UI = MainUI.getInstance();
+            if (UI != null) {
+                UI.LoadControllerInfo();
+            }
+        }
+
         CctvTcpClientCommService ctlrCommClientService = (CctvTcpClientCommService)AppUtils.getBean(CctvTcpClientCommService.class);
         ctlrCommClientService.run();
 
+        CctvTcpServerService dsrcTcpCommServerService = (CctvTcpServerService)AppUtils.getBean(CctvTcpServerService.class);
+        dsrcTcpCommServerService.run();
+
 //        CenterTcpServerService centerService = (CenterTcpServerService)AppUtils.getBean(CenterTcpServerService.class);
 //        centerService.run();
 
         // schedule enable
         applicationConfig.setStartSchedule(true);
-
-        if (OS.isWindows()) {
-            MainUI UI = MainUI.getInstance();
-            if (UI != null) {
-                UI.LoadControllerInfo();
-            }
-        }
     }
 
     public void terminateApplication() {

+ 65 - 1
src/main/java/com/its/cctv/config/RunningConfig.java

@@ -24,9 +24,73 @@ public class RunningConfig {
     private int writerIdleTime = 0;
     private int allIdleTime = 0;
 
+    private boolean dumpRecv = false;
+    private boolean dumpSend = false;
+    private String commBindingAddr = "";
+    private int commBindingPort = 7800;
+    private int commBacklog = 0;
+    private int commAcceptThreads = 0;
+    private int commWorkerThreads = 0;
+    private int commRcvBuf = 0;
+    private int commSndBuf = 0;
+    private int commReaderIdleTimeSeconds = 0;
+    private int commWriterIdleTimeSeconds = 0;
+    private int commAllIdleTimeSeconds = 0;
+    private int commConnectTimeoutSeconds = 0;
+    private int commMaxConnection = 0;
+    private int commPacketWorkers = 0;
+    private int commLoggingWorkers = 0;
+    private int commDbmsWorkers = 1;
+
     @PostConstruct
     private void init() {
-        log.info("{}", this);
+        int DEFAULT_EVENT_THREADS = Runtime.getRuntime().availableProcessors();
+        if (this.commBindingAddr.equals("")) {
+            this.commBindingAddr = "0.0.0.0";
+        }
+
+        if (this.commBacklog == 0) {
+            this.commBacklog = 64;
+        }
+
+        if (this.commAcceptThreads == 0) {
+            this.commAcceptThreads = DEFAULT_EVENT_THREADS * 2;
+        }
+
+        if (this.commWorkerThreads == 0) {
+            this.commWorkerThreads = DEFAULT_EVENT_THREADS * 2;
+        }
+
+        if (this.commRcvBuf == 0) {
+            this.commRcvBuf = 32768;
+        }
+        if (this.commSndBuf == 0) {
+            this.commSndBuf = 32768;
+        }
+
+        if (this.commReaderIdleTimeSeconds == 0) {
+            this.commReaderIdleTimeSeconds = 10;
+        }
+
+        if (this.commMaxConnection == 0) {
+            this.commMaxConnection = 24;
+        }
+        if (this.commPacketWorkers == 0) {
+            this.commPacketWorkers = DEFAULT_EVENT_THREADS / 2;
+            if (this.commPacketWorkers == 0) this.commPacketWorkers = 1;
+        }
+        if (this.commLoggingWorkers == 0) {
+            this.commLoggingWorkers = DEFAULT_EVENT_THREADS / 4;
+            if (this.commLoggingWorkers == 0) this.commLoggingWorkers = 1;
+        }
+        if (this.commDbmsWorkers == 0) {
+            this.commDbmsWorkers = DEFAULT_EVENT_THREADS / 4;
+            if (this.commDbmsWorkers == 0) this.commDbmsWorkers = 1;
+        }
+
+        // IDLE TIME OUT CHECK HERE...
+
+        log.info("{}", this);        log.info("{}", this);
     }
 
 }

+ 18 - 6
src/main/java/com/its/cctv/dao/mapper/batch/CctvCtlrDao.java

@@ -46,29 +46,41 @@ public class CctvCtlrDao extends BatchDaoService {
     }
 
     public int updateStts(List<TbCctvCtlrStts> req, boolean isHistory) {
-        log.info("{}.updateStts: START. {} EA. History {}", this.serviceName, req.size(), isHistory);
+        if (req.size() > 1) {
+            log.info("{}.updateStts: START. {} EA. History {}", this.serviceName, req.size(), isHistory);
+        }
         Elapsed elapsed = new Elapsed();
         this.mapper = this.mapperName + "updateCtlrStts";
         int total = updateBatch(this.mapper, getSttsList(req));
-        log.info("{}.updateStts: ..END. {} EA. {} ms.", this.serviceName, total, elapsed.milliSeconds());
+        if (req.size() > 1) {
+            log.info("{}.updateStts: ..END. {} EA. {} ms.", this.serviceName, total, elapsed.milliSeconds());
+        }
         return total;
     }
 
     public int insertStts(List<TbCctvCtlrStts> req) {
-        log.info("{}.insertStts: START. {} EA.", this.serviceName, req.size());
+        if (req.size() > 1) {
+            log.info("{}.insertStts: START. {} EA.", this.serviceName, req.size());
+        }
         Elapsed elapsed = new Elapsed();
         this.mapper = this.mapperName + "insertCtlrSttsHs";
         int total = insertBatch(this.mapper, getSttsList(req));
-        log.info("{}.insertStts: ..END. {} EA. {} ms.", this.serviceName, total, elapsed.milliSeconds());
+        if (req.size() > 1) {
+            log.info("{}.insertStts: ..END. {} EA. {} ms.", this.serviceName, total, elapsed.milliSeconds());
+        }
         return total;
     }
 
     public int updateParam(List<HashMap<String, Object>> req) {
-        log.info("{}.updateParam: START. {} EA.", this.serviceName, req.size());
+        if (req.size() > 1) {
+            log.info("{}.updateParam: START. {} EA.", this.serviceName, req.size());
+        }
         Elapsed elapsed = new Elapsed();
         this.mapper = this.mapperName + "updateCtlrParam";
         int total = updateBatch(this.mapper, req);
-        log.info("{}.updateParam: ..END. {} EA. {} ms.", this.serviceName, total, elapsed.milliSeconds());
+        if (req.size() > 1) {
+            log.info("{}.updateParam: ..END. {} EA. {} ms.", this.serviceName, total, elapsed.milliSeconds());
+        }
         return total;
     }
 

+ 1 - 0
src/main/java/com/its/cctv/entity/TbCctvCtlr.java

@@ -120,6 +120,7 @@ public class TbCctvCtlr {
 		this.paramRequest = true;
 		getStts().initStts(true);
 		setConnectTm();
+		this.netState = NET.LOGINED;
 	}
 
 	public synchronized void channelLogin(Channel channel) {

+ 1 - 1
src/main/java/com/its/cctv/service/CctvCtlrService.java

@@ -153,7 +153,7 @@ public class CctvCtlrService {
                 ByteBuffer sendBuffer = obj.getReqState().getByteBuffer();
                 obj.sendData(sendBuffer, 0, "cctv_StateReq");
             } else {
-                log.info("[{}]. cctv_StateReq: Request Failed. Not Connected. {}", obj.getCCTV_CTLR_ID(), obj.getCCTV_CTLR_IP());
+                //log.info("[{}]. cctv_StateReq: Request Failed. Not Connected. {}", obj.getCCTV_CTLR_ID(), obj.getCCTV_CTLR_IP());
             }
             MDC.remove(obj.getLogKey());
             MDC.clear();

+ 8 - 8
src/main/java/com/its/cctv/ui/CtlrSttsTableModel.java

@@ -102,24 +102,24 @@ public class CtlrSttsTableModel extends AbstractTableModel {
                 heater = "-?-";
                 temper = "-?-";
                 video = "-?-";
-                if (stts.getCBOX_DOOR_STTS_CD().equals("CDS0")) {
+                if ("CDS0".equals(stts.getCBOX_DOOR_STTS_CD())) {
                     door = "닫힘";
-                } else if (stts.getCBOX_DOOR_STTS_CD().equals("CDS1")) {
+                } else if ("CDS1".equals(stts.getCBOX_DOOR_STTS_CD())) {
                     door = "열림";
                 }
-                if (stts.getFAN_STTS_CD().equals("PAS0")) {
+                if ("PAS0".equals(stts.getFAN_STTS_CD())) {
                     fan = "가동";
-                } else if (stts.getFAN_STTS_CD().equals("PAS1")) {
+                } else if ("PAS1".equals(stts.getFAN_STTS_CD())) {
                     fan = "중지";
                 }
-                if (stts.getHETR_STTS_CD().equals("HTS0")) {
+                if ("HTS0".equals(stts.getHETR_STTS_CD())) {
                     heater = "가동";
-                } else if (stts.getHETR_STTS_CD().equals("HTS1")) {
+                } else if ("HTS1".equals(stts.getHETR_STTS_CD())) {
                     heater = "중지";
                 }
-                if (stts.getVIDEO_INPUT().equals("VDI0")) {
+                if ("VDI0".equals(stts.getVIDEO_INPUT())) {
                     video = "정상";
-                } else if (stts.getHETR_STTS_CD().equals("VDI1")) {
+                } else if ("VDI1".equals(stts.getVIDEO_INPUT())) {
                     video = "이상";
                 }
                 temper = String.valueOf(stts.getCBOX_TMPR());

+ 8 - 1
src/main/java/com/its/cctv/ui/MainUI.java

@@ -416,7 +416,7 @@ public class MainUI {
         getColumnModel.getColumn(9).setPreferredWidth(50);  //  "히터",
         getColumnModel.getColumn(10).setPreferredWidth(50); //  "온도",
         getColumnModel.getColumn(11).setPreferredWidth(50); //  "Video",
-        getColumnModel.getColumn(12).setPreferredWidth(145); //  "sttsText",
+        getColumnModel.getColumn(12).setPreferredWidth(170); //  "sttsText",
 
         getColumnModel.getColumn(13).setPreferredWidth(125);
         getColumnModel.getColumn(14).setPreferredWidth(125);
@@ -449,6 +449,13 @@ public class MainUI {
         }
         lblTotal.setText(" " + ctlrTotal + " ");
         lblError.setText(" " + ctlrError + " ");
+
+        if (this.isUpdatable) {
+            CtlrSttsTableModel tableModel = (CtlrSttsTableModel) tblCtlrList.getModel();
+            if (tableModel != null) {
+                tableModel.fireTableDataChanged();
+            }
+        }
     }
 
     public void LoadControllerInfo() {

+ 3 - 3
src/main/java/com/its/cctv/xnettcp/cctv/CctvTcpClient.java

@@ -69,9 +69,9 @@ public class CctvTcpClient implements Callable<Object> {
                         ch.pipeline().addLast(new LoggingHandler(LogLevel.ERROR));
                     }
                     ch.pipeline().addLast("ctlrClientIdleHandler",    new CctvTcpClientIdleHandler(commConfig.getReaderIdleTime(), commConfig.getWriterIdleTime(), commConfig.getAllIdleTime(), TimeUnit.SECONDS));
-                    ch.pipeline().addLast("ctlrClientDecoder",        new CctvTcpClientDecoder());            // Decoding handler
-                    ch.pipeline().addLast("ctlrClientInboundHandler", new CctvTcpClientInboundHandler(cctvDataProcess));     // Packet Inbound handler
-                    ch.pipeline().addLast("ctlrClientEncoder",        new CctvTcpClientEncoder());            // Encoding handler
+                    ch.pipeline().addLast("ctlrClientDecoder",        new CctvTcpClientDecoder());                          // Decoding handler
+                    ch.pipeline().addLast("ctlrClientInboundHandler", new CctvTcpClientInboundHandler(cctvDataProcess));    // Packet Inbound handler
+                    ch.pipeline().addLast("ctlrClientEncoder",        new CctvTcpClientEncoder());                          // Encoding handler
                 }
             });
         }

+ 1 - 1
src/main/java/com/its/cctv/xnettcp/cctv/CctvTcpClientCommService.java

@@ -45,7 +45,7 @@ public class CctvTcpClientCommService {
          */
         for (Map.Entry<String, TbCctvCtlr> e : AppRepository.getInstance().getCtlrMap().entrySet()) {
             TbCctvCtlr obj = e.getValue();
-            if (StringUtils.equals("N", obj.getDEL_YN())) {
+            if (StringUtils.equals("N", obj.getDEL_YN()) && obj.getCCTV_CTLR_PORT() != commConfig.getCommBindingPort()) {
                 CctvTcpClient ctlrClient = new CctvTcpClient(obj, this.commConfig, this.cctvDataProcess, this.bootstrapFactory);
                 this.clientTasks.add(ctlrClient);
             }

+ 3 - 3
src/main/java/com/its/cctv/xnettcp/cctv/codec/CctvTcpClientDecoder.java

@@ -29,13 +29,13 @@ public class CctvTcpClientDecoder extends ByteToMessageDecoder {
 
         TbCctvCtlr obj = AppRepository.getInstance().getCtlrIpMap().get(ipAddress);
         if (obj == null) {
-            log.error("CtlrTcpClientDecoder.decode: Unknown Controller IP: {}. will be close.", ipAddress);
+            log.error("CctvTcpClientDecoder.decode: Unknown Controller IP: {}. will be close.", ipAddress);
             CctvTcpClientIdleHandler.disconnectChannel(channel);
             return;
         }
 
         if (!channel.isOpen() || !channel.isActive()) {
-            log.error("[{}]. RECV: CtlrTcpClientDecoder.decode: isOpen: {}, isActive: {}. [{}]", obj.getCCTV_CTLR_ID(), channel.isOpen(), channel.isActive(), obj.getLogKey());
+            log.error("[{}]. RECV: CctvTcpClientDecoder.decode: isOpen: {}, isActive: {}. [{}]", obj.getCCTV_CTLR_ID(), channel.isOpen(), channel.isActive(), obj.getLogKey());
             CctvTcpClientIdleHandler.disconnectChannel(channel);
             return;
         }
@@ -43,7 +43,7 @@ public class CctvTcpClientDecoder extends ByteToMessageDecoder {
         MDC.put("id", obj.getLogKey());
         try {
             int readableBytes = byteBuf.readableBytes();
-            log.info("[{}]. RECV: ReadableBytes: {} Bytes, ReaderIndex: {}", obj.getCCTV_CTLR_ID(), readableBytes, byteBuf.readerIndex());
+            //log.info("[{}]. RECV: ReadableBytes: {} Bytes, ReaderIndex: {}", obj.getCCTV_CTLR_ID(), readableBytes, byteBuf.readerIndex());
 
             if (obj.isDump()) {
                 byte[] debugBytes = new byte[byteBuf.readableBytes()];

+ 5 - 5
src/main/java/com/its/cctv/xnettcp/cctv/process/CctvDataProcess.java

@@ -72,11 +72,11 @@ public class CctvDataProcess {
             byte opCode = framePacket.getOpCode();
             int address = framePacket.getHead().getAddress();
             if (address != obj.getCCTV_CTLR_LOCAL_NO()) {
-                log.error("ProtocolDataProcess.process: ID: {}, ChannelNo, Controller No Miss Matched: [{}]-[{}]",
-                        obj.getCCTV_CTLR_NMBR(), address, obj.getCCTV_CTLR_LOCAL_NO());
+//                log.warn("ProtocolDataProcess.process: ID: {}, ChannelNo, Controller No Miss Matched: [{}]-[{}]",
+//                        obj.getCCTV_CTLR_NMBR(), address, obj.getCCTV_CTLR_LOCAL_NO());
                 //return;
             }
-            log.info("ProtocolDataProcess.process: {}, {}, ThreadId: {}", ipAddress, CctvProtocol.getOpCodeName(opCode), Thread.currentThread().getId());
+            //log.info("ProtocolDataProcess.process: {}, {}, ThreadId: {}", ipAddress, CctvProtocol.getOpCodeName(opCode), Thread.currentThread().getId());
             if (!checkStatus(obj, framePacket)) {
                 return;
             }
@@ -150,8 +150,8 @@ public class CctvDataProcess {
         int Fan        = SysUtils.getBitValue(stts1, 2);
         int Heater     = SysUtils.getBitValue(stts1, 3);
 
-        log.info("[{}]. RECV: checkStatus. Door:{}, Video:{}, Fan:{}, Heater:{}",
-                obj.getCCTV_CTLR_ID(), DoorOpen, VideoInput, Fan, Heater);
+        log.info("[{}]. RECV: checkStatus. Door:{}, Video:{}, Fan:{}, Heater:{}. {}",
+                obj.getCCTV_CTLR_ID(), DoorOpen, VideoInput, Fan, Heater, stts1);
 
         String CBOX_DOOR_STTS_CD  = (DoorOpen   == 0) ? "CDS0" : "CDS1";
         String VIDEO_INPUT	      = (VideoInput == 0) ? "VDI0" : "VDI1";

+ 6 - 6
src/main/java/com/its/cctv/xnettcp/cctv/process/Job_StateRes.java

@@ -42,12 +42,12 @@ public class Job_StateRes implements JobProtocol {
 		obj.getStts().setZOOM(zoom);
 		obj.getStts().setFOCUS(focus);
 
-		if (obj.isParamRequest()) {
-			// 파라미터 요청
-			ByteBuffer sendBuffer = obj.getReqParam().getByteBuffer();
-			obj.sendData(sendBuffer, 0, "cctv_ParamReq");
-			obj.setParamRequest(false);
-		}
+//		if (obj.isParamRequest()) {
+//			// 파라미터 요청
+//			ByteBuffer sendBuffer = obj.getReqParam().getByteBuffer();
+//			obj.sendData(sendBuffer, 0, "cctv_ParamReq");
+//			obj.setParamRequest(false);
+//		}
 
 		MainUI mainUI = MainUI.getInstance();
 		if (mainUI != null) {

+ 1 - 1
src/main/java/com/its/cctv/xnettcp/cctv/protocol/CctvResFramePacket.java

@@ -26,7 +26,7 @@ public class CctvResFramePacket {
 
             // BODY
             int bodyLength = size - CctvResFrameHead.SIZE - CctvResFrameTail.SIZE;
-            log.info("FrameSize: {}, BodyLength: {}", size, bodyLength);
+            //log.info("FrameSize: {}, BodyLength: {}", size, bodyLength);
             if (bodyLength > 0) {
                 this.body = new byte[bodyLength];
                 System.arraycopy(packet, CctvResFrameHead.SIZE, this.body, 0, bodyLength);

+ 56 - 0
src/main/java/com/its/cctv/xnettcp/cctvserver/CctvTcpServerInitializer.java

@@ -0,0 +1,56 @@
+package com.its.cctv.xnettcp.cctvserver;
+
+import com.its.cctv.config.RunningConfig;
+import com.its.cctv.xnettcp.cctv.codec.CctvTcpClientDecoder;
+import com.its.cctv.xnettcp.cctv.codec.CctvTcpClientEncoder;
+import com.its.cctv.xnettcp.cctv.handler.CctvTcpClientInboundHandler;
+import com.its.cctv.xnettcp.cctv.process.CctvDataProcess;
+import com.its.cctv.xnettcp.cctvserver.codec.CctvTcpServerEncoder;
+import com.its.cctv.xnettcp.cctvserver.handler.CctvServerIdleStateConnectionHandler;
+import com.its.cctv.xnettcp.cctvserver.handler.CctvServerPacketInboundHandler;
+import io.netty.channel.Channel;
+import io.netty.channel.ChannelInitializer;
+import io.netty.channel.ChannelPipeline;
+import io.netty.handler.logging.LogLevel;
+import io.netty.handler.logging.LoggingHandler;
+import io.netty.handler.timeout.IdleStateHandler;
+import lombok.RequiredArgsConstructor;
+
+import java.util.concurrent.TimeUnit;
+
+@RequiredArgsConstructor
+public class CctvTcpServerInitializer extends ChannelInitializer<Channel> {
+
+    private final RunningConfig commConfig;
+    private final CctvServerPacketInboundHandler dsrcAsn1ServerPacketInboundHandler;
+    private final CctvTcpServerEncoder dsrcTcpServerEncoder;
+    private final CctvDataProcess cctvDataProcess;
+
+    @Override
+    protected void initChannel(Channel channel) throws Exception {
+        IdleStateHandler idleStateHandler = new IdleStateHandler(
+                this.commConfig.getCommReaderIdleTimeSeconds(),
+                this.commConfig.getCommWriterIdleTimeSeconds(),
+                this.commConfig.getCommAllIdleTimeSeconds(),
+                TimeUnit.SECONDS);
+
+        CctvServerIdleStateConnectionHandler connectionHandler = new CctvServerIdleStateConnectionHandler(this.commConfig);
+
+        ChannelPipeline pipeline = channel.pipeline();
+        pipeline.addLast(new LoggingHandler(LogLevel.INFO));
+//        pipeline.addLast("ctlrClientIdleHandler",    new CctvTcpClientIdleHandler(commConfig.getReaderIdleTime(), commConfig.getWriterIdleTime(), commConfig.getAllIdleTime(), TimeUnit.SECONDS));
+//        pipeline.addLast("dsrcAsn1ServerConnectionHandler", connectionHandler);
+        pipeline.addLast("serverConnectionIdleStateHandler", idleStateHandler);
+        pipeline.addLast("serverConnectionHandler", connectionHandler);
+        pipeline.addLast("ctlrClientDecoder",        new CctvTcpClientDecoder());                          // Decoding handler
+        pipeline.addLast("ctlrClientInboundHandler", new CctvTcpClientInboundHandler(cctvDataProcess));    // Packet Inbound handler
+        pipeline.addLast("ctlrClientEncoder",        new CctvTcpClientEncoder());                          // Encoding handler
+
+//        pipeline.addLast("serverConnectionIdleStateHandler", idleStateHandler);
+//        pipeline.addLast("dsrcAsn1ServerConnectionHandler", connectionHandler);
+//        pipeline.addLast("dsrcAsn1ServerDecoder", new CctvTcpServerDecoder(this.commConfig));  // Decoding handler
+//        pipeline.addLast("dsrcAsn1ServerPacketInboundHandler", this.dsrcAsn1ServerPacketInboundHandler); // packet distribute handler add
+//        pipeline.addLast("dsrcAsn1ServerEncoder", this.dsrcTcpServerEncoder);  // Encoding handler
+    }
+
+}

+ 114 - 0
src/main/java/com/its/cctv/xnettcp/cctvserver/CctvTcpServerService.java

@@ -0,0 +1,114 @@
+package com.its.cctv.xnettcp.cctvserver;
+
+import com.its.app.utils.NettyUtils;
+import com.its.app.utils.OS;
+import com.its.cctv.config.RunningConfig;
+import com.its.cctv.xnettcp.cctv.process.CctvDataProcess;
+import com.its.cctv.xnettcp.cctvserver.codec.CctvTcpServerEncoder;
+import com.its.cctv.xnettcp.cctvserver.handler.CctvServerPacketInboundHandler;
+import io.netty.bootstrap.ServerBootstrap;
+import io.netty.channel.ChannelFuture;
+import io.netty.channel.ChannelOption;
+import io.netty.channel.EventLoopGroup;
+import io.netty.channel.epoll.Epoll;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Service;
+
+@Slf4j
+@RequiredArgsConstructor
+@Service
+public class CctvTcpServerService {
+
+    private final RunningConfig config;
+    private final CctvTcpServerEncoder tcpServerEncoder;
+    private final CctvServerPacketInboundHandler packetInboundHandler;
+    private final CctvDataProcess cctvDataProcess;
+
+    private EventLoopGroup acceptGroups = null;
+    private EventLoopGroup workerGroups = null;
+    private ServerBootstrap serverBootstrap = null;
+    private ChannelFuture channelFuture = null;
+
+    public void run() {
+        if (!OS.isWindows()) {
+            if (!Epoll.isAvailable()) {
+                Epoll.unavailabilityCause().printStackTrace();
+            }
+        }
+        if (NettyUtils.isEpollAvailable()) {
+            log.info("서버가 리눅스 EPOLL 모드에서 실행됩니다.");
+        }
+        else {
+            log.info("서버가 윈도우 NIO 모드에서 실행됩니다.");
+        }
+
+        this.serverBootstrap = createBootstrap();
+
+        log.info("*********************************************************************************");
+        log.info("**                   CCTV Communication Server Information                    **");
+        log.info("**     bindAddress: {}", this.config.getCommBindingAddr());
+        log.info("**      listenPort: {}", this.config.getCommBindingPort());
+        log.info("**         backlog: {}", this.config.getCommBacklog());
+        log.info("**   acceptThreads: {}", this.config.getCommAcceptThreads());
+        log.info("**   workerThreads: {}", this.config.getCommWorkerThreads());
+        log.info("*********************************************************************************");
+
+        try {
+            if (this.config.getCommBindingAddr().equals("0.0.0.0")) {
+                this.channelFuture = this.serverBootstrap.bind(this.config.getCommBindingPort());
+            }
+            else {
+                this.channelFuture = this.serverBootstrap.bind(this.config.getCommBindingAddr(), config.getCommBindingPort());
+            }
+        }
+        catch (Exception e) {
+            log.error("start, InterruptedException");
+            this.acceptGroups.shutdownGracefully();
+            this.workerGroups.shutdownGracefully();
+        }
+    }
+
+    public void stop() {
+        try {
+            this.acceptGroups.shutdownGracefully().sync();
+            this.workerGroups.shutdownGracefully().sync();
+            this.channelFuture.channel().closeFuture().sync();
+        } catch (InterruptedException e) {
+            log.error("stop, InterruptedException");
+        }
+    }
+
+    public ServerBootstrap createBootstrap() {
+        ServerBootstrap serverBootstrap = new ServerBootstrap();
+        EventLoopGroup acceptGroups;
+        EventLoopGroup workerGroups;
+
+        acceptGroups = NettyUtils.newEventLoopGroup(config.getCommAcceptThreads(), "Accept");
+        workerGroups = NettyUtils.newEventLoopGroup(config.getCommWorkerThreads(), "Worker");
+        serverBootstrap.channel(NettyUtils.getServerSocketChannel());
+        serverBootstrap.group(acceptGroups, workerGroups);
+
+        serverBootstrap.option(ChannelOption.AUTO_READ, true);
+        serverBootstrap.option(ChannelOption.SO_BACKLOG, config.getCommBacklog());
+        serverBootstrap.option(ChannelOption.SO_RCVBUF, config.getCommRcvBuf());
+        serverBootstrap.option(ChannelOption.SO_REUSEADDR, true);
+        serverBootstrap.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, config.getCommConnectTimeoutSeconds()*1000);
+
+        serverBootstrap.childOption(ChannelOption.SO_LINGER, 0);           // 4way-handshake 비활성
+        serverBootstrap.childOption(ChannelOption.SO_KEEPALIVE, false);    // KEEPALIVE 비활성(활성: true)
+        serverBootstrap.childOption(ChannelOption.SO_REUSEADDR, true);     // 소켓 재사용
+        serverBootstrap.childOption(ChannelOption.TCP_NODELAY, true);      // Nagle 알고리즘 비활성화
+
+        CctvTcpServerInitializer cctvTcpServerInitializer = new CctvTcpServerInitializer(
+                this.config,
+                this.packetInboundHandler,
+                this.tcpServerEncoder,
+                this.cctvDataProcess
+        );
+        serverBootstrap.childHandler(cctvTcpServerInitializer);
+
+        return serverBootstrap;
+    }
+
+}

+ 131 - 0
src/main/java/com/its/cctv/xnettcp/cctvserver/codec/CctvTcpServerDecoder.java

@@ -0,0 +1,131 @@
+package com.its.cctv.xnettcp.cctvserver.codec;
+
+import com.its.app.utils.NettyUtils;
+import com.its.app.utils.SysUtils;
+import com.its.cctv.config.RunningConfig;
+import com.its.cctv.entity.TbCctvCtlr;
+import com.its.cctv.global.AppRepository;
+import com.its.cctv.xnettcp.cctv.handler.CctvTcpClientIdleHandler;
+import com.its.cctv.xnettcp.cctv.protocol.CctvProtocol;
+import com.its.cctv.xnettcp.cctv.protocol.CctvResFramePacket;
+import com.its.cctv.xnettcp.cctvserver.handler.CctvServerIdleStatePacketHandler;
+import io.netty.buffer.ByteBuf;
+import io.netty.channel.Channel;
+import io.netty.channel.ChannelHandlerContext;
+import io.netty.handler.codec.ByteToMessageDecoder;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.slf4j.MDC;
+
+import java.util.List;
+
+import static com.its.cctv.xnettcp.cctv.protocol.CctvProtocol.cctv_DLE;
+
+@Slf4j
+@RequiredArgsConstructor
+public class CctvTcpServerDecoder extends ByteToMessageDecoder {
+
+    private final RunningConfig runningConfig;
+
+    @Override
+    protected void decode(ChannelHandlerContext ctx, ByteBuf byteBuf, List<Object> list) throws Exception {
+
+        String ipAddress = NettyUtils.getRemoteIpAddress(ctx.channel());
+        Channel channel = ctx.channel();
+        TbCctvCtlr obj = AppRepository.getInstance().getCtlrIpMap().get(ipAddress);
+        if (obj == null) {
+            log.error("CctvTcpServerDecoder.decode: Unknown Controller IP: {}. will be close.", ipAddress);
+            CctvServerIdleStatePacketHandler.disconnectChannel(null, channel);
+            return;
+        }
+
+        if (!channel.isOpen() || !channel.isActive()) {
+            log.error("CctvTcpServerDecoder.decode: {}, isOpen: {}, isActive: {}. [{}]", ipAddress, channel.isOpen(), channel.isActive(), obj.getLogKey());
+            //CctvServerIdleStatePacketHandler.disconnectChannel(obj, channel);
+            CctvTcpClientIdleHandler.disconnectChannel(channel);
+            return;
+        }
+
+        MDC.put("id", obj.getLogKey());
+        try {
+            int readableBytes = byteBuf.readableBytes();
+            //log.info("[{}]. RECV: ReadableBytes: {} Bytes, ReaderIndex: {}", obj.getCCTV_CTLR_ID(), readableBytes, byteBuf.readerIndex());
+
+            if (obj.isDump()) {
+                byte[] debugBytes = new byte[byteBuf.readableBytes()];
+                byteBuf.getBytes(byteBuf.readerIndex(), debugBytes);
+                log.info("[{}]. RECV: {} Bytes. {}", obj.getCCTV_CTLR_ID(), debugBytes.length, SysUtils.byteArrayToHex(debugBytes));
+            }
+
+            byteBuf.markReaderIndex();
+            byte[] recvBytes = new byte[readableBytes];
+            byte[] packets = new byte[readableBytes];
+            byteBuf.readBytes(recvBytes);
+
+            int stuffing = 0;
+            int msgSize = 0;        // 순수 데이터 사이즈
+            boolean foundDle = false;
+            boolean requiredEtx = false;
+            for (int readIdx = 0; readIdx < readableBytes; readIdx++) {
+                if (readIdx == 2) {
+                    if (recvBytes[0] != CctvProtocol.cctv_DLE || recvBytes[1] != CctvProtocol.cctv_STX) {
+                        log.error("[{}]. RECV: DLE/STX Data Error. {}/{}", obj.getCCTV_CTLR_ID(), recvBytes[0], recvBytes[1]);
+                        CctvTcpClientIdleHandler.disconnectChannel(channel);
+                        return;
+                    }
+                }
+
+                byte data = recvBytes[readIdx];
+                if (foundDle) {
+                    foundDle = false;
+                    if (data == CctvProtocol.cctv_DLE) {
+                        // Skip DLE Stuffing Data
+                        stuffing++;
+                        //log.warn("RECV_0: [{}]. ReadableBytes: {} Bytes, ReadIndex: {}, DLE Stuffing Data Recv.", ipAddress, readableBytes, readIdx);
+                    } else if (data == CctvProtocol.cctv_STX) {
+                        // Packet
+                        packets[msgSize++] = data;
+                    } else if (data == CctvProtocol.cctv_ETX) {
+                        packets[msgSize++] = data;
+
+                        // 이전데이터가 DLE 이고 현재 데이터가 ETX 이면 패킷의 끝임.
+                        // 다음 2바이트는 CRC 데이터임, 남아 있는 데이터가 CRC 2바이트를 포함한 크기보다 작으면
+                        // 하나의 패킷을 완전히 수신하지 못한 것임
+                        if ((readIdx + 2) > (readableBytes)) {
+                            // 하나의 패킷을 읽지 못함
+                            log.warn("[{}]. RECV: ReadableBytes: {} Bytes, ReadIndex: {}, DLE Data Remain.", obj.getCCTV_CTLR_ID(), readableBytes, readIdx);
+                            break;
+                        }
+                        packets[msgSize++] = recvBytes[readIdx+1];  // CRC1
+                        packets[msgSize++] = recvBytes[readIdx+2];  // CRC2
+
+                        // 하나의 패킷을 읽었음 ***************
+                        byteBuf.readerIndex(readIdx+3);
+                        byteBuf.markReaderIndex();
+                        byteBuf.discardReadBytes();
+
+                        CctvResFramePacket frame = new CctvResFramePacket(obj, packets, msgSize);
+                        list.add(frame);
+                        break;
+                    } else {
+                        // DLE 다음에 와야할 데이터가 들어오지 않았다. 패킷에 오류가 발생함.
+                        log.error("[{}]. RECV: ReadableBytes: {} Bytes, ReadIndex: {}, DLE Next Data Recv Error.", obj.getCCTV_CTLR_ID(), readableBytes, readIdx);
+                        CctvTcpClientIdleHandler.disconnectChannel(channel);
+                        return;
+                    }
+                } else {
+                    packets[msgSize++] = data;
+                    if (data == cctv_DLE) {
+                        foundDle = true;
+                    }
+                }
+            }
+        }
+        finally {
+            byteBuf.resetReaderIndex();
+            MDC.remove(obj.getLogKey());
+            MDC.clear();
+        }
+
+    }
+}

+ 53 - 0
src/main/java/com/its/cctv/xnettcp/cctvserver/codec/CctvTcpServerEncoder.java

@@ -0,0 +1,53 @@
+package com.its.cctv.xnettcp.cctvserver.codec;
+
+import com.its.app.utils.NettyUtils;
+import com.its.cctv.config.RunningConfig;
+import com.its.cctv.entity.TbCctvCtlr;
+import com.its.cctv.global.AppRepository;
+import com.its.cctv.xnettcp.cctvserver.handler.CctvServerIdleStatePacketHandler;
+import io.netty.buffer.ByteBuf;
+import io.netty.channel.Channel;
+import io.netty.channel.ChannelHandler;
+import io.netty.channel.ChannelHandlerContext;
+import io.netty.handler.codec.MessageToByteEncoder;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.slf4j.MDC;
+import org.springframework.stereotype.Component;
+
+@Slf4j
+@RequiredArgsConstructor
+@Component
+@ChannelHandler.Sharable
+public class CctvTcpServerEncoder extends MessageToByteEncoder<Object> {
+
+    private final RunningConfig runningConfig;
+
+     @Override
+    protected void encode(ChannelHandlerContext ctx, Object msg, ByteBuf byteBuf) throws Exception {
+
+        String ipAddress = NettyUtils.getRemoteIpAddress(ctx.channel());
+        Channel channel = ctx.channel();
+         TbCctvCtlr obj = AppRepository.getInstance().getCtlrIpMap().get(ipAddress);
+        if (obj == null) {
+            log.error("DsrcTcpServerEncoder.encode: Unknown Controller IP: {}. will be close.", ipAddress);
+            CctvServerIdleStatePacketHandler.disconnectChannel(null, channel);
+            return;
+        }
+
+        MDC.put("id", obj.getLogKey());
+
+        if (obj.getChannel() != null && (channel != obj.getChannel())) {
+            log.error("DsrcTcpServerEncoder.encode: {}, channel error, curr: {}, old: {}", ipAddress, channel.toString(), obj.getChannel().toString());
+            CctvServerIdleStatePacketHandler.disconnectChannel(obj, channel);
+            return;
+        }
+
+        if (!channel.isOpen() || !channel.isActive()) {
+            log.error("DsrcTcpServerEncoder.encode: {}, isOpen: {}, isActive: {}. [{}]", ipAddress, channel.isOpen(), channel.isActive(), obj.getLogKey());
+            CctvServerIdleStatePacketHandler.disconnectChannel(obj, channel);
+            return;
+        }
+
+    }
+}

+ 198 - 0
src/main/java/com/its/cctv/xnettcp/cctvserver/handler/CctvServerIdleStateConnectionHandler.java

@@ -0,0 +1,198 @@
+package com.its.cctv.xnettcp.cctvserver.handler;
+
+import com.its.app.utils.NettyUtils;
+import com.its.cctv.config.RunningConfig;
+import com.its.cctv.entity.TbCctvCtlr;
+import com.its.cctv.global.AppRepository;
+import com.its.cctv.ui.MainUI;
+import io.netty.channel.Channel;
+import io.netty.channel.ChannelDuplexHandler;
+import io.netty.channel.ChannelFuture;
+import io.netty.channel.ChannelHandlerContext;
+import io.netty.handler.timeout.IdleState;
+import io.netty.handler.timeout.IdleStateEvent;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.slf4j.MDC;
+
+import java.nio.ByteBuffer;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * 소켓 채널이 연결된 후 최소 데이터 송수신 까지의 타임아웃을 체크한다.
+ */
+@Slf4j
+@RequiredArgsConstructor
+public class CctvServerIdleStateConnectionHandler extends ChannelDuplexHandler {
+
+    private final RunningConfig config;
+
+    @Override
+    public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
+        if (evt instanceof IdleStateEvent) {
+            IdleStateEvent e = (IdleStateEvent) evt;
+
+            if (e.state() == IdleState.READER_IDLE) {
+                // 최초 통신 연결후 어떠한 데이터의 수신이 없는 경우 해당 채널 Close.
+                //String tcpAddress = NettyUtils.getTcpAddress(ctx.channel());
+                String ipAddress  = NettyUtils.getRemoteIpAddress(ctx.channel());
+                TbCctvCtlr obj = AppRepository.getInstance().getCtlrIpMap().get(ipAddress);
+                if (obj != null) {
+                    MDC.put("id", obj.getLogKey());
+                }
+                log.error("CctvServerIdleStateConnectionHandler.userEventTriggered, ----READER_IDLE: {},  will be closed.", ipAddress);
+                ctx.channel().disconnect();
+                ctx.channel().close();
+                if (obj != null) {
+                    MDC.remove(obj.getLogKey());
+                    MDC.clear();
+                }
+            }
+            else if (e.state() == IdleState.WRITER_IDLE) {
+            }
+        }
+    }
+
+    @Override
+    public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
+        // 클라이언트 접속시 이벤트 발생(1)
+        // 활성화된 채널의 디비 등록여부에 따라 열결을 활성화 하거나 종료시킨다.
+        //logger.warn("DsrcAsn1ServerIdleStateConnectionHandler.-----handlerAdded: {}", NettyUtils.getTcpAddress(ctx.channel()));
+        super.handlerAdded(ctx);    // channelActive 이벤트 발생시킴
+    }
+
+    @Override
+    public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {
+        // channelInactive 이벤트 다음에 발생(2)
+        // 이곳 핸들러에서 종료되는 경우는 채널연결이 인증되지 않는 경우이므로 그냥 종료시키면 된다.
+        //logger.warn("DsrcAsn1ServerIdleStateConnectionHandler.---handlerRemoved: {}", NettyUtils.getTcpAddress(ctx.channel()));
+        super.handlerRemoved(ctx);
+    }
+
+    @Override
+    public void channelActive(ChannelHandlerContext ctx) throws Exception {
+        // handlerAdded 다음 이벤트 발생(2)
+        // 클라이언트 접속 요청 처리, 활성화된 채널의 디비 등록여부에 따라 열결을 활성화 하거나 종료시킨다.
+        String ipAddress = NettyUtils.getRemoteIpAddress(ctx.channel());
+        TbCctvCtlr obj = AppRepository.getInstance().getCtlrIpMap().get(ipAddress);
+        if (obj == null) {
+            log.error("CctvServerIdleStateConnectionHandler.----channelActive: {}, Unknown ip address. will be closed.", ipAddress);
+            CctvServerIdleStateConnectionHandler.disconnectChannel(ctx.channel());
+        }
+        else {
+            MDC.put("id", obj.getLogKey());
+            log.info("CctvServerIdleStateConnectionHandler.----channelActive: {}, ID: {}, {}", ipAddress, obj.getCCTV_CTLR_ID(), ctx.channel().toString());
+
+            if (obj.getChannel() != null) {
+                try {
+                    log.error("CctvServerIdleStateConnectionHandler.----channelActive: Old Connection Active: {}, ID: {}, {}", ipAddress, obj.getCCTV_CTLR_ID(), obj.getChannel().toString());
+                    obj.getChannel().disconnect();
+                    obj.getChannel().close();
+                }
+                catch(Exception e) {
+                }
+            }
+
+            /**
+             * 1. 데이터 송수신 타임아웃 핸들러 등록
+             * 2. 현재 핸들러를 삭제한다.
+             */
+            int allIdleTimeSec = 120;   // 2분
+            CctvServerIdleStatePacketHandler cctvServerIdleStatePacketHandler = new CctvServerIdleStatePacketHandler(
+                    0,
+                    0,
+                    allIdleTimeSec,
+                    TimeUnit.SECONDS
+            );
+            ctx.channel().pipeline().addAfter("serverConnectionIdleStateHandler","dsrcAsn1ServerIdleStateHandler", cctvServerIdleStatePacketHandler);    // packet idle handler add
+            ctx.channel().pipeline().remove("serverConnectionIdleStateHandler");     // login idle handler remove
+            ctx.channel().pipeline().remove(this);                              // First packet recv/send timeout handler(dsrcAsn1ServerConnectionHandler)
+
+            obj.channelOpen(ctx.channel());
+
+            // 온도정보 요청
+            ByteBuffer sendBuff;
+            sendBuff = obj.getReqState().getByteBuffer();
+            obj.sendData(sendBuff, 0, "cctv_StateReq");
+
+            MainUI mainUI = MainUI.getInstance();
+            if (mainUI != null) {
+                mainUI.updateCtlrStts(obj);
+            }
+
+            MDC.remove(obj.getLogKey());
+            MDC.clear();
+        }
+    }
+
+    @Override
+    public void channelInactive(ChannelHandlerContext ctx) throws Exception {
+        // 클라이언트 연결 종료시 이벤트 발생(1)
+        // 이곳 핸들러에서 종료되는 경우는 채널연결이 인증되지 않는 경우이므로 그냥 종료시키면 된다.
+        // 채널이 이미 연결 종료된 상태에서의 이벤트 수신
+        String ipAddress = NettyUtils.getRemoteIpAddress(ctx.channel());
+        TbCctvCtlr obj = AppRepository.getInstance().getCtlrIpMap().get(ipAddress);
+        if (obj != null) {
+            MDC.put("id", obj.getLogKey());
+        }
+        log.error("CctvServerIdleStateConnectionHandler.--channelInactive: {}, {}", ipAddress, ctx.channel().toString());
+
+        super.channelInactive(ctx); // handlerRemoved 이벤트 발생 시킴
+
+        try {
+            ctx.channel().close();
+        }
+        catch(Exception e) {
+        }
+
+        if (obj != null) {
+            MDC.remove(obj.getLogKey());
+            MDC.clear();
+        }
+    }
+
+    @Override
+    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
+        // 이곳 핸들러에서 종료되는 경우는 채널연결이 인증되지 않는 경우이므로 그냥 종료시키면 된다.
+        String ipAddress = NettyUtils.getRemoteIpAddress(ctx.channel());
+        TbCctvCtlr obj = AppRepository.getInstance().getCtlrIpMap().get(ipAddress);
+        if (obj != null) {
+            MDC.put("id", obj.getLogKey());
+        }
+        log.error("CctvServerIdleStateConnectionHandler.--exceptionCaught: {}, {}, Exception: {}", ipAddress, ctx.channel().toString(), cause);
+        CctvServerIdleStateConnectionHandler.disconnectChannel(ctx.channel());
+        //super.exceptionCaught(ctx, cause);
+        if (obj != null) {
+            MDC.remove(obj.getLogKey());
+            MDC.clear();
+        }
+    }
+
+    public static void disconnectChannel(Channel channel) {
+        // 로그인 하지 않은 또는 비정상 접속 네트워크 세션 종료(로그인 처리를 수행하지 않은 세션에 대한 종료)
+        String ipAddress = NettyUtils.getRemoteIpAddress(channel);
+        TbCctvCtlr obj = AppRepository.getInstance().getCtlrIpMap().get(ipAddress);
+        if (obj != null) {
+            MDC.put("id", obj.getLogKey());
+        }
+        try {
+            log.error("CctvServerIdleStateConnectionHandler.disconnectChannel: {}, {}", ipAddress, channel.toString());
+            if (!channel.isActive()) {
+                log.error("CctvServerIdleStateConnectionHandler.disconnectChannel: {}, channel already closed.", ipAddress);
+            }
+
+            channel.flush();
+            ChannelFuture f = channel.disconnect().awaitUninterruptibly();  // channelInactive event fire
+            if (!f.isDone() || !f.isSuccess()) {
+                log.error("CctvServerIdleStateConnectionHandler.disconnectChannel: {}, isDone: {}, isSuccess: {}", ipAddress, f.isDone(), f.isSuccess());
+            }
+        }
+        catch(Exception e) {
+        }
+
+        if (obj != null) {
+            MDC.remove(obj.getLogKey());
+            MDC.clear();
+        }
+    }
+}

+ 156 - 0
src/main/java/com/its/cctv/xnettcp/cctvserver/handler/CctvServerIdleStatePacketHandler.java

@@ -0,0 +1,156 @@
+package com.its.cctv.xnettcp.cctvserver.handler;
+
+import com.its.app.AppUtils;
+import com.its.app.utils.NettyUtils;
+import com.its.cctv.entity.TbCctvCtlr;
+import com.its.cctv.entity.TbCctvCtlrStts;
+import com.its.cctv.global.AppRepository;
+import com.its.cctv.process.DbmsData;
+import com.its.cctv.process.DbmsDataProcess;
+import com.its.cctv.process.DbmsDataType;
+import com.its.cctv.ui.MainUI;
+import io.netty.channel.Channel;
+import io.netty.channel.ChannelFuture;
+import io.netty.channel.ChannelHandlerContext;
+import io.netty.handler.timeout.IdleState;
+import io.netty.handler.timeout.IdleStateEvent;
+import io.netty.handler.timeout.IdleStateHandler;
+import lombok.extern.slf4j.Slf4j;
+import org.slf4j.MDC;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+
+@Slf4j
+public class CctvServerIdleStatePacketHandler extends IdleStateHandler {
+
+    private DbmsDataProcess dbmsDataProcess;
+
+    public CctvServerIdleStatePacketHandler(long readerIdleTime, long writerIdleTime, long allIdleTime, TimeUnit unit) {
+        super(readerIdleTime, writerIdleTime, allIdleTime, unit);
+        this.dbmsDataProcess = (DbmsDataProcess) AppUtils.getBean(DbmsDataProcess.class);
+    }
+
+    @Override
+    public void channelActive(ChannelHandlerContext ctx) throws Exception {
+        log.error("CctvServerIdleStatePacketHandler.-----channelActive: {}", NettyUtils.getTcpAddress(ctx.channel()));
+    }
+
+    @Override
+    protected void channelIdle(ChannelHandlerContext ctx, IdleStateEvent evt) throws Exception {
+        String ipAddress = NettyUtils.getRemoteIpAddress(ctx.channel());
+        TbCctvCtlr obj = AppRepository.getInstance().getCtlrIpMap().get(ipAddress);
+        if (obj == null) {
+            log.error("CctvServerIdleStatePacketHandler.-------channelIdle: ALL_IDLE, {}, Unknown ip address, will be closed.", ipAddress);
+            CctvServerIdleStatePacketHandler.disconnectChannel(null, ctx.channel());
+            return;
+        }
+
+        MDC.put("id", obj.getLogKey());
+
+        if (evt.state() == IdleState.ALL_IDLE) {
+            log.error("CctvServerIdleStatePacketHandler.-------channelIdle: ALL_IDLE, {}, ID: {}, {}", ipAddress, obj.getCCTV_CTLR_ID(), ctx.channel().toString());
+            // 클라이언트로 종료 메시지를 전송한다.(AI_Terminate)
+            CctvServerIdleStatePacketHandler.disconnectChannel(obj, ctx.channel());
+        }
+        else if (evt.state() == IdleState.READER_IDLE) {
+            log.warn("CctvServerIdleStatePacketHandler.-------channelIdle: READER_IDLE, {}, ID: {}", ipAddress, obj.getCCTV_CTLR_ID());
+        }
+        else if (evt.state() == IdleState.WRITER_IDLE) {
+            log.warn("CctvServerIdleStatePacketHandler.-------channelIdle: WRITER_IDLE, {}, ID: {}", ipAddress, obj.getCCTV_CTLR_ID());
+        }
+        MDC.remove(obj.getLogKey());
+        MDC.clear();
+    }
+
+    @Override
+    public void channelInactive(ChannelHandlerContext ctx) throws Exception {
+        String ipAddress = NettyUtils.getRemoteIpAddress(ctx.channel());
+        TbCctvCtlr obj = AppRepository.getInstance().getCtlrIpMap().get(ipAddress);
+        if (obj != null) {
+            MDC.put("id", obj.getLogKey());
+        }
+        log.error("CctvServerIdleStatePacketHandler.---channelInactive: {}, {}", ipAddress, ctx.channel().toString());
+
+        Channel oldChannel = null;
+
+        if (obj.getChannel() != null) {
+            log.error("[{}]. CctvServerIdleStatePacketHandler.---channelInactive: channel {}", obj.getCCTV_CTLR_ID(), ctx.channel());
+            // 연결 되어 있다가 종료 된 경우임....
+            // 제어기 상태정보 DB 업데이트
+            DbmsDataProcess dbmsDataProcess = (DbmsDataProcess) AppUtils.getBean(DbmsDataProcess.class);
+            List<TbCctvCtlrStts> ctlrSttsList = Collections.synchronizedList(new ArrayList<>());
+            ctlrSttsList.add(obj.getStts());
+            dbmsDataProcess.add(new DbmsData(DbmsDataType.DBMS_DATA_CTLR_STTS, false, ctlrSttsList));
+            MainUI mainUI = MainUI.getInstance();
+            if (mainUI != null) {
+                mainUI.updateCtlrStts(obj);
+            }
+        }
+        obj.channelClosed();
+
+        super.channelInactive(ctx);
+        try {
+            ctx.channel().close();
+            if (oldChannel != null) {
+                log.error("CctvServerIdleStatePacketHandler.---channelInactive: {}, old: {}, curr: {}", ipAddress, obj.getChannel().toString(), ctx.channel().toString());
+                if (ctx.channel() != oldChannel && oldChannel.isActive()) {
+                    log.error("CctvServerIdleStatePacketHandler.---channelInactive: {}, close, old: {}, curr: {}", ipAddress, obj.getChannel().toString(), ctx.channel().toString());
+                    oldChannel.close();
+                }
+            }
+        }
+        catch (Exception e) {
+        }
+
+        if (obj != null) {
+            MDC.remove(obj.getLogKey());
+            MDC.clear();
+        }
+    }
+
+    public static void disconnectChannel(TbCctvCtlr obj, Channel channel) {
+        // 로그인 한 세션에 대한 종료처리를 수행
+        String ipAddress = NettyUtils.getRemoteIpAddress(channel);
+        if (obj != null) {
+            MDC.put("id", obj.getLogKey());
+        }
+
+        try {
+            log.error("CctvServerIdleStatePacketHandler.-disconnectChannel: {}, {}", ipAddress, channel.toString());
+            if (!channel.isActive()) {
+                log.error("CctvServerIdleStatePacketHandler.-disconnectChannel: {}, channel already closed.", ipAddress);
+            }
+
+            channel.flush();
+            ChannelFuture f = channel.disconnect().awaitUninterruptibly();  // channelInactive event fire
+            if (!f.isDone() || !f.isSuccess()) {
+                log.error("CctvServerIdleStatePacketHandler.-disconnectChannel: {}, isDone: {}, isSuccess: {}", ipAddress, f.isDone(), f.isSuccess());
+            }
+        }
+        catch(Exception e) {
+        }
+
+        if (obj != null) {
+            MDC.remove(obj.getLogKey());
+        }
+    }
+
+    @Override
+    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
+        String ipAddress = NettyUtils.getRemoteIpAddress(ctx.channel());
+        TbCctvCtlr obj = AppRepository.getInstance().getCtlrIpMap().get(ipAddress);
+        if (obj != null) {
+            MDC.put("id", obj.getLogKey());
+        }
+        log.error("CctvServerIdleStatePacketHandler.---exceptionCaught: {}, {}, Exception: {}", ipAddress, ctx.channel().toString(), cause);
+        CctvServerIdleStatePacketHandler.disconnectChannel(obj, ctx.channel());
+        super.exceptionCaught(ctx, cause);
+        if (obj != null) {
+            MDC.remove(obj.getLogKey());
+        }
+    }
+
+}

+ 28 - 0
src/main/java/com/its/cctv/xnettcp/cctvserver/handler/CctvServerPacketInboundHandler.java

@@ -0,0 +1,28 @@
+package com.its.cctv.xnettcp.cctvserver.handler;
+
+import com.its.cctv.xnettcp.cctvserver.process.CctvServerDataProcess;
+import io.netty.channel.ChannelHandler;
+import io.netty.channel.ChannelHandlerContext;
+import io.netty.channel.ChannelInboundHandlerAdapter;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Component;
+
+@Slf4j
+@RequiredArgsConstructor
+@Component
+@ChannelHandler.Sharable
+public class CctvServerPacketInboundHandler extends ChannelInboundHandlerAdapter {
+
+    private final CctvServerDataProcess tcpServerDataProcess;
+
+    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
+//        if (!(msg instanceof C2CAuthenticatedMessage)) {
+//            log.error("{} | Received Data is not C2CAuthenticatedMessage", NettyUtils.getRemoteAddress(ctx.channel()));
+//            return;
+//        }
+//        C2CAuthenticatedMessage c2c = (C2CAuthenticatedMessage)msg;
+//        log.info("DsrcAsn1ServerPacketHandler: channelRead: {}, {}", NettyUtils.getRemoteAddress(ctx.channel()), c2c);
+//        this.tcpServerDataProcess.add(new CctvServerData(CctvServerData.DATA_TYPE_PACKET, null, ctx, msg));
+    }
+}

+ 24 - 0
src/main/java/com/its/cctv/xnettcp/cctvserver/process/CctvServerData.java

@@ -0,0 +1,24 @@
+package com.its.cctv.xnettcp.cctvserver.process;
+
+import com.its.cctv.entity.TbCctvCtlr;
+import io.netty.channel.ChannelHandlerContext;
+import lombok.Getter;
+import lombok.Setter;
+
+@Getter
+@Setter
+public class CctvServerData {
+    public static final int DATA_TYPE_PACKET = 0;
+
+    private int        type;
+    private TbCctvCtlr obj;
+    private ChannelHandlerContext ctx;
+    private Object     data;
+
+    public CctvServerData(int type, TbCctvCtlr obj, ChannelHandlerContext ctx, Object data) {
+        this.type = type;
+        this.obj  = obj;
+        this.ctx  = ctx;
+        this.data = data;
+    }
+}

+ 161 - 0
src/main/java/com/its/cctv/xnettcp/cctvserver/process/CctvServerDataProcess.java

@@ -0,0 +1,161 @@
+package com.its.cctv.xnettcp.cctvserver.process;
+
+import com.its.app.AppUtils;
+import com.its.app.utils.NettyUtils;
+import com.its.cctv.config.RunningConfig;
+import com.its.cctv.entity.TbCctvCtlr;
+import com.its.cctv.global.AppRepository;
+import io.netty.channel.Channel;
+import io.netty.channel.ChannelHandlerContext;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.slf4j.MDC;
+import org.springframework.stereotype.Service;
+
+import javax.annotation.PostConstruct;
+import java.util.concurrent.Executors;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.ThreadPoolExecutor;
+
+@Slf4j
+@RequiredArgsConstructor
+@Service
+public class CctvServerDataProcess {
+
+    private final RunningConfig runningConfig;
+
+    public static LinkedBlockingQueue<CctvServerData> SERVER_DATA_QUEUE = new LinkedBlockingQueue<>(1000);
+    private ThreadPoolExecutor taskExecutor = (ThreadPoolExecutor)Executors.newFixedThreadPool(1);
+    int MAX_CORE = Runtime.getRuntime().availableProcessors();
+
+    @PostConstruct
+    private void init() {
+    }
+    public void run() {
+        log.info("CctvServerDataProcess.run: Start.");
+        if (this.MAX_CORE < 8) {
+            this.MAX_CORE = 8;
+        }
+//        ItsThreadPoolInitializer poolInitializer = (ItsThreadPoolInitializer) AppUtils.getBean(ItsThreadPoolInitializer.class);
+        int executePool = this.MAX_CORE;//Math.max(this.MAX_CORE, poolInitializer.getThreadPoolWork());
+        for (int ii = 0; ii < executePool; ii++) {
+            log.info("CctvServerDataProcess.Task: {}", ii);
+            this.taskExecutor.execute(() -> {
+                while (true) {
+                    try {
+                        CctvServerData serverData = CctvServerDataProcess.SERVER_DATA_QUEUE.take();
+                        if (serverData != null) {
+                            CctvServerDataTask handler = (CctvServerDataTask) AppUtils.getBean(CctvServerDataTask.class);
+                            handler.run(this, serverData);
+                        }
+                        else {
+                            log.error("CctvServerDataProcess.Task: Received data null");
+                        }
+                    }
+                    catch (Exception e) {
+                        log.error("CctvServerDataProcess.Task: Exception: {}", e.getMessage(), e);
+                    }
+                }
+            });
+        }
+
+        log.info("CctvServerDataProcess.run: ..End.");
+    }
+
+    public void process(CctvServerData data) {
+        try {
+            ChannelHandlerContext ctx = data.getCtx();
+            Channel channel = ctx.channel();
+            //String tcpAddress = NettyUtils.getTcpAddress(channel);    // local/remote address 를 폼함한 문자열
+            String ipAddress = NettyUtils.getRemoteIpAddress(channel);
+            TbCctvCtlr obj = AppRepository.getInstance().getCtlrIpMap().get(ipAddress);;
+//            C2CAuthenticatedMessage c2c = (C2CAuthenticatedMessage)data.getData();
+
+//            if (obj == null || channel == null || c2c == null) {
+//                log.error("CctvServerDataProcess.process: {}, data null. controller: {}, channel: {}, c2c: {{}", ipAddress, obj, channel, c2c);
+//                return;
+//            }
+
+            MDC.put("id", obj.getLogKey());
+//            eAuthInfo cmd = eAuthInfo.getByValue(c2c.getDatexAuthenticationInfoText().value[0]);
+
+//            log.info("CctvServerDataProcess.process: {}, {}, ThreadId: {}", ipAddress, cmd.toString(), Thread.currentThread().getId());
+
+//            DsrcAsn1Response response = null;
+//            switch (cmd) {
+//                case AI_Initiate    :   //(0x01, "AI_Initiate"),           /* 초기 통신연결을 위한 개시 요청 데이터 패킷 */
+//                    // 서버모드 처리내용 없음(평택 서버인데 클라이언트가 서버로 동작 => 로그인 요청해야함)
+//                    response = new InitiateResponse(obj, ctx, c2c);
+//                    break;
+//                case AI_Login       :   //(0x02, "AI_Login"),              /* 서버에 접속하기 위한 클라이언트의 로그인 데이터 패킷 */
+//                    response = new LoginResponse(obj, ctx, c2c);
+//                    break;
+//                case AI_FrED        :   //(0x03, "AI_FrED"),               /* 서버와 클라이언트의 연결을 유지하기 위한 확인 데이터 패킷 */
+//                    response = new FredResponse(obj, ctx, c2c);
+//                    break;
+//                case AI_Terminate   :   //(0x04, "AI_Terminate"),          /* 연결을 종료하고자 할 때, 서버에서 클라이언트에 요청하는 데이터 패킷 */
+//                    response = new TerminateResponse(obj, ctx, c2c);
+//                    break;
+//                case AI_Logout      :   //(0x05, "AI_Logout"),             /* 접속을 종료하기 위한 클라이언트의 로그아웃 데이터 패킷 */
+//                    response = new LogoutResponse(obj, ctx, c2c);
+//                    break;
+//                case AI_Subscription:   //(0x06, "AI_Subscription"),       /* 클라이언트가 서버에 정보를 요청할 경우 송신하는 데이터 패킷 */
+//                    response = new SubscriptionResponse(obj, ctx, c2c);
+//                    break;
+//                case AI_Publication :   //(0x07, "AI_Publication"),        /* 클라이언트가 요청한 정보를 제공하기 위한 데이터 패킷 - 요청에 대한 정보공개*/
+//                    response = new PublicationResponse(obj, ctx, c2c);
+//                    break;
+//                case AI_Accept      :   //(0x09, "AI_Accept"),             /* 클라이언트의 요청에 대한 수용 */
+//                    response = new AcceptResponse(obj, ctx, c2c);
+//                    break;
+//                case AI_Reject      :   //(0x0A, "AI_Reject");             /* 클라이언트의 요청에 대한 거부 */
+//                    // 운영단말 명령에 대한 거부도 발생할 수 있으므로 운영단말로 결과를 전송한다.
+//                    response = new RejectResponse(obj, ctx, c2c);
+//                    break;
+//                case AI_TransferDone:   //(0x08, "AI_TransferDone"),       /* 클라이언트가 요청한 정보를 파일형태로 제공하기 위한 데이터 패킷 */
+//                    // 처리내용 없음
+//                    response = new TransferDoneResponse(obj, ctx, c2c);
+//                    break;
+//                case AI_Null        :   //(0x00, "AI_Null"),               /* NULL */
+//                    // 처리내용 없음
+//                    response = new NullResponse(obj, ctx, c2c);
+//                    break;
+//                default:
+//                    log.warn("{}. process: Unknown packet: {}, {}", ipAddress, cmd.toString(), obj.toString());
+//                    break;
+//            }
+
+//            if (response != null) {
+//                if (!response.response(this.runningConfig)) {
+//                    log.error("CctvServerDataProcess.process: {}, response error. will be closed.", ipAddress);
+////                    DsrcAsn1ServerIdleStatePacketHandler.disconnectChannel(obj, ctx.channel());
+//                }
+//            }
+//            else {
+//                log.error("CctvServerDataProcess.process: {}, unknown packet. will be closed.", ipAddress);
+////                DsrcAsn1ServerIdleStatePacketHandler.disconnectChannel(obj, ctx.channel());
+//            }
+            MDC.remove(obj.getLogKey());
+            MDC.clear();
+        } catch (Exception e) {
+            log.error("CctvServerDataProcess.process: Exception: {}", e.toString());
+        }
+    }
+
+    /*
+    *  작업큐에 데이터 추가
+     */
+    public void add(CctvServerData data) {
+        try {
+            //offer => full -> return
+            //add   => full -> wait
+            //큐가 차더라도 바로 리턴함.
+            if (!SERVER_DATA_QUEUE.offer(data)) {
+                log.error("CctvServerDataProcess-QueueFull: {}", SERVER_DATA_QUEUE.size());
+            }
+        } catch (Exception e) {
+            log.error("CctvServerDataProcess-QueueAddError: {}", e.getMessage(), e);
+        }
+    }
+
+}

+ 17 - 0
src/main/java/com/its/cctv/xnettcp/cctvserver/process/CctvServerDataTask.java

@@ -0,0 +1,17 @@
+package com.its.cctv.xnettcp.cctvserver.process;
+
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.scheduling.annotation.Async;
+import org.springframework.stereotype.Service;
+
+@Slf4j
+@Service
+public class CctvServerDataTask {
+
+    @Async("appJobExecutor")
+    public void run(CctvServerDataProcess serverDataProcess, CctvServerData serverData) {
+
+        serverDataProcess.process(serverData);
+    }
+
+}

+ 2 - 1
src/main/resources/application.yml

@@ -7,6 +7,7 @@ application:
   listen-port: 9903
 
 running:
+  comm-binding-port: 7800
   comm-logging: true
   retry-seconds: 20
   connect-timeout: 5
@@ -27,7 +28,7 @@ spring:
   application:
     name: cctv-comm-server
   profiles:
-    active: dev
+    active: prod
   main:
     #web-application-type: none
     log-startup-info: true