KRX 시세 데이터를 TCP로 받는 방식은 보통 **시세 중계 서버(Relay Server)**에 접속하여 스트림 데이터를 받아오는 형태입니다. TCP는 UDP와 달리 데이터의 경계가 없으므로, 전문 헤더에 정의된 길이만큼 정확하게 잘라서 읽는 로직이 핵심입니다.
Java의 Socket과 BufferedInputStream을 이용한 실무형 샘플 코드를 작성해 드립니다.
KRX 시세 TCP 수신 Java 샘플
import java.io.BufferedInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.net.SocketSocketType;
public class KrxTcpReceiver {
public static void main(String[] args) {
// 1. 접속 정보 설정 (중계 서버 IP 및 포트)
String serverIp = "123.123.123.123";
int port = 20001;
int connectTimeout = 5000; // 5초
// 2. TCP 소켓 연결
try (Socket socket = new Socket()) {
System.out.println("Connecting to KRX Relay Server... " + serverIp + ":" + port);
socket.connect(new InetSocketAddress(serverIp, port), connectTimeout);
socket.setKeepAlive(true); // 연결 유지 옵션
System.out.println("Connected successfully.");
InputStream is = socket.getInputStream();
BufferedInputStream bis = new BufferedInputStream(is);
// 3. 데이터 수신 루프
while (true) {
// 예시: KRX 공통 헤더가 40바이트이고, 그 중 데이터 길이를 나타내는 부분이 있다고 가정
// 실제 규격서의 Header 길이를 사용하세요.
int headerLength = 40;
byte[] headerBuffer = new byte[headerLength];
// 헤더 읽기 (헤더가 다 들어올 때까지 읽음)
if (!readExactly(bis, headerBuffer, headerLength)) {
System.out.println("Connection closed by server.");
break;
}
// 4. 데이터 길이 파싱
// 예: 헤더의 30~34번째 바이트가 바디 길이를 의미하는 ASCII 숫자라고 가정
// 실제 규격서(Fixed Length)에 따라 이 부분을 수정해야 합니다.
int bodyLength = parseBodyLength(headerBuffer);
// 5. 바디(데이터 본문) 읽기
byte[] bodyBuffer = new byte[bodyLength];
if (!readExactly(bis, bodyBuffer, bodyLength)) {
break;
}
// 6. 데이터 처리 (파싱)
processMessage(headerBuffer, bodyBuffer);
}
} catch (IOException e) {
System.err.println("Network Error: " + e.getMessage());
e.printStackTrace();
}
}
/**
* TCP 스트림에서 지정한 길이만큼 데이터를 완전히 읽을 때까지 대기하는 유틸리티
*/
private static boolean readExactly(InputStream is, byte[] buffer, int length) throws IOException {
int totalRead = 0;
while (totalRead < length) {
int read = is.read(buffer, totalRead, length - totalRead);
if (read == -1) return false; // 스트림 종료
totalRead += read;
}
return true;
}
/**
* 전문 헤더에서 바디 길이를 추출 (규격에 따라 구현 필요)
*/
private static int parseBodyLength(byte[] header) {
// 예시: 헤더 특정 위치의 문자열을 숫자로 변환
// String lenStr = new String(header, 30, 4).trim();
// return Integer.parseInt(lenStr);
return 120; // 테스트를 위한 가상 길이
}
/**
* 수신된 전문 출력 및 처리
*/
private static void processMessage(byte[] header, byte[] body) {
try {
// KRX 시세는 보통 EUC-KR(또는 CP949) 인코딩을 사용함
String headerStr = new String(header, "EUC-KR");
String bodyStr = new String(body, "EUC-KR");
System.out.println("[Recv] Header: " + headerStr);
System.out.println("[Recv] Body: " + bodyStr);
System.out.println("--------------------------------------------------");
} catch (Exception e) {
e.printStackTrace();
}
}
}
주요 구현 포인트 및 주의사항

-
readExactly 메서드의 중요성:
-
TCP는 데이터가 쪼개져서 올 수 있습니다. is.read(buffer)를 한 번 호출한다고 해서 100바이트 전문이 한 번에 다 들어온다는 보장이 없습니다.
-
따라서 루프를 돌며 내가 원하는 길이(headerLength 또는 bodyLength)를 다 채울 때까지 읽어야 전문이 깨지지 않습니다.
-
-
전문 규격 파싱 (Message Framing):
-
KRX TCP 데이터는 보통 고정 길이(Fixed Length) 전문입니다.
-
전체 전문이 항상 동일한 길이(예: 150바이트)라면 readExactly(bis, buffer, 150)만 반복하면 됩니다.
-
가변 길이라면, 헤더 어딘가에 포함된 Body Length 필드를 먼저 읽고 그만큼 더 읽는 방식으로 구현합니다.
-
-
인코딩 (Character Set):
-
국내 금융권 데이터는 전통적으로 EUC-KR 혹은 **MS949(CP949)**를 사용합니다. new String(bytes, “EUC-KR”) 처리가 필요할 수 있습니다.
-
-
성능 최적화 (NIO/Netty):
-
연결해야 할 세션이 많거나, 데이터 처리 속도가 매우 빨라야 한다면 위와 같은 OIO(Blocking IO) 방식보다는 Netty 프레임워크를 사용하는 것을 강력히 권장합니다. Netty의 LengthFieldBasedFrameDecoder를 쓰면 위 복잡한 읽기 로직을 한 줄로 해결할 수 있습니다.
-
-
Keep-Alive 및 재접속:
-
장시간 데이터가 없는 야간 시간대에는 방화벽에 의해 세션이 끊길 수 있습니다. socket.setKeepAlive(true)를 설정하고, while문 바깥에 재접속(Retry) 로직을 추가하는 것이 좋습니다.
-
이 코드는 개발 서버에서 실제 데이터가 어떻게 들어오는지 확인하고, 기본적인 파싱 로직을 테스트하기에 적합한 구조입니다.