# Chapter 8\~14

## Chapter 08 도메인 이름과 인터넷 주소

### 08-1 Domain Name System

도메인 이름 : IP 주소의 별칭

* DNS 서버가 IP 주소로 변환시켜준다
* 모든 컴퓨터에는 디폴트 DNS 서버의 주소가 등록돼있다
* 디폴트 DNS 서버는 모르면 다른 DNS 서버에 물어본다

### 08-2 IP주소와 도메인 이름 사이의 변환

`gethostbyname()` : 도메인 이름 문자열로부터 IP 주소 정보 얻는다

* hostent라는 구조체가 반환된다
  * h\_name : 공식 도메인 이름
  * h\_aliases : 공식 도메인 이름 외에 접속할 수 있는 다른 이름
  * h\_addrtype : h\_addr\_list로 반환된 IP 주소의 주소체계 정보
  * h\_length : IP주소의 크기정보
  * h\_addr\_list : 도메인 이름에 대한 IP주소가 정수 형태로 반환됨
* 도메인 이름을 IP로 변환할 땐 반환 정보 중 h\_addr\_list만 신경써도 된다

```c
	host=gethostbyname(argv[1]);
	if(!host)
		error_handling("Gethost... error");

	printf("Official name: %s \n", host->h_name);
	for(i=0; host->h_aliases[i]; i++)
		printf("Aliases %d: %s \n", i+1, host->h_aliases[i]);
	printf("Address type: %s \n", 
		(host->h_addrtype==AF_INET)?"AF_INET":"AF_INET6");
	for(i=0; host->h_addr_list[i]; i++)
		printf("IP addr %d: %s \n", i+1,
     		inet_ntoa(*(struct in_addr*)host->h_addr_list[i]));
```

gethostbyname 함수 활용 예시

* h\_addr\_list는 문자열 포인터 배열이고,
* 그 배열이 실제 가리키는 건 in\_addr 구조체 변수 주소값
* IPv6 기반도 저장하기 위해 char형으로 선언한 것
* void형 포인터가 표준화되기 전이라 char형 포인터 쓴 것

`gethostbyaddr` : IP주소를 이용해서 도메인 정보를 얻는다

## Chapter 09 소켓의 다양한 옵션

### 09-1 소켓의 옵션과 입출력 버퍼의 크기

소켓의 특성을 조회하거나 변경할 수 있다

* IPPROTO\_IP : IP 프로토콜 관련
* IPPROTO\_TCP : TCP 프로토콜 관련
* SOL\_SOCKET : 소켓 관련 일반적 옵션

`getsockopt()` : 옵션 확인 `setsockopt()` : 옵션 변경

* SO\_TYPE : 소켓의 타입. 소켓 생성 이후 변경 불가.
* SO\_RCVBUF : 입력 버퍼의 크기
* SO\_SNDBUF : 출력 버퍼의 크기
* 입출력 버퍼의 크기는 100% 요구대로 반영되지 않는다

### 09-2 SO\_REUSEADDR

클라가 먼저 연결을 끊으면 서버 재실행 문제 없다. 하지만 서버가 먼저 연결을 끊으면 (프로그램 종료하면) 서버 재실행 문제 생긴다. 3분 정도 기다려야 재실행 된다. 서버가 먼저 연결 종료를 요청핶기 때문에, 아직 TCP의 Time-wait 상태에 있기 때문이다. 클라 소켓은 PORT가 임의로 할당되기 때문에, Time-wait를 신경쓰지 않아도 된다.

* 서버 먼저 연결 종료 : 3분 간 재실행 안됨
  * 연결 종료 요청한 쪽은 Time-wait 상태로 들어가기 때문
  * FIN 보낸 후 ACK 오는지 확인하기 위해 Time-wait
* 클라 먼저 연결 종료 : 문제 없음
  * 클라 소켓은 PORT가 임의로 할당되어 문제 발생 안 함
* 서버 즉시 재실행 해야 하는데 Time-wait하고 있으면 곤란
* FIN 보냈는데 클라가 못 받으면 무한 대기해버려서 더 곤란
* SO\_REUSEADDR 상태 변경하면 사용중인 PORT를 새로운 소켓에 할당 가능

```c
	optlen=sizeof(option);
	option=TRUE;
	setsockopt(serv_sock,SOL_SOCKET,SO_REUSEADDR,(void*)&option, optlen);
```

### 09-3 TCP\_NODELAY

Nagle 알고리즘

* 이전에 보낸 데이터에 대한 ACK 받아야만 다음 데이터 전송
* 용량 큰 파일 보낼 때는 좋지 않다
  * 파일 데이터 출력 버퍼로 넣는 작업은 빠르게 완료됨
  * 따라서 Nagle 적용 안해도 출력버퍼 거의 꽉 채운 상태에서 패킷 전송
  * ACK를 안 기다리고 연속 전송하면 전송 속도 향상

## Chapter 10 멀티프로세스 기반의 서버구현

### 10-1 프로세스의 이해와 활용

전체적 서비스 제공 시간이 조금 늦어져도 모든 클라에게 동시 서비스 제공이 좋다

* 멀티프로세스 기반 서버 : 다수의 프로세스를 생성
* 멀티플렉싱 기반 서버 : 입출력 대상을 묶어서 관리
* 멀티쓰레딩 기반 서버 : 클라 수만큼 쓰레드 생성

프로세스

* 메로리 공간을 차지한 상태에서 실행중인 프로그램
* 운영체제로부터 ID 부여받는다

`fork()`

* 호출한 프로세스의 복사본 생성
* `fork()`를 호출한 프로세스를 복사하는 것
* 두 프로세스 모두 fork 호출 이후 문장을 실행하게 된다
  * 부모 프로세스의 fork 반환 값 : 자식 프로세스 ID
  * 자식 프로세스의 fork 반환 값 : 0

```c
int gval = 10;
int main(int argc, char *argv[])
{
	pid_t pid;
	int lval=20;
	gval++, lval+=5;

	pid=fork();
	if(pid==0) // if child
	{
		gval+=2, lval+=2;
		printf("Child Proc: [%d, %d] \n", gval, lval);
	}
	else // if parent
	{
		gval-=2, lval-=2;
		printf("Parent Proc: [%d, %d] \n", gval, lval);
	}
	return 0;
}
```

### 10-2 프로세스 & 좀비 프로세스

`ps au`로 프로세스 확인 가능

fork로 생성한 자식 프로세스가 종료되는 상황

* 인자를 전달하면서 exit를 호출하는 경우
* main 함수에서 return문을 실행하면서 값을 반환하는 경우

좀비 프로세스

* 할 일 다하고도 안 사라짐
* 부모 프로세스에게 exit 함수 인자 값이나 return문의 반환 값 전달돼야 좀비 죽일 수 있다

#### 좀비 프로세스의 생성 이유

```c
int main(int argc, char *argv[])
{
	pid_t pid=fork();

	if(pid==0) // if child
	{
		puts("Hi, I am child process");
		puts("End child process");
	}
	else
	{
		printf("Child Process ID: %d \n", pid);
		sleep(30);
		puts("End parent process");
	}
	return 0;
}
```

* child가 return돼서 끝나도, parent가 return해야 child의 자원이 반환된다.
  * wait()를 호출하지 않고 return까지 갔다면 커널이 고아 프로세스에게 wait() 호출

#### 좀비 프로세스의 소멸1: wait 함수의 사용

wait() 함수

* wait()가 호출됐을 때, 이미 종료된 자식 프로세스가 있다면, 종료 당시 전달 값이 인자로 넣은 주소 변수에 저장된다
* 이 변수에 들어있는 다른 값을 매크로 함수로 분리
  * WIFEXITED : 자식 프로세스가 정상 종료했다면 true
  * WEXITSTATUS : 자식 프로세스의 전달 값

```c
int main(int argc, char *argv[])
{
	int status;
	pid_t pid=fork();

	if(pid==0)
	{
		return 3;
	}
	else
	{
		printf("Child PID: %d \n", pid);
		pid=fork();
		if(pid==0)
		{
			exit(7);
		}
		else
		{
			printf("Child PID: %d \n", pid);
			wait(&status);
			if(WIFEXITED(status))
				printf("Child send one: %d \n", WEXITSTATUS(status));

			wait(&status);
			if(WIFEXITED(status))
				printf("Child send two: %d \n", WEXITSTATUS(status));
			sleep(30);
		}
	}
	return 0;
}
```

* 실행하면 각 프로세스의 PID와 return 값이 출력된다
* wait()는 프로세스 한 개만 소멸시킨다
* 호출된 시점에서 종료된 자식 프로세스가 없으면 종료될 때까지 Blocking된다

#### 좀비 프로세스의 소멸2: waitpid 함수의 사용

`waitpid()`는 좀비 프로세스 생성 막으면서도 블로킹 문제 해결한다

```c
int main(int argc, char *argv[])
{
	int status;
	pid_t pid=fork();

	if(pid==0)
	{
		sleep(15);
		return 24;
	}
	else
	{
		while(!waitpid(-1, &status, WNOHANG))
		{
			sleep(3);
			puts("sleep 3sec.");
		}

		if(WIFEXITED(status))
			printf("Child send %d \n", WEXITSTATUS(status));
	}
	return 0;
}
```

* 자식 프로세스는 15초 동안 기다린 다음 종료된다
* waitpid()는 블로킹을 안 하기 때문에, 3초 마다 문구를 출력한다

### 10-3 시그널 핸들링

부모 프로세스가 자식 프로세스의 종료를 언제까지고 기다릴 순 없다. 자식 프로세스 종료를 운영체제가 전달해주고, 그때 종료하면 좋다.

시그널 핸들링: `signal()`로 자식이 종료되면 실행할 함수를 운영체제에 등록

#### 시그널과 signal 함수

`signal()`

* 매개변수 : `int signo`, `void(*func)(int)`
* 반환형 : 매개변수가 int고 반환형이 void인 함수 포인터
* int로 특정 상황에 대한 정보를, 함수 포인터로 특정 상황에 호출될 함수 포인터 전달
  * SIGARLM : alarm()으로 등록된 시간이 지난 상황
  * SIGINT : CTRL+C가 입력된 상황
  * SIGCHLD : 자식 프로세스가 종료된 상황
  * 예시 : signal(SIGCHLD, mychild);

`alarm()`

* 지정한 시간 이후 SIGALRM 시그널 발생
* 0을 인자로 전달하면 이전에 설정된 SIGALRM 시그널 예약 취소
* alarm()으로 예약만 해두고 signal()로 함수 호출 지정 안 하면 프로세스 그냥 종료됨

```c
void timeout(int sig)
{
	if(sig==SIGALRM)
		puts("Time out!");
	alarm(2);
}

void keycontrol(int sig)
{
	if(sig==SIGINT)
		puts("CTRL+C pressed");
}

int main(int argc, char *argv[])
{
	int i;
	signal(SIGALRM, timeout);
	signal(SIGINT, keycontrol);
	alarm(2);

	for(i=0; i<3; i++)
	{
		puts("wait...");
		sleep(100);
	}
	return 0;
}
```

* 100초를 3번, 300초를 기다리지 않고 10초 이내에 종료된다
* alarm()이 프로세스를 깨운 뒤에 다시 재워주진 않기 때문이다
* CTRL+C를 누르면 더 빨리 끝난다

#### sigaction 함수를 이용한 시그널 핸들링

sigaction()

* signal() 대체 가능
* signal()과 달리 운영체제 별 동작 방식에 차이가 없다
* 그래서 대부분 sigaction() 쓴다

```c
struct sigaction
{
    void (*sa_handler)(int);
    sigset_t sa_mask;
    int sa_flags;
}
```

sigaction() 호출하려면 sigaction이라는 구조체 선언 및 초기화해야 함

* sa\_handler에 시그널 핸들러 함수 포인터 지정
* sa\_mask는 모든 비트를 0으로 초기화
* sa\_flags는 0으로 초기화
* sa\_mask와 sa\_flag는 시그널 관련 옵션 및 특성 지정에 사용됨

```c
void timeout(int sig)
{
	if(sig==SIGALRM)
		puts("Time out!");
	alarm(2);
}

int main(int argc, char *argv[])
{
	int i;
	struct sigaction act;
	act.sa_handler=timeout;
	sigemptyset(&act.sa_mask);
	act.sa_flags=0;
	sigaction(SIGALRM, &act, 0);

	alarm(2);

	for(i=0; i<3; i++)
	{
		puts("wait...");
		sleep(100);
	}
	return 0;
}
```

#### 시그널 핸들링을 통한 좀비 프로세스의 소멸

```c
void read_childproc(int sig)
{
	int status;
	pid_t id=waitpid(-1, &status, WNOHANG);
	if(WIFEXITED(status))
	{
		printf("Removed proc id: %d \n", id);
		printf("Child send: %d \n", WEXITSTATUS(status));
	}
}

int main(int argc, char *argv[])
{
	pid_t pid;
	struct sigaction act;
	act.sa_handler=read_childproc;
	sigemptyset(&act.sa_mask);
	act.sa_flags = 0;
	sigaction(SIGCHLD, &act, 0);

	pid=fork();
	if(pid==0) /* 자식 프로세스 실행 영역 */
	{
		puts("Hi! I'm child process");
		sleep(10);
		return 12;
	}
	else /* 부모 프로세스 실행 영역 */
	{
		printf("Child proc id: %d \n", pid);
		pid=fork();
		if(pid==0) /* 또 다른 자식 프로세스 실행 영역 */
		{
			puts("Hi! I'm child process");
			sleep(10);
			exit(24);
		}
		else
		{
			int i;
			printf("Child proc id: %d \n", pid);
			for(i=0; i<5; i++)
			{
				puts("wait...");
				sleep(5);
			}
		}
	}
	return 0;
}
```

### 10-4 멀티태스킹 기반의 다중 접속 서버

클라가 연결요청 할 때마다 에코 서버가 자식 프로세스 생성해서 서비스 제공

* 1 : 에코 서버가 accept()로 연결 요청 수락
* 2 : 소켓 파일 디스크립터를 자식 프로세스 생성해서 넘겨준다
* 3 : 자식 프로세스는 전달받은 파일 디스크립터로 서비스 제공

```c
int main(int argc, char *argv[])
{
	int serv_sock, clnt_sock;
	struct sockaddr_in serv_adr, clnt_adr;

	pid_t pid;
	struct sigaction act;
	socklen_t adr_sz;
	int str_len, state;
	char buf[BUF_SIZE];
	if(argc!=2) {
		printf("Usage : %s <port>\n", argv[0]);
		exit(1);
	}

	act.sa_handler=read_childproc;
	sigemptyset(&act.sa_mask);
	act.sa_flags=0;
	state=sigaction(SIGCHLD, &act, 0);
	serv_sock=socket(PF_INET, SOCK_STREAM, 0);
	memset(&serv_adr, 0, sizeof(serv_adr));
	serv_adr.sin_family=AF_INET;
	serv_adr.sin_addr.s_addr=htonl(INADDR_ANY);
	serv_adr.sin_port=htons(atoi(argv[1]));

	if(bind(serv_sock, (struct sockaddr*) &serv_adr, sizeof(serv_adr))==-1)
		error_handling("bind() error");
	if(listen(serv_sock,5)==-1)
		error_handling("listen() error");

	while(1)
	{
		adr_sz=sizeof(clnt_adr);
		clnt_sock=accept(serv_sock, (struct sockaddr*)&clnt_adr, &adr_sz);
		if(clnt_sock==-1)
			continue;
		else
			puts("new client connected...");
		pid=fork();
		if(pid==-1)
		{
			close(clnt_sock);
			continue;
		}
		if(pid==0) // 자식 프로세스 실행 영역
		{
			close(serv_sock);
			while((str_len=read(clnt_sock,buf,BUF_SIZE))!=0)
				write(clnt_sock, buf, str_len);

			close(clnt_sock);
			puts("client disconnected...");
			return 0;
		}
		else
			close(clnt_sock);
	}
	close(serv_sock);
	return 0;
}

void read_childproc(int sig)
{
	pid_t pid;
	int status;
	pid=waitpid(-1, &status, WNOHANG);
	printf("removed proc id: %d \n", pid);
}
```

이제 여러 클라 접속 가능해진다

fork()하면 소켓은 하나지만 연결된 파일 디스크립터가 하나 더 생긴다. 부모 프로세스에선 새로 만들어진 클라 소켓 파일 디스크립터 필요 없으니까 닫고, 자식 프로세스에선 서버 소켓 파일 디스크립터 필요 없으니까 닫는다 안 닫고 냅두면 나중에 하나만 지워도 소켓 소멸이 안됨

### 10-5 TCP의 입출력 루틴(Routine) 분할

기존 에코 클라 : 데이터 전송 후 돌아올 때까지 데이터 입력 못 함 fork() 이용하면 데이터 송신과 수신 분리 가능

이렇게 프로세스에서 루틴을 분리하면 코드 구현이 수월해진다. (에코 클라는 루틴 분리할 근거가 있진 않고, 예시로 구현해보는거)

```c
	pid=fork();
	if(pid==0)
		write_routine(sock,buf);
	else
		read_routine(sock,buf);

	close(sock);
	return 0;
}

void read_routine(int sock, char *buf)
{
	while(1)
	{
		int str_len=read(sock, buf, BUF_SIZE);
		if(str_len==0)
			return;

		buf[str_len]=0;
		printf("Message from server: %s", buf);
	}
}
void write_routine(int sock, char *buf)
{
	while(1)
	{
		fgets(buf, BUF_SIZE, stdin);
		if(!strcmp(buf,"q\n") || !strcmp(buf,"Q\n"))
		{
			shutdown(sock, SHUT_WR);
			return;
		}
		write(sock, buf, strlen(buf));
	}
}
```

## Chapter 11 프로세스간 통신 (Inter Process Communication)

### 11-1 프로세스간 통신의 기본 개념

파이프

* 두 프로세스간 통신에 쓰인다
* 프로세스에 속하지 않고, 소켓처럼 운영체제에 속한다 (fork로 복사 안된다)

pipe()

* 배열에 파일 디스크립터 2개 담는다
* 각각 파이프의 출구와 입구로 사용된다
* 부모가 파이프 만든 후에 디스크립터 하나는 자식 프로세스에게 전달해야 한다
* 그냥 변수에 저장해두면 입출력 디스크립터 부모 자식 둘 다 갖게된다

#### 파이프 기반의 프로세스간 양방향 통신

```c
int main(int argc, char *argv[])
{
	int fds[2];
	char str1[] = "who are you?";
	char str2[] = "Thank you for your message";
	char buf[BUF_SIZE];
	pid_t pid;

	pipe(fds);
	pid=fork();
	if(pid==0) // 자식 프로세스
	{
		write(fds[1], str1, sizeof(str1));
		sleep(2);
		read(fds[0], buf, BUF_SIZE);
		printf("Child proc output: %s \n", buf);
	}
	else // 부모 프로세스
	{
		read(fds[0], buf, BUF_SIZE);
		printf("Parent proc output: %s \n", buf);
		write(fds[1], str2, sizeof(str2));
		sleep(3);
	}
	return 0;
}
```

sleep()을 주석처리 해버려서 자식 프로세스가 write()한 후에 안 기다리게 되면 자기가 쓴거 read()로 다시 가져가버린다.

이를 방지하기 위해, 파이프를 두 개 생성할 수 있다.

```c
	pipe(fds1), pipe(fds2);
	pid = fork();
	if(pid==0)
	{
		write(fds1[1], str1, sizeof(str1));
		read(fds2[0], buf, BUF_SIZE);
		printf("Child proc output: %s \n", buf);
	}
	else
	{
		read(fds1[0], buf, BUF_SIZE);
		printf("Parent proc output: %s \n", buf);
		write(fds2[1], str2, sizeof(str2));
		sleep(3);
	}
```

### 11-2 프로세스간 통신의 적용

프로세스간 통신은 서버 구현에 직접적 연관은 없다. 운영체제 이해한다는 것에 의의가 있다.

```c
	if(bind(serv_sock, (struct sockaddr*) &serv_adr, sizeof(serv_adr))==-1)
		error_handling("bind() error");
	if(listen(serv_sock, 5)==-1)
		error_handling("listen() error");

	pipe(fds);
	pid=fork();
	if(pid==0)
	{
		FILE *fp = fopen("echomsg.txt", "wt");
		char msgbuf[BUF_SIZE];
		int i, len;

		for(i=0; i<10; i++)
		{
			len=read(fds[0],msgbuf,BUF_SIZE);
			fwrite((void*)msgbuf, 1, len, fp);
		}
		fclose(fp);
		return 0;
	}
	while(1)
	{
		adr_sz=sizeof(clnt_adr);
		clnt_sock=accept(serv_sock,(struct sockaddr*)&clnt_adr, &adr_sz);
		if(clnt_sock==-1)
			continue;
		else
			puts("new client connected...");

		pid=fork();
		if(pid==0)
		{
			close(serv_sock);
			while((str_len=read(clnt_sock,buf,BUF_SIZE))!=0)
			{
				write(clnt_sock, buf, str_len);
				write(fds[1], buf, str_len);
			}

			close(clnt_sock);
			puts("client disconnected...");
			return 0;
		}
		else
			close(clnt_sock);
	}
	close(serv_sock);
```

첫 if문 : 파이프로 들어오는 데이터 읽어서 파일에 적는 자식 프로세스 생성. while문의 if문 : read()의 반환 값이 0일 때까지 파이프에 데이터를 보내는 자식 프로세스 생성

## Chapter 12 IO 멀티플렉싱 (Multiplexing)

### 12-1 IO 멀티플렉싱 기반의 서버

멀티 프로세스

* 요구 연산량과 메모리 공간이 큰 편이다.
* 프로세스 간 데이터 주고 받으려면 IPC 같은 복잡한 방법 써야 한다

멀티플렉싱 : 프로세스 하나가 여러 클라에게 데이터 전송

### 12-2 select 함수의 이해와 서버의 구현

select()

* 여러 파일 디스크립터 동시 관찰 가능
  * 변화를 관찰할 디스크립터를 1로 설정한다
  * 변화가 발생한 파일 디스크립터 비트만 1로 남아있다
* 관찰 가능한 이벤트들
  * 수신한 데이터를 지니고 있는 소켓이 존재하는가?
  * 블로킹되지 않고 데이터 전송 가능한 소켓은 무엇인가?
  * 예외상황이 발생한 소켓은 무엇인가?
* 호출 과정
  * Step 1
    * 파일 디스크립터 설정
    * 검사 범위 지정
    * 타임아웃 설정
  * Step 2 : select() 호출
  * Step 3 : 호출 결과 확인

#### 파일 디스크립터의 설정

파일 디스크립터를 모을 땐 관찰항목(수신, 전송, 예외)에 따라서 구분해서 모아야 한다. 이를 위해 `fd_set`형 변수를 사용한다.

`fd_set` 값 편집 매크로 함수

* `FD_ZERO()` : 모든 비트 0 초기화
* `FD_SET()` : 파일 디스크립터 정보 등록
* `FD_CLR()` : 파일 디스크립터 정보 삭제
* `FD_ISSET()` : 파일 디스크립터 정보 있으면 양수 반환

#### 검사(관찰) 범위 지정과 타임아웃 설정

관찰 범위 : 가장 큰 파일 디스크립터 값에 1 더함

타임아웃 설정

* 타임아웃 시간 : timeval 자료형으로 지정
* 타임아웃 안 하면 값 반환될때까지 무한정 블로킹
* 타임아웃 반환은 0
* 타임아웃 설정 안 하고 싶으면 인자에 NULL 전달

#### select 함수 호출 예제

```c
int main(int argc, char *argv[])
{
	fd_set reads, temps;
	int result, str_len;
	char buf[BUF_SIZE];
	struct timeval timeout;

	FD_ZERO(&reads);
	FD_SET(0, &reads); // 0 is standard input(console)
	
	/*
	timeout.tv_sec=5;
	timeout.tv_usec=5000;
	*/

	while(1)
	{
		temps=reads;
		timeout.tv_sec=5;
		timeout.tv_usec=0;
		result=select(1, &temps, 0, 0, &timeout);
		if(result==-1)
		{
			puts("select() error!");
			break;
		}
		else if(result==0)
		{
			puts("Time-out!");
		}
		else
		{
			if(FD_ISSET(0, &temps))
			{
				str_len=read(0, buf, BUF_SIZE);
				buf[str_len]=0;
				printf("message from console: %s", buf);
			}
		}
	}
	return 0;
}
```

* 표준 입력을 받으면 값을 읽어서 출력하는 예제
* reads를 temps에 복사해두는 이유 : select 호출 끝나면 변화 생긴 파일 디스크립터 제외하면 다 0 되니까
* 주석친 부분에서 timeout 설정하면 안되는 이유 : select 함수 호출 끝나면 타임아웃 발생하기까지 남은 시간으로 바뀌기 때문

#### 멀티플렉싱 서버 구현

```c
int main(int argc, char *argv[])
{
	int serv_sock, clnt_sock;
	struct sockaddr_in serv_adr, clnt_adr;
	struct timeval timeout;
	fd_set reads, cpy_reads;

	socklen_t adr_sz;
	int fd_max, str_len, fd_num, i;
	char buf[BUF_SIZE];
	if(argc!=2) {
		printf("Usage : %s <port>\n", argv[0]);
		exit(1);
	}

	serv_sock=socket(PF_INET, SOCK_STREAM, 0);
	memset(&serv_adr, 0, sizeof(serv_adr));
	serv_adr.sin_family=AF_INET;
	serv_adr.sin_addr.s_addr=htonl(INADDR_ANY);
	serv_adr.sin_port=htons(atoi(argv[1]));

	if(bind(serv_sock,(struct sockaddr*) &serv_adr, sizeof(serv_adr))==-1)
		error_handling("bind() error");
	if(listen(serv_sock, 5)==-1)
		error_handling("listen() error");

	FD_ZERO(&reads);
	FD_SET(serv_sock, &reads);
	fd_max=serv_sock;

	while(1)
	{
		cpy_reads=reads;
		timeout.tv_sec=5;
		timeout.tv_usec=5000;

		if((fd_num=select(fd_max+1,&cpy_reads,0,0,&timeout))==-1)
			break;
		if(fd_num==0)
			continue;

		for(i=0; i<fd_max+1; i++)
		{
			if(FD_ISSET(i, &cpy_reads))
			{
				if(i==serv_sock) // connection request!
				{
					adr_sz=sizeof(clnt_adr);
					clnt_sock=accept(serv_sock, (struct sockaddr*)&clnt_adr, &adr_sz);
					FD_SET(clnt_sock, &reads);
					if(fd_max<clnt_sock)
						fd_max=clnt_sock;
					printf("connected client: %d \n", clnt_sock);
				}
				else // read message
				{
					str_len=read(i, buf, BUF_SIZE);
					if(str_len==0) // close request;
					{
						FD_CLR(i, &reads);
						close(i);
						printf("closed client: %d \n", i);
					}
					else
					{
						write(i, buf, str_len); // echo!
					}
				}
			}
		}
	}
	close(serv_sock);
	return 0;
}

```

* select()의 세번째와 네번째 인자는 필요 없으므로 비어있다.
* for문 안 첫 if문 : `FD_ISSET`으로 확인하려는 파일 디스크립터가 맞는지 확인한다
  * if문 : 서버 소켓이 변했으면 accept()하고 클라 소켓도 새로 관찰 시작한다
  * else문 : EOF면 소켓 종료하고, 아니면 echo

### 12-3 윈도우 기반으로 구현하기

윈도우의 `fd_set`은 비트 배열이 아니라 소켓 핸들 수 기록할 `fd_count`와 소켓 핸들 저장할 `fd_array`로 이루어졌다. 윈도우 소켓 핸들은 0부터 시작하지도 않고, 정수 값 사이에도 관계성 없기 때문이다.

## Chapter 13 다양한 입출력 함수들

### 13-1 send & recv 입출력 함수

send, recv 함수 마지막 인자에 옵션을 전달할 수 있다

`MSG_OOB` : 긴급 메시지 전송

* SIGURG에 sighandler를 등록하면 긴급 메시지 수신 시 발생.
  * 핸들러 안에 긴급 메시지 처리 로직 구현하면 됨
* fcntl()로 소켓 소유자를 getpid()이 반환하는 ID의 프로세스 변경해야 함
* 어느 프로세스의 핸들러 함수 호출할지 지정해줘야 하기 때문
* Urgent mode는 더 빨리 가지도 못하고, 1바이트만 지정한다
  * 긴급 메시지가 존재한다는 것과 긴급 메시지의 마지막 포인터만 전달한다
  * 메시지 처리를 재촉하는데 의미가 있는 것이기 때문

`MSG_PEEK` : 입력 버퍼에 수신된 데이터 존재하는지 확인

* 이 옵션으로 recv() 호출하면 입력버퍼에 존재하는 데이터 읽혀도 데이터 안 지워짐
* `MSG_DONTWAIT` 옵션과 묶여, 블로킹 안 하고 데이터 존재유무만 판단하는 함수에 사용됨

### 13-2 readv & writev 입출력 함수

데이터를 모아서 전송, 모아서 수신

* writev : 여러 버퍼 데이터를 한 번에 전송 가능
* readv : 데이터를 여러 버퍼에 나눠서 수신 가능
* iovec 구조체 배열로 버퍼 주소 및 크기 정보 제공
* iovec 구조체 정보 바탕으로 버퍼들 그러모아서 writev() 또는 readv()

### 13-3 윈도우 기반으로 구현하기

윈도우에선 리눅스에서 썼던 방식의 시그널 핸들링이 불가능 Out-of-band 데이터 수신도 예외상황이므로, select() 써서 대체 가능

writev, readv에 대응하는 함수는 윈도우에 없음. 대신 윈도우에선 중첩 입출력(Overlapped IO)를 이용할 수 있음.

## Chapter 14 멀티캐스트 & 브로드캐스트

### 14-1 멀티캐스트

멀티캐스트는 UDP 기반.

* UDP 데이터 전송은 목적지가 하나지만,
* 멀티캐스트는 특정 그룹의 다수 호스트에게 전송.
* 라우터에 패킷 도착하면 패킷 복사해서 호스트들에게 전송

TTL (Time to Live)

* 패킷을 얼마나 멀리 전달할 것인지 결정하는 주 요소
* 라우터를 하나 거칠 때마다 1씩 감소하고, 0 되면 패킷 소멸
* 너무 크게 설정하면 트래픽에 악영향, 작게 설정하면 목적지 도달 안 함
* setsockopt()로 `IP_MULTICAST_TTL` 옵션에서 TTL 설정 가능

멀티캐스트 그룹 가입

* setsockopt()로 `IP_ADD_MEMBERSHIP` 옵션에서 가입 가능
* `ip_mreq`에 가입할 그룹의 IP 주소 정보 및 소켓이 속한 호스트의 IP 주소 명시

#### Sender와 Receiver 구현

멀티캐스트에서 서버는 Sender, 클라는 Receiver라 한다

```c
	send_sock=socket(PF_INET, SOCK_DGRAM, 0);
	memset(&mul_adr, 0, sizeof(mul_adr));
	mul_adr.sin_family=AF_INET;
	mul_adr.sin_addr.s_addr=inet_addr(argv[1]); // Multicast IP
	mul_adr.sin_port=htons(atoi(argv[2])); // Multicast Port
	
	setsockopt(send_sock, IPPROTO_IP, IP_MULTICAST_TTL,
	    (void*)&time_live, sizeof(time_live));
	if((fp=fopen("news.txt", "r"))==NULL)
		error_handling("fopen() error");

	while(!feof(fp))
	{
		fgets(buf, BUF_SIZE, fp);
		sendto(send_sock, buf, strlen(buf), 0,
	 	(struct sockaddr*)&mul_adr, sizeof(mul_adr));
		sleep(2);
	}
	fclose(fp);
	close(send_sock);
	return 0;
```

Sender.c

* UDP 소켓 생성
* setsockopt()로 TTL 지정
* IP 주소는 멀티 캐스트 주소를 줘야 함
  * (224.0.0.0/24 대역은 라우터가 포워딩 안 해서 로컬 서브넷 안 벗어난다)
* udp니까 sendto()를 사용한다

```c
	recv_sock=socket(PF_INET, SOCK_DGRAM, 0);
	memset(&adr, 0, sizeof(adr));
	adr.sin_family=AF_INET;
	adr.sin_addr.s_addr=htonl(INADDR_ANY);
	adr.sin_port=htons(atoi(argv[2]));

	if(bind(recv_sock, (struct sockaddr*) &adr, sizeof(adr))==-1)
		error_handling("bind() error");
	
	join_adr.imr_multiaddr.s_addr=inet_addr(argv[1]);
	join_adr.imr_interface.s_addr=htonl(INADDR_ANY);

	setsockopt(recv_sock, IPPROTO_IP, IP_ADD_MEMBERSHIP, (void*)&join_adr, sizeof(join_adr));

	while(1)
	{
		str_len=recvfrom(recv_sock, buf, BUF_SIZE-1, 0, NULL, 0);
		if(str_len<0)
			break;
		buf[str_len]=0;
		fputs(buf, stdout);
	}
```

Receiver.c

### 14-2 브로드캐스트

멀티캐스트 : 서로 다른 네트워크라도 그룹에 가입만 됐으면 데이터 수신 가능 브로드캐스트 : 동일한 네트워크 호스트로만 전송 대상 제한

* 동일한 네트워크 모든 호스트에게 동시에 데이터 전송
* UDP 기반
* Directed 브로드캐스트, Local 브로드캐스트로 구분됨
  * Directed 브로드캐스트 주소 : `192.12.34` 네트워크 모든 호스트 → `192.12.34.255`
  * Local 브로드캐스트 주소 : `255.255.255.255`로 전송하면 자신이 연결된 네트워크 모든 호스트에게 전달
* UDP 구현과 거의 차이 없다.
  * 실행 시 설정하는 IP만 달라짐
  * 코드에서 소켓 옵션 변경만 해주면 됨. (Receiver에선 옵션 변경 필요 없음)


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://lazyartisan.gitbook.io/note/main-page/books/tcp-ip/part-01/chapter-8-14.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
