1. 인터넷의 구조
1-1. IP 주소
- 인터넷에 연결된 호스트는 반드시 하나 이상의 IP 주소를 가지고 있어 이것으로 해당 호스트를 특정 지을 수 있다.
- IP 주소는 부호 없는 32비트 수치인데 사람이 보기 쉽도록 8비트 숫자 네개로 분할하여
192.168.1.3
과 같이 표시한다.
- 포트 번호는 16비트 부호 없는 숫자다.
- 하나의 IP 주소에도 여러 회선이 연결될 수 있다.
- 포트에서 접속을 기다리면서 어떤 작업을 해주는 프로세스를 서버 프로세스라고 한다.
- 서버에 접속해서 어떤 작업을 수행하는 프로세스를 클라이언트 프로세스라고 한다.

- 유명한 서비스들은 사전에 공유된 정해진 포트를 사용한다. 이를 알려진 포트라고 한다.
- 예를 들어 메일을 송신할 때 사용하는 SMTP는 25번, 웹 브라우저가 사용하는 HTTP는 80번 포트를 사용한다.
1-2. IP
- 인터넷에서 사용하는 통신 규약을 IP라고 한다.
- IP 레벨에서는 패킷이란 개념만 존재한다. 패킷이란 데이터의 뭉치, 즉 바이트 열을 말한다.
- 인터넷에서는 호스트 간에 패킷을 주고받음과 동시에 통신도 주고받는다.

- 패킷을 받은 호스트는 자신에게 온 패킷이라면 받고, 아니라면 다른 곳으로 보낸다. 따라서 순서와 수신 여부를 보장하지 않는다.
1-3. TCP와 UDP
- TCP 프로토콜에서 패킷이 스트림으로 추상화되는 과정은 다음과 같다. 스트림은 바이트의 열이다.
- 이 바이트의 열을 앞에서부터 일정 크기로 자른다.
- 각 부분에 번호를 붙여서 패킷으로써 전송한다.
- 받는 쪽에서는 패킷 번호를 보고 데이터를 재형성한다.
- 빠진 부분 없이 바이트 열이 만들어지면 스트림으로 다룰 수 있게 된다.
- 다음과 같이 3번 패킷이 길을 잃었다. 일정 시간이 지나도 오지 않는다면 전송 측에서는 다시 한번 패킷을 보낸다.

- UDP 프로토콜은 패킷이 도착하는 순서와 수신 여부를 보장하지 않는다. 대신 TCP에 비해 속도가 빠르고 처리가 간단하다.
1-4. IPv6
- IPv4와 IPv6의 차이점은 주소의 크기이다. IPv4는 32비트이고 IPv6는 128비트이다.
- IPv6의 주소 표시는 16비트 묶음 8개를 16진수로 표기하고 구분자로
:
를 사용한다.
- 이때
0000
이 반복해서 나타난다면 한 곳에만 ::
이라고 표기할 수 있다.
2. 호스트 이름과 리졸버
2-1. 호스트명
- 네트워크 상의 호스트는 IP 주소로 식별되지만 사람이 다루기 어렵다. 그래서 IP 주소 대신에 호스트명을 사용한다.
- 호스트명과 IP 주소를 대응시켜 놓으면 사람은 호스트명을, 컴퓨터는 IP 주소를 다룰 수 있게 된다.
- 이러한 호스트명과 IP 주소의 매핑은
/etc/hosts
에 기록하여 관리할 수 있다.
- 하지만 호스트가 늘어날 때마다 기록해야하므로 현실적이지 않다.
2-2. DNS와 도메인
- DNS는 호스트명을 도메인이라고 하는 영역에 나눠서 관리함으로써 호스트명의 관리를 전 세계에 분산시켰다.
- 도메인은 트리 구조로 루트 디렉터리에 해당하는 루트 도메인, 그 밑에 com, org, kr과 같은 최상위 도메인, 그 밑에 계속 배치되는 구조이다. 각각의 도메인을 도메인 이름이라고 한다.
- 도메인 이름은 오른쪽이 루트에 해당한다. 예를 들어 www.example.com/은
/
밑에, com
밑에, example.com
밑에, www.example.com
도메인이 있다.
- 인터넷 상의 호스트를 루트 도메인에서부터 전부 기술한 것을 FQDN이라고 한다.
2-3. DNS에 의한 도메인 이름 관리
example.com
처럼 호스트명에 대응되지 않는 도메인명도 있다. 즉, example.com
도메인은 com
도메인과 다른 관리자가 있어서, 그 밑의 도메인을 독자적으로 관리한다.
example.com
도메인 밑의 도메인은 example.com
도메인 관리자에게 물으면 알 수 있다. 이러한 서버를 DNS 서버라고 한다.
- 도메인은 트리 구조로 되어 있어서
example.com
의 DNS 서버는 com
도메인의 DNS 관리자에게 물으면 된다. com
의 DNS 서버는 루트 도메인의 DNS 서버에게 물으면 된다.
/
도메인의 DNS 서버의 IP 주소는 모든 DNS 서버에 직접 등록이 되어 있다. 이러한 방식으로 DNS가 호스트명을 IP 주소로 바꿔준다.
2-4. 리졸버
- 호스트명과 IP 주소를 교환해주는 존재를 리졸버라고 한다.
- 리눅스에서는 IP 주소의 리졸버로 libc가 있고 해당 설정은
/etc/nsswitch.conf
에 기술된다.
3. 소켓 API
3-1. 소켓
- 리눅스에서는 네트워크 통신을 위해 소켓을 사용한다. 스트림을 연결하는 역할을 한다.
- 소켓은 넓은 범위에서 응용할 수 있는데, 예를 들면 서버와 클라이언트, TCP와 UDP나 IP, 인터넷 이외의 프로토콜에서 사용할 수 있다.

3-2. 클라이언트 측 소켓 API
- 클라이언트 측에서 서버에 스트림을 연결시키는 시스템 콜은 다음과 같다.
- socket(2)
- connect(2)
3-3. socket(2)
#include <sys/socket.h>
#include <sys/types.h>
int socket(int domain, int type, int protocol);
- socket()은 소켓을 만들고 이에 대응하는 파일 디스크립터를 반환한다.
- 인자 domain, type, protocol은 전부 합쳐서 만들 소켓에 무엇을 연결할지 지정한다. 예를 들어 IPv4 상의 TCP라면, socket(PF_INET, SOCK_STREAM, 0)을 호출한다.
3-4. connect(2)
#include <sys/socket.h>
#include <sys/types.h>
int. connect(int sock, const struct sockaddr *addr, socklen_t addrlen);
- connect()는 소켓 sock에서 스트림을 꺼내서 addr로 지정한 주소의 서버에 스트림을 연결한다.
- addr은 open()에서의 경로와 유사하다. 인터넷이라면 호스트 이름이 아닌 IP 주소와 포트 번호를 지정하면 된다.
- addrlen은 *addr의 크기를 지정한다.
3-5. 서버 측 소켓 API
- 스트림의 연결을 기다리고 있는 서버 측 시스템 콜은 다음과 같다.
- socket(2)
- bind(2)
- listen(2)
- accept(2)
3-6. bind(2)
#include <sys/socket.h>
#include <sys/types.h>
int bind(int sock, struct sockaddr *addr, socklen_t addrlen);
- bind()는 접속을 기다리는 주소 addr을 소켓 sock에 할당한다.
- addrlen은 *addr의 크기이다.
3-7. listen(2)
#include <sys/socket.h>
int listen(int sock, int backlog);
- listen()은 소켓 sock이 서버용 소켓, 즉 접속을 기다리는 소켓임을 커널에 알린다.
- backlog는 동시에 받아들일 수 있는 커넥션의 최대 수이다.
3-8. accept(2)
#include <sys/socket.h>
#include <sys/types.h>
int accept(int sock, struct sockaddr *addr, socklen_t *addrlen);
- accept()는 sock에 클라이언트가 접속하는 것을 기다리다 접속이 완료되면 연결된 스트림의 파일 디스트립터를 반환한다.
- addr에는 클라이언트의 주소가 기재되며, addrlen에는 *addr의 크기가 적힌다.
4. 이름 해결
4-1. 호스트명과 IP 주소를 변환하기
- 호스트명, 서비스명으로부터 IP 주소를 변환해주는 API는 다음과 같다.
- getaddrinfo()
- getnameinfo()
- freeaddrinfo()
- gai_strerror()
- getnameinfo()는 IP 주소나 포트 번호로부터 도메인명이나 서비스명을 얻기 위해 사용한다.
4-2. getaddrinfo(3)
#include <sys/socket.h>
#include <sys/types.h>
#include <netdb.h>
int getaddrinfo(const char *node, const char *service,
const struct addrinfo *hints, struct addrinfo **res);
void freeaddrinfo(struct addrinfo *res);
const char *gai_strerror(int err);
struct addrinfo{
int ai_flags;
int ai_family;
int ai_socketype;
int ai_protocol;
socklen_t ai_addrlen;
struct sockaddr *ai_addr;
char *ai_canonname;
struct addrinfo *ai_next;
};
- getaddrinfo()는 접속 대상인 node의 주소 후보를 res에 기재한다.
- service와 hint로 범위를 좁힐 수 있다.
- res는 struct addrinfo의 링크드 리스트 형태를 가진다.

- 이 struct addrinfo의 메모리 영역은 malloc()으로 할당되므로 명시적으로 해제해야 한다. 이를 freeaddrinfo()가 수행한다.
5. daytime 클라이언트 작성
5-1. daytime 명령어 만들기
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
static int open_connection(char *host, char *service);
int main(int argc, char *argv[]){
int sock;
FILE *f;
char buf[1024];
// daytime 서비스에 연결
sock = open_connection((argc>1 ? argv[1] : "localhost"), "daytime");
f = fdopen(sock, "r");
if (!f) {
perror("fdopen(3)");
exit(1);
}
// 연결된 소켓으로부터 현재 시간 정보를 출력
fgets(buf, sizeof buf, f);
fclose(f);
fputs(buf, stdout);
exit(0);
}
static int open_connection(char *host, char *service){
int sock;
struct addrinfo hints, *res, *ai;
int err;
memset(&hints, 0, sizeof(struct addrinfo));
hints.ai_family = AF_UNSPEC; // ipv4, ipv6 둘 다 사용
hints.ai_socktype = SOCK_STREAM; // TCP 소켓 사용
// 호스트와 서비스에 대한 주소 정보를 가져옴
if ((err = getaddrinfo(host, service, &hints, &res)) != 0){
fprintf(stderr, "getaddrinfo(3): %s\n", gai_strerror(err));
exit(1);
}
for (ai = res; ai; ai = ai->ai_next) {
// 소켓 생성
sock = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol);
if (sock < 0) {
continue;
}
// 서버에 연결
// 성공 시 소켓 디스크립터 반환
if (connect(sock, ai->ai_addr, ai->ai_addrlen) < 0) {
close(sock);
continue;
}
/* success */
freeaddrinfo(res);
return sock;
}
fprintf(stderr, "socket(2)/connect(2) failed");
freeaddrinfo(res); // 할당한 addrinfo 해제
exit(1);
}
5-2. 인터넷 슈퍼 서버
- 작성한 코드를 테스트하려면 daytime 서버를 먼저 구동해야 한다.
- daytime은 inetd와 xinetd 내부에 포함된 프로그램이다.
- inetd는 지정된 포트에 클라이언트가 접속하는 것을 기다린다. 인터넷 슈퍼 서버라고도 한다.
- 접속이 완료되면 셸과 마찬가지로 dup()를 사용하여 소켓을 표준 입력과 표준 출력에 옮겨서 서브 프로그램을 exec한다.
- 그러면 프로그램은 표준 입출력에 입출력함으로써 네트워크 통신이 가능해진다.
- xinetd는 inetd의 보안과 관련된 부분이 개선된 개량판이다.
5-3. 우분투에서의 xintd 설정
5-4. daytime 커맨드 실행 예
