SP - 3.2 네트워크 프로그래밍 개론 (2)
지난 시간까지 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를 제공!
- TCP/IP Protocol Family : TCP, IP, UDP를 묶은 것!
-
IP (Internet Protocol)
-
Host 식별을 위한 Naming Scheme을 제공한다.
-
Host 간의 "불안정한 Data Packet(Datagram) Delivery"를 제공한다.
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는 물리적인 전송을 수행한다.
- 이 안에는 Embedded Software인 Firmware가 존재한다.
-
OS Kernel 안에는 TCP/IP Protocol이 존재한다.
-
그리고, Host의 Application에서는 이 OS Kernel 내의 Protocol을 사용하기 위해 System Call을 호출한다. ★
- Socket을 사용할 수 있는 Interface가 제공된다. 이를 'Socket Interface'라 한다. ★
- Socket Interface의 내부는 UNIX File I/O로 구성된다. ★★
- Socket을 사용할 수 있는 Interface가 제공된다. 이를 'Socket Interface'라 한다. ★
네트워크 통신은 맨 첫 포스팅에서도 언급했듯이, 비동기적인 Interrupt 방식이다.
- 서버가 네트워크를 통해 데이터를 받으면, 이를 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개라는 어마어마한 숫자를 커버한다.
- ex) 128.101.200.175 ~> dot을 기준으로 1바이트씩 구분되어 총 4바이트를 이룬다.
-
한편, 그럼에도 불구하고, 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)'로 저장된다. ★
-
알다시피, CPU는 Little-Endian을 취급하는 경우가 많다.
-
따라서, 네트워크 통신 시에는 '엔디안 변환 과정'이 필요하다.
-
이를 '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)
- 단순 비트로 표현되어 있는 IP 주소는 직관적이지 않기 때문에 이런 'Dotted Decimal Notation'이 도입되었다.
- 32비트 IP 주소의 각 바이트(Dot로 구분되는)를 Decimal 값으로 바꾸어 보여주는 방식을 의미한다.
- getaddrinfo 함수와 getnameinfo 함수를 통해 IP 주소와 Dotted Decimal Notation 간의 변환을 수행할 수 있다. 이는 추후 자세히 설명할 것이다.
Internet Domain Name
Internet Domain Name은 계층 구조이다.
-
Second-Level까지는 Fixed, 즉, 변하지 않는다.
-
Third-Level부터는 서버 도메인으로, 각 기관에서 재량껏 자유롭게 정의할 수 있다.
- 최하단에는 IP Address가 위치한다. ★
-
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 상에서 맵핑되는 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에 도달할때, 전송할 때의 바이트 스트림 순서 그대로 유지되어 전송된다.
-
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)
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하느냐이다. (후술)
-
즉, 네트워킹 프로그래밍에서 소켓은 그냥 파일 디스크립터이다. 우리가 Open하고 읽고 쓰는 그 파일 디스크립터 말이다.
-
일반적인 Socket Address 구조
- 우리는 앞서 Wrapper 소개 시 언급한, Stevens 교수의 구조를 따른다. (Stevens는 이 포스팅의 참조 교재 저자이다. System Programming Concept의 본좌다.)
struct sockaddr {
uint16_t sa_family; /* Protocol Family 정보 */
char sa_data[14]; /* Address Data */
};
- 앞서, 포트는 16비트 정수로, 프로세스를 구분하는데에 쓰인다고 했다.
- 그리고, Socket 주소는 'IP:Port' 형식이라고 했다.
- 따라서, 우리는 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 기준이다.
getaddrinfo
getaddrinfo : hostname, host address, port, service name을 받아와 Socket Address 구조체에 반영하는 함수이다. ★
-
장점
-
Reentrant하다. (Async-Signal-Safety : "Reentrant" or "Non-Interruptable by Signals")
- Thread Programming에서 사용해도 문제가 없다는 의미
-
Portable한 'Protocol Independent Code'를 작성할 수 있다.
-
-
단점
- getaddrinfo는 사용법이 복잡하다.
- 그나마 사용패턴이 정형화되어 있어서 할만하다.
- 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; // name 대신 address를 보여주겠다는 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
~> 엄청 중요한 내용은 아니다. getaddrinfo, getnameinfo 함수의 자세한 내용을 다 다루기엔 너무 많다. 단지, getaddrinfo와 getnameinfo 함수를 이용해 Internet 정보를 받아올 수 있고, Socket Interface를 이용해 네트워크 프로그래밍을 할 수 있다는 점을 인지하는 것이 중요하다.
Author And Source
이 문제에 관하여(SP - 3.2 네트워크 프로그래밍 개론 (2)), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다 https://velog.io/@junttang/SP-3.2-네트워크-프로그래밍-개론-2저자 귀속: 원작자 정보가 원작자 URL에 포함되어 있으며 저작권은 원작자 소유입니다.
우수한 개발자 콘텐츠 발견에 전념 (Collection and Share based on the CC Protocol.)