[CS] 네트워크 - 2

sarang_daddy·2023년 2월 16일
0
post-thumbnail

👨‍🎓 학습 목표

앞서 브라우저에서 URL을 입력시 화면에 보이는 과정 중
서버로 부터 리소스를 받으면 받은 리소스가 렌더링되어 화면에 보여짐을 학습했다.

이번에는 서버로 리소스를 요청하고 응답 받는 과정이 어떻게 일어나는지 학습하고 실습해보자.

🌱 학습 키워드

1. 서버와 연결하기

네크워크

네트워크는 둘 이상의 컴퓨터와 이들을 연결하는 링크의 조합이다.
Node + Link + Node 형태로 표현된다.

DNS (Domain Name System)

DNS는 Domain을 IP 주로소 변환하는 작업을 수행한다.

DNS Server

전화번호부처럼 Domain의 IP를 가지고 있는게 DNS Server다.
통신사, 구글 등 기업이 운영하고 있으며, 로컬(사용 컴퓨터) 에도 존재한다.

클라이언트를 통해 url(도메인)을 입력하여 요청하면 DNS Server에서 해당 IP를 반환해준다.
반환된 IP는 다시 사용 될 수 있도록 로컬 DNS Server 캐시에 저장된다.

DNS 모듈

호스트 이름으로 IP를 look up 할수 있다.

const dns = require("node:dns");

dns.lookup("example.org", (err, address, family) => {
  console.log("address: %j family: IPv%s", address, family);
});
// address: "93.184.216.34" family: IPv4

TCP/IP 네트워크 모델

인터넷은 한 가지의 프로토콜만 쓰지 않는다. 네트워크(TCP/IP 모델)은 4가지의 계층으로 이루어진다.
각 계층마다 사용 하는 프로토콜이 존재하며, 여기서 Web에 사용되는 프로토콜이 HTTP다.

TCP (Transmission Control Protocol)

서버에 요청을 보내기 전에 나의(클라이언트) 존재를 알려주는 과정에 TCP 프로토콜이 사용된다.
서버와 처음 인사할 때 3-way Handshake 기법으로 예의바르게 서버 요청을 준비한다.

TCP Socket

서버는 특정 포트가 바인딩된 소켓을 가지고 있다.
해상 서버는 클라이언트의 연결 요청을 소켓을 통해 리스닝 하면서 기다린다.
클라이언트는 서버의 호스트 이름과 포트 번호로 연결을 시도한다.

Socket이란 네트워크 환경에 연결할 수 있게 만들어진 연결부로 TCP에서 동작하는 소켓을 TCP Socket라 한다.

소켓이란?
소켓 프로그래밍

TCP 기반 서버 & 클라이언트의 함수 호출 순서 및 관계

TCP 기반 서번/클라이언트

net.Socket

TCP 소켓을 추상화한 클래스이며 EventEmitter 이기도 하다.
사용자가 생성하여 반환되는 net.createConnection()로 서버와 연결하는데 사용할 수 있다.
또한, 서버 입장에서 net.Server에서 발생한 이벤트 리스너로 클라이언트와 상호작용할 수 있다.

new net.Socket()

new net.Socket()는 Node.js의 net 모듈을 사용하여 TCP 소켓을 생성하는 방법 중 하나다. net.Socket()은 서버와 클라이언트 모두에서 사용할 수 있는 TCP 소켓 인터페이스를 제공한다.

이를 이용하여 소켓을 만들고 socket.connect() 메소드를 사용하여 서버와 연결하거나, socket.write() 메소드를 사용하여 소켓에 데이터를 쓰고, socket.on("data") 이벤트 핸들러를 사용하여 소켓으로부터 데이터를 읽을 수 있다. 또한, socket.on("error") 이벤트 핸들러를 사용하여 소켓 에러를 처리할 수도 있다.

const net = require("net");

const socket = new net.Socket();
socket.connect({ port: 80, host: "example.com" }, () => {
  console.log("Connected to server!");
  socket.write("Hello, server!");
});

socket.on("data", (data) => {
  console.log(`Received data: ${data}`);
});

socket.on("error", (err) => {
  console.error(`Socket error: ${err}`);
});

socket.on("close", () => {
  console.log("Socket closed");
});

TCP Socket in NodeJS
net Socket 모듈

UDP

User Datagram Protocol의 축약어로 컴퓨터가 다른 컴퓨터와 데이터 통신을 하기 위한 규약(프로토콜)의 일종이다. UDP는 세계 통신표준으로 개발된 OSI 모형에서 4번째 계층인 전송 계층(Transport Layer)에서 사용하는 규약이다.
동일 계층에서 사용하는 또 다른 프로토콜로 TCP가 존재한다.

간단하게 TCP의 모든 신뢰성 기능이 없다고 보면 된다.
상대와 접속했고, 전송속도를 48 kbps로 설정했으면 48 kbps로 데이터를 전송하기만 한다.
받는 쪽에서 데이터를 제대로 받고 있는지조차도 신경 안 쓴다.

UDP 헤더에는 목적지주소, 데이터순서, checksum과 실데이터만 포함되고,
확인응답 같은 것이 없기 때문에 TCP보다 용량이 가볍고 송신속도가 빠르다.
하지만 확인응답을 하지 못하기때문에 신뢰도가 TCP보다 떨어지게 된다.
따라서 UDP는 비연결형이라 부르며 TCP는 연결형이라 구분한다.

UDP 나무위키

TCP vs UDP

TCP와 UDP는 모두 인터넷 프로토콜(IP) 스택에서 사용되는 전송 프로토콜이다. 하지만 TCP와 UDP는 목적과 특성이 다르다.

TCP (Transmission Control Protocol)는 연결 지향적인 프로토콜로서 데이터를 전송하기 전에 먼저 연결을 설정하고, 데이터를 보내고 나서는 연결을 종료하는 방식으로 동작한다. 이러한 접근 방식은 신뢰성과 안정성을 보장 한다는 특징이 있다. TCP는 데이터를 분할하여 전송하고, 이를 받는 측에서는 이를 재조립한다. 또한, 데이터 전송 도중에 오류가 발생하면 TCP는 다시 보내는 것으로 재전송을 시도 한다. 이러한 기능은 인터넷 브라우징, 이메일, 파일 전송 등과 같이 데이터 무결성이 중요한 애플리케이션에서 사용된다.

반면, UDP (User Datagram Protocol)는 연결이 없는 프로토콜이다. 이것은 데이터를 보내기만 하고, 수신측에서는 어떠한 응답도 하지 않는다. UDP는 TCP와 달리 데이터를 분할하지 않으며, 데이터의 무결성을 검사하지 않는다. 이러한 기능은 비디오 스트리밍, DNS 조회, 온라인 게임 등과 같이 속도와 실시간 성능이 중요한 애플리케이션에서 사용된다.

따라서, TCP는 안정적인 데이터 전송을 위해 사용되며, UDP는 빠른 속도와 실시간 성능이 필요한 애플리케이션에서 사용된다.

OSI 7 계층 네트워크 모델

네트워크 모델에는 TCP/IP 모델과 OSI 7계층 모델이 있다.
OSI 7계층 모델은 ISO(국제표준화기구)에서 개발한 모델로 네트워크 프로토콜이 통신하는 구조를 7개의 계층으로 분리하여 각 계층간 상호 작동하는 방식을 정해 놓은 것이다.

계층이름단위(PDU)예시프로토콜(Protocols)
7응용 계층 (Application Layer)Data텔넷(Telnet), 구글 크롬, 이메일, 데이터베이스 관리HTTP, SMTP, SSH, FTP, Telnet, DNS, modbus, SIP, AFP, APPC, MAP
6표현 계층 (Presentation Layer)Data인코딩, 디코딩, 암호화, 복호화ASCII, MPEG, JPEG, MIDI, EBCDIC, XDR, AFP, PAP
5세션 계층 (Session Layer)DataNetBIOS, SAP, SDP, PIPO, SSL, TLS, NWLink, ASP, ADSP, ZIP, DLC
4전송 계층 (Transport Layer)TCP-Segment, UDP-datagram특정 방화벽 및 프록시 서버TCP, UDP, SPX, SCTP, NetBEUI, RTP, ATP, NBP, AEP, OSPF
3네트워크 계층 (Network Layer)Packet라우터IP, IPX, IPsec, ICMP, ARP, NetBEUI, RIP, BGP, DDP, PLP
2데이터링크 계층 (DataLink Layer)FrameMAC 주소, 브리지 및 스위치Ethernet, Token Ring, AppleTalk, PPP, ATM, MAC, HDLC, FDDI, LLC, ALOHA
1물리 계층 (Physical Layer)Bit전압, 허브, 네트워크 어댑터, 중계기 및 케이블 사양, 신호 변경(디지털,아날로그)10BASE-T, 100BASE-TX, ISDN, wired, wireless, RS-232, DSL, Twinax

OSI 7 계층의 역할

(7) 응용 계층(Application Layer) : 7 계층

  • OSI 모델에서 가장 상위 계층에 속하며 실제 통신의 최종 목표에 해당하는 가장 중요한 계층이다.
  • 사용자로부터 정보를 입력받아 하위 계층으로 전달하고 하위 계층에서 전송한 데이터를 사용자에게 전달하는 기능을 한다.
  • 응용 프로세스(사용자, 응용 프로그램)가 네트워크에 접근하는 수단을 제공하여 서로 간에 데이터를 교환할 수 있는 창구 역할을 한다.
  • 파일 전송, DB, 원격 접속, 메일 전송 등 응용 서비스를 네트워크에 접속시키는 역할을 담당하는 계층이다. FTP, SMTP, POP3, HTTP 와 같은 프로토콜이 관련 있다. 응용 계층의 데이터 단위는 '메시지(message)' 이다.

(6) 표현 계층(Presentation Layer) : 6 계층

  • 데이터 표현 차이를 해결하기 위하여 서로 다른 데이터 형식을 변환해 주거나 공통 형식을 제공해 주는 계층을 말한다.
  • 송신 측과 수신 측 사이에서 표준화된 데이터 형식에 대해 규정한다. 예를 들면 이미지가 bmp 인지 jpg 인지, 압축이 되었는지 등의 표현과 관련된 구분을 짓는다.
  • 이는 두 시스템 간에 서로 다르게 사용하는 문자 및 그래픽 문자 등을 위해 번역을 수행하며 전송된 데이터를 서로 이해할 수 있도록 하는 것이 목적이다.
  • ex) 유니코드(UTF-8)로 인코딩 되어있는 문서를 ASCII로 인코딩 된 문서로 변환하는 계층이다

(5) 세션 계층(Session Layer) : 5 계층

  • 두 컴퓨터 간의 대화나 세션을 관리하며, 포트(Port)연결이라고도 한다
  • 응용 프로그램 계층 간의 통신에 대한 제어 구조를 제공하기 위해 응용 프로그램 사이의 접속을 설정, 유지, 종료 시켜주는 역할을 하는 계층이다.
  • 통신 장치들 간의 설정을 유지하며 동기화하고, 데이터 단위를 전송 계층으로 전송할 순서를 결정하고, 데이터에 대한 점검 및 복구를 위해 동기화를 위한 위치를 제공한다.
  • 또한 세션을 종료할 필요가 있을 때 적절한 시간을 수신자에게 알려 준다. 세션 계층의 데이터 단위는 '메시지(message)' 이다.

(4) 전송 계층(Transport Layer) : 4 계층

  • 시스템 종단 간에 투명한 데이터 전송을 양방향으로 행하는 계층이며 두 시스템 간의 신뢰성 있는 데이터 전송을 보장한다.
  • 프로토콜은 TCP, UDP, SPX 등과 관련이 있다. 오류 복구와 흐름 제어 기능을 담당하는 계층이기도 하다.
  • 데이터 헤더에는 포트 주소 또는 소켓(socket) 주소를 포함하고 있고 순서(sequence) 또는 세그먼트 번호가 포함되어 있다.
  • 세션 계층으로부터 온 데이터를 수신할 때 데이터를 전송할 수 있는 세그먼트로 나누고 수신 측에서 수신자가 재조립 할 수 있도록 순서를 헤더에 표시하게 된다.
  • 네트워크 계층은 전송해야 하는 시스템에게 각 패킷을 전송하는 일을 하고 전송 계층은 해당 시스템의 응용 프로그램에게 모든 데이터를 전송하는 역할을 한다. 전송 계층에 있어서 데이터 단위는 '세그먼트(segment)' 라 한다.

(3) 네트워크 계층(Network Layer) : 3 계층

  • 네트워크 계층은 패킷을 송신 측으로부터 수신 측으로 전송하는 구간이며 상위 계층에 연결하는데 필요한 데이터 전송과 경로를 선택하는 기능을 제공한다.
  • 데이터가 전송될 수신 측의 주소를 찾고 수신된 데이터의 주소를 확인하여 자신의 것이라면 상위 계층인 전송계층으로 전송한다. 라우팅 프로토콜을 사용하여 최적의 경로를 선택하는 계층이기도 하다.
  • 데이터를 '패킷(Packet)' 단위로 분할하여 전송한 후 재결합하는 방식을 사용한다. 데이터링크 계층이 인접하는 두 노드 간에 전송을 담당한다고 한다면, 네트워크 계층은 각 패킷이 송신지에서 수신지까지 정확하게 전송되도록 경로를 책임지는 구간이다.
  • 트워크 계층의 데이터 단위는 '패킷(Packet)' 이라 한다. 네트워크 계층의 프로토콜로는 IP, X.25 등이 있으며, 네트워크 장비로는 라우터 등이 있다.
  • 물리적 링크를 통해 데이터를 신뢰성 있게 전송하는 계층을 말한다. 하위 계층에 속하며 물리 계층 바로 위에 위치한다.
  • 비트들은 프레임(Frame) 이라는 논리적인 단위로 구성하여 전송한다. 시스템 간에 오류 없는 데이터 전송을 위하여 네트워크에서 받은 데이터 단위(패킷)를 프레임으로 구성하여 물리 계층으로 전송한다.
  • 데이터 링크 계층의 데이터 단위를 '프레임(Frame)' 이라 한다. 프레임은 데이터 + 헤더 + 트레일러로 구성되어 있다.
  • 송신자의 프레임을 수신자 측에서 받는다면 헤더와 트레일러를 제거한 후 네트워크 계층으로 전송한다. 데이터 링크 계층의 프로토콜로는 HDLS, CSMA/CD, ADCCP, LAP-B 등이 있으며, 네트워크 장비로는 브리지, 스위치 등이 있다.

(1) 물리 계층(Physical Layer) : 1 계층

  • 두 시스템 간의 데이터 전송을 위해 링크를 활성화하고 관리하기 위한 기계적, 전기적, 기능적, 절차적 특성 등을 정의하며 허브나 라우터, 네트워크 카드, 케이블 등의 전송매체를 통해 비트들을 전송하는 계층이다. 물리 계층의 데이터 단위는 '비트(bit)' 이다.
  • 송신측 : 데이터 링크 계층에서 0 과 1 로 구성된 비트열의 데이터를 받아 전기적 신호로 변환 후 전송 매체를 통하여 수신 측에 보내게 된다.
  • 수신측 : 송신 측으로부터 받은 전기 신호를 0 과 1 로 구성된 비트열로 복원하여 수신 측의 데이터링크 계층에 전송한다.
  • OSI 모델에서 최하위 계층에 속하며 상위계층에서 전송된 데이터를 물리 매체를 통하여 다른 시스템에 전기적 신호로 전송한다. 랜카드, 케이블, 허브, 라우터와 같은 물리적인 것과 데이터 전송을 위해 사용하는 전압도 물리 계층에 속한다. 물리 계층 프로토콜로는 X.21, RS-232C, RS-449/422-A/423-A 등이 있으며, 네트워크 장비로는 허브, 리피터 등이 있다.

OSI 7 계층 각 레이어의 역할


2. 서버에 요청하기

컴퓨터 간 데이터를 주고받을 때 HTTP,FTP 등의 프로토콜을 사용한다.
이 프로토콜들은 '응용 계층'에 해당되며, 브라우저에서 사용되는 프로토콜이다.

위 TCP/IP 모델에서 처럼 응용 계층에서 전송 -> 인터넷 -> 인터페이스 계층으로 전달되면서
정보가 추가되고, 상대 컴퓨터에게 보내는 방식이다.

HTTP

Hyper Text(HTML)을 전송하기 위한 프로토콜

HTTP Request

HTTP 요청은 클라이언트가 서버로 보내는 이전 데이터 패키지다.

  1. 요청(Request) 라인
  2. 요청에 0개 이상의 요청 헤더
  3. 요청의 선택적(optional) 본문(body)

HTTP 요청이란?

HTTP Request 헤더의 구성

필수 구성

  • Request Line : HTTP 메서드, 요청 URI, HTTP 버전
  • Host: 요청이 전송될 서버의 호스트 이름과 포트 번호를 나타냅다.
  • User-Agent: 클라이언트가 사용하는 웹 브라우저나 애플리케이션의 정보를 나타낸다.
  • Accept: 클라이언트가 받을 수 있는 콘텐츠 타입(미디어 타입)을 서버에 알려준다.

선택 구성

  • Accept-Language: 클라이언트가 선호하는 언어를 서버에 알려준다.
  • Accept-Encoding: 클라이언트가 받을 수 있는 콘텐츠 인코딩을 서버에 알려준다.
    • 대표적으로 gzip, deflate, br 등이 있다.
  • Connection: 클라이언트와 서버 간의 연결 옵션을 제어하는 헤더다.
    • 일반적으로 keep-alive 값을 사용하여 지속적인 연결을 유지한다.

이 외에도 다른 헤더들이 존재한다. 헤더들은 요청의 내용과 목적에 따라 변경된다.
또한, 헤더의 일부는 선택적이기도 하며, 일부 서버는 이러한 선택적 헤더를 무시할 수도 있다.

HTTPS

HTTPS 란 HyperText Transfer Protocol Security로 사용하는 HTTP 프로토콜의 보안 버전이다.

  • SSL 인증서(Secure Socket Layer)을 이용한다.
  • TLS 프로토콜을 통해 세션 데이터를 암호화한다.
    • Transport Layer Security : 과거 SSL에서 발전하며 이름이 변경 된 것이다.
  • HTTPS는 기본 골격이나 사용 목적 등은 HTTP와 거의 동일하지만,
    데이터를 주고 받는 과정에 ‘보안’ 요소가 추가되었다는 것이 가장 큰 차이점이다.
    HTTPS를 사용하면 서버와 클라이언트 사이의 모든 통신 내용이 암호화된다.

HTTP2.0

하나의 TCP Connection에서 여러 요청을 처리할 수 있는 stream을 지원하게 되었다.

HTTP3.0

TCP 방식의 오버헤드 때문에 자주 연결하는 HTTP 요청에는 비효율 적이라고 하여
UDP 기반 QUIC 프로토콜과 HTTP3.0이 논의되고 있다.

HTTP/3 : 과거, 현재 그리고 미래


3. 서버 응답

앞서 HTTP를 통해 서버에 리소스를 요청하면 서버로 부터 응답을 받는다.
응답 받은 리소스를 어떻게 해석하는지는 이전 글로 확인 할 수 있다.

HTTP Response

HTTP Response는 요청에 대한 서버의 답변이다.


추가 키워드

HTTP header

HTTP Header는 아래와 같이 구분된다.

  • HTTP 헤더 내 일반 헤더 (General Header)
  • HTTP 헤더 내 엔터티/개체 헤더 (Entity Header)
  • HTTP 헤더 내 요청 헤더 (Request Header)
  • HTTP 헤더 내 응답 헤더 (Response Header)
  • HTTP 헤더 내 캐시/쿠키

HTTP 헤더의 종류

Content-Length

HTTP에서의 Content-Length는 HTTP 요청 또는 응답에서 전송될 콘텐츠의 크기를 바이트 단위로 나타내는 헤더다. 이 헤더는 메시지 본문의 크기를 알려주기 때문에 수신자는 메시지 본문의 크기를 미리 알 수 있으므로 메시지를 끊임없이 읽어오는 대신에, 내용을 빠르게 처리할 수 있다.

Content-Length 헤더는 요청 메시지와 응답 메시지 모두에 사용될 수 있으며, 값은 0보다 큰 정수여야 한다. 예를 들어, Content-Length: 1234 헤더는 해당 요청 또는 응답 메시지의 본문이 1234바이트라는 것을 의미한다.

Content-Length 헤더는 HTTP/1.0과 HTTP/1.1 모두에서 지원되며, 이 헤더가 포함되어 있지 않으면, 수신자는 서버로부터 전체 메시지를 받을 때까지 대기해야 하므로, Content-Length 헤더는 HTTP의 성능을 향상시키는 중요한 역할을 한다.

Status Code

HTTP의 상태 코드는 HTTP 응답 메시지에 포함되는 3자리 숫자로, 클라이언트가 요청한 작업의 처리 결과를 나타내는 중요한 정보를 제공한다.

HTTP 응답 메시지에서는 "HTTP/1.x" 형태의 상태 라인으로 시작하며, 이 상태 라인은 상태 코드, 간단한 상태 메시지와 함께 구성되는데, 상태 코드의 첫 번째 숫자가 응답 클래스를, 뒤의 두 숫자가 응답의 상세한 상태를 나타낸다.

1xx (Informational) - 정보 전달
2xx (Successful) - 성공적으로 완료
3xx (Redirection) - 추가 조치 필요
4xx (Client Error) - 클라이언트 오류
5xx (Server Error) - 서버 오류

대표적인 상태 코드로는 200 OK (성공적인 요청), 404 Not Found (요청한 자원이 존재하지 않음), 500 Internal Server Error (서버에서 오류가 발생) 등이 있다.

HTTP 상태 코드는 클라이언트와 서버 간의 통신을 보다 쉽게 이해할 수 있도록 돕는 중요한 통신 수단이다.

캐시

캐시란 일반적으로 시스템이나 응용 프로그램에서 자주 사용되는 데이터를 저장하는 임시 저장소다.
캐시를 사용하면 데이터 접근 속도를 높이고 시스템 성능을 향상시킬 수 있다.

캐시정책

캐시 정책은 캐시된 데이터를 언제 유지하고, 언제 삭제하고, 어떤 데이터를 캐시할 것인지를 결정하는 규칙들의 모음이다.

캐시 정책은 캐시에서 데이터를 관리하는 방법에 따라 다양하게 구성된다.
여러 가지 캐시 정책 중에서 대표적인 것은 다음과 같다.

  • Least Recently Used (LRU) : 가장 최근에 사용되지 않은 데이터를 삭제한다.
  • First In, First Out (FIFO) : 가장 먼저 캐시에 들어온 데이터를 삭제한다.
  • Least Frequently Used (LFU) : 가장 적게 사용된 데이터를 삭제한다.
  • Time-to-Live (TTL) : 일정 시간이 지난 데이터를 삭제한다.

이외에도 여러 가지 다른 캐시 정책이 있으며,
어떤 정책이 가장 적합한지는 캐시 사용 목적과 데이터 특성에 따라 달라진다.

MTU (Maximum Transmission Unit)

컴퓨터 네트워킹에서, 레이어의 커뮤니케이션 프로토콜의 최대 전송 단위란
해당 레이어가 전송할 수 있는 최대 프로토콜 데이터 단위의 크기이다.
MTU 지표는 일반적으로 커뮤니케이션 인터페이스와 관련되어 나타난다.

위키백과


📝 실습해보기

JavaScript Socket Programming Examples

클라이언트가 서버에 요청하고 응답 받는 흐름을 예제로 살펴보자.
본 페이지를 따라서 연습한 내용입니다.

dateserver

클라이언트가 컨택하면 현재 Datetime을 콜백해주는 "서버"를 만들어보자.

import net from "net";

const server = net.createServer((socket) => {
  socket.end(`${new Date()}\n`);
});

server.listen(59090);
  • 노드를 사용하면 문자열이나 Buffer 객체를 보낼 수 있다.
  • 문자열을 보내면 노드는 소켓의 현재 인코딩 설정을 사용하여 문자열을 바이트로 인코딩한다.
  • 노드의 콜백 아키텍처는 "기본적으로 루프다"
  • 1회 사용 서버를 만들려면 명시적으로 연결을 종료해야 한다. (write 대신 end를 사용한다.)

dateclient

클라이언트를 만들어보자.
클라이언트가 서버에 접속하면 datetime를 얻고 종료한다.
서버는 연결을 자동으로 닫으므로 클라이언트가 명시적으로 닫을 필요가 없다.

import net from "net";

const client = new net.Socket();
client.connect({ port: 59090, host: process.argv[2] ?? "localhost" });
client.on("data", (data) => {
  console.log(data.toString("utf-8"));
});

  • 기본적으로 소켓에서 받는 데이터는 Buffer 객체이므로 문자열을 얻으려면 인코딩해야한다.
  • 노드 네트워킹은 이벤트 기반이기에 클라이언트는 루프에서 실행된다.
  • 때문에 일회성 클라이언트로 만들려면 명시적으로 client.destroy()를 호출하거나
  • client.end*()를 사용하여 데이터를 제출해야한다.
  • 또는 서버가 종료하도록 할수 있다.

capitalzerserver

  • 클라이언트로부터 데이터를 받아보는 예제
  • 클라이언트는 서버에 텍스트 줄을 보내고 서버는 대문자로 된 줄을 다시 보낸다.
  • JS는 비동기이므로 여러 클라이언트가 자동으로 동시에 처리된다.
  • 각 클라이언트는 대문자화 세션을 실행하는 동안 연결된 자신의 커넥션을 가진다.
import net from "net";

const server = net.createServer((socket) => {
  console.log(
    "Connection from",
    socket.remoteAddress,
    "port",
    socket.remotePort
  );

  socket.on("data", (buffer) => {
    console.log(
      "Request from",
      socket.remoteAddress,
      "port",
      socket.remotePort
    );
    socket.write(`${buffer.toString("utf-8").toUpperCase()}\n`);
  });

  socket.on("end", () => {
    console.log("Closed", socket.remoteAddress, "port", socket.remotePort);
  });
});

server.maxConnections = 20;
server.listen(59898);

capitalzerclient

대문자화 서버용 클라이언트
서버로 보내면 대문자로 답이 온다.

import net from "net";
import readline from "readline";

const client = new net.Socket();
client.connect(59898, process.argv[2] ?? "localhost", () => {
  console.log("Connected to server");
});

client.on("data", (data) => {
  console.log(data.toString("utf-8"));
});

const reader = readline.createInterface({ input: process.stdin });
reader.on("line", (line) => {
  client.write(`${line}\n`);
});

reader.on("close", () => {
  client.end();
});

onetimecapitalizeclient

한 라인만 보내는 클라이언트를 원하는 경우
클라이언트는 수신한 데이터를 처리 후 소켓을 파괴해야 한다.

import net from "net";

const client = new net.Socket();
client.connect({ port: 59898 }, process.argv[2] ?? "localhost", () => {
  client.write(`${process.argv[3]}\r\n`);
});
client.on("data", (data) => {
  console.log(`Server says: ${data.toString("utf-8")}`);
  client.destroy();
});


🔥 구현해보기

특정 url를 입력받으면 DNS Server를 통해 IP를 확인하고,
TCP socket을 통해 이 IP와 소통하고 HTTP request를 해보자.

참고자료

HTTP requests with TCP Sockets in NodeJs
JavaScript Socket Programming Examples

// socket을 위한 모듈
const net = require("net");
const socket = new net.Socket();

// DNS Server를 위한 모듈
const dns = require("dns");

// URL 모듈
const URL = require("url");

// Readline 모듈
const readline = require("readline");
const rl = readline.createInterface({
  input: process.stdin,
  output: process.stdout,
});

// Readline으로 input값 받기
// Test URL 1) http://example.com
// Test URL 2) http://www.disney.co.kr
const main = () => {
  rl.setPrompt(`\x1b[32mURL > \x1b[0m`);
  rl.prompt();
  rl.on("line", (line) => {
    const http = new HttpRequest(line);
    http.init();
  }).on("close", () => {
    rl.close();
    process.exit();
  });
};

// HTTP request 클래스
class HttpRequest {
  constructor(url) {
    this.url = URL.parse(url);
    this.host = this.url.hostname;
    // HTTP 디폴트 PORT = 80, HTTPS 디폴트 PORT = 443
    this.port = this.url.port || this.url.protocol === "https:" ? 443 : 80;
    this.path = this.url.path;
    this.ip;
  }

  // DNS로 IP 가져오기
  getIP() {
    return new Promise((resolve, reject) => {
      dns.lookup(this.host, (err, address, family) => {
        if (err) reject(err);
        else resolve(address);
      });
    });
  }

  // request 요청
  makeRequest() {
    // socket으로 URL 연결
    socket.connect(this.port, this.host);

    // 연결 후 이벤트
    socket.on("connect", () => {
      console.log(`\n\x1b[32mConnected to\x1b[0m ${this.host}`);
      console.log(`\x1b[32mTCP Connetction :\x1b[0m ${this.ip} ${this.port}\n`);

      // Request Message (request line + header 등)
      const rawHttpRequest =
        `GET ${this.path}home/index.jsp HTTP/1.1\r\n` +
        `Accept: text/html\r\n` +
        `Host: ${this.host}\r\n` +
        `User-Agent: node.js/v18.12.0\r\n\r\n`;

      console.log(`\x1b[32mHTTP Request >\x1b[0m\n${rawHttpRequest}`);

      // 소켓에 Request Message 전송
      socket.write(rawHttpRequest);
    });

    let response = "";

    // response 이벤트
    socket.on("data", (data) => {
      // response 문자열 저장
      response += data.toString();
      console.log(`\x1b[32mHTTP Response >\x1b[0m\n${response}`);

      // 소켓 파괴
      socket.destroy();
    });

    // 에러 발생시 이벤트
    socket.on("error", (error) => {
      console.error(`Socket error: ${error}`);
    });
  }

  async init() {
    this.ip = await this.getIP();
    this.makeRequest();
  }
}

main();

😿 출력내용

profile
한 발자국, 한 걸음 느리더라도 하루하루 발전하는 삶을 살자.

0개의 댓글