네트워크 프로그래밍

Moon·2022년 11월 7일
0
post-thumbnail

컴퓨터시스템(CS:APP) 11장을 바탕으로 작성하였습니다👏

학습 목표 : 클라이언트의 request를 받고, response를 내어주는 웹서버를 만들어보자. 웹서버를 완성했다면 프록시 서버 과제(CMU)에 도전해보자.

(아래 질문에 답할 수 있다면 패스하셔도 좋습니다.👏👏)

  1. 소켓(BSD 소켓)과 소켓인터페이스를 설명할 수 있다.

  2. IP, TCP에 대하여 설명할 수 있다

  3. file descriptor를 설명할 수 있다.

  4. 인터넷 도메인 이름이 등장하게 된 배경과 DNS의 역할을 설명할 수 있다.

  5. HTTP의 정의와 HTTP 트랜잭션을 설명할 수 있다.


📚참고도서📚
Computer Systems: A Programmer's Perspective, Global Edition (Paperback, 3 ed)
Chapter 11. Network Programming


🌻들어가기 전🌼

모든 네트워크 애플리케이션들은 동일한 기본적인 프로그래밍 모델에 기초한다!

1. 클라이언트-서버 프로그래밍 모델

모든 네트워크 애플리케이션은 '클라이언트-서버' 모델에 기초하고 있다. 애플리케이션은 한 개의 서버 프로세스와 한 개 이상의 클라이언트 프로레스로 구성된다.

💻서버💻 : 리소스를 관리하고 리소스를 조작하여 클라이언트에게 서비스 제공(웹 서버는 디스크파일을 관리, 이메일 서버는 읽고 갱신하는 스풀 파일 관리)

👪클라이언트👪와 👨‍💻서버👨‍💻 모델의 근본적인 연산은 트랜잭션이며, 클라이언트-서버 트랜잭션은 네 단계로 구성된다.
➡ 1단계 : 클라이언트는 한 개의 요청을 서버에 보내는 것으로 트랜잭션을 시작
 📚예시 : 웹브라우저에서 웹서버로 html파일을 요청
➡ 2단계 : 서버는 요청을 받고, 자신의 자원을 적절한 방법으로 조작
 📚예시 : 요청을 받은 후 disk file 읽음
➡ 3단계 : 서버는 클라이언트에게 응답을 보내고, 다음 요청까지 대기
 📚예시 : 웹서버에서 웹브라우저로 html 파일 전송
➡ 4단계 : 클라이언트는 응답을 받고 처리
 📚예시 : 응답받은 html 페이지를 화면에 출력

한 개의 호스트는 서로 다른 많은 클라이언트와 서버를 동시에 실행 할 수 있다.


2. 네트워크

🌻등장🌼 : 클라이언트와 서버는 종종 별도의 호스트에서 돌아간다. 컴퓨터 네트워크의 하드웨어 및 소프트웨어 자원을 사용해서 통신하는데, 호스트에게 네트워크는 단지 또 다른 I/O 디바이스다.

네트워크는 기하학적 위치로 구성된 계층구조 시스템이다.

먼저, 하위 수준은 LAN으로 빌딩이나 캠퍼스에 설치한다. 가장 대중적인 LAN기술은 이더넷(Ethernet)이다.
이더넷 세그먼트는 몇 개의 전선들과 허브로 구성된다. 한쪽 끝은 호스트의 어댑터에 연결하고, 다른 끝은 허브의 포트에 연결한다.

이더넷 어댑터는 어댑터의 비휘발성 메모리에 저장된 고유한 48비트 주소를 갖는다. 호스트는 프레임이라고 부르는 비트들을 세그먼트의 다른 호스트에게 보낼 수 있다. 각 프레임은 프레임의 소스, 목적지, 길이를 식별할 수 있는 고정된 헤더 비트와 그 뒤에 데이터 비트가 이어진다.

다음 수준은 다수의 이더넷 세그먼트가 연결되어 브릿지형 이더넷을 형성하며, 전체 빌딩이나 캠퍼스 규모로 설치한다. 브릿지는 허브보다 더 높은 전선의 대역폭(bandwidth)을 갖는다. 같은 세그먼트 내 전송은 종료될 때, 이것을 기억하지 않는 방식으로 대역폭을 절약하는 반면, 다른 세그먼트로 전송되는 포트는 이를 기억하고 프레임을 복사한다.

계층구조의 상부는 다수의 비호환성 LAN들을 라우터라고 부르는 특별한 컴퓨터에 의해 연결된다. 라우터는 다음과 같은 특성을 갖는다.

  • 상호 연결 네트워크를 구성한다
  • 연결되는 각 네트워크에 대해 어댑터(포트)를 갖는다
  • 고속의 point-to-point 연결을 할 수 있으며, WAN(Wide-Area Network)이라고 하는 네트워크의 사례다
  • 라우터는 LAN과 WAN을 통해 internet을 만들기 위해 사용한다

internet의 중요한 특성은 이것이 매우 다르고 비호환적인 기술을 갖는 여러 LAN과 WAN으로 이루어졌다는 것이다.

🤔어떻게 어떤 소스 호스트가 모든 비호환적인 네트워크들을 지나서 데이터비트를 다른 목적지 호스트로 전송할까?🤔

정답은 👏👏프로토콜 소프트웨어 계층👏👏이다.
이 프로토콜은 두 가지 기능을 제공한다.
1) 명명법(Naming Scheme) : 각 호스트가 동일한 형식의 고유한 주소(IP주소)를 갖는다
2) 전달기법(Delivery Mechanism) : 데이터 비트를 패킷이라고 부르는 비연속 단위로 묶는다
-> 패킷은 헤더(패킷 크기, 목적지 주소 등)와 데이터(데이터 비트)로 구성됨


🤸🤸 internet에서 데이터가 하나의 호스트에서 다른 호스트로 이동하는 방법🤸🤸

1단계) 호스트 A는 클라이언트 가상 주소공간에서 커널 버퍼로 데이터를 복사하는 시스템 콜을 호출

2단계) 호스트 A의 프로토콜 소프트웨어는 internet 헤더(to 호스트B)와 LAN1 프레임 헤더(to 라우터)를 데이터에 추가해서 LAN1 프레임 생성 후 어댑터로 전송
-> LAN1 프레임의 데이터가 internet의 패킷임

3단계) LAN1 어댑터는 이 프레임을 네트워크로 복사
-> 주의)호스트A의 'LAN1 어댑터'와 라우터의 'LAN1 어댑터'는 다름!

4단계) 라우터의 LAN1 어댑터는 이 프레임을 전선에서 읽어들이고 프로토콜 소프트웨어로 전달

5단계) 라우터는 목적지 internet 주소를 가져와서 패킷을 전달할 곳을 경정하기 위해 라우팅 테이블을 검색한 후, 이전의 LAN1 프레임 헤더를 벗기고, 호스트 B의 주소를 갖는 새로운 LAN2 프레임 헤더를 앞에 붙여서 어댑터로 전달
-> 라우팅 테이블 : 현재의 네트워크에서 다른 네트워크로 가는 최적의 경로가 등록된 테이블

6단계) 라우터의 LAN2 어댑터는 이 프레임을 네트워크로 복사

7단계) LAN2 어댑터는 이 프레임을 전선에서 읽어들이고 프로토콜 소프트웨어로 전달

8단계) 호스트B의 프로토콜 소프트웨어는 패킷 헤더와 프레임 헤더를 벗기고, 이 데이터를 서버가 읽는 시스템콜을 호출할 때 서버의 가상 주소공간으로 복사


3. 글로벌 IP 인터넷

각 인터넷 호스트는 TCP/IP 프로토콜을 구현한 소프트웨어를 실행한다. 인터넷 클라이언트와 서버는 소켓 인터페이스와 Unix I/O를 사용해 통신한다. 소켓 함수들은 일반적으로 시스템 콜들로 구현된다. 이 시스템콜은 커널에서 트랩을 발생시키며, TCP/IP에서 다양한 커널 모드 함수들을 호출한다.

TCP/IP 프로토콜은 각각 서로 다른 기능을 제공하지만, 여기서는 하나의 독립 프로토콜로 다룬다.

  • IP프로토콜 : 위에 언급한 명명법과 전달기법을 제공한다. IP는 데이터를 잃어버리더라도 복구하려고 하지 않는 점에서 안정적이지 못 하다.
  • TCP프로토콜 : IP 위 구현한 프로토콜로서, 프로세스들 간에 안전한 완전 양방향 연결을 제공한다.

프로그래머 관점에서, 인터넷은 다음 특징을 갖는 호스트의 집합이다.

  1. 호스트의 집합은 32비트 IP주소 집합에 매핑되며, 인터넷 도메인 이름이라고 부르는 식별자 집합에 매핑된다
  2. 인터넷 호스트 프로세스는 연결을 통해 다른 인터넷 호스트 프로세스와 통신한다.

그렇다면 IP주소는 어떻게 매핑되는 걸까?🤔

1) IP주소

IP주소는 비부호형 32비트 정수로 구조체로 저장된다. 인터넷 호스트들이 서로 다른 호스트 바이트 순서를 갖을 수 있기 때문에, TCP/IP는 빅 엔디안 바이트 순서로 통일하여 저장한다.

IP주소는 dotted-decimal표기법을 사용한다. 각 바이트가 십진수 값을 사용하고 다른 바이트들과는 점을 사용해 구분한다.
-> 128.2.194.242는 주소 0x8002c2f2의 dotted-decimal 표현이다

IP주소와 dotted-decimal 스트링 사이를 inet_pton과 inet_ntop을 사용해서 상호변환한다 ("n"은 네트워크, "a"는 어플리케이션, "to"는 방향, "p"는 presentation(host))

2) 인터넷 도메인 이름

IP주소의 집합을 사람에게 친숙한 도메인 이름 집합으로 매핑한다. 도메인 이름 집합은 계층구조를 형성하고 있으며, 각 도메인 이름은 계층구조에서 자신의 위치를 인코드한다. 아래 그림을 보면, 계층구조는 트리로 나타낸다.

  • 0단계 : 이름 없는 루트 노드
  • 1단계 : 비영리조직(ICANN)이 정의한 도메인(com, edu, gov 등)
  • 2단계 : 비영리조직이 인정하는 대행사가 요청한 도메인(cmu 등)
  • 3단계 : 서브도메인 내에서 자유롭게 생성

인터넷은 도메인 이름의 집합과 IP 주소 집합 사이에 매핑을 정의한다. 이들의 매핑을 관리하는 데이터베이스를 DNS(Domain Name System)라고 한다.

3) 인터넷 연결

클라이언트와 서버는 연결을 통해서 바이트 스트림을 주고받는 방식으로 통신하며, 두 개의 프로세스 간 연결이라는 점에서 point-to-point 연결이다

소켓은 연결의 종단점(terminal point)다. 각 소켓은 인터넷 주소와 16비트의 정수 포트로 이루어진 소켓 주소를 갖는다.

소켓은 address : port로 나타낸다.

클라이언트의 소켓 주소 내의 포트는 클라이언트가 연결 요청을 할 때, 커널이 자동으로 할당하며 이것은 단기 포트라고 한다.

서버의 소켓 주소에 있는 포트는 대개 영구적으로 연결되는 잘 알려진 포트들이다.
예를 들어) 웹서버의 포트는 80, 이메일서버의 포트는 25

연결(connection)은 두 개의 종단점의 소켓 주소에 의해 식별된다. 이 두개의 소켓 주소를 소켓 쌍이라고 부르며 tuple로 표현한다

소켓 쌍은 (cliaddr : cliport, servaddr : servport)로 나타낸다

예시) 소켓쌍
(128.2.194.242:51213, 208216.181.15:80)


4. 소켓 인터페이스

🌻등장🌼 : 네트워크 어플리케이션을 만들기 위한 Unix I/O 함수들과 함께 사용되는 함수들의 집합으로 대부분 현대 시스템에서 구현되었다

1) 소켓 주소 구조체

리눅스 커널에게 '소켓'은 통신을 위한 종단점(끝점)
Unix 프로그램에게 '소켓'은 해당 식별자를 가지는 열린파일(read, write가 가능한 파일)을 의미한다.

IP주소와 포트 번호는 항상 네트워크 바이트 순서(빅 엔디안)로 저장된다.

connect, bind, accept 함수는 프로토콜에 특화된 소켓 주소 구조체를 가리키는 포인터를 필요로 한다. 이를 구현하기 위해, 소켓 함수를 sockaddr 구조체로의 포인터를 기대하도록 하고 어플리케이션이 프로토콜에 특화된 구조체로의 모든 포인터를 이 포괄적인 구조체로 캐스팅하도록 정의했다

/* IP socket address structure */
struct sockaddr_in {
uint16_t sin_family; /* Protocol family (always AF_INET) */
uint16_t sin_port; /* Port number in network byte order */
struct in_addr sin_addr; /* IP address in network byte order */
unsigned char sin_zero[8]; /* Pad to sizeof(struct sockaddr) */
};
/* Generic socket address structure (for connect, bind, and accept) */
struct sockaddr {
uint16_t sa_family; /* Protocol family */
char sa_data[14]; /* Address data */
};

2) Socket 함수

클라이언트와 서버는 소켓 식별자를 생성하기 위해 socket 함수를 사용한다

#include <sys/types.h>
#include <sys/socket.h>

int socket(int domain, int type, int protocol);

// Returns: nonnegative descriptor if OK, −1 on error

만약, 소켓을 끝점으로 만들고 싶다면 아래와 같이 하드코드된 인자로 socket함수를 호출하면 된다. 매개변수들을 자동으로 생성해서 코드가 프로토콜에 무관하게 되도록 하는 함수가 존재한다. socket에 의해 리턴된 clientfd 식별자는 부분적으로 열린 것이며, 아직은 읽거나 쓸 수 없다.

clientfd = Socket(AF_INET, SOCK_STREAM, 0); // 32비트IP주소, 소켓이 인터넷 연결의 끝점

3) connect 함수

connect함수는 소켓 주소 addr의 서버와 인터넷 연결을 시도한다. addrlen은 sizeof(sockaddr_in)이 된다.

#include <sys/socket.h>
int connect(int clientfd, const struct sockaddr *addr, socklen_t addrlen);
// Returns: 0 if OK, −1 on error

connect함수는 연결이 성공할 때까지 블록되어 있거나 에러가 발생한다. 연결이 성공된다면 clientfd는 이제 읽거나 쓸 준비가 되었으며, 다음과 같은 소켓 쌍으로 표현한다.

(x:y, addr.sin_addr:addr.sin_port)
클라이언트IP주소 : 클라이언트 단기포트

4) bind 함수

bind, listen, accept 함수는 서버가 클라이언트와 연결하기 위해 사용되는 함수다.

#include <sys/socket.h>
int bind(int sockfd, const struct sockaddr *addr,
socklen_t addrlen);
// Returns: 0 if OK, −1 on error

bind함수는 커널에게 addr에 있는 '서버의 소켓 주소'를 '소켓 식별자 sockfd'와 연결할 지를 체크한다. addrlen 인자는 sizeof(sockaddr_in)이 된다.

5) listen 함수

클라이언트는 연결 요청을 하는 능동적 개체이다. 반면, 서버는 요청을 기다리는 수동적 개체다. 기본적으로, 커널은 socket함수가 만든 식별자는 한 연결의 클라이언트 쪽 끝에서 존재하는 활성화된 소켓에 대응된다. 서버는 listen 함수를 호출해서 이 식별자를 클라이언트 대신에 서버가 사용할 것임을 알려준다.

listen함수는 sockfd를 능동 소켓에서 듣기 소켓으로 변환한다.

6) accept 함수

서버는 accept 함수를 호출해서 클라이언트로부터의 요청을 기다린다.

#include <sys/socket.h>
int accept(int listenfd, struct sockaddr *addr, int *addrlen);
// Returns: nonnegative connected descriptor if OK, −1 on error

accept함수는 연결 요청이 듣기 식별자 listenfd에 도달하기를 기다린다. 그 후 addr 내의 클라이언트의 소켓 주소를 채우고, Unix I/O함수들을 사용해 클라이언트와 통신하기 위해 사용될 수 있는 연결 식별자를 리턴한다.

  • 듣기 식별자 : 클라이언트 연결 요청에 대한 끝점(한 번 생성되고 계속 존재)
  • 연결 식별자 : 클라이언트와 서버 사이의 성립된 연결에 대한 끝점(요청 수락 때마다 생성, 서버가 클라이언트에 서비스하는 동안만 존재)

5. 웹 서버

1) 웹 기초

웹 클라이언트와 서버는 텍스트 기반 프로토콜 - HTTP(Hypertext Transfer Protocol) 을 사용해 상호 연동한다.

HTTP는 간단한 프로토콜?
1. 웹클라이언트(브라우저)는 서버로 인터넷 연결을 오픈하고 컨텐츠 요청
2. 서버는 요청에 응답하고 연결을 닫아줌
3. 브라우저는 컨텐츠를 읽고 이것을 스크린에 출력

FTP(전통 파일 전송 서비스)와 웹 서비스의 큰 구별점은 HTML이라는 언어 사용 여부다. HTML 프로그램은 명령들을 포함하고 있어서 브라우저에게 여러가지 텍스트, 그래픽 객체를 어떻게 표시할 지 알려준다. 그뿐만 아니라 HTML은 페이지에 특정 인터넷 호스트에 저장된 컨텐츠로의 포인터(하이퍼링크)로 보내줄 수 있다.

2) 웹 컨텐츠

웹 클라이언트와 서버에게, 컨텐츠는 연관된 MIME 타입을 갖는 바이트 배열이다.

웹 서버는 두 가지 방법으로 클라이언트(브라우저)에게 컨텐츠를 제공한다.

  • 정적 컨텐츠 : 디스크 파일을 가져와서 전송
  • 동적 컨텐츠 : 실행파일을 돌리고, 그 출력을 전송

웹 서버가 리턴하는 모든 내용들은 서버가 관리하는 파일에 저장되며, 이 파일을 URL이라고 한다.

예를 들어,

http://www.google.com:80/index.html

라는 URL은 포트 80에서 듣고(Listen)있는 웹서버가 관리하는 인터넷 호스트 www.google.com의 /index.html이라는 html파일을 지정한다.

  • 실행 파일을 위한 URL은 파일 이름 뒤에 프로그램의 인자를 포함할 수 있다.
  • '?' 문자는 파일 이름과 인자를 구분하며, 각 인자는 '&'로 구분된다.

클라이언트와 서버는 트랜잭션 동안 URL의 서로 다른 부분을 사용한다.

  • 클라이언트는 http://www.google.com:80 를 사용해서 어떤 종류의 서버에 접속해야 하는 지를 결정한다.
  • 서버는 /index.html 자신의 파일 시스템 상의 파일을 검색하고, 이 요청이 정적 또는 동적 컨텐츠인지 판단한다.

👏어떻게 서버가 URL의 접미어를 해석하는가?👏

  • URL이 정적 또는 동적 컨텐츠를 참조하는 지를 결정하기 위한 표준 규칙은 없다
  • 각각의 서버는 자신이 관리하는 파일들을 위한 자신만의 규칙들이 존재한다
  • 접미어 앞 부분의 '/'는 리눅스의 루트 디렉토리를 나타내는 것이 아니다. 오히려 이 것은 홈 디렉토리를 나타낸다
  • 최소한의 URL은 '/'문자이며, 모든 서버는 이것을 /index.html같은 특정 기본 홈페이지로 확장한다

3) HTTP 트랜잭션

HTTP는 텍스트 라인을 기반으로 하기 때문에, 리눅스 TELNET 프로그램을 사용해서 인터넷 상의 모든 웹 서버와 트랜잭션을 실행할 수 있다.

1번 줄 : TELNET을 리눅스 쉘에서 실행
5~7번 줄 : HTTP 요청
8~17번 줄 : HTTP 응답

  • HTTP 요청
    HTTP는 GET, POST, HEAD 등 메소드(method)를 지원한다. GET 메소드는 서버에게 URI(Unifor Resource Identifier)에 의해 식별되는 내용을 return한다. URI 는 파일 이름과 옵션인 인자들을 포함하는 URL의 접미어다.
    method URI version (GET / HTTP/1.1)

  • HTTP 응답
    응답 라인은 다음과 같은 형태를 가진다.
    version status-code status-message(HTTP/1.0 200 OK)
    버전(version)은 응답이 준수해야할 HTTP 버전을 설명한다. 상태 코드(status-code)는 3비트 양수로, 요청의 특성을 나타낸다.

4) 동적 컨텐츠의 처리

동적컨텐츠는 CGI(Common Gateway Interface)라고 부르는 표준에 따라 처리된다.

(1) 서버가 다음과 같은 요청을 받는다

GET /cgi-bin/adder?15000&213 HTTP/1.1

(2) fork를 호출해 자식 프로세스를 생성한다
(3) execve를 호출해 /cgi-bin/adder 프로그램을 자식의 컨텍스트에서 실행한다
(4) execve를 호출하기 전에, 자식 프로세스는 CGI 환경변수 QUERY-STRING을 "15000&213"으로 설정
(5) adder 프로그램은 런타임에 리눅스 getenv함수를 사용해서 이 값을 참조
(6) 자식 프로세스가 adder을 실행하기 전에 리눅스 dup2함수를 사용해, 표준 출력을 클라이언트와 연계된 연결 식별자로 재지정한다.
(7) 부모는 자식이 생성한 컨텐츠의 종류와 크기를 알지 못 하기 때문에 자식은 Content-type과 Content-length 응답 헤더와 헤더를 종료하는 빈 줄까지 생성해야한다.

*CGI 환경변수의 예


6. 요약

모든 네트워크 어플리케이션은 클라이언트-서버 모델에 기초한다. 어플리케이션은 한 개의 서버와 한 개 이상의 클라이언트로 구성된다. 서버는 자원을 관리하고, 자원을 조작해서 클라이언트에게 서비스를 제공한다. 클라이언트-서버 모델에서 기본 연산은 트랜잭션이며, 이것은 클라이언트로부터의 요청과 이에 대한 서버의 응답으로 이루어진다.

클라이언트와 서버는 인터넷이라고 하는 글로벌 네트워크를 통해서 통신한다. 인터넷은 다음과 같은 특성을 갖는 전 세계적인 규모의 호스트 집단이다.
(1) 각 인터넷 호스트는 IP주소라는 고유한 32비트 이름를 갖는다.
(2) IP주소 집합은 인터넷 도메인 이름의 집합으로 매핑된다.
(3) 서로 다른 인터넷 호스트들은 '연결'을 통해 서로 통신한다.

클라이언트와 서버는 소켓 인터페이스를 사용해서 연결한다. 소켓은 연결의 끝점이며 애플리케이션에게는 파일 식별자의 형태로 제공된다. 소켓 인터페이스는 소켓 식별자를 열고 닫기 위한 함수들을 제공한다. 클라이언트와 서버는 이 식별자들을 서로 읽고 쓰는 방식으로 통신한다.

웹 서버와 이들의 클라이언트(브라우저)는 HTTP 프로토콜을 사용해서 통신한다. 브라우저는 서버로부터 정적, 동적 컨텐츠를 요청한다. 정적 요청은 디스크에서 파일을 가져와서 이것을 클라이언트에게 돌려주는 방식으로 처리된다. 동적 요청은 서버에서 자식 프로세스 컨텍스트에서 프로그램을 수행한 후 출력을 클라이언트로 리턴해서 처리된다.

profile
안녕하세요. Moon입니다!

0개의 댓글