UbiAnyCard Ver2 2.0
KICC ED785 단말기 연동 TCP 서버
로딩중...
검색중...
일치하는것 없음
UbiAnyCard Ver2 프로젝트

프로그램 개요

**UbiAnyCard Ver2**는 KICC ED785 단말기를 통한 카드 결제 시스템의 TCP 서버 애플리케이션입니다.

주요 기능

  • TCP 서버: 외부 클라이언트로부터 TCP 연결을 받아 결제 명령 처리
  • 카드 결제: 승인/취소/조회 등의 결제 명령을 KICC ED785 단말기와 통신하여 수행
  • 전자화폐 지원: 티머니, 캐시비, 레일플러스 등 전자화폐 결제 지원
  • 연결 모델: 거래당 연결 모델로 안정적인 거래 처리
  • Keep-Alive: PING 명령을 통한 연결 유지 기능

시스템 특징

  • 동기 처리: TCP 요청부터 ED785 응답까지 동기적으로 처리
  • 스레드 안전성: 원자적 연산과 뮤텍스로 동시성 제어
  • 리소스 관리: RAII 패턴으로 자동 정리
  • 인코딩 지원: UTF-8 ↔ EUC-KR 변환 처리

1. 전체 아키텍처 개요

시스템 구성 요소

dot_inline_dotgraph_1.png

주요 설계 원칙

  • 단일 책임 원칙: 각 클래스가 명확한 역할
  • 동기 처리: TCP 요청 → ED785 응답까지 동기적으로 처리
  • 스레드 안전성: 원자적 연산과 뮤텍스로 동시성 제어
  • 리소스 관리: RAII 패턴으로 자동 정리

2. 데이터 흐름 분석

2.1 전체 처리 플로우

dot_inline_dotgraph_2.png

2.2 거래당 연결 모델 시퀀스 (승인/취소 등)

dot_inline_dotgraph_3.png

2.3 PING Keep-Alive 시퀀스

dot_inline_dotgraph_4.png

2.4 세부 처리 단계

A. TcpServer (TCP 통신 계층)

// 1. 연결 수락 및 스레드 생성
void acceptLoop() {
while (m_running.load()) {
SOCKET client = ::accept(m_listen, ...);
// 거래당 연결: 각 요청마다 별도 스레드
std::thread([this, client]() {
// 요청 수신(개행 기준) → ACK → 처리 → 응답 → (선택)ACK 대기 → 연결 종료
}).detach();
}
}

특징:

  • 거래당 연결 모델: 각 거래마다 새로운 TCP 연결
  • PING Keep-Alive: PING 명령만 연결 유지
  • KICC 프로토콜 준수: ACK 패킷 (0x06 0x06 0x06) 처리
  • 요청 종료 규칙: 개행(
    )으로 요청 종료 판단. 세미콜론(;)은 데이터 내 K=V 구분자로만 사용

B. ProtocolAdapter (프로토콜 변환 계층)

// 2. 명령 파싱 및 변환
std::string onCommand(const std::string &cmdline) {
auto [cmd, arg] = splitOnce(line, ' ');
if (cmd == "APPROVE") {
// 승인 요청 → ED785 명령 변환
return sendAndWait(0x14, 0x04, kvs, 60000);
}
// ... 다른 명령들
}

특징:

  • 동기 처리: std::promise/future로 응답 대기
  • 트랜잭션 관리: 각 요청마다 고유 ID 할당
  • 타임아웃 처리: 요청별 다른 타임아웃 설정
  • 상태 이벤트 분리 처리: 0x14/0x09(단말기 상태) 응답은 비동기 이벤트로만 로깅하고 트랜잭션 매칭에서 제외

C. ED785Worker (하드웨어 통신 계층)

// 3. 하드웨어 통신 및 폴링
void workerThread() {
while (m_running.load()) {
// 윈도우 메시지 처리
// 요청 큐에서 명령 꺼내기
// KReqCmd로 명령 전송
// KGetEvent로 응답 폴링
// 콜백으로 응답 전달
}
}

특징:

  • 단일 스레드: KICC DLL 요구사항 준수
  • 폴링 방식: 200ms 간격으로 이벤트 확인
  • 윈도우 메시지: 숨겨진 윈도우로 DLL 메시지 처리

3. 주요 명령어 처리 흐름

3.1 APPROVE 명령 (카드 승인)

// 1. 클라이언트 요청
"APPROVE S01=D4;S12=12345678;S13=20231201;..."
// 2. ProtocolAdapter 파싱
auto kvList = ParseKvOrdered(kvs);
// S23 자동 생성 (POS + 타임스탬프 + 시퀀스)
// S23 길이 제한: 20 바이트 초과 시 절단
// 3. ED785 요청 변환
req.cmd = 0xFB;
req.gcd = 0x14; // 승인 그룹
req.jcd = 0x04; // 승인 작업
req.payload = asciiPayload;
// 4. 응답 대기 (60초)
std::future<TxResponse> future = pending->promise.get_future();
auto status = future.wait_for(std::chrono::milliseconds(60000));
// 5. 전자화폐 파싱
ParseAndAppendElectronicMoney(kv);
// 티머니, 캐시비, 레일플러스 자동 감지 및 파싱
// 6. 최종 응답
"RC=RC_SUCCESS;S01=D4;S12=12345678;EM_TYPE=TMONEY;..."
static std::vector< std::pair< std::string, std::string > > ParseKvOrdered(const std::string &s)
Definition ProtocolAdapter.cpp:31
Definition ED785Worker.h:22
int jcd
Definition ED785Worker.h:25
std::vector< byte > payload
Definition ED785Worker.h:26
int gcd
Definition ED785Worker.h:24
int cmd
Definition ED785Worker.h:23

3.2 CANCEL 명령 (승인 취소)

// 1. 클라이언트 요청
"CANCEL S12=원승인번호;S13=원승인일자;..."
// 2. 필수값 검증
// S12(원승인번호), S13(원승인일자) 필수. 누락 시
"ERR=MISSING_APPROVAL_INFO"
// 3. ED785 요청
// 승인과 동일 그룹/작업 코드 사용 (0x14, 0x04)
// S23 자동 생성 및 20바이트 제한 적용
// 4. 응답
"RC=RC_SUCCESS;..."

3.3 TERMINFO 명령 (단말기 정보)

// 1. 클라이언트 요청
"TERMINFO"
// 2. ED785 요청
req.gcd = 0x14; // 시스템 그룹
req.jcd = 0x02; // 단말기 정보
// 3. 응답 (10초 타임아웃)
"RC=RC_SUCCESS;DATA=단말기정보문자열"

3.4 PING 명령 (연결 유지)

// 1. 클라이언트 요청
"PING"
// 2. 즉시 응답 (ED785 통신 없음)
return "PONG";
// 3. TcpServer에서 연결 유지
keepAlive = true;
do {
// 계속 요청 대기
} while (keepAlive);

3.5 DISPLAY 명령 (표시)

// 1. 클라이언트 요청
"DISPLAY <ASCII_PAYLOAD>"
// 2. ED785 요청 (10초 타임아웃)
req.gcd = 0x14; // 시스템 그룹
req.jcd = 0x03; // 표시
// 3. 응답
"RC=RC_SUCCESS;DATA=..."

3.6 PRINT 명령 (출력)

// 1. 클라이언트 요청
"PRINT <ASCII_PAYLOAD>"
// 2. ED785 요청 (10초 타임아웃)
req.gcd = 0x14;
req.jcd = 0x05;
// 3. 응답
"RC=RC_SUCCESS;DATA=..."

3.7 READ_SNO 명령 (시리얼/식별 정보 읽기)

// 1. 클라이언트 요청
"READ_SNO" // 기본
"READ_SNO R" // 옵션 'R' 전달
// 2. ED785 요청 (10초 타임아웃)
req.gcd = 0x01; // 그룹
req.jcd = 0x06; // 작업
req.payload = (arg == "R") ? "R" : "";
// 3. 응답
"RC=RC_SUCCESS;SNO=<HEX_STRING>"

3.8 INIT 명령 (단말기 초기화)

// 1. 클라이언트 요청
"INIT"
// 2. 처리
// 대기 중인 모든 트랜잭션 강제 취소 후
// 0x14/0x01 Fire-and-Forget 전송(응답 대기 없음)
// 3. 응답
"INIT_OK"

3.9 단말기 상태 이벤트 (비동기)

// 0x14/0x09 단말기 상태 정보는 트랜잭션과 매칭하지 않고
// 연결된 모든 클라이언트에게 비동기 이벤트 메시지로 전송됩니다.
// Format: EVENT=TERMINAL_STATUS;CODE=<XX>;DETAIL=<DESC>;TXID=<ID>
/*
[상태 코드 (CODE)]
01: CARD_INSERTED (신용카드 투입)
02: SERVER_CONNECTING (서버 접속 시도)
03: FALLBACK (FALLBACK 상황 발생)
04: CARD_REJECTED (승인 불가능한 카드 인입)
*/

4. 스레드 모델 분석

4.1 스레드 구성

Main Thread (AppMain)
├── TcpServer::acceptThread (연결 수락)
├── ED785Worker::workerThread (하드웨어 통신)
└── Client Threads (거래당 연결, 동적 생성)

4.2 동시성 제어

// 원자적 연산
std::atomic<bool> m_running{false};
std::atomic<uint64_t> m_txSeq{0};
// 뮤텍스 보호
std::mutex m_queueMutex; // ED785Worker 요청 큐
std::mutex m_pendingMutex; // ProtocolAdapter 대기 트랜잭션
std::mutex m_currentMutex; // ED785Worker 현재 요청
// RAII 뮤텍스
std::scoped_lock lock(m_queueMutex);

5. 문자열 인코딩 처리

5.1 인코딩 변환 체인

Client (UTF-8) → TcpServer (UTF-8) → ProtocolAdapter (UTF-8)
ED785Worker (EUC-KR) ← ED785 DLL (EUC-KR) ← KICC 단말기 (EUC-KR)

5.2 변환 함수들 (Windows,하드웨어요구사항,TCP인코딩에 따른 변환 유틸리티)

// EUC-KR → UTF-8
static std::string ConvertEucKrToUtf8(const std::string &euckr) {
// EUC-KR → UTF-16 → UTF-8
int wlen = ::MultiByteToWideChar(949, 0, euckr.c_str(), ...);
std::wstring wstr(wlen);
::MultiByteToWideChar(949, 0, euckr.c_str(), ..., wstr.data(), wlen);
int u8len = ::WideCharToMultiByte(CP_UTF8, 0, wstr.c_str(), ...);
std::string utf8(u8len);
::WideCharToMultiByte(CP_UTF8, 0, wstr.c_str(), ..., utf8.data(), u8len);
return utf8;
}
// UTF-8 → UTF-16
int wlen = ::MultiByteToWideChar(CP_UTF8, 0, response.c_str(), ...);
std::wstring wResp(wlen);
::MultiByteToWideChar(CP_UTF8, 0, response.c_str(), ..., wResp.data(), wlen);
static std::string ConvertEucKrToUtf8(const std::string &euckr)
Definition ProtocolAdapter.cpp:5

6. 에러 처리 및 로깅

6.1 에러 처리 전략

// 1. 가드 클래스 사용
if (!m_running.load()) return false;
// 2. 예외 안전성 (C 스타일)
int result = m_ed785->KReqCmd(...);
if (result != 0) {
// 에러 처리
return false;
}
// 3. 타임아웃 처리
auto status = future.wait_for(std::chrono::milliseconds(timeoutMs));
if (status == std::future_status::timeout) {
return "ERR=TIMEOUT";
}

6.2 로깅 레벨

Logger::info(L"정상 처리"); // 일반 정보
Logger::warn(L"경고 상황"); // 경고
Logger::error(L"에러 발생"); // 에러
Logger::debug(L"디버그 정보"); // 디버그 (성능 제한)
Logger::pay(L"결제 성공"); // 결제성공 (선택적 사용)

7. 성능 최적화 요소

7.1 메모리 관리

// 스마트 포인터 사용
std::unique_ptr<CED785> m_ed785;
std::unique_ptr<PendingTx> pending;
// move 소유권이전
m_callback = std::move(callback);
req.payload.assign(asciiPayload.begin(), asciiPayload.end());

7.2 네트워크 최적화

// SO_REUSEADDR로 포트 재사용
BOOL reuse = TRUE;
::setsockopt(m_listen, SOL_SOCKET, SO_REUSEADDR, ...);
// 거래당 연결로 메모리 효율성
std::thread([this, client]() { ... }).detach();

7.3 폴링 최적화

// 폴링 간격 조절
static constexpr uint32_t POLL_INTERVAL_MS = 200;
// 디버그 로그 제한
static int pollCount = 0;
if (++pollCount % 50 == 0) { // 10초마다
Logger::debug(...);
}
static constexpr uint32_t POLL_INTERVAL_MS
Definition ED785Worker.cpp:8

8. 설정 및 초기화

8.1 설정 로드

// INI 파일에서 설정 로드
// tcpPort, vanPort, vanBaud 등
// 단일 인스턴스 보장
HANDLE hMutex = CreateMutexW(nullptr, TRUE, L"UbiAnyCard_Ver2.Instance");
AppSettings LoadSettings()
Definition Settings.h:23
Definition Settings.h:6

8.2 컴포넌트 초기화 순서

1. Logger 초기화
2. 설정 로드
3. ProtocolAdapter 생성 (의존성 주입)
4. TcpServer 핸들러 등록 후 시작
5. ED785Worker 시작 (시리얼 포트 연결)
6. 메인 루프 시작
Definition ED785Worker.h:14
Definition ProtocolAdapter.h:12
Definition TcpServer.h:11

9. 주요 클래스 상세 분석

9.1 ED785Worker 클래스

역할: KICC ED785 단말기와의 통신을 담당하는 워커 클래스

핵심 특징:

  • 동기 방식, 단일 스레드: KICC DLL이 단일 스레드 요구사항이 있어 모든 DLL 호출을 하나의 워커 스레드에서 처리
  • 윈도우 메시지 처리: DLL 내부에서 윈도우 메시지를 사용하므로 핸들을 넘겨줘야 함
  • 폴링 방식: 요청 큐 → KReqCmd → KGetEvent 폴링 → 응답 콜백

데이터 구조:

struct Request {
int cmd; // 명령 코드
int gcd; // Group Code (그룹 코드)
int jcd; // Job Code (작업 코드)
std::vector<byte> payload; // 전송할 ASCII 문자열 데이터
};
struct Response {
int cmd; // 응답 명령 코드
int gcd; // Group Code
int jcd; // Job Code
int rcd; // Return Code (응답 코드)
std::vector<byte> data; // RDATA (ASCII 응답 데이터)
size_t dataLen; // RDATA 유효 길이
std::vector<byte> hex; // HEXDATA (바이너리 응답 데이터)
size_t hexLen; // HEXDATA 유효 길이
};

std::deque 사용 이유:

  • 성능상의 이점: 양방향에서 O(1) 성능
  • 직접적인 인터페이스 접근: 불필요한 래핑 오버헤드 제거
  • 메모리 효율성: 블록 단위로 메모리 할당
  • 디버깅 및 모니터링 용이성: 큐 크기 직접 접근

9.2 ProtocolAdapter 클래스

역할: 서버 텍스트 명령을 ED785 요청으로 변환하고 응답을 처리

핵심 특징:

  • 동기 처리: std::promise/future로 응답 대기
  • 트랜잭션 관리: 각 요청마다 고유 ID 할당
  • 전자화폐 파싱: 티머니, 캐시비, 레일플러스 자동 감지 및 파싱

주요 메서드:

std::string onCommand(const std::string &cmdline); // 명령 처리
void onEd785Response(const ED785Worker::Response &resp); // ED785 응답 처리
std::string sendAndWait(int gcd, int jcd, const std::string &asciiPayload, uint32_t timeoutMs); // 요청 전송 및 응답 대기
Definition ED785Worker.h:31

9.3 TcpServer 클래스

역할: KICC 프로토콜 준수 TCP 서버

핵심 특징:

  • 거래당 연결 모델: 각 거래마다 새로운 TCP 연결
  • PING Keep-Alive: PING 명령만 연결 유지
  • KICC 프로토콜 준수: ACK 패킷 (0x06 0x06 0x06) 처리

처리 흐름:

클라이언트 연결 수락
→ 요청 수신 (개행 종료 판단)
→ PING 명령 확인
→ ACK 전송 (0x06 0x06 0x06)
→ 핸들러 호출 (동기 처리)
→ 응답 전송
→ ACK 대기 (선택적)
→ PING이면 계속 요청 대기, 아니면 연결 종료

10. 정리

계층화된 설계 동기 처리: 신뢰성 있는 거래 처리를 보장 스레드 안전성

주요 장점:

  • 모듈화: 각 계층이 명확히 분리되어 유지보수성 향상
  • 안정성: 동기 처리와 타임아웃으로 안정적인 거래 처리
  • 확장성: 새로운 명령어나 전자화폐 추가 용이
  • 성능: 효율적인 메모리 관리와 네트워크 최적화

기술적 특징:

  • C++17 표준: RAII, 스마트 포인터, 원자적 연산 활용
  • Windows API: 소켓, 윈도우 메시지, 문자 인코딩 처리
  • KICC DLL 연동: 단일 스레드 요구사항 준수