Skip to content

Network ​

Packet으로 메시지를 정의하고 TCP/UDP로 전송하거나, HTTP, FTP, SFTP로 프로토콜 작업을 수행합니다. 네트워크 I/O는 Promise를 반환하며, 호출자는 작업 상태도 확인해야 합니다.

API 안내 · 핵심 개념 · 루프백 예제

실제 Network 구현은 Node.js 전송 계층으로 SFML 스타일 인터페이스를 제공합니다. FTP는 basic-ftp, SFTP는 ssh2, IPv6 파싱은 ipaddr.js를 사용합니다. SFML C++ 네트워크 모듈을 호출하지 않으며, Packet 형식과 일부 값의 의미는 독립적인 C++ 프로그램으로 대조합니다.

인터페이스와 호출 방식 ​

인터페이스구현 및 조정
IpAddressIPv4 정수/4바이트 생성, IPv6 16바이트 생성, 복사, fromString, toString, toInteger, toBytes, getType, isV4/isV6. 정적 resolve, getLocalAddress, getPublicAddress는 Promise 반환
Dnsresolve, queryNs, queryMx, querySrv, queryTxt, getPublicAddress. 독립 Resolver와 서버 지정 지원
SocketsetBlocking, isBlocking, AnyPort, Status. getLastError는 가장 최근의 하위 계층 오류 반환
TcpSocketconnect, disconnect, send, receive, 포트/원격 주소 조회; setupTlsClient, setupTlsServer, getCurrentCiphersuiteName
TcpListenerlisten, close, accept, getLocalPort
UdpSocketbind, unbind, send, receive, getLocalPort, MaxDatagramSize
SocketSelectoradd, remove, clear, wait, isReady, dispatchReadyCallbacks, 복사 생성자, assign; Receive/Send 비트 플래그
Packetappend, clear, getData, getDataSize, getReadPosition, endOfPacket, isValid, 복사 생성자; 타입이 명확한 write/read; onSend/onReceive 재정의 가능
HttpsetHost, sendRequest, Request, Response; 명시적인 HTTP 요청 버전
Ftpconnect, disconnect, login, keepAlive, 작업 디렉터리, 디렉터리 목록, 디렉터리 이동/생성/삭제, 파일 이름 변경/삭제, 업로드/이어 쓰기/다운로드, sendCommand
Sftpconnect와 login 분리, 비밀번호/키 인증, getSessionInfo, 경로 해석, 디렉터리 및 속성, 이름 변경/삭제, 스트리밍 업로드와 다운로드, TimeoutWithPredicate

I/O나 대기가 있는 메서드는 Promise를 반환하고, 단순 상태 조회와 설정은 동기식입니다. TcpSocket의 disconnect(), listener.close, udp.unbind는 로컬 전송을 동기식으로 종료합니다. FTP/SFTP의 disconnect는 Promise를 반환하며, 진행 중인 작업을 중단하는 동기식 close도 제공합니다.

C++ 출력 참조는 반환 객체로 표현합니다.

작업반환값
TCP send(Uint8Array){ status, sent }
TCP receive(Uint8Array){ status, received }
TCP send/receive(Packet)SocketStatus
UDP send(Uint8Array 또는 Packet, address, port)SocketStatus
UDP receive(Uint8Array 또는 Packet){ status, received, remoteAddress?, remotePort }

SocketHandle은 Node transport 객체의 타입 별칭이며, protected getNativeHandle에서만 사용합니다. 플랫폼 간에 전달할 수 있는 운영체제 fd/SOCKET 숫자가 아닙니다.

TCP, UDP, Selector ​

js
const sf = require('sfml.js');

function check(status) {
  if (status !== sf.SocketStatus.Done) {
    throw new Error(`Socket operation failed: ${status}`);
  }
}

async function exchange() {
  const listener = new sf.TcpListener();
  const client = new sf.TcpSocket();
  const peer = new sf.TcpSocket();
  try {
    check(await listener.listen(0, sf.IpAddress.LocalHost));
    const accepting = listener.accept(peer);
    check(await client.connect(sf.IpAddress.LocalHost,
      listener.getLocalPort(), sf.seconds(1)));
    check(await accepting);

    const received = new sf.Packet();
    const waiting = peer.receive(received);
    check(await client.send(
      new sf.Packet().write('string', '你好').write('u64', 42n)));
    check(await waiting);
    console.log(received.read('string'), received.read('u64'));
  } finally {
    listener.close();
    client.disconnect();
    peer.disconnect();
  }
}
exchange().catch(error => {
  console.error(error);
  process.exitCode = 1;
});

setBlocking(true)는 필요한 데이터나 쓰기 완료를 Promise가 기다린다는 뜻이며, 이벤트 루프를 차단하지 않습니다. setBlocking(false)는 현재 래퍼 계층의 상태에 따라 반환한다는 뜻입니다. TCP connect는 연결을 시작하고 NotReady를 반환하며, Selector.Send로 연결 준비를 기다릴 수 있습니다. TLS 초기화가 HandshakeStarted를 반환한 뒤에는 같은 setup 메서드를 반복 호출하여 핸드셰이크 상태를 확인할 수 있습니다. UDP bind/send와 TCP listen은 실제 포트와 작업 결과를 제공하기 위해 여전히 Node의 완료 알림을 기다립니다.

TCP 원시 바이트 전송의 sent는 이번에 래퍼 계층이 받아들인 바이트 수입니다. 논블로킹 모드에서는 현재 Node writable highWaterMark의 남은 용량까지만 제출합니다. 일부만 수락하면 Partial, 공간이 없으면 NotReady를 반환합니다. 이어서 보낼 때는 아직 보내지 않은 뒷부분을 전달하세요. Node write가 false를 반환하는 것은 백프레셔만 뜻하며, 이번에 이미 큐에 들어간 바이트를 호출자에게 다시 보내라고 요구하지 않습니다. Done은 상대 애플리케이션이 데이터를 이미 소비하거나 영구 저장했다는 뜻이 아닙니다.

TCP Packet은 빅 엔디언 uint32 길이 접두사를 사용합니다. Partial 이후에는 같은 Packet을 계속 전달하세요. 래퍼 계층이 첫 전송의 스냅샷과 오프셋을 유지합니다. 완료되기 전에 다른 전송을 시도하면 NotReady를 반환하여 패킷 내용이 섞이지 않도록 합니다. 수신은 헤더와 본문이 임의로 분할되어 도착하는 경우를 지원하며, 논블로킹 NotReady에서도 이미 받은 부분을 유지합니다. 수신 중인 Packet이 완료되기 전에는 원시 바이트 수신으로 전환할 수 없습니다.

TCP 수신은 Node readable 큐와 백프레셔를 사용하며, 무한히 늘어나는 별도 데이터 복사본을 만들지 않습니다. UDP는 데이터그램 경계를 유지합니다. 대상 배열이 데이터그램보다 작으면 해당 데이터그램을 버리고 Error를 반환합니다. UDP 수신 큐는 최대 4096개 데이터그램 또는 8 MiB를 담으며, 초과하면 새 데이터그램을 버립니다. 단일 데이터그램의 최대 크기는 65507바이트입니다. TCP Packet 수신 한도는 64 MiB이며, 초과하면 연결을 닫고 Error를 반환합니다.

Selector는 래퍼 계층의 읽기/쓰기 준비 상태를 관찰하며, 원시 시스템 select와 동일하다고 보장하지 않습니다. wait 한 번마다 준비 상태 스냅샷을 저장하고, isReady는 그 스냅샷을 조회하며, dispatchReadyCallbacks는 명시적으로 콜백을 전달합니다. wait는 타임아웃 시 false를 반환합니다. 멤버 집합을 변경하거나 모든 멤버를 닫으면 대기가 깨어납니다. 하나의 selector에서는 동시에 하나의 wait만 허용하며, 변경 후 다시 wait할 수 있습니다. selector 복사는 멤버 참조와 준비 상태 스냅샷을 복사할 뿐 socket을 복사하지 않습니다.

연결을 닫으면 대기 중인 읽기, accept, Selector 대기가 깨어납니다. 새 연결은 새 세대 번호를 사용하므로 이전 작업은 새 연결의 데이터를 읽을 수 없습니다. 사용을 마치면 네트워크 객체를 명시적으로 닫으세요. 활성 Node 연결은 프로세스를 계속 실행 상태로 유지합니다.

Packet ​

이후 코드의 sf는 모두 require('sfml.js')를 뜻합니다. 쓰기와 읽기에서 필드 순서와 타입이 일치해야 합니다.

js
const packet = new sf.Packet()
  .write('bool', true)
  .write('i32', -100)
  .write('u64', 18446744073709551615n)
  .write('f32', 1.5)
  .write('string', '音频🌟')
  .write('bytes', new Uint8Array([ 255, 0, 254 ]));
타입 매개변수JS 타입SFML 바이트 형식
boolboolean1바이트
i8/u8, i16/u16, i32/u32number해당 비트 폭의 정수, 빅 엔디언
i64/u64bigint64비트 정수, 빅 엔디언
f32/f64numberIEEE 부동소수점. 실행 호스트의 바이트 순서를 사용하며 SFML memcpy와 동일
stringstringsf::String에 대응: uint32 문자 수 + UTF-32 빅 엔디언 코드 포인트
utf8stringUTF-8로 인코딩한 std::string: uint32 바이트 수 + 바이트
bytesUint8Array이진 std::string: uint32 바이트 수 + 원본 바이트

read(type)은 해당 타입을 반환하며, 데이터가 부족하면 undefined를 반환하고 clear할 때까지 isValid를 false로 만듭니다. 실패해도 누락된 필드를 건너뛰지 않습니다. 문자열 길이를 성공적으로 읽은 상태라면 커서는 문자열 본문 시작에 멈춥니다. 정수 쓰기는 비트 폭에 맞게 변환하며, 64비트 값은 number를 거치지 않습니다. 엔디언이 다른 플랫폼 간의 부동소수점 Packet은 원본 SFML과 마찬가지로 양측에서 변환 방식을 합의해야 합니다.

내부에서는 확장 가능한 Buffer를 사용하며, 정수와 부동소수점을 읽을 때 같은 메모리에 직접 접근합니다. getData는 유효 영역의 뷰를 반환하고, 빈 패킷은 undefined를 반환합니다. 이 뷰는 읽기 전용으로 사용해야 합니다. 용량 확장이나 clear 후에도 이전 뷰는 이전 할당의 수명을 유지합니다. append는 입력을 복사하며, 전송은 입력을 받아들일 때 스냅샷을 생성하므로 이후 호출자가 입력을 수정해도 이미 제출한 바이트는 바뀌지 않습니다. 추가 NAN 매핑이나 바이트별 언어 경계 호출은 없습니다.

onSend는 전송할 바이트를 반환하고, onReceive는 완전한 메시지를 받습니다. 하위 클래스에서 압축이나 사용자 정의 인코딩을 구현할 수 있습니다. 네트워크 수신은 onReceive 호출 전에 기존 패킷을 clear합니다. 두 훅은 동기식으로 실행되므로 비동기 작업은 send/receive 밖에서 수행해야 합니다.

주소, DNS, TLS ​

IpAddress는 불변 값입니다. toBytes는 독립적인 IPv6 바이트 복사본을 반환하고, toInteger는 IPv4에만 적용됩니다. fromString은 DNS 조회를 하지 않으며 축약 IPv4, 8진수 형식, zone ID가 있는 IPv6를 받지 않습니다. equal, notEqual, less, lessEqual, greater, greaterEqual은 주소를 지원합니다. 정렬은 먼저 IPv4/IPv6 순서, 그다음 바이트 순서로 수행합니다.

Dns.resolve는 서버를 지정하지 않으면 hosts 파일을 포함한 시스템 lookup을 사용합니다. 이 경로의 완료 시간은 시스템 리졸버가 결정합니다. 서버를 지정하면 A/AAAA를 병렬 조회합니다. queryNs/Mx/Srv/Txt는 독립적인 Resolver를 사용하며, 타임아웃되면 해당 조회만 취소하고 다른 조회에는 영향을 주지 않습니다. MX의 priority는 preference로, SRV의 name은 target으로 매핑됩니다. 기본 서버 조회가 실패하면 undefined나 빈 배열을 반환합니다.

getLocalAddress는 UDP 라우팅 선택으로 로컬 주소를 구하며, 애플리케이션 데이터를 전송하지 않습니다. 공인 주소 조회에는 외부 DNS/HTTP 서비스가 필요하므로 오프라인 테스트에서는 실제 공인 주소를 조회하지 않습니다. Time.Zero는 타임아웃을 설정하지 않는다는 뜻입니다.

TLS는 연결된 TCP를 클라이언트나 서버로 업그레이드하는 기능을 지원합니다. 클라이언트는 기본적으로 시스템 CA와 hostname을 검증합니다. 인증서 문자열/바이트를 CA로 전달하거나, 명시적으로 false를 전달하여 검증을 끌 수 있습니다. 서버는 인증서, 개인 키, 선택적인 암호문구를 받습니다. 업그레이드 전에 plaintext 읽기/쓰기 큐를 비워야 합니다. 인증서와 암호 스위트는 Node/OpenSSL에서 제공하며, 그 집합이 SFML의 Mbed TLS와 동일하다고 보장하지 않습니다.

HTTP ​

javascript
const sf = require('sfml.js');

async function fetchText(host, uri) {
  const http = new sf.Http(host);
  const request = new sf.Http.Request(uri);
  const response = await http.sendRequest(request, sf.seconds(5));
  if (response.getStatus() !== sf.HttpStatus.Ok) {
    throw new Error(`HTTP status: ${response.getStatus()}`);
  }
  return response.getBody();
}

Request는 setField, setMethod, setUri, setHttpVersion, setBody를 지원합니다. Response는 상태, HTTP 버전, 필드, 본문 조회를 지원합니다. getBody는 UTF-8로 디코딩하고, getBodyBytes는 독립적인 원시 바이트를 가져옵니다. setBody에는 Uint8Array를 직접 전달할 수 있습니다.

SFML에서 HTTP/1.0/1.1 요청 줄을 명시적으로 지정하는 기능을 지원하기 위해 Node TCP/TLS로 요청을 보내고 응답을 점진적으로 파싱합니다. 파서는 Content-Length, chunked, trailer, 중간 1xx 응답, HEAD/본문 없는 상태, 연결 종료로 구분하는 본문을 지원합니다. 요청 본문은 Content-Length를 사용하며, 충돌하는 길이와 Transfer-Encoding은 금지합니다. 리디렉션을 자동으로 따르거나 본문을 압축 해제하거나 HTTP/2로 업그레이드하지 않습니다.

sendRequest의 Time은 요청 전체를 제한하며, 네 번째 인수로 AbortSignal을 전달하여 취소할 수 있습니다. 잘못되거나 잘린 응답은 InvalidResponse로, 연결/TLS 실패와 취소는 ConnectionFailed로 매핑됩니다. 응답 본문의 상한은 64 MiB이고, 미완성 헤더나 chunk framing의 상한은 64 KiB입니다. setHost는 주소를 동기식으로 파싱하며, 실제 DNS와 연결은 sendRequest에서 수행합니다.

FTP ​

FTP는 basic-ftp의 제어 연결, 수동 데이터 채널, 스트리밍 파일 전송을 사용합니다. connect는 로그인하지 않으며, login의 기본값은 anonymous입니다. 열거형과 응답 타입은 패키지에 포함된 SFML 헤더에서 생성하며, 원본 FTP 상태 코드는 그대로 유지합니다. 디렉터리 목록 조회에는 NLST를 보냅니다.

download(remoteFile, localDirectory, mode)는 로컬 디렉터리에 원격 파일 이름으로 저장합니다. upload(localFile, remoteDirectory, mode, append)는 원격 디렉터리에 로컬 파일 이름으로 씁니다. Binary/Ascii/Ebcdic은 각각 TYPE I/A/E를 보내며, 모드 지원 여부는 서버 응답에 따라 결정됩니다. renameFile, deleteDirectory 등은 해당 작업만 수행하고 암묵적으로 재귀 삭제하지 않습니다. sendCommand로 다른 FTP 명령을 보낼 수 있으며, 인수의 줄바꿈과 NUL은 거부합니다.

같은 Ftp 객체의 작업은 직렬 실행합니다. connect의 Time은 연결 수립만 제한하며, 다른 명령은 close로 직접 중단할 수 있습니다. close는 제어 연결과 데이터 연결을 닫습니다. 연결 실패, 연결 종료, 로컬 파일 오류는 해당 SFML 응답으로 매핑하며, 서버 오류는 상태 코드를 유지합니다.

SFTP ​

SFTP는 ssh2를 사용합니다. connect는 SSH 협상을 마치고 인증 직전에서 멈춥니다. getSessionInfo로 호스트 공개 키, SHA-1/SHA-256 지문, 협상된 알고리즘을 읽을 수 있습니다. login은 비밀번호 또는 일치하는 공개 키와 개인 키 및 암호문구를 지원하며, 인증 실패 후 재시도할 수 있습니다.

호스트 키를 known_hosts와 자동 비교하지 않습니다. SFML처럼 SessionInfo를 공개하여 호출자가 판단하도록 합니다. 서버 신원을 검증해야 한다면 login 전에 hostKey.sha256을 신뢰할 수 있는 지문과 비교하세요.

아래 함수에는 애플리케이션이 IpAddress, 인증 정보, 이미 신뢰할 수 있는 지문, 소비 콜백을 전달합니다.

javascript
async function download(address, username, password, trustedFingerprint, consume) {
  const client = new sf.Sftp();
  try {
    const connection = await client.connect(address, 22, sf.seconds(5));
    if (!connection.isOk()) throw new Error(connection.getMessage());
    const info = client.getSessionInfo();
    if (!info || info.hostKey.sha256 !== trustedFingerprint) {
      throw new Error('Unexpected SFTP host key');
    }
    const authentication = await client.login(username, password, sf.seconds(5));
    if (!authentication.isOk()) throw new Error(authentication.getMessage());
    const result = await client.download('/file', async bytes => {
      await consume(bytes);
      return true;
    });
    if (!result.isOk()) throw new Error(result.getMessage());
  } finally {
    client.close();
  }
}

download 콜백은 최대 65536바이트의 Buffer 뷰를 받습니다. false를 반환하면 중단하고, EOF에서는 0바이트 콜백으로 끝납니다. upload 콜백은 { data, size }를 받아 Buffer를 채우고 size를 유효 바이트 수로 설정합니다. false를 반환해도 이번 유효 데이터를 쓴 뒤 중단합니다. 콜백은 Promise를 반환할 수 있습니다. 매번 독립적인 Buffer를 할당하므로 콜백이 이전 뷰를 보관해도 이후 블록에 덮어써지지 않습니다. 업로드 콜백이 완료된 뒤에는 호출자가 제출된 블록을 계속 수정해서는 안 됩니다.

속성에는 경로, FileType, 권한, bigint 크기/UID/GID, Date 시간이 포함됩니다. ssh2의 오프셋 및 속성 수치 인터페이스는 number를 사용하므로, 이 바인딩은 정밀도를 잃지 않도록 0~2⁵³−1 범위의 정확한 오프셋만 받으며 범위를 초과하면 거부합니다. 시간 정밀도는 프로토콜이 제공하는 초 단위입니다. rename(overwrite=true)는 OpenSSH POSIX rename 확장을 사용하며, 지원하지 않는 서버는 오류를 반환합니다.

Time은 한 번의 무진행 대기 주기로 변환됩니다. TimeoutWithPredicate는 연결에 들어오는 데이터나 작업 완료 알림 없이 한 주기가 지나면 조건 함수를 호출합니다. true면 계속 기다리고, false면 Timeout을 반환한 뒤 세션을 닫습니다. Time.Zero는 타이머를 설정하지 않습니다. 이 메커니즘은 Node 이벤트 계층의 대기이므로, libssh2의 EAGAIN/커널 준비 상태 세부 동작과 동일하게 볼 수 없습니다.

close는 진행 중인 대기를 중단합니다. 사용자 콜백의 Promise를 강제 종료할 수는 없지만, 이후 완료되더라도 다시 수립된 세션에서 계속 읽거나 쓰지 않습니다. SFTP 프로토콜 상태는 해당 Result.Value로 매핑합니다. 하위 계층 오류를 libssh2 전용 값과 일대일로 대응할 수 없으면 Error와 원본 메시지를 반환하며, 전용 오류를 임의로 만들어 내지 않습니다.

검증과 데모 ​

npm run demo:network는 로컬 루프백 주소에서 TCP, Packet, UDP, Selector 예제를 실행하며, 끝나면 모든 연결을 닫습니다.

npm run test:runtime에는 네트워크 테스트가 포함됩니다. 로컬 TCP/UDP/DNS/FTP/SSH 서버를 시작하여 실제 소켓, 분할된 메시지, 전송 백프레셔, TLS 신뢰 및 hostname 실패, 비밀번호 및 공개 키 인증, 디렉터리와 스트리밍 파일 작업, 타임아웃, 취소를 테스트합니다. network-oracle.test.cjs는 임시 디렉터리에서 SFML C++ 프로그램을 컴파일하여 Packet 각 필드의 바이트와 IPv4/IPv6 문자열을 대조합니다. Windows에서는 이 Unix 컴파일 명령을 건너뜁니다.

의존성 버전은 package.json과 잠금 파일을 기준으로 합니다. 로컬 루프백 테스트는 공용 인터넷 서비스의 가용성을 검증하지 않습니다. 플랫폼 구성과 실행 방법은 개발 가이드를 참고하세요.

SFML C++ 레퍼런스 ​

원본 클래스 문서: Packet, TcpSocket, UdpSocket, Http, Ftp, Sftp.

패킷 형식과 프로토콜 개념은 업스트림 문서를 참고하세요. 이 바인딩은 Node.js 전송 계층과 Promise I/O를 사용하므로, 상태값과 제한은 JS 문서의 설명을 우선합니다.

Icons made by Lorc, Delapouite & Skoll · game-icons.net · CC BY 3.0