Post

Lecture 21: Network Programming: Part I

Computer Systems: A Programmer's Perspective (CS:APP)

Networks


A Client-Server Transaction

대부분의 네트워크 시스템은 클라이언트-서버 모델에 기초한다.

---
config:
    flowchart:
        htmlLabels: false
---
flowchart LR
    client(["Client<br>process"])
    resource[("Resource")]
    server(["Server<br>process"])

    client -- "1. Client sends request" --> server
    server <-- "2. Server<br>handles<br>request" --> resource
    server -- "3. Server sends response" --> client

Hardware Organization of a Network Host

1
2
3
4
5
6
7
8
9
10
11
12
13
14
 CPU
+—————————————————+
|       ALU       |
|        ↕        |
|  Register file  |  System bus      Memory bus
|        ↕        | /               /
|  Bus interface ←+—→ I/O bridge ←——→ Main memory
+—————————————————+        ↑
←—————+—————————————+——————+—————+———————————+——————→
      ↓             ↓            ↓           ↓     ㄴ I/O bus
     USB         Graphics       Disk      Network
  controller      adapter    controller   adapter
  ↑       ↑         ↓            ↕           ↕
Mouse  Keyboard   Monitor       Disk      Network

네트워크와 컴퓨터 사이의 하드웨어 인터페이스를 네트워크 인터페이스 카드(Network Interface Card, NIC)라 하며, 이는 컴퓨터에게 입출력 장치처럼 보인다. Unix API는 네트워크라는 일종의 가상 파일을 읽고 씀으로써 데이터를 주고받는다.

Computer Networks

네트워크(Network)란 호스트라 불리는 시스템들의 집합으로, 이들은 통신 시스템을 통해 서로 통신할 수 있다.

  • 근거리 통신망 (Local Area Network, LAN): 건물~캠퍼스 규모
  • 광역 통신망 (Wide Area Network, WAN): 도시 규모 이상

인터네트워크(Internetwork)는 네트워크들의 네트워크를 의미한다. 줄여서 internet이라고도 하지만, 고유명사 인터넷(Internet)과 구별하기 위해 소문자 i를 사용한다.

Lowest Level: Ethernet Segment

---
config:
    flowchart:
        htmlLabels: false
---
graph TB
    host1["host"]
    host2["host"]
    host3["host"]
    hub("hub")

    host1 & host2 & host3 --o hub

Next Level: Bridged Ethernet Segment

---
config:
    flowchart:
        htmlLabels: false
---
graph LR
    bridge1("bridge")
    bridge2("bridge")
    host111["host"]
    host112["host"]
    host113["host"]
    host121["host"]
    host122["host"]
    host211["host"]
    host212["host"]
    host221["host"]
    host222["host"]
    host223["host"]
    host224["host"]
    host225["host"]
    hub11("hub")
    hub12("hub")
    hub21("hub")
    hub22("hub")

    host111 & host112 & host113 --- hub11
    host121 & host122 --- hub12
    hub11 & hub12 --- bridge1
    bridge1 === bridge2
    bridge2 --- hub21 & hub22
    hub21 --- host211 & host212
    hub22 --- host221 & host222 & host223 & host224 & host225

Next Level: internets

---
config:
    flowchart:
        htmlLabels: false
---
graph LR
    host11["host"]
    host12["host"]
    host13["host"]
    host21["host"]
    host22["host"]
    host23["host"]
    lan1("LAN 1")
    lan2("LAN 2")
    router1("router")
    router2("router")
    router3("router")

    host11 & host12 & host13 --- lan1 --- router1
    router1 == "`**WAN**`" === router3 == "`**WAN**`" === router2
    router2 --- lan2 --- host21 & host22 & host23

internet Protocol

다양한 시스템들이 서로 통신하기 위해서는 프로토콜(Protocol), 즉 데이터 송수신에 대한 공통된 규칙 체계가 필요하다.

  • 명명 규칙 (Naming scheme): 호스트 주소에 대한 통일된 형식을 정의한다.
  • 전달 메커니즘 (Delivery mechanism): 데이터를 표준 전송 단위인 패킷(Packet)으로 분할하여, 모든 통신은 패킷 단위로 이루어진다.

Transferring internet Data via Encapsulation

---
config:
    flowchart:
        htmlLabels: false
---
flowchart TB
    hostA["client"]
    hostB["server"]
    protocol1["protocol software"]
    protocol2["protocol software"]
    protocolR["protocol software"]
    adapter1["LAN1 adapter"]
    adapter2["LAN2 adapter"]
    adapterR1["LAN1 adapter"]
    adapterR2["LAN2 adapter"]

    subgraph lan1["`**LAN1**`"]
        hostA -- "(1) data" --> protocol1
        protocol1 -- "(2) data | PH | FH1" --> adapter1
    end

    subgraph router["`**Router**`"]
        adapter1 -- "(3) data | PH | FH1" --> adapterR1
        adapterR1 -- "(4) data | PH | FH1" --> protocolR
        protocolR -- "(5) data | PH | FH2" --> adapterR2
    end

    subgraph lan2["`**LAN2**`"]
        adapterR2 -- "(6) data | PH | FH2" --> adapter2
        adapter2 -- "(7) data | PH | FH2" --> protocol2
        protocol2 -- "(8) data" --> hostB
    end
  • 패킷 헤더 (Packet Header, PH): 인터넷 라우팅에 대한 정보
  • 프레임 헤더 (Frame Header, FH): LAN 라우팅에 대한 정보


Gloabl IP Internet


인터넷은 인터네트워크의 가장 대표적인 예로, 다음과 같은 프로토콜 집합에 기반한다.

  • IP (Internet Protocol): 호스트 명명 규칙과 호스트 간 패킷(데이터그램) 전달 기능을 제공한다. IP 자체는 패킷 전송의 신뢰성을 보장하지 않으며, 상위 계층의 프로토콜(TCP)에서 이를 처리한다.

  • UDP (Unreliable Datagram Protocol): 프로세스 간 데이터그램 전달 기능을 제공한다. 신뢰성을 보장하지 않으며, TCP에 비해 전송 속도가 빠르다.

  • TCP (Transmission Control Protocol): 프로세스 간 연결을 설정하여, 신뢰할 수 있는 데이터 전송 기능을 제공한다. (데이터의 순서를 보장하고, 손실된 패킷을 재전송하며, 중복 패킷을 제거한다.)

이러한 프로토콜은 소켓 인터페이스를 통해 추상화되므로, 우리는 프로토콜의 세부 사항을 다루지 않고 단지 파일을 읽고 씀으로써 패킷을 주고받을 수 있다.

IPv4 and IPv6

IPv4는 1980년대 초반부터 사용되어 온 표준 IP 주소 체계이다.

1
128.2.203.179
  • 주소는 32비트이며, 점(.)으로 구분된 10진수 형식으로 표현한다.
  • 각 숫자는 1바이트 크기의 정보(0~255)를 나타낸다.
  • 빅 엔디언 순서로 전달되고 저장된다.

128.2로 시작하는 IP 주소는 CMU(카네기 멜런 대학교)에 할당되어 있다. CMU는 약 $2^{16}$개의 IP 주소를 소유하고 있는 셈이다.

IPv6는 128비트 주소를 제공하는 차세대 IP 주소 체계이다. 전 세계 인구가 70억 명이 넘고, 한 사람이 여러 개의 IP 주소를 사용하는 상황에서 32비트를 사용하는 IPv4 주소가 고갈되어 가자, 이에 대한 대안으로서 도입되었다. 꾸준히 채택률이 증가하고 있으나, 아직까지는 IPv4의 점유율이 훨씬 높은 상황이다.

(1) IP Address

네트워크 프로그래밍에는 다양한 구조체가 사용되는데, 그 중 하나가 in_addr이다.

1
2
3
4
/* Internet address structure */
struct in_addr {
    uint32_t    s_addr;
}

네트워크 바이트 순서는 빅 엔디언이므로, 호스트와 네트워크 간 바이트 순서를 변환하는 표준 라이브러리 함수가 제공된다.

1
2
3
4
uint32_t htonl(uint32_t hostlong);
uint16_t htons(uint16_t hostshort);
uint32_t ntohl(uint32_t netlong);
uint16_t ntohs(uint16_t netshort);

IP 주소에 대해 점으로 구분된 10진수 형식(128.2.194.242)과 이진 형식(0x8002C2F2) 간의 변환을 위한 함수 또한 제공된다.

1
2
int inet_pton(int af, const char *src, void *dst);
const char *inet_ntop(int af, const void *src, char *dst, socklen_t size);

(2) Internet Domain Names

일반적으로 사용자는 IP 주소 대신 도메인 이름(Domain name)을 사용한다. 이는 계층적인 명명 시스템으로, .com, .edu, .net과 국가 코드 등이 최상위 도메인에 해당한다.

---
config:
    flowchart:
        htmlLabels: false
---
graph TB
    amazon["amazon"]
    berkeley["berkeley"]
    com[".com"]
    edu[".edu"]
    gov[".gov"]
    mit["mit"]
    net[".net"]
    root["unnamed root"]
    www["www"]

    root --- com & edu & gov & net

    com --- amazon
    edu --- berkeley & mit

    amazon --- www

도메인 이름을 IP 주소로 매핑하는 것은 DNS(Domain Name System)라는 대규모 분산 시스템의 역할이다. 중앙에서는 최상위 도메인만 관리하며, 각 최상위 도메인에는 해당 도메인에 속한 IP 주소를 추적하는 대규모 시스템이 존재한다.

DNS는 개념적으로 거대한 데이터베이스로 생각할 수 있다. nslookup, dig와 같은 애플리케이션을 통해 DNS를 탐색하여 도메인 이름으로 IP 주소를 찾거나, IP 주소로 도메인 이름을 찾을 수 있다.

1
2
3
4
5
6
7
$ nslookup www.cs.cmu.edu
⋮
Non-authoritative answer:
www.CS.cmu.edu  canonical name = SCS-WEB-LB.ANDREW.cmu.edu.
Name:   SCS-WEB-LB.ANDREW.cmu.edu
Address: 128.2.42.95

도메인 이름과 IP 주소 간의 매핑은 일대일이 아닐 수 있다.

  • 다수의 도메인 이름이 동일한 IP 주소에 매핑되어 있는 경우

    1
    2
    3
    4
    5
    6
    7
    
    $ nslookup cs.mit.edu | grep 'Address'
    ⋮
    Address: 18.25.0.23
    
    $ nslookup eecs.mit.edu | grep 'Address'
    ⋮
    Address: 18.25.0.23
    
  • 다수의 도메인 이름이 다수의 IP 주소에 매핑되어 있는 경우

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    
    $ nslookup www.naver.com | grep 'Address'
    ⋮
    Address: 223.130.200.219
    Address: 223.130.192.247
    Address: 223.130.200.236
    Address: 223.130.192.248
    
    $ nslookup naver.com | grep 'Address'
    ⋮
    Address: 223.130.192.247
    Address: 223.130.192.248
    Address: 223.130.200.219
    Address: 223.130.200.236
    
  • 유효한 도메인 이름이 어떠한 IP 주소에도 매핑되어 있지 않은 경우

    1
    2
    3
    4
    
    $ nslookup ics.cs.cmu.edu
    ⋮
    Non-authoritative answer:
    *** Can't find ics.cs.cmu.edu: No answer
    

(3) Internet Connections

인터넷은 주로 TCP라는 연결 지향 프로토콜을 기반으로 한다. 한 호스트가 다른 호스트와 연결(Connection)을 설정하고, 서로 임의의 길이의 바이트 스트림을 주고받는다. 연결의 각 종단점(endpoint)을 소켓(Socket)이라 하며, 소켓 주소는 IP 주소와 포트로 구성된다.

포트(Port)는 16비트 정수로, 클라이언트나 서버 측의 특정 종단점을 식별하기 위해 사용된다.

  • 임시 포트 (Ephemeral port): 클라이언트가 연결을 요청할 때 동적으로 할당되는 포트이다. 통신이 종료되면 할당이 해제된다.
  • 잘 알려진 포트 (Well-known port): 서버에 특정 서비스를 요청하기 위해 사용되는 포트이다.1

커널은 시스템으로 들어오는 다양한 연결을 구분하여, 다른 시스템으로부터 데이터가 도착했을 때 어떤 프로세스를 깨워야 할지 결정한다.


Sockets Interface


---
config:
    flowchart:
        htmlLabels: false
---
sequenceDiagram
    participant C as Client
    participant S as Server

    Note over S: getaddrinfo()
    Note over S: socket()
    Note over S: bind()
    Note over S: listen()
    Note over S: accept()
    Note over C: getaddrinfo()
    Note over C: socket()
    Note over C: connect()
    C-->>S: Connection request
    Note over S: Connection<br>established

    loop Exchange data
        C->>S: Socket write/read
        S->>C: Socket read/write
    end

    Note over C: close()
    C-->>S: EOF
    Note over S: Socket read
    Note over S: close()

서버는 클라이언트와의 연결이 종료된 후, 다른 클라이언트의 연결 요청을 받아들이기 위해 accept() 단계로 돌아갈 수 있다. 이를 반복 서버(Iterative server)라 한다.

Sockets Interface: socket()

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

새로운 소켓을 생성하여, 해당 소켓의 파일 디스크립터를 반환한다.

Sockets Interface: bind()

1
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

생성된 소켓을 지정한 소켓 주소에 바인딩한다.

Sockets Interface: listen()

1
int listen(int sockfd, int backlog);

바인딩된 소켓을 수신 대기 상태(Listening socket)로 만든다.

Sockets Interface: accept()

1
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);

클라이언트로부터 연결 요청이 올 때까지 대기(호출자 차단)하다가, 연결 요청이 도착하면 수락한다. 클라이언트와 통신하는 데 사용할 새로운 소켓(Connected socket)을 생성하여, 해당 소켓의 파일 디스크립터를 반환한다.

Sockets Interface: connect()

1
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

클라이언트와 서버(connected socket) 간의 연결을 설정한다.

Socket Address Structures

1
2
3
4
5
6
7
8
9
10
struct sockaddr {
    uint16_t        sa_family;      /* Protocol family */
    char            sa_data[14];    /* Address data */
};

struct sockaddr_in {
    uint16_t        sin_family;     /* AF_INET */
    uint16_t        sin_port;       /* Port number in network byte order*/
    struct in_addr  sin_addr;       /* IPv4 address in network byte order */
};
  • sockaddr_insockaddr의 하위 클래스에 해당하며, IPv4 소켓에 사용된다.

Host and Service Conversion: getaddrinfo()

1
2
3
4
5
6
int getaddrinfo(const char *node,               /* Hostname or address */
                const char *service,            /* Port or service name */
                const struct addrinfo *hints,   /* Input parameters */
                struct addrinfo **res);         /* Output linked list */
void freeaddrinfo(struct addrinfo *res);
const char *gai_strerror(int errcode);

getaddrinfo()는 호스트 이름/주소, 서비스 이름 등을 소켓 주소 구조체로 변환한다.

  • 호스트 이름에 여러 개의 IP 주소가 매핑되어 있는 경우, 모든 IP 주소를 반환한다.
  • IPv4와 IPv6를 모두 지원하므로, 프로토콜에 독립적이다.
  • Reentrant 함수이다.

Linked List Returned by getaddrinfo()

---
config:
    flowchart:
        htmlLabels: false
---
graph TD
    ai_addr1["ai_addr"]
    ai_addr2["ai_addr"]
    ai_addr3["ai_addr"]
    ai_canonname1["ai_canonname"]
    ai_canonname2["NULL"]
    ai_canonname3["NULL"]
    ai_next1["ai_next"]
    ai_next2["ai_next"]
    ai_next3["NULL"]
    res["res"]

    subgraph addrinfo1["addrinfo struct"]
        ai_canonname1
        ai_addr1
        ai_next1
    end
    subgraph addrinfo2[" "]
        ai_canonname2
        ai_addr2
        ai_next2
    end
    subgraph addrinfo3[" "]
        ai_canonname3
        ai_addr3
        ai_next3
    end

    res --> addrinfo1
    ai_next1 --> addrinfo2
    ai_next2 --> addrinfo3

addrinfo Struct

1
2
3
4
5
6
7
8
9
10
struct addrinfo {
    int              ai_flags;      /* Additional options */
    int              ai_family;     /* 1st argument of socket() */
    int              ai_socktype;   /* 2nd argument of socket() */
    int              ai_protocol;   /* 3rd argument of socket() */
    socklen_t        ai_addrlen;    /* Size of ai_addr struct */
    struct sockaddr *ai_addr;       /* Pointer to socket address structure */
    char            *ai_canonname;  /* Canonical hostname */
    struct addrinfo *ai_next;       /* Pointer to next item in linked list */
};

Host and Service Conversion: getnameinfo()

1
2
3
int getnameinfo(const struct sockaddr *addr, socklen_t addrlen,
                char *host, socklen_t hostlen,
                char *serv, socklen_t servlen, int flags);

getnameinfo()는 소켓 주소 구조체를 호스트와 서비스로 변환한다. (getaddrinfo()의 반대 역할)

Conversion Example

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
#define MAXLINE 8192

int main(int argc, char *argv[]) {
    struct addrinfo hints, *listp, *p;
    char buf[MAXLINE];
    int errcode, flags;

    if (argc != 2) {
        fprintf(stderr, "Usage: %s <domain name>\n", argv[0]);
        return 2;
    }
    /* Get a list of addrinfo records */
    memset(&hints, 0, sizeof(struct addrinfo));
    hints.ai_socktype = SOCK_STREAM;  /* Connections only */
    if ((errcode = getaddrinfo(argv[1], NULL, &hints, &listp)) != 0) {
        fprintf(stderr, "getaddrinfo() error: %s\n", gai_strerror(errcode));
        return 1;
    }
    /* Walk the list and display each IP address */
    flags = NI_NUMERICHOST;  /* Numeric form of the hostname */
    for (p = listp; p; p = p->ai_next) {
        Getnameinfo(p->ai_addr, p->ai_addrlen, buf, MAXLINE, NULL, 0, flags);
        printf("%s\n", buf);
    }
    /* Clean up */
    Freeaddrinfo(listp);
    return 0;
}
1
2
3
4
5
6
7
8
9
10
11
$ ./a.out www.cs.cmu.edu
128.2.42.95
$ ./a.out www.naver.com
223.130.200.219
223.130.192.247
223.130.200.236
223.130.192.248
$ ./a.out ics.cs.cmu.edu
getaddrinfo() error: No address associated with hostname
$ ./a.out localhost
127.0.0.1

localhost는 현재 사용 중인 컴퓨터를 가리키는 호스트 이름이다. 일반적으로 IP 주소 127.0.0.1로 표시되며, IPv6에서는 ::1로 표시된다.


References


Footnote

이 글은 저작자의 CC BY-SA 4.0 라이선스를 따릅니다.