# Part 02 리눅스 기반 프로그래밍

## Chapter 15 소켓과 표준 입출력

### 15-1 표준 입출력 함수의 장점

표준 입출력 함수 장점

* 이식성이 좋다
* 버퍼링을 통한 성능 향상
  * 소켓 입출력 버퍼에 더해 추가로 버퍼 제공받고, 그걸로 성능 향상 가능
  * 패킷은 일정한 크기가 있기 때문에, 한 번에 모아서 보내는게 낫다

표준 입출력 함수 단점

* 양방향 통신 쉽지 않다
* 상황에 따라서 fflush() 자주 호출해야 한다
  * 읽기 ↔ 쓰기 작업 형태 바꿀 때마다 호출해야 하는데, 성능에도 영향 미친다
* 파일 디스크립터를 FILE 구조체의 포인터로 변환해야 한다

### 15-2 표준 입출력 함수 사용하기

```c
int main(void)
{
	FILE *fp;
	int fd=open("data.dat", O_WRONLY|O_CREAT|O_TRUNC);
	if(fd==-1)
	{
		fputs("file open error", stdout);
		return -1;
	}

	fp = fdopen(fd, "w");
	fputs("Network C programming \n", fp);
	fclose(fp);
	return 0;
}
```

fdopen()을 이용하여 FILE 구조체 포인터로 변환할 수 있다 예제는 open()으로 파일을 생성한 뒤, fdopen()으로 FILE 포인터를 가져온다.

fileno()는 반대다. FILE 포인터를 파일 디스크립터로 바꿔준다.

### 15-3 소켓 기반에서의 표준 입출력 함수 사용

```c
		readfp=fdopen(clnt_sock, "r");
		writefp=fdopen(clnt_sock, "w");
		while(!feof(readfp))
		{
			fgets(message, BUF_SIZE, readfp);
			fputs(message, writefp);
			fflush(writefp);
		}
		fclose(readfp);
		fclose(writefp);
```

echo\_server.c에서 write()를 사용하던 부분을 문자열 기반의 fgets, fputs로 변경

```c
	readfp=fdopen(sock, "r");
	writefp=fdopen(sock, "w");
	while(1)
	{
		fputs("Input message(Q to quit): ", stdout);
		fgets(message, BUF_SIZE, stdin);

		if(!strcmp(message, "q\n") || !strcmp(message,"Q\n"))
			break;

		fputs(message,writefp);
		fflush(writefp);
		fgets(message, BUF_SIZE, readfp);
		printf("Message from server : %s", message);
	}
```

echo\_client.c를 문자열 기반 fgets, fputs로 변경 원래는 데이터 마지막에 0 삽입해서 수신 데이터를 문자열로 구성해야 했지만, 위에선 표준 입출력 함수를 사용하기 때문에 생략됐다.

보다시피 적용에 따른 부가적인 코드 발생 때문에 생각만큼 즐겨쓰진 않는다.

## Chapter 16 입출력 스트림의 분리에 대한 나머지 이야기

### 16-1 입력 스트림과 출력 스트림의 분리

half close : 연결 종료해도 받을 데이터 있을 수 있으니 일부만 닫는거 Chapter 10에선 shutdown()으로 half close 구현했다. 출력모드 FILE 포인터로 fclose() 호출하면? half close가 아니라 입출력 다 닫혀버린다.

### 16-2 파일 디스크립터의 복사와 Half-close

FILE 포인터

* 파일 디스크립터 기반 생성됨
* fclose() 해도 연결된 파일 디스크립터 종료, 이어 소켓도 종료
* 파일 디스크립터 복사해서 FILE 포인터 따로 만들어야 함
* 따로 만든 FILE 포인터 닫아도 Half-close는 아님
  * 상대 호스트로 EOF도 전송 안됨
  * 파일 디스크립터를 쓰면 출력도 여전히 가능

dup() : 파일 디스크립터 복사. 새 정수값 할당됨. dup2() : 복사될 정수 값도 지정

writefp 만들 때 클라 소켓의 dup()을 인자로 주고, `shutdown(fileno(writefp), SHUT_WR)` 호출하면 Half-close 진행됨.

## Chapter 17 select보다 나은 epoll

### 17-1 epoll의 이해와 활용

select는 성능 구리다. 리눅스에선 대신 epoll을 사용한다.

* select는 모든 파일 디스크립터를 대상으로 반복문 돌기 때문
* select 함수 호출할 때마다 인자로 관찰대상 정보 제공해야 하기 때문

운영체제에 관찰 대상 정보 매번 전달하는게 반복문보다 성능 병목

* 운영체제에게 관찰 대상 정보 한 번만 알려주고, 변경 있을 때만 변경 사항만 알려주게 하면 개선 가능
* 리눅스에선 이를 epoll이라 하고, 윈도우에선 IOCP라 한다
* 성능 상관 없고, 운영체제 이식성 좋아야 한다면 select 쓰는 것도 좋음

#### epoll 구현에 필요한 함수와 구조체

epoll 장점

* 상태 변화 확인하기 위한 전체 파일 디스크립터 반복문 없어도 됨
* select에 대응되는 `epoll_wait()` 호출 시 관찰 대상 매번 전달 필요 없음

epoll 서버 구현 필요한 함수

* `epoll_create()` : epoll 파일 디스크립터 저장소 생성
* `epoll_ctl()` : 저장소에 파일 디스크립터 등록 및 삭제
* `epoll_wait()` : 파일 디스크립터 변화 대기

select에선 `fd_set` 변수 변화를 통해 상태 변화 확인하지만, epoll에선 `epoll_event` 구조체 기반으로 상태변화 발생한 파일 디스크립터가 별도로 묶인다 (`epoll_event`는 이벤트 유형 등록할 때도 사용된다)

`epoll_create()`

* size 매개변수는 완전히 무시됨
* 생성된 리소스는 파일 디스크립터 반환. 나중에 close() 해줘야 함.

`epoll_ctl()`

* 2번째 인자 전달 가능 상수
  * `EPOLL_CTL_ADD` : 파일 디스크립터를 epoll 인스턴스에 등록
  * `EPOLL_CTL_DEL` : 파일 디스크립터를 epoll 인스턴스에서 삭제
  * `EPOLL_CTL_MOD` : 등록된 파일 디스크립터의 이벤트 발생상황 변경
* 호출 예시
  * `epoll_ctl(A, EPOLL_CTL_ADD, B, C)` : epoll 인스턴스 A에, 파일 디스크립터 B를 등록하되, C를 통해 전달된 이벤트의 관찰을 목적으로 등록
  * `epoll_ctl(A, EPOLL_CTL_DEL, B, NULL)` : epoll 인스턴스 A에서 파일 디스크립터 B를 삭제한다

`epoll_wait()` : 이벤트 발생하면 두번째 인자로 전달한 버퍼에 이벤트 발생한 파일 디스크립터 묶인다

```c
	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");

	epfd = epoll_create(EPOLL_SIZE);
	ep_events=malloc(sizeof(struct epoll_event)*EPOLL_SIZE);

	event.events=EPOLLIN; // 수신할 데이터가 존재하는 상황
	event.data.fd=serv_sock;
	epoll_ctl(epfd, EPOLL_CTL_ADD, clnt_sock, &event);
	
	while(1)
	{
		event_cnt=epoll_wait(epfd, ep_events, EPOLL_SIZE, -1);
		if(event_cnt==-1)
		{
			puts("epoll_wait() error");
			break;
		}

		for(i=0; i<event_cnt; i++)
		{
			if(ep_events[i].data.fd==serv_sock)
			{
				adr_sz=sizeof(clnt_adr);
				clnt_sock=accept(serv_sock, (struct sockaddr*)&clnt_adr, &adr_sz);
				event.events=EPOLLIN;
				event.data.fd=clnt_sock;
				epoll_ctl(epfd, EPOLL_CTL_ADD, clnt_sock, &event);
				printf("connected client: %d \n", clnt_sock);
			}
			else
			{
				str_len = read(ep_events[i].data.fd, buf, BUF_SIZE);
				if(str_len == 0) // close request!
				{
					epoll_ctl(epfd, EPOLL_CTL_DEL, ep_events[i].data.fd, NULL);
					close(ep_events[i].data.fd);
					printf("closed client: %d \n", ep_events[i].data.fd);
				}
				else
				{
					write(ep_events[i].data.fd, buf, str_len);
				}
			}
		}
	}
	close(serv_sock);
	close(epfd);
```

* 소켓 만든 후에 epoll에 등록
* epollwait한 후에
  * 서버 소켓에 뭔가 잡혔으면 accept
  * 연결 소켓에 뭔가 잡혔으면 읽거나 연결 종료

### 17-2 레벨 트리거와 엣지 트리거

레벨 트리거 : 입력 버퍼에 데이터 남아있는 동안 계속해서 이벤트 등록됨 엣지 트리거 : 입력 버퍼 데이터 수신됐을 때 한 번만 이벤트 등록

#### 레벨 트리거 기반 서버

위 예제 코드 for문 앞에 `puts("return epoll_wait")`를 넣고, 버퍼 크기를 4로 줄이면 이벤트가 여러 번 발생한다. 기본적으로 레벨 트리거 방식이라, 버퍼 크기에 맞추어 이벤트가 나누어 발생한다

클라 소켓을 `event.events=EPOLLIN|EPOLLET`으로 바꾸면 엣지 트리거로 바꿀 수 있다. 대신 버퍼에 남아있는 나머지 결과는 출력하지 못한다.

#### 엣지 트리거 기반 서버

엣지 트리거 구현에 있어 필요한 2가지

변수 errno를 이용한 오류 원인 확인 방법

* 리눅스 소켓 관련 함수는 오류 나면 -1 반환
* 오류 관련 추가 정보는 전역 변수 errno 확인하면 됨
* errno 접근하려면 errno.h 포함해야됨
* 오류 발생 시 errno에 저장되는 값이 달라짐
* 지금 알아야 할 것
  * read()는 입력 버퍼에 데이터 비었을 때 -1을 반환
  * 이때 errno에는 상수 EAGAIN 저장됨

Non-blocking IO를 위한 소켓 특성 변경 방법

* `fcntl()` : 파일 특성 변경 및 참조
* 인자로 `F_GETFL` 전달하면 첫 번째 인자로 전달한 파일 디스크립터 특성 정보를 int로 얻을 수 있다
* 인자로 `F_SETFL` 전달하면 특성 정보 변경할 수 있다
* 파일 넌-블로킹 모드 변경 방법
  * `int flag=fcntl(fd, F_GETFL, 0);` : 기존 특성 가져온 뒤
  * `fcntl(fd, F_SETFL, flag|O_NONBLOCK);` : NONBLOCK 추가하여 재설정
  * 이제 read, write 호출해도 데이터 유무 관계없이 블로킹 안된다

클라 소켓을 non-blocking 모드로 설정하고, 버퍼에 아무것도 안 남을 때까지 while문으로 read한다.

```c
		puts("return epoll_wait");
		for(i=0; i<event_cnt; i++)
		{
			if(ep_events[i].data.fd == serv_sock)
			{
				adr_sz=sizeof(clnt_adr);
				clnt_sock=accept(serv_sock, (struct sockaddr*)&clnt_adr, &adr_sz);
				setnonblockingmode(clnt_sock);
				event.events=EPOLLIN|EPOLLET;
				event.data.fd=clnt_sock;
				epoll_ctl(epfd, EPOLL_CTL_ADD, clnt_sock, &event);
				printf("connected client: %d \n", clnt_sock);
			}
			else
			{
				while(1)
				{
					str_len = read(ep_events[i].data.fd, buf, BUF_SIZE);
					if(str_len==0)
					{
						epoll_ctl(epfd, EPOLL_CTL_DEL, ep_events[i].data.fd, NULL);
						close(ep_events[i].data.fd);
						printf("closed client: %d \n", ep_events[i].data.fd);
						break;
					}
					else if(str_len < 0)
					{
						if(errno==EAGAIN) break;
					}
					else {
						write(ep_events[i].data.fd, buf, str_len); // echo!
					}
				}
			}
		}
```

이번 예제는 123456789 보내면 이벤트가 한 번만 발생하는 대신, while문을 계속 돌면서 read가 non-blocking으로 소켓을 확인한다 데이터 다 없어지면 (EAGAIN 뜨면) while문 탈출한다. non-blocking 안 해두면 EAGAIN을 확인할 수가 없어서 탈출 불가.

(그리고 이번 예제도, 저번 예제도 이벤트 횟수만 다른거지, 수신 측에선 echo를 전부 받는다. wirte() 자체는 4개씩 나누어서 하지만, 수신 측에서 read()할 때 알아서 합쳐지기 때문이다.)

엣지 트리거의 장점 : 데이터의 수신과 데이터 처리 시점을 분리할 수 있다. 레벨 트리거는 데이터 남으면 계속 깨워서 처리 미루기 곤란.

## Chapter 18 멀티쓰레드 기반의 서버구현

### 18-1 쓰레드의 이론적 이해

멀티프로세스 기반 단점

* 프로세스 생성 비싸다
* 프로세스 사이 데이터 교환 IPC 번거롭다
* (가장 큼) 컨텍스트 스위칭이 일어난다

쓰레드

* 데이터, 힙 영역은 공유
* 스택 영역만 별도

프로세스 : 운영체제 관점에서 별도의 실행흐름을 구성하는 단위 쓰레드 : 프로세스 관점에서 별도의 실행흐름을 구성하는 단위

### 18-2 쓰레드의 생성 및 실행

POSIX(Portable Operating System Interface for Computer Environment) : UNIX 계열 운영체제 간에 이식성을 높이기 위한 표준 API 규격. 쓰레드 생성 방식도 POSIX 표준 근거.

쓰레드는 별도 실행흐름을 갖기 때문에, 쓰레드만의 main 함수 필요 `pthread_create()` : 특정 함수를 시작으로 별도 실행흐름 형성할 것을 운영체제에 요청

```c
int main(int argc, char *argv[])
{
	pthread_t t_id;
	int thread_param=5;

	if(pthread_create(&t_id, NULL, thread_main, (void*)&thread_param)!=0)
	{
		puts("pthread_create() error");
		return -1;
	};
	sleep(10); puts("end of main");
	return 0;
}

void* thread_main(void *arg)
{
	int i;
	int cnt=*((int*)arg);
	for(i=0; i<cnt; i++)
	{
		sleep(1); puts("running thread");
	}
	return NULL;
}
```

* 프로세스 종료되면 쓰레드도 종료됨. 그거 막기 위해 sleep(10)
* thread\_param을 주소 값으로 전달하면 쓰레드에서 int 포인터로 변환
* 책과 다르게 컴파일 시 -lpthread 옵션 추가 안 했는데, 멀쩡히 작동됨. Ubuntu같은 배포판은 gcc가 자동 포함하도록 설정돼있다고 함

지금처럼 쓰레드 끝날 때까지 sleep()하는 건 비현실적 `pthread_join()` : 인자로 전달된 쓰레드가 종료될 때까지, 해당 함수 호출한 프로세스(또는 쓰레드) 대기상태에 둔다.

```c
	if(pthread_create(&t_id, NULL, thread_main, (void*)&thread_param)!=0)
	{
		puts("pthread_create() error");
		return -1;
	};

	if(pthread_join(t_id, &thr_ret)!=0)
	{
		puts("pthread_join() error");
		return -1;
	};

	printf("Thread return message: %s \n", (char*)thr_ret);
```

#### 임계영역 내 호출 가능 함수

임계 영역 : 둘 이상의 쓰레드가 동시에 실행하면 문제 일어나는 문장 존재하는 함수

쓰레드 안전 함수

* 동시 호출 및 실행돼도 문제 안 일어나는 함수
* 임계영역 있어도 적절한 조치 취하면 쓰레드 안전해진다.
* 대부분의 표준함수는 쓰레드 안전하다
  * 안전하지 않은 함수 있어도 그에 대응되는 쓰레드 안전 함수가 있다
  * 리눅스에서 쓰레드 안전한 형태로 재구현된 함수에는 `_r`이 붙는다
  * 현대 리눅스는 대부분 아예 다른 대체 API 제공하거나 쓰레드 안전하게 바꿔놓음

#### 워커(Worker) 쓰레드 모델

1부터 10까지의 덧셈 결과를 출력하는 프로그램 만들 때, 쓰레드 두 개가 각각 1~~5, 6~~10 연산하면 이것을 Worker thread 모델이라 함.

```c
long long num = 0;

int main(int argc, char *argv[])
{
	pthread_t thread_id[NUM_THREAD];
	int i;

	printf("sizeof long long: %d \n", sizeof(long long));
	for(i=0; i<NUM_THREAD; i++)
	{
		if(i%2)
			pthread_create(&(thread_id[i]), NULL, thread_inc, NULL);
		else
			pthread_create(&(thread_id[i]),NULL, thread_des, NULL);
	}

	for(i=0; i<NUM_THREAD; i++)
		pthread_join(thread_id[i], NULL);

	printf("result: %lld \n", num);
	return 0;
}

void * thread_inc(void * arg)
{
	int i;
	for(i=0; i<5000000; i++)
		num+=1;
	return NULL;
}
void * thread_des(void * arg)
{
	int i;
	for(i=0; i<5000000; i++)
		num-=1;
	return NULL;
}
```

두 쓰레드가 하나의 전역변수 num에 여러 번 접근하기 때문에, 오류 발생

### 18-3 쓰레드의 문제점과 임계영역

두 쓰레드가 같은 값에 동시에 접근하면, 연산 끝낸 후에 결과 저장하기 전에 다음 쓰레드가 접근해버릴 수 있기 때문에 문제 발생.

동기화 필요.

임계영역

* 함수 내에 둘 이상에 쓰레드가 동시에 실행하면 문제 일으키는 코드 블록
* 전역 변수 num 자체는 임계 영역이 아니다

### 18-4 쓰레드 동기화

동기화 필요한 상황

* 동일한 메모리 영역 동시 접근 발생
* 동일한 메모리 영역에 접근하는 쓰레드 실행 순서 지정해야 하는 상황
  * ex) 쓰레드 A는 메모리에 갖다놓고, 쓰레드 B는 그 값을 가져감

#### 뮤텍스 (Mutex)

* Mutual Exclusion의 줄임말. 쓰레드의 동시 접근 허용하지 않는다.
* `pthread_mutex_init()` : 자물쇠 생성
* `pthread_mutex_destroy()` : 자물쇠 소멸
* `pthread_mutex_lock()` : 자물쇠 잠그기
* `pthread_mutex_unlock()` : 자물쇠 풀기
* 임계영역 빠져나갈 때 unlock 호출 안 하면 lock 호출했던 다른 쓰레드는 무한 블로킹된다

```c
void* thread_inc(void* arg)
{
	int i;
	pthread_mutex_lock(&mutex);
	for(i=0; i<500000; i++)
		num+=1;
	pthread_mutex_unlock(&mutex);
	return NULL;
}

void* thread_des(void* arg)
{
	int i;
	for(i=0; i<500000; i++)
	{
		pthread_mutex_lock(&mutex);
		num-=1;
		pthread_mutex_unlock(&mutex);
	}
	return NULL;
}
```

* inc처럼 임계영역 넓게 잡으면 lock 호출 횟수 줄어들어서 성능 좋아지지만, 다른 쓰레드 접근 불가

#### 세마포어 (Semaphore)

* `sem_init()` : 세마포어 생성
* `sem_destroy()` : 세마포어 소멸
* `sem_post()` : 세마포어 값 1 증가
* `sem_wait()`
  * 세마포어 값 1 감소
  * 세마포어 값이 0일 때 호출하면 다시 1 될때까지 블로킹

```c
static sem_t sem_one;
static sem_t sem_two;
static int num;

int main(int argc, char *argv[])
{
	pthread_t id_t1, id_t2;
	sem_init(&sem_one, 0, 0);
	sem_init(&sem_two, 0, 1);
	
	pthread_create(&id_t1, NULL, read, NULL);
	pthread_create(&id_t2, NULL, accu, NULL);

	pthread_join(id_t1, NULL);
	pthread_join(id_t2, NULL);

	sem_destroy(&sem_one);
	sem_destroy(&sem_two);
	return 0;
}

void* read(void* arg)
{
	int i;
	for(i=0; i<5; i++)
	{
		fputs("Input num: ", stdout);
		sem_wait(&sem_two);
		scanf("%d", &num);
		sem_post(&sem_one);
	}
	return NULL;
}
void* accu(void* arg)
{
	int sum=0, i;
	for(i=0; i<5; i++)
	{
		sem_wait(&sem_one);
		sum+=num;
		sem_post(&sem_two);
	}
	printf("Result: %d \n", sum);
	return NULL;
}
```

* wait는 화장실 쓸게요, post는 화장실 다 썼어요
* read에서 two를 쓴다고 선언하면, accu에서 two 써도 된다고 할 때까지 다음 read 진행 막아줌
* read에서 one 써도 된다고 하면, 그제야 accu가 돌아감

### 18-5 쓰레드의 소멸과 멀티쓰레드 기반의 다중접속 서버의 구현

쓰레드 소멸

* `pthread_join()` : 쓰레드 소멸해주지만, 종료될 때까지 블로킹됨
* `pthread_detach()` : 종료되지 않은 쓰레드 종료되지도 않고, 블로킹되지도 않음.

```c
int clnt_cnt=0;
int clnt_socks[MAX_CLNT];
pthread_mutex_t mutx;

int main(int argc, char *argv[])
{
	int serv_sock, clnt_sock;
	struct sockaddr_in serv_adr, clnt_adr;
	int clnt_adr_sz;
	pthread_t t_id;
	if(argc!=2) {
		printf("Usage : %s <port>\n", argv[0]);
		exit(1);
	}

	pthread_mutex_init(&mutx, NULL);
	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)
	{
		clnt_adr_sz=sizeof(clnt_adr);
		clnt_sock=accept(serv_sock, (struct sockaddr*)&clnt_adr, &clnt_adr_sz);

		pthread_mutex_lock(&mutx);
		clnt_socks[clnt_cnt++]=clnt_sock;
		pthread_mutex_unlock(&mutx);

		pthread_create(&t_id, NULL, handle_clnt, (void*)&clnt_sock);
		pthread_detach(t_id);
		printf("Connected client IP: %s", inet_ntoa(clnt_adr.sin_addr));
	}
	close(serv_sock);
	return 0;
}

void* handle_clnt(void* arg)
{
	int clnt_sock=*((int*)arg);
	int str_len=0, i;
	char msg[BUF_SIZE];

	while((str_len=read(clnt_sock, msg, sizeof(msg)))!=0)
		send_msg(msg, str_len);

	pthread_mutex_lock(&mutx);
	for(i=0; i<clnt_cnt; i++) // remove disconnected client
	{
		if(clnt_sock == clnt_socks[i])
		{
			while(i++<clnt_cnt-1)
				clnt_socks[i]=clnt_socks[i+1];
			break;
		}
	}
	clnt_cnt--;
	pthread_mutex_unlock(&mutx);
	close(clnt_sock);
	return NULL;
}
void send_msg(char* msg, int len) // send to all
{
	int i;
	pthread_mutex_lock(&mutx);
	for(i=0; i<clnt_cnt; i++)
		write(clnt_socks[i], msg, len);
	pthread_mutex_unlock(&mutx);
}
```

chat 서버

* 소켓 cnt 올리고 할당할 때 lock 사용
* 클라 소켓 만들어지면, 그 소켓을 위한 쓰레드 따로 만들어준다
* read 계속 하면서 메시지 받을 때마다 모든 클라에게 뿌린다
* 메시지 끝나면 lock 걸고 클라 소켓 자리 앞으로 다 땡김

```c
int main(int argc, char *argv[])
{
	int sock;
	struct sockaddr_in serv_addr;
	pthread_t snd_thread, rcv_thread;
	void* thread_return;
	if(argc!=4) {
		printf("Usage : %s <IP> <port> <name>\n", argv[0]);
		exit(1);
	}

	sprintf(name, "[%s]", argv[3]);
	sock=socket(PF_INET, SOCK_STREAM, 0);

	memset(&serv_addr, 0, sizeof(serv_addr));
	serv_addr.sin_family=AF_INET;
	serv_addr.sin_addr.s_addr=inet_addr(argv[1]);
	serv_addr.sin_port=htons(atoi(argv[2]));

	if(connect(sock, (struct sockaddr*)&serv_addr, sizeof(serv_addr))==-1)
		error_handling("connect() error");

	pthread_create(&snd_thread, NULL, send_msg, (void*)&sock);
	pthread_create(&rcv_thread, NULL, recv_msg, (void*)&sock);
	pthread_join(snd_thread, &thread_return);
	pthread_join(rcv_thread, &thread_return);
	close(sock);
	return 0;
}

void* send_msg(void* arg)
{
	int sock=*((int*)arg);
	char name_msg[NAME_SIZE+BUF_SIZE];
	while(1)
	{
		fgets(msg, BUF_SIZE, stdin);
		if(!strcmp(msg,"q\n")||!strcmp(msg,"Q\n"))
		{
			close(sock);
			exit(0);
		}
		sprintf(name_msg,"%s %s",name,msg);
		write(sock, name_msg, strlen(name_msg));
	}
	return NULL;
}
void* recv_msg(void* arg)
{
	int sock=*((int*)arg);
	char name_msg[NAME_SIZE+BUF_SIZE];
	int str_len;
	while(1)
	{
		str_len=read(sock, name_msg, NAME_SIZE+BUF_SIZE-1);
		if(str_len==-1)
			return (void*)-1;
		name_msg[str_len]=0;
		fputs(name_msg, stdout);
	}
	return NULL;
}
```

* 메시지 받는 쓰레드와 보내는 쓰레드로 나눈다


---

# 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-02.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.
