SP - 3.2 Network Programming (2)

hyeok's Log·2022년 4월 13일
1

SystemProgramming

목록 보기
10/29
post-thumbnail

  지난 시간까지 Application, 즉, Host 입장에서의 internet Networking 매커니즘을 알아보았다. 본 연재는, 지난 시간에도 언급한 것처럼, 네트워크가 주된 포인트가 아니다. 우리가 지금 네트워크 개념을 잠시 다루는 이유는, 곧 학습할 'Concurrent Program', '네트워크와 통신하는 동시 프로그램'을 만들기 위한 배경 지식을 얻기 위함이다.


Global IP Internet & TCP/IP Protocol

Global IP Internet : 가장 유명한 internet의 예시이다.

"우리가 사용하는 바로 '그 인터넷'을 의미한다."

  • 1969년 등장하였다.

  • Worldwide Computer의 Connection이다. Wired, Wireless 상관없이.

  • TCP/IP Protocol Family를 기반으로 한다.

    • TCP/IP Protocol Family : TCP, IP, UDP를 묶은 것!
      • 대부분의 컴퓨터에서 이 TCP, IP, UDP를 제공!

  • IP (Internet Protocol)

    • Host 식별을 위한 Naming Scheme(Host Address)을 제공한다.

    • Host 간의 "불안정한 Data Packet(Datagram) Delivery(Mechanism)"를 제공한다.

    IP 프로토콜은 데이터의 손실을 막지 못한다. Duplicate Packet 문제도 해결하지 못한다.


  • UDP (Unreliable Datagram Protocol)

    • IP 프로토콜을 활용한 형태이다. 하지만, UDP는 여전히 Unreliable하다.

    • IP를 이용해 Process 간의 "불안정한 Packet(Datagram) Delivery"를 제공한다.

      • 불안정하긴 하지만, 굉장히 속도가 빠르다.
    • ~> 따라서, UDP는 데이터 패킷이 손실되어도 크게 문제되지 않는 상황에서 유용하게 쓰일 수 있다. ★


  • TCP (Transmission Control Protocol)

    • IP를 이용해, Connection을 가진 Process 간의 "안정된(Reliable) Byte Stream"을 제공한다.

    TCP 프로토콜은 데이터 패킷이 사라지지 않고, 전달 순서도 보장되며, Duplicate도 제거한다.


=> 상기한 세 가지 프로토콜을 합쳐서 TCP/IP Family라고 한다. 그리고 Global IP Internet이 바로 이 세 프로토콜을 기준으로 구축된 것이다. ★


Global IP Internet은, Application 입장에서 Socket Interface에 있는 UNIX File I/O 및 함수들을 통해 접근할 수 있다.


Internet Application Structure

  • Network Adapter는 하드웨어이다. 각각의 Host Device에 붙어있다.

    • 이 안에는 Embedded Software인 Firmware가 존재한다.
      • Network Adapter와 Firmware는 물리적인 전송을 수행한다.
  • OS Kernel 안에는 TCP/IP Protocol 소프트웨어가 존재한다.

  • 그리고, Host의 Application에서는 이 OS Kernel 내의 Protocol을 사용하기 위해 System Call을 호출한다. ★

    • Socket을 사용할 수 있는 Interface가 제공된다. 이를 'Socket Interface'라 한다. ★
      • Socket Interface의 내부는 UNIX File I/O로 구성된다. ★★

네트워크 통신은 맨 첫 포스팅에서도 언급했듯이, 비동기적인 Interrupt 방식이다. 왜냐? I/O니까!

  • 서버가 네트워크를 통해 데이터를 받으면, 이를 Interrupt 방식으로 커널에게 알린다. Network Adapter가 직접 이 메세지를 보낸다.
    • 이 인터럽트를 커널 내의 Protocol이 수신한다. Protocol은 데이터를 받았다는 사실을 Host에게 알린다.

IP Address & Internet Domain Name

Global IP Internet, 줄여서 Internet(의도적인 대문자)은, 총 32비트의 IP 주소 집합에 Host를 맵핑시킨다.

ex) 128.101.200.175

IP 주소 집합은 '식별자 집합'인, "Internet Domain Names"와 맵핑이 가능하다.

  • IP 주소를 Domain에 맵핑시킬 수 있다. ★

    • 보기 어려운 IP 주소 대신, 식별을 쉽게 하기 위해 도메인이 도입되었다. ★
  • 이때, IP 주소 하나에 대해서 Domain이 하나가 맵핑되는 것이 아니라, IP 주소와 Domain이 모두 서로 서로 복수로 맵핑될 수 있다. ★★

    • Domain A에 여러 IP 주소가 맵핑될 수 있고, 동시에 하나의 IP에도 여러 Domain이 맵핑될 수 있다.

하나의 Internet Host의 Process는 또 다른 Internet Host의 Process와 Connection을 이루어 소통할 수 있다.


IPv4 and IPv6

  • 기존에는 IPv4, 32비트 주소 집합으로 전세계 Host를 감당했었다.

    • ex) 128.101.200.175 ~> dot을 기준으로 1바이트씩 구분되어 총 4바이트를 이룬다.
      • 2^32개라는 어마어마한 숫자를 커버한다.
  • 한편, 그럼에도 불구하고, Internet 사용자가 너무 많아져 32비트 주소 집합만으로는 다가올 미래의 호스트를 전부 커버할 수는 없을 것이라 예측해, 'Internet Engineering Task Force(IETF)'에서는 1996년 IPv6를 새로 도입했다.

    • IPv6는 128비트 주소 집합이다. ★
  • 여전히 IPv4를 잘 사용하고 있지만, 최근들어 IPv6의 점유율도 높아지고 있다.

    • 하지만, 아직까진 IPv4가 대세이므로, IPv4를 기준으로 설명을 진행한다.

IP Address

IP Address라는 구조체에 32비트 IP 주소가 저장된다.

  • IP 주소는 항상 메모리에 'Network Byte Order(Big-Endian Byte Order: MSB가 낮은 번지 주소부터 할당)'로 저장된다. ★
    • 알다시피, CPU는 Little-Endian을 취급하는 경우가 많다. (Intel)

      • 따라서, 네트워크 통신 시에는 '엔디안 변환 과정'이 필요하다. (Intel 기준)

      • 이를 'Host Byte Order'와 'Network Byte Order' 간의 Conversion이라 한다.

        • 이는 프로그래머의 몫이다. ★
struct in_addr {			/* IP Address Structure */
	uint32_t s_addr; 		/* Network Byte Order (Big-Endian) */
};
  • Dotted Decimal Notation
    • 32비트 IP 주소의 각 바이트(Dot로 구분되는)를 Decimal 값으로 바꾸어 보여주는 방식을 의미한다.
      • 단순 비트로 표현되어 있는 IP 주소는 직관적이지 않기 때문에 이런 'Dotted Decimal Notation'이 도입되었다.
        • ex) IP주소 : 0x8065C8AF = 128.101.200.175 (Dotted Decimal Notation)

  • getaddrinfo 함수와 getnameinfo 함수를 통해 IP 주소와 Dotted Decimal Notation 간의 변환을 수행할 수 있다. 이는 추후 자세히 설명할 것이다.

Internet Domain Name

Internet Domain Name은 계층 구조이다.

  • Second-Level까지는 Fixed, 즉, 변하지 않는다.

  • Third-Level부터는 서버 도메인으로, 각 기관에서 재량껏 자유롭게 정의할 수 있다.

    • 최하단에는 IP Address가 위치한다. ★
      • 마치 Directory Hierarchy같은 형태이다.
  • Domain Name도 Dot으로 구분한다.

  • 위의 계층 구조에서 예시 Domain Name을 뽑아내보면 아래와 같다.

    ex) "www.pdl.cs.cmu.edu"       <=>       128.2.131.66

    즉, 'Internet Domain Name 계층 구조'에서, Leaf Node부터 First-Level까지 거슬러 올라가는 순서로 Dot을 찍으면서 나열하면 Domain 주소를 얻을 수 있다.


DNS(Domain Naming System)

Global IP Internet은 IP Address와 Domain Name의 Mapping 관계를 세계구급의 분산 데이터베이스 DNS에 보관한다.

  • 프로그래머 관점에서, DNS 데이터베이스는 '수많은 Host Entry의 집합'이다.

    • 각 Host Entry는 IP 주소와 Domain Name의 맵핑 관계를 정의한다.
    • 즉, Host Entry는 Domain Name, IP 주소와 동등한 Class이다.
  • DNS 데이터베이스에는 수많은 도메인 리스트가 있고, 우리는 이 DNS에서 제공하는 서비스를 받아서 프로그래밍을 하면 된다. ★


  • 우리는 'nslookup'이라는 유틸리티를 이용해 DNS 맵핑 관계를 탐색할 수 있다.

  • 각 Host는 'Localhost'를 가진다.

    • Localhost : 항상 'Loopback Address'인 127.0.0.1로 맵핑되는 Domain Name을 'Localhost'라고 한다.
  • Hostname : Localhost의 해당 Local에서의 "실제 Domain Name"을 의미한다. 즉, 얘는 호스트마다 다르다.

    • 아래의 Shell Example을 보면서 이해하자.
> nslookup localhost			// 현재 로컬 컴퓨터의 Loopback Address
Address: 127.0.0.1
>
> hostname						// Localhost의 Domain Name이 무엇인가
hongildong.ics.cs.cmu.edu
>
> nslookup hongildong.ics.cs.cmu.edu	// 이 도메인의 주소는 무엇인가
Address: 163.230.25.150
>
> nslookup cs.mit.edu
Address: 18.25.0.23
>
> nslookup eecs.mit.edu			// 하나의 IP 주소에 서로 다른 도메인이 존재!
Address: 18.25.0.23

~> MIT 공대 컴퓨터공학과의 IP 주소를 보면, 하나의 IP주소에 대해 서로 다른 Domain Name이 맵핑되어 있음을 알 수 있다. ★

> nslookup www.instagram.com
Address: 31.13.82.174
Address: 31.13.82.176
Address: 31.13.82.125
Address: 31.13.82.199
>
> nslookup instagram.com
Address: 31.13.82.56
Address: 31.13.82.67
Address: 31.13.82.44
Address: 31.13.82.121

~> 여러 도메인 이름에 여러 개의 IP 주소가 맵핑될 수 있음을 확인할 수 있다. (그냥 예시일 뿐, 실제로 저 주소가 저렇진 않다.

  • nslookup은 위와 같이, 인자로 받은 도메인 주소에 대해 DNS(Domain Naming System) 상에서 맵핑되는 IP 주소를 알려준다.
    • 우리는 앞서 이야기한 getaddrinfo, getnameinfo같은 함수로 nslookup 기능을 구현할 수 있다.

Internet Connection

Internet에서, Client와 Server는 'Connection'을 통해 바이트 스트림을 주고받을 수 있다.

  • 이를 'Internet Connection', 줄여서 'Connection'이라 한다.

  • Connection의 특성

    • Point-to-Point : Process들을 각각 Point로 보아, Process의 Pair끼리 Connection을 형성한다.

    • Full-Duplex : 데이터는 Connection의 양방향에서 동시에 전송될 수 있다. (Bidirectional Communication)

    • Reliable : Source에서 보낸 데이터가 Destination에 도달할때, 전송할 때의 바이트 스트림 순서 그대로 유지되어 전송된다. (TCP is Reliable!)


Connection의 양 끝 말단 Endpoint를 Socket이라 한다. ★

Socket의 주소는 "IP주소:Port"의 Pair로 이루어진다.

  • 두 프로세스 간의 Connection Channel이 형성되어 있고, 이 Connection은 프로세스 Pa와 Pb가 이루는데, 각 프로세스의 Endpoint를 Socket이라 부른다.

  • 각 소켓은 IP주소와 Port 넘버를 가진다. ★

  • 포트(Port)는 16비트 정수로, 프로세스를 식별하는데 사용된다. 두 종류의 Port가 있다.

    • Ephemeral Port : OS Kernel이 Client의 Connection Request가 발생할 때 알아서 자동적으로 할당하는 포트이다.

      • 아래의 Well-Known Port를 제외한 모든 포트넘버이다.
    • Well-Known Port : 서버에 의해 제공되는 서비스와 연관된, 미리 예약된 포트 넘버이다.

      • 대표적인 예시(포트넘버/서비스명)

        • echo server : 7/echo
        • ssh serever : 22/ssh
        • email server : 25/smtp
        • Web server : 80/http
      • 이 예약 포트 정보는 /etc/services에 명시되어 있다.


Connection은 양쪽 Endpoint Socket의 Socket Address의 Pair로 식별한다.

ex) (exClient:exCliPort, exServer:exSerPort)
~> Pair의 Pair


Socket Interface

  Network와 Internet에 대한 기본 개념을 익혔으니, 이제, Application, 프로그래머 입장에서의 네트워크 프로그래밍에 대해 본격적으로 알아보자. 우선, 네트워크 프로그래밍 시에 가장 많이 사용하는 Socket Interface이다.

Socket Interface : UNIX I/O를 사용해 Network Application을 만드는데 쓰이는 Set of System-Level Functions (System Calls)

Socket Interface를 이용해 네트워크 Application을 작성할 수 있다.

  • UNIX, Windows, IOS, Android, ARM 등에서 소켓 인터페이스를 제공한다.

Socket

Socket : Kernel 입장에서 Socket은 Connection의 Endpoint이다.
Socket : Application 입장에서 Socket은 File Descriptor이다. ★

Application이 Network에 읽고 쓰는데 사용하는 File Descriptor 말이다!

앞서 UNIX I/O 포스팅에서 말했듯이,
"모든 것은 파일이다. 그말은 즉슨, 네트워크도 파일이다." ★

  • Client와 Server라는 두 Host는 Socket Descriptor를 이용해 서로에게 읽고 쓰는 것이다.

  • 일반적인 File Descriptor와 Socket Descriptor의 차이는, Appliation에서 이를 어떻게 Open하느냐이다. 일반 Descriptor는 우리가 늘 하던대로, Socket Descriptor의 경우 후술할 루틴으로 Open한다.

  • 즉, 네트워크 프로그래밍에서 소켓은 그냥 파일 디스크립터이다. 우리가 Open하고 읽고 쓰는 그 파일 디스크립터 말이다.

  • 일반적인 Socket Address 구조

    • 우리는 앞서 Wrapper 소개 시 언급한, Stevens 교수의 스타일을 따른다.
struct sockaddr { 
	uint16_t sa_family; 		/* Protocol Family 정보 */ 
	char sa_data[14]; 			/* Address Data */ 
};
  • 우리는 sockaddr 구조체를 위와 같이 구성한다. 총 16바이트이다.
    • 앞의 두 바이트는 sa_family로, 프로토콜 정보를 담는다.
    • 뒤의 14 바이트는 주소를 명시한다.

struct sockaddr_in { 
	uint16_t sin_family; 			/* Protocol Family (항상 AF_INET) */ 
	uint16_t sin_port; 				/* Port 넘버 (network byte order) */ 
	struct in_addr sin_addr; 		/* IP 주소 (network byte order) */ 
	unsigned char sin_zero[8]; 		/* 크기 유지를 위한 공간 */ 
};

~> Internet을 위한 Socket Address는 좀 더 구체적이다. 따라서, 아래와 같이 특수 구조체를 도입한다. IPv4 기준이다. (처음 두 바이트는 인터넷 프로토콜 정보, 그 다음 두 바이트는 포트 넘버, 그 다음 네 바이트는 IP주소, 나머지 바이트는 크기 유지용)


getaddrinfo

getaddrinfo : hostname, host address, port, service name을 받아와 Socket Address 구조체에 반영하는 함수이다. ★

  • 장점

    • Reentrant하다. (Async-Signal-Safety : "Reentrant" or "Non-Interruptable by Signals"에서의 그 Reentrant를 의미한다.)

      Reentrant : Thread Programming에서 사용해도 문제가 없다는 의미
      ~> 즉, getaddrinfo함수는 Thread Programming에서 사용해도 무방하다.

    • getaddrinfo 함수를 이용해 Portable한 'Protocol Independent Code'를 작성할 수 있다.

  • 단점

    • getaddrinfo는 사용법이 복잡하다.
      • 그나마 사용패턴이 정형화되어 있어서 할만하다.

int getaddrinfo(
const char *host, 				/* Hostname 또는 Address */
const char *service, 			/* Port 또는 Service 이름 */
const struct addrinfo *hints,	/* 옵션 */
struct addrinfo **result		/* Output 연결리스트 */
); 

void freeaddrinfo(struct addrinfo *result); 	/* Free 연결리스트 */
  • getaddrinfo 함수는 주소 정보에 대한 Linked List를 반환한다.

    • 연결리스트의 각 노드에는 Socket Address 구조체에 대한 정보와, Socket Interface 함수들에 대한 인자 정보들이 담긴다. ★

    • freeaddrinfo 함수로 반환 연결리스트를 해제할 수 있다.


  • getaddrinfo 함수로 인해 반환되는 연결리스트는 다음과 같이 사용된다.

    • Client : 연결리스트를 순회하며, Socket에 대한 호출과 연결이 성공할 때까지 각 Socket Address를 탐색한다.

    • Server : 연결리스트를 순회하며, Socket에 대한 호출과 Binding이 성공할 때까지 각 Socket Address를 탐색한다.

struct addrinfo {
	int ai_flags;
	int ai_family;
	int ai_socktype; 
	int ai_protocol;
	char *ai_canonname; 		/* Host Name */
	size_t ai_addrlen; 			
	struct sockaddr *ai_addr; 	/* 소켓 주소 구조체에 대한 포인터 */
	struct addrinfo *ai_next; 	/* 연결리스트 다음 노드에 대한 포인터 */
};

~> 주석이 달린 필드 변수들만 자주 사용한다.

  • getaddrinfo 함수에서 업데이트하는 addrinfo 구조체에는 여러 정보가 들어있다. ★
    • 이 필드 변수들은 모두 Socket Interface에서 사용할 수 있다.
    • 연결리스트의 각 노드가 가리키는 Socket Address Structure는 Client와 Server에서 직접 사용할 수 있다.
      • 예를 들어, 후술할 connect나 bind 함수 등에서 사용할 수 있다. ★

getnameinfo

getnameinfo 함수는 getaddrinfo 함수의 반대 역할이다. Socket Address로부터 Host와 Service 정보를 받아온다.

  • 역시나 Reentrant하고, Protocol-Independent하다.

  아래는 getaddrinfo, getnameinfo 함수를 이용한 간이 nslookup 구현 코드이다. 이를 통해 어느 정도 가닥을 잡아보자.

int main(int argc, char **argv) {
	struct addrinfo *p, *listp, hints;	// 연결리스트를 받을 준비!
	char buf[MAXLINE];
	int rc, flags;

	memset(&hints, 0, sizeof(struct addrinfo));
	hints.ai_family = AF_INET; 			/* Internet Connection (IPv4) */
	hints.ai_socktype = SOCK_STREAM;
	if ((rc = getaddrinfo(argv[1], NULL, &hints, &listp)) != 0) {
		fprintf(stderr, "getaddrinfo error: %s\n", gai_strerror(rc));
		exit(1);
	}									// Linked List를 리턴으로 받아온다. ★

	flags = NI_NUMERICHOST;			// Domain Name 대신 IP A/ddress를 보여주겠다는 flag
	for (p = listp; p; p = p->ai_next) {
		Getnameinfo(p->ai_addr, p->ai_addrlen, buf, MAXLINE, NULL, 0, flags);
		printf("Address: %s\n", buf);	// 리스트를 순회하면서 IP 주소 화면에 뿌린다. ★
	}

	Freeaddrinfo(listp);				// 받아온 리스트를 해제한다.
	exit(0);
}

(출력)
> ./exnslookup localhost
Address: 127.0.0.1
> ./exnslookup instagram.com
Address: 31.13.82.56
Address: 31.13.82.67
Address: 31.13.82.44
Address: 31.13.82.121

(127.0.0.1 is localhost's localhost, that is a 'Loopback Address')


~> 엄청 중요한 내용은 아니다. getaddrinfo, getnameinfo 함수의 자세한 내용을 다 다루기엔 너무 많다. 단지, getaddrinfo와 getnameinfo 함수를 이용해 Internet 정보를 받아올 수 있고, Socket Interface를 이용해 네트워크 프로그래밍을 할 수 있다는 점을 인지하는 것이 중요하다.


0개의 댓글