지난 시간까지 Application, 즉, Host 입장에서의 internet Networking 매커니즘을 알아보았다. 본 연재는, 지난 시간에도 언급한 것처럼, 네트워크가 주된 포인트가 아니다. 우리가 지금 네트워크 개념을 잠시 다루는 이유는, 곧 학습할 'Concurrent Program', '네트워크와 통신하는 동시 프로그램'을 만들기 위한 배경 지식을 얻기 위함이다.
Global IP Internet : 가장 유명한 internet의 예시이다.
"우리가 사용하는 바로 '그 인터넷'을 의미한다."
1969년 등장하였다.
Worldwide Computer의 Connection이다. Wired, Wireless 상관없이.
TCP/IP Protocol Family를 기반으로 한다.
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)
TCP 프로토콜은 데이터 패킷이 사라지지 않고, 전달 순서도 보장되며, Duplicate도 제거한다.
=> 상기한 세 가지 프로토콜을 합쳐서 TCP/IP Family라고 한다. 그리고 Global IP Internet이 바로 이 세 프로토콜을 기준으로 구축된 것이다. ★
Global IP Internet은, Application 입장에서 Socket Interface에 있는 UNIX File I/O 및 함수들을 통해 접근할 수 있다.
Network Adapter는 하드웨어이다. 각각의 Host Device에 붙어있다.
OS Kernel 안에는 TCP/IP Protocol 소프트웨어가 존재한다.
그리고, Host의 Application에서는 이 OS Kernel 내의 Protocol을 사용하기 위해 System Call을 호출한다. ★
네트워크 통신은 맨 첫 포스팅에서도 언급했듯이, 비동기적인 Interrupt 방식이다. 왜냐? I/O니까!
Global IP Internet, 줄여서 Internet(의도적인 대문자)은, 총 32비트의 IP 주소 집합에 Host를 맵핑시킨다.
ex) 128.101.200.175
IP 주소 집합은 '식별자 집합'인, "Internet Domain Names"와 맵핑이 가능하다.
IP 주소를 Domain에 맵핑시킬 수 있다. ★
이때, IP 주소 하나에 대해서 Domain이 하나가 맵핑되는 것이 아니라, IP 주소와 Domain이 모두 서로 서로 복수로 맵핑될 수 있다. ★★
하나의 Internet Host의 Process는 또 다른 Internet Host의 Process와 Connection을 이루어 소통할 수 있다.
기존에는 IPv4, 32비트 주소 집합으로 전세계 Host를 감당했었다.
한편, 그럼에도 불구하고, Internet 사용자가 너무 많아져 32비트 주소 집합만으로는 다가올 미래의 호스트를 전부 커버할 수는 없을 것이라 예측해, 'Internet Engineering Task Force(IETF)'에서는 1996년 IPv6를 새로 도입했다.
여전히 IPv4를 잘 사용하고 있지만, 최근들어 IPv6의 점유율도 높아지고 있다.
IP Address라는 구조체에 32비트 IP 주소가 저장된다.
알다시피, 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) */
};
Internet Domain Name은 계층 구조이다.
Second-Level까지는 Fixed, 즉, 변하지 않는다.
Third-Level부터는 서버 도메인으로, 각 기관에서 재량껏 자유롭게 정의할 수 있다.
Domain Name도 Dot으로 구분한다.
위의 계층 구조에서 예시 Domain Name을 뽑아내보면 아래와 같다.
ex) "www.pdl.cs.cmu.edu" <=> 128.2.131.66
즉, 'Internet Domain Name 계층 구조'에서, Leaf Node부터 First-Level까지 거슬러 올라가는 순서로 Dot을 찍으면서 나열하면 Domain 주소를 얻을 수 있다.
Global IP Internet은 IP Address와 Domain Name의 Mapping 관계를 세계구급의 분산 데이터베이스 DNS에 보관한다.
프로그래머 관점에서, DNS 데이터베이스는 '수많은 Host Entry의 집합'이다.
DNS 데이터베이스에는 수많은 도메인 리스트가 있고, 우리는 이 DNS에서 제공하는 서비스를 받아서 프로그래밍을 하면 된다. ★
우리는 'nslookup'이라는 유틸리티를 이용해 DNS 맵핑 관계를 탐색할 수 있다.
각 Host는 'Localhost'를 가진다.
Hostname : Localhost의 해당 Local에서의 "실제 Domain Name"을 의미한다. 즉, 얘는 호스트마다 다르다.
> 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 주소가 맵핑될 수 있음을 확인할 수 있다. (그냥 예시일 뿐, 실제로 저 주소가 저렇진 않다.
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 : 서버에 의해 제공되는 서비스와 연관된, 미리 예약된 포트 넘버이다.
대표적인 예시(포트넘버/서비스명)
이 예약 포트 정보는 /etc/services에 명시되어 있다.
Connection은 양쪽 Endpoint Socket의 Socket Address의 Pair로 식별한다.
ex) (exClient:exCliPort, exServer:exSerPort)
~> Pair의 Pair
Network와 Internet에 대한 기본 개념을 익혔으니, 이제, Application, 프로그래머 입장에서의 네트워크 프로그래밍에 대해 본격적으로 알아보자. 우선, 네트워크 프로그래밍 시에 가장 많이 사용하는 Socket Interface이다.
Socket Interface : UNIX I/O를 사용해 Network Application을 만드는데 쓰이는 Set of System-Level Functions (System Calls)
Socket Interface를 이용해 네트워크 Application을 작성할 수 있다.
Socket : Kernel 입장에서 Socket은 Connection의 Endpoint이다.
Socket : Application 입장에서 Socket은 File Descriptor이다. ★Application이 Network에 읽고 쓰는데 사용하는 File Descriptor 말이다!
앞서 UNIX I/O 포스팅에서 말했듯이,
"모든 것은 파일이다. 그말은 즉슨, 네트워크도 파일이다." ★
즉, 네트워크 프로그래밍에서 소켓은 그냥 파일 디스크립터이다. 우리가 Open하고 읽고 쓰는 그 파일 디스크립터 말이다.
일반적인 Socket Address 구조
struct sockaddr {
uint16_t sa_family; /* Protocol Family 정보 */
char sa_data[14]; /* Address Data */
};
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 : 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'를 작성할 수 있다.
단점
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; /* 연결리스트 다음 노드에 대한 포인터 */
};
~> 주석이 달린 필드 변수들만 자주 사용한다.
getnameinfo 함수는 getaddrinfo 함수의 반대 역할이다. Socket Address로부터 Host와 Service 정보를 받아온다.
아래는 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를 이용해 네트워크 프로그래밍을 할 수 있다는 점을 인지하는 것이 중요하다.