TCP를 통한 파일 전송: 개발자를 위한 실용 가이드
Source: Dev.to
Introduction
파일을 다운로드하거나 비디오를 스트리밍하거나 기기 간에 데이터를 동기화할 때마다, 신뢰할 수 있는 파일 전송이 배경에서 이루어집니다. 이 신뢰성의 핵심은 TCP (Transmission Control Protocol) 로, 데이터가 완전하고 순서대로 도착하도록 보장하는 네트워크 프로토콜입니다.
개발자에게 TCP 파일 전송을 구현하는 방법을 이해하는 것은 단순히 학문적인 것이 아니라, HTTP부터 맞춤형 실시간 애플리케이션까지 모든 것에 동력을 제공하는 기본 지식입니다. 이 가이드에서는 Node.js 를 사용해 실용적인 TCP 파일 전송 시스템을 구축하면서, 소켓과 스트림이 어떻게 함께 작동해 데이터를 네트워크를 통해 안정적으로 이동시키는지 살펴볼 것입니다.
끝까지 읽으면 파일을 원활하게 전송할 수 있는 TCP 서버와 클라이언트용 코드를 모두 직접 실행해 볼 수 있습니다.
TCP와 소켓, 간단히
TCP를 데이터의 신뢰할 수 있는 택배 서비스라고 생각해 보세요. UDP(울타리 너머에 엽서를 던지고 도착하기를 바라는 것)와 달리 TCP는 전송을 보장하고, 순서를 유지하며, 패킷이 손실될 경우 재전송을 처리합니다.
소켓은 통신 엔드포인트이며—두 애플리케이션 사이의 전용 전화선이라고 생각하면 됩니다. 클라이언트 소켓이 서버 소켓에 연결되면 양방향 데이터 흐름을 위한 신뢰할 수 있는 채널이 구축됩니다.
핵심 개념: Node.js에서 net 모듈은 이러한 소켓 프로그래밍 기능을 제공하여 저수준 네트워크 세부 사항을 다루지 않고도 TCP 서버와 클라이언트를 만들 수 있게 합니다.
Source: …
TCP 서버 구축
클라이언트로부터 파일을 받는 TCP 서버를 만드는 방법은 다음과 같습니다:
import { createWriteStream } from "node:fs";
import net from "node:net";
const clientsList = [];
const server = net.createServer((socket) => {
clientsList.push(socket);
const writeStream = createWriteStream("OpsDev.png");
socket.pipe(writeStream);
socket.on("close", () => {
console.log(socket.remoteAddress, ": Client disconnected");
});
socket.on("error", () => {
console.log("Client Lost");
});
console.log("Client Connected", socket.remoteAddress);
});
server.listen(8080, "0.0.0.0", () => {
console.log("Server started on port 8080");
});
구성 요소 설명
net.createServer(callback)– TCP 서버를 생성합니다. 클라이언트가 연결될 때마다 콜백이 실행되어 해당 연결에 대한socket객체를 제공합니다.clientsList배열 – 현재 연결된 모든 클라이언트를 추적합니다. 다중 클라이언트 상황에서 메시지를 브로드캐스트하거나 연결을 관리할 때 유용합니다.socket.pipe(writeStream)– 네트워크 소켓으로 들어오는 데이터 패킷을 파일 쓰기 스트림으로 직접 파이프합니다. Node.js 스트림은 버퍼링과 백프레셔를 자동으로 처리하므로 수동으로 청크를 나눌 필요가 없습니다.- 이벤트 리스너
'close'– 클라이언트가 정상적으로 연결을 끊을 때 발생합니다.'error'– 네트워크 오류와 같은 연결 문제를 처리합니다.
server.listen(8080, "0.0.0.0")– 서버를 모든 네트워크 인터페이스의 8080 포트에 바인딩하여 다른 머신에서도 접근할 수 있게 합니다.
동작 흐름: 클라이언트가 연결 → 서버가 소켓을 생성 → 들어오는 데이터가 자동으로 OpsDev.png에 기록 → 전송이 완료되면 연결이 종료됩니다.
Source: …
TCP 클라이언트 구축
이제 파일을 전송하는 클라이언트를 만들어 보겠습니다:
import { createReadStream, createWriteStream } from "node:fs";
import net from "node:net";
process.stdin.on("data", (input) => {
const inputString = input.toString().trim();
if (inputString === "send") {
const readStream = createReadStream("/Users/sudipos/Desktop/DevOps.png");
readStream.pipe(socket);
readStream.on("end", () => {
console.log("File ended");
});
}
});
const socket = net.createConnection({ host: "172.168.29.148", port: 8080 });
const writeStream = createWriteStream("/Users/sudipos/Desktop/OpsDev.png");
socket.on("error", () => {
console.log("Server Lost");
});
구성 요소 설명
net.createConnection()– 지정된 IP 주소와 포트의 서버에 TCP 소켓 연결을 설정합니다. 이것이 클라이언트 전용 채널이 됩니다.process.stdin.on('data')– 터미널 입력을 감시합니다.send를 입력하고 Enter를 누르면 파일 전송이 시작되어, 파일을 언제 보낼지 수동으로 제어할 수 있습니다.createReadStream(filePath)– 원본 파일에 대한 읽기 스트림을 엽니다. 스트림은 데이터를 청크 단위로 읽어 메모리 사용량을 최소화합니다(대용량 파일에도 적합).readStream.pipe(socket)– 파일 데이터를 TCP 소켓으로 직접 파이프합니다. 파일에서 읽힌 각 청크가 자동으로 TCP 패킷으로 서버에 전송됩니다.readStream.on('end')– 전체 파일이 읽히고 전송이 완료되면 발생하여 전송이 끝났음을 확인합니다.
동작 흐름: 클라이언트가 연결 → send 입력 → 파일이 청크 단위로 읽힘 → 데이터가 소켓을 통해 스트리밍 → 서버가 수신하여 디스크에 기록.
전체 흐름
TCP 파일 전송을 위한 전체 데이터 흐름은 다음과 같습니다:
클라이언트 측 → createReadStream가 파일 바이트를 읽음 → pipe()가 청크를 소켓으로 전송 → TCP 레이어가 데이터를 패킷으로 나눔
네트워크 레이어 → TCP가 패킷 전송, 확인 응답, 재전송 및 순서를 처리함
서버 측 → TCP가 패킷을 재조립 → 소켓이 바이트 스트림을 수신 → pipe()가 청크를 createWriteStream에 기록 → 파일이 디스크에 저장됨
시각적 흐름
[Source File] → [Read Stream] → [Client Socket]
↓
[TCP Network]
↓
[Destination File] ← [Write Stream] ← [Server Socket]
Node.js 스트림의 장점은 **역압(back‑pressure)**을 자동으로 처리한다는 점입니다. 서버가 디스크에 충분히 빠르게 쓰지 못하면, 클라이언트의 읽기 스트림이 서버가 따라잡을 때까지 일시 정지하므로 데이터 손실이 없습니다.
개발자를 위한 주요 고려 사항
- 오류 처리: 예외가 발생하지 않도록 소켓과 스트림 모두에
'error'리스너를 항상 연결하십시오. - 우아한 종료: 전송이 완료되었거나 종료 신호가 발생했을 때 소켓과 스트림을 깔끔하게 닫으세요 (
socket.end(),stream.close()). - 보안: 특히 신뢰할 수 없는 네트워크에서 암호화된 전송을 위해 TLS(
tls모듈)를 사용하십시오. - 확장성: 동시에 많은 업로드가 발생할 경우, 동시 연결 수를 제한하거나 큐 시스템을 사용하는 것을 고려하십시오.
- 파일 무결성: 전송 중에 손상이 발생하지 않았는지 확인하기 위해 전송된 파일을 검증하십시오(예: 체크섬).
오류 처리
- 견고한 오류 처리 – 파일이 존재하지 않으면 어떻게 할까요? 네트워크가 전송 중에 끊기면 어떻게 할까요?
항상try…catch블록으로 작업을 감싸고 스트림 오류를 처리하세요.
보안
- 이 순수 TCP 구현은 데이터를 unencrypted 상태로 전송합니다. 네트워크에 있는 누구든지 패킷을 가로챌 수 있습니다.
프로덕션에서는 TLS/SSL(Node의tls모듈 사용) 또는 HTTPS나 SFTP와 같은 고수준 프로토콜을 사용하세요.
파일 무결성
- 수신된 파일이 원본과 일치하는지 확인하기 위해 체크섬(MD5, SHA‑256)을 추가하는 것을 고려하십시오.
TCP는 손상을 방지하지만, 애플리케이션 수준 검증은 추가적인 안전 계층을 제공합니다.
Scalability
- The
clientsListarray grows with connections.
For high‑traffic servers, implement connection pooling and cleanup logic to prevent memory leaks.
프로토콜 진화
- 이 기본 개념은 HTTP, FTP, WebSocket와 같은 프로토콜이 내부적으로 작동하는 방식을 설명합니다.
예를 들어 HTTP는 단순히 TCP 위에서 구조화된 텍스트 메시지를 주고받는 것입니다.
원시 소켓을 이해하면 상위 레벨 프로토콜을 디버깅하고 최적화하는 데 도움이 됩니다.
결론
TCP를 통해 파일을 전송하는 것은 복잡해 보일 수 있지만, 보셨듯이 Node.js는 net 모듈과 스트림을 사용해 놀라울 정도로 간단하게 만들어 줍니다. 이제 여러분은 네트워크 전반에 걸쳐 데이터를 안정적으로 이동시키는 작동 중인 클라이언트‑서버 시스템을 갖추게 되었습니다—이는 여러분이 매일 사용하는 애플리케이션에서도 사용되는 기본적인 접근 방식과 동일합니다.
다음 단계
- 코드 실험 – 다양한 파일 유형을 전송해 보세요.
- 진행 표시기 추가.
- 중단된 전송을 위한 재개 기능 구현.
각 개선은 네트워크 프로그래밍 및 스트리밍 아키텍처에 대한 이해를 깊게 할 것입니다. TCP와 소켓 프로그래밍을 마스터하는 가장 좋은 방법은 구축하고, 깨고, 다시 구축하는 것입니다. 이 예제들을 시작으로 곧 견고하고 실시간 네트워크 애플리케이션을 설계하게 될 것입니다.