# Chapter 1 \~ 7

Part 01 네트워크 프로그래밍의 시작

## Chapter 01 네트워크 프로그래밍과 소켓의 이해

### 01-1 네트워크 프로그래밍과 소켓의 이해

데이터 송수신 원리 이론적으로 잘 몰라도 소켓으로 데이터 주고받을 수 있다. 송신 소켓과 수신 소켓은 다르다.

#### 소켓 관련 기본 함수

```c
#include <sys/socket.h>
int socket(int domain, int type, int protocol); // 소켓 생성 함수. 성공 시 파일 디스크립터, 실패 시 -1 반환.
int bind(int sockfd, struct sockaddr *myaddr, socklen_t addrlen); // 소켓에 주소 정보 할당. 성공 시 0, 실패 시 -1 반환
int listen(int sockfd, int backlog); // 연결 요청 가능 상태로 변경. 성공 시 0, 실패 시 -1 반환
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen); // 연결 요청 수락. 성공 시 파일 디스크립터, 실패 시 -1 반환
```

```c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
void error_handling(char *message);

int main(int argc, char *argv[])
{
	int serv_sock;
	int clnt_sock;

	struct sockaddr_in serv_addr;
	struct sockaddr_in clnt_addr;
	socklen_t clnt_addr_size;

	char message[] = "Hello World!";

	if(argc!=2)
	{
		printf("Usage : %s <port>\n", argv[0]);
		exit(1);
	}

	serv_sock=socket(PF_INET, SOCK_STREAM, 0);
	if(serv_sock == -1)
		error_handling("socket() error");

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

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

	if(listen(serv_sock, 5) == -1)
		error_handling("listen() error");

	clnt_addr_size=sizeof(clnt_addr);
	clnt_sock=accept(serv_sock, (struct sockaddr*)&clnt_addr, &clnt_addr_size);
	if(clnt_sock==-1)
		error_handling("accept() error");
	
	write(clnt_sock, message, sizeof(message));
	close(clnt_sock);
	close(serv_sock);
	return 0;
}

void error_handling(char *message)
{
	fputs(message, stderr);
	fputc('\n', stderr);
	exit(1);
}
```

연결 요청 기다리는 서버 프로그램

* `AF_INET` : IPv4 인터넷 프로토콜
* `SOCK_STREAM` : TCP 소켓
* `INADDR_ANY` : 0.0.0.0 의미. 모든 IP 주소 연결 수락.
* `htonl()` : host to network long. 네트워크 바이트 오더(빅 엔디안)로 변환하여 프로토콜 표준 맞춤.
* `htnos()` : host to network short. 포트 번호도 네트워크 바이트 오더로 맞춤.

```c
int connect(int sockfd, struct sockaddr *serv_addr, socklen_t addrlen);
```

```c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
void error_handling(char *message);

int main(int argc, char* argv[])
{
	int sock;
	struct sockaddr_in serv_addr;
	char message[30];
	int str_len;

	if(argc!=3)
	{
		printf("Usage : %s <IP> <port>\n", argv[0]);
		exit(1);
	}

	sock=socket(PF_INET, SOCK_STREAM, 0);
	if(sock == -1)
		error_handling("connect() error!");

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

	str_len=read(sock,message,sizeof(message)-1);
	if(str_len==-1)
		error_handling("read() error!");

	printf("Message from server :%s \n", message);
	close(sock);
	return 0;
}

void error_handling(char *message)
{
	fputs(message, stderr);
	fputc('\n', stderr);
	exit(1);
}
```

AWS 서버에 있는 서버 프로그램과 랩탑의 클라 프로그램을 연결하여 테스트해봄

### 01-2 리눅스 기반 파일 조작하기

리눅스는 소켓 조작을 파일 조작과 동일하게 간주. 파일 입출력 함수를 소켓 입출력에 사용 가능. 윈도우는 파일과 소켓을 구분.

파일 디스크립터 : 파일 또는 소켓에 부여된 정수

#### 파일 조작 기본 함수

```c
int open(const char *path, int flag); // 데이터를 읽거나 쓰기 위해 파일 열 때 사용
int close(int fd); // 파일 닫기
ssize_t write(int fd, const void * buf, size_t nbytes); // 파일에 데이터 출력
ssize_t read(int fd, void *buf, size_t nbytes); // 성공 시 수신한 바이트 수
```

* size\_t : unsigned int
* ssize\_t : singed int
* `_t` 붙는 이유 : 시스템 바뀌어서 비트 바뀌면 typedef 지정된 자료형을 바꿔서 컴파일하여 맞추기 위해 별도 이름 붙인 것

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

#### 윈도우 소켓 개발에 필요한 것

**헤더 파일 winsock2.h 포함**

```c
#include <winsock2.h>
#include <ws2tcpip.h>  // IPv6, getaddrinfo 같은 함수 쓰려면 필요
// #include <windows.h> 보다 반드시 먼저 포함
```

**ws2\_32.lib 라이브러리 링크**

```c
#pragma comment(lib, "ws2_32.lib")
```

또는 프로젝트 속성 → Linker → Input → Additional Dependencies에 ws2\_32.lib 추가.

이건 visual studio가 아니면 의미가 없음.

직접 컴파일할 땐 `gcc server.c util.c -o server.exe -lws2_32` 처럼 뒤에 라이브러리 링크를 붙여야 한다.

**winsock의 초기화**

```c
#include<winsock2.h>
int WSAStartup(WORD wVersionRequested, LPWSADATA lpWSAData);
```

윈도우 소켓 초기화 함수

* 첫 번째 매개변수 : 소켓 버전정보 (MAKEWORD()로 버전 정보 쉽게 구성 가능)
* 두 번째 매개변수 : WSADATA의 주소 값

```c
int main(int argc, char* argv[])
{
	WSADATA wsaData;
	....
	if(WSAStartup(MAKEWORD(2,2), &wsaData) != 0)
		ErrorHandling("WSAStartup() error!:);
	....
	return 0;
}
```

사용 예시

```c
int WSACleanup(void);
```

초기화된 라이브러리 해제

### 01-4 윈도우 기반의 소켓관련 함수와 예제

#### 윈도우 기반 소켓관련 함수들

```c
SOCKET socket(int af, int type, int protocol); // 소켓 생성
int bind(SOCKET s, const struct sockaddr * name, int namelen); // IP주소와 PORT 번호 할당
int listen(SOCKET s, int backlog); // 소켓이 클라 연결 요청 받을 준비
SOCKET accept(SOCKET s, struct sockaddr * addr, int * addrlen); // 연결 수락
int connect(SOCKET s, const struct sockaddr * name, int namelen); // 연결 요청
int closesocket(SOCKET s); // 소켓 닫기
```

#### 윈도우에서의 파일 핸들과 소켓 핸들

리눅스는 소켓도 파일 취급. 파일 디스크립터가 할당됨. 윈도우는 파일 생성할 때 핸들 반환. 근데 파일 핸들과 소켓 핸들 구분함.

SOCKET : 정수로 표현되는 소켓 핸들 값 저장 위해 typdef로 재정의된 자료형

```c
int send(SOCKET s, const char * buf, int len, int flags); // 소켓에 데이터 전송 (이건 리눅스에도 존재)
int recv(SOCKET s, const char * buf, int len, int flgas); // 소켓에서 데이터 수신
```

#### 윈도우 기반 서버, 클라이언트 예제

```c
#include<stdio.h>
#include<stdlib.h>
#include<winsock2.h>
void ErrorHandling(char* message);

int main(int argc, char* argv[])
{
	WSADATA wsaData;
	SOCKET hServSock, hClntSock;
	SOCKADDR_IN servAddr, clntAddr;

	int szClntAddr;
	char message[] = "Hello World!";
	if (argc != 2)
	{
		printf("Usage : %s <port>\n", argv[0]);
		exit(1);
	}

	if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0)
		ErrorHandling("WSAStartup() error!");

	hServSock = socket(PF_INET, SOCK_STREAM, 0);
	if (hServSock == INVALID_SOCKET)
		ErrorHandling("socket() error");

	memset(&servAddr, 0, sizeof(servAddr));
	servAddr.sin_family = AF_INET;
	servAddr.sin_addr.s_addr = htonl(INADDR_ANY);
	servAddr.sin_port = htons(atoi(argv[1]));

	if (bind(hServSock, (SOCKADDR*)&servAddr, sizeof(servAddr)) == SOCKET_ERROR)
		ErrorHandling("bind() error");

	if (listen(hServSock, 5) == SOCKET_ERROR)
		ErrorHandling("listen() error");

	szClntAddr = sizeof(clntAddr);
	hClntSock = accept(hServSock, (SOCKADDR*)&clntAddr, &szClntAddr);
	if (hClntSock == INVALID_SOCKET)
		ErrorHandling("aceept() error");

	send(hClntSock, message, sizeof(message), 0);
	closesocket(hClntSock);
	closesocket(hServSock);
	WSACleanup();
	return 0;
}

void ErrorHandling(char* message)
{
	fputs(message, stderr);
	fputc('\n', stderr);
	exit(1);
}
```

서버 예제

```c
#include <stdio.h>
#include <stdlib.h>
#include <winsock2.h>
void ErrorHandling(char* message);

int main(int argc, char* argv[])
{
	WSADATA wsaData;
	SOCKET hSocket;
	SOCKADDR_IN servAddr;

	char message[30];
	int strLen;
	if(argc != 3)
	{
		printf("Usage : %s <IP> <port>\n", argv[0]);
		exit(1);
	}

	if(WSAStartup(MAKEWORD(2,2), &wsaData) != 0)
		ErrorHandling("WSAStartup() error!");

	hSocket=socket(PF_INET, SOCK_STREAM, 0);
	if(hSocket==INVALID_SOCKET)
		ErrorHandling("WSAStartup() error!");

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

	if(connect(hSocket, (SOCKADDR*)&servAddr, sizeof(servAddr))==SOCKET_ERROR)
		ErrorHandling("connect() error");

	strLen=recv(hSocket, message, sizeof(message)-1, 0);
	if(strLen==-1)
		ErrorHandling("read() error!");
	printf("Message from server: %s \n", message);

	closesocket(hSocket);
	WSACleanup();
	return 0;
}

void ErrorHandling(char* message)
{
	fputs(message, stderr);
	fputc('\n', stderr);
	exit(1);
}
```

## Chapter 2 소켓의 타입과 프로토콜의 설정

### 02-1 소켓의 프로토콜과 그에 따른 데이터 전송 특성

#### 프로토콜

프로토콜 : 컴퓨터 상호간의 대화에 필요한 통신규약

```c
int socket (int domain, int type, int protocol);
// domain : 소켓이 사용할 프로토콜 체계(Protocol Family) 정보
// type : 소켓의 데이터 전송방식에 대한 정보
// protocol : 두 컴퓨터 간 통신에 사용되는 포로토콜 정보
```

프로토콜 체계(Protocol Family) : IPv4, IPv6 등 소켓 타입

* 연결 지향형 소켓(SOCK\_STREAM)
  * 중간에 데이터가 소멸되지 않고 목적지로 전송된다
  * 전송 순서대로 데이터가 수신된다
  * 전송되는 데이터의 경계(boundary)가 존재하지 않는다 : 데이터 전송 횟수와 읽기 횟수가 관련 없음
* 비 연결 지향형 소켓(SOCK\_DGRAM)
  * 전송된 순서에 상관없이 가장 빠른 전송
  * 전송된 데이터는 손실과 파손의 우려 있음
  * 전송되는 데이터의 경계가 존재 : 데이터 2번 보내면 2번 받는다
  * 한 번에 전송할 수 있는 데이터의 크기가 제한됨 프로토콜 : 프로토콜 체계와 소켓 타입이 동일한 프로토콜이 존재할 때 지정해주기 위해 필요

IPv4 + 연결지향형 데이터 전송 : TCP 소켓이 유일 IPv4 + 비 연결지향형 데이터 전송 : UDP 소켓이 유일

### 02-2 윈도우 기반에서 이해 및 확인

```c
SOCKET socket();
```

반환이 SOCKET인데, 사실 정수값. 근데 자료형으로 제대로 써라.

## Chapter 03 주소체계와 데이터 정렬

### 03-1 소켓에 할당되는 IP 주소와 PORT 번호

IP(Internet Protocol) : 인터넷 사용하기 위해 컴퓨터에 부여되는 값

* IPv4 : 4바이트 주소체계
* IPv6 : 16바이트 주소체계
* 네트워크 주소로 해당 네트워크에 접근 후 나머지 호스트 주소로 해당 컴퓨터 찾는다

PORT 번호 : 소켓들 구분하기 위해 소켓에 부여되는 번호

* 동일한 PORT를 둘 이상의 소켓에 할당할 수 없다
* 0\~1023들은 특정 프로그램에 할당하기로 예약돼있어서 못 쓴다
* TCP 소켓과 UDP 소켓은 PORT 번호 공유 안 해서 중복돼도 상관 없다

### 03-2 주소 정보의 표현

sockaddr\_in 구조체

* `sa_family_t sin_family` : 주소체계
* `uint16_t sin_port` : 16비트 TCP/UDP PORT 번호, 네트워크 바이트 순서로 저장, `_t` 붙여서 바이트 보장
* `struct in_addr sin_addr` : 32비트 IP주소, 네트워크 바이트 순서로 저장
* `char sin_zero[8]` : 반드시 0으로 채운다. `sockaddr_in`의 크기를 구조체 `sockaddr`와 일치시키기 위해 삽입.
* sockaddr 요구 데이터를 쉽게 할당한 후 `sockaddr`로 형변환하여 사용
* `sockaddr_in`은 IPv4인데 주소체계 저장해두는 이유? : `sockaddr`로 형변환 할건데, `sockaddr`에는 주소체계 명시해야 하기 때문

### 03-3 네트워크 바이트 순서와 인터넷 주소 변환

4바이트 정수 1을 CPU에 따라 다르게 저장한다

* 빅 엔디안 : 상위 바이트 값(더 큰 자리수)을 작은 번지수에 저장한다. `00000000 00000000 00000000 00000001`
* 리틀 엔디안 : 상위 바이트 값(더 큰 자리수)을 큰 번지수에 저장한다 `00000001 00000000 00000000 00000000`
* 네트워크 바이트 순서 : 빅 엔디안

바이트 순서 변환 함수

* h는 호스트 바이트 순서
* n은 네트워크 바이트 순서
* s, l은 short, long을 의미
* `unsinged short htons(unsinged short)`
* `unsinged short ntohs(unsinged short)`
* `unsinged long htonl(unsinged short)`
* `unsinged long ntohl(unsinged long)`
* ㅇㅇ형 데이터를 ㅁㅁ바이트 순서에서 ㅁㅁ바이트 순서로 변환해라
* 빅 엔디안 시스템이면 굳이 바꿀 필요는 없지만, 시스템에 무관한 코드를 작성하는게 좋다.
* 소켓에 데이터 넣을 때 말고는 바이트 순서 신경 안 써도 된다

### 03-4 인터넷 주소의 초기화와 할당

IP 주소 할당할 때 `201.211 ...`을 하나의 정수로 바꿔야 한다. `inet_addr()` : 문자열 IP 주소를 32비트 정수형이자 네트워크 바이트 순서로 변환 `inet_aton()` : 구조체 변수 `in_addr`만 넘겨주면 32비트 정수형, 네트워크 바이트 순서 변환 `inet_ntoa()`

* 반대로 문자열 IP 정보로 변환.
* 내부적으로 할당했던 char 포인터를 반환하니, 유지해야하면 별도 메모리로 할당 필요.
* 반대로 inet\_ntoa가 반환하는 포인터는 항상 내부 static 버퍼를 가리키므로 다른 주소 넣었을 때 돌려써도 된다.

```c
struct sockaddr_in addr;
char *serv_ip = "211.217.168.13"; // IP주소 문자열 선언
char *serv_port = "9190"; // PORT 번호 문자열 선언
memset(&addr, 0, sizeof(addr)); // 구조체 변수 addr의 모든 멤버 0으로 초기화 (sin_zero를 초기화하기 위함)
addr.sin_family = AF_INET; // 주소체계 지정
addr.sin_addr.s_addr = inet_addr(serv_ip);  // 문자열 기반의 IP 주소 초기화
addr.sin_port = htons(atoi(serv_port)); // 문자열 기반의 PORT 번호 초기화
```

인터넷 주소의 초기화

* `s_addr`을 상수 `INADDR_ANY`로 초기화하면 소켓 동작 컴퓨터 IP 주소 자동 할당. 여러 IP 주소 갖고 있을 시 어느 IP 주소여도 PORT만 일치하면 수신.
* 이제 이걸 bind()로 소켓에 넣으면 된다

### 03-5 윈도우 기반으로 구현하기

윈도우에는 inet\_aton() 대신 WSAStringToAddress()와 WSAAddressToString()이 있다. IPv4와 IPv6에서도 사용이 가능하다. 하지만 운영체제 종속적인 코드가 된다.

### 내용 확인 문제

스위치 : MAC 주소로 같은 네트워크 장치 연결 라우터 : IP 주소로 다른 네트워크 간 데이터 전달

bind()할 때 프로토타입과 달리 sockaddr이 아닌 sockaddr\_in을 사용하는 이유 : sockaddr은 프로토콜 독립적, sockaddr\_in은 IPv4 전용 주소 구조체

0x12345678을 저장한다고 가정 빅 엔디안

* 가장 상위 바이트(0x12)를 메모리의 낮은 주소에 저장
* 0x12 0x34 0x56 0x78 리틀 엔디안
* 가장 하위 바이트(0x78)를 메모리의 낮은 주소에 저장
* 0x78 0x56 0x34 0x12 네트워크 바이트 순서 : 빅 엔디안

## Chapter 04 TCP 기반 서버/클라이언트 1

### 04-1 TCP와 UDP에 대한 이해

TCP (Transmission Control Protocol) 소켓

* 연결 지향, 스트림 기반
* APPLICATION 계층 → TCP 계층 → IP 계층 → LINK 계층

LINK 계층 : 물리적인 연결 IP 계층 : 데이터 전송 경로를 위한 프로토콜 TCP/UDP 계층 : IP계층 경로 정보 바탕으로 데이터 송수신 APPLICATION 계층 : 클라 서버 송수신 관한 규칙

### 04-2 TCP 기반 서버, 클라 구현

#### TCP 서버 함수 호출 순서

* socket() : 소켓 생성
* bind() : 소켓 주소 할당
* listen() : 연결 요청 대기 상태
* accept() : 연결 허용
* read() / write() : 데이터 송수신
* close() : 연결 종료

listen() 호출하면 클라 연결 요청 받기 시작한다 연결요청 대기 큐에 연결 요청이 클라가 호출한 connect()를 통해 들어온다 서버가 accept()를 하면 서버 소켓과 별도인 소켓이 새로 만들어져 연결 성립

#### TCP 클라 함수 호출 순서

* socket() : 소켓 생성
* connect() : 연결 요청
* read()/write() : 데이터 송수신
* close() : 연결 종료

connect() 함수 반환 조건

* 서버에 의해 연결 요청 접수 (accept() 등록이 아니라 대기 큐에 들어왔다는거)
* 네트워크 단절 등 오류 발생하여 연결 요청 중단

클라 소켓 주소 정보는 어디에?

* 서버 소켓에는 IP와 PORT를 할당한다
* 클라에선 bind()로 소켓에 자기 주소와 자기 PORT 할당 하지 않는다
* connect()에서 커널이 알아서 자기 주소와 임의 PORT 할당

### 04-3 Iterative 기반의 서버, 클라 구현

반복문을 삽입해서 여러 클라에게 accept()문 호출 및 read(), write() 서버 소켓이 아니라 accept()에서 생성된 소켓을 close()까지 한 번에 하나의 클라가 아니라 여러 클라 응답하려면 멀티프로세스나 멀티쓰레딩 필요

```c
#define BUF_SIZE 1024
void error_handling(char *message);

int main(int argc, char *argv[])
{
	int serv_sock, clnt_sock;
	char message[BUF_SIZE];
	int str_len, i;

	struct sockaddr_in serv_adr, clnt_adr;
	socklen_t clnt_adr_sz;

	if(argc!=2) {
		printf("Usage : %s <port>\n", argv[0]);
		exit(1);
	}

	serv_sock = socket(PF_INET, SOCK_STREAM, 0);
	if(serv_sock == -1)
		error_handling("socket() error");

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

	clnt_adr_sz = sizeof(clnt_adr);

	for(i=0; i<5; i++)
	{
		clnt_sock=accept(serv_sock, (struct sockaddr*)&clnt_adr, &clnt_adr_sz);
		if(clnt_sock==-1)
			error_handling("accept() error");
		else
			printf("Connected clinet %d \n", i+1);

		while((str_len=read(clnt_sock, message, BUF_SIZE)) != 0)
			write(clnt_sock, message, str_len);

		close(clnt_sock);
	}
	close(serv_sock);
	return 0;
}
```

echo\_server.c

```c
#define BUF_SIZE 1024
void error_handling(char *message);

int main(int argc, char *argv[])
{
	int sock;
	char message[BUF_SIZE];
	int str_len;
	struct sockaddr_in serv_adr;

	if(argc!=3) {
		printf("Usage : %s <IP> <port>\n", argv[0]);
		exit(1);
	}

	sock = socket(PF_INET, SOCK_STREAM, 0);
	if(sock == -1)
		error_handling("socket() error");

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

	if(connect(sock, (struct sockaddr*)&serv_adr, sizeof(serv_adr))==-1)
		error_handling("connect() error!");
	else
		puts("Connected.....");

	while(1)
	{
		fputs("Input message(Q to quit): ", stdout);
		fgets(message, BUF_SIZE, stdin);

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

		write(sock, message, strlen(message));
		str_len=read(sock, message, BUF_SIZE-1);
		message[str_len]=0;
		printf("Message from server : %s", message);
	}
	close(sock);
	return 0;
}
```

echo\_client.c

현재 문제점

* 문자열이 나눠서 오지 않는다고 가정함
* 지금은 문자열 작아서 오류 일어나지 않은거

### 04-4 윈도우 기반 구현

```c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <winsock2.h>

#define BUF_SIZE 1024
void ErrorHandling(char *message);

int main(int argc, char *argv[])
{
	WSADATA wsaData;
	SOCKET hServSock, hClntSock;
	char message[BUF_SIZE];
	int strLen, i;

	SOCKADDR_IN servAdr, clntAdr;
	int clntAdrSize;

	if(argc!=2) {
		printf("Usage : %s <port>\n", argv[0]);
		exit(1);
	}

	if(WSAStartup(MAKEWORD(2,2), &wsaData)!=0)
		ErrorHandling("WSAStartup() error!");

	hServSock=socket(PF_INET, SOCK_STREAM, 0);
	if(hServSock==INVALID_SOCKET)
		ErrorHandling("socket() error");

	memset(&servAdr, 0, sizeof(servAdr));
	servAdr.sin_family=AF_INET;
	servAdr.sin_addr.s_addr=htonl(INADDR_ANY);
	servAdr.sin_port=htons(atoi(argv[1]));

	if(bind(hServSock, (SOCKADDR*)&servAdr, sizeof(servAdr))==SOCKET_ERROR)
		ErrorHandling("bind() error");

	if(listen(hServSock, 5)==SOCKET_ERROR)
		ErrorHandling("listen() error");

	clntAdrSize=sizeof(clntAdr);

	for(i=0; i<5; i++)
	{
		hClntSock=accept(hServSock, (SOCKADDR*)&clntAdr, &clntAdrSize);
		if(hClntSock==-1)
			ErrorHandling("accept() error");
		else
			printf("Connected client %d \n", i+1);

		while((strLen=recv(hClntSock, message, BUF_SIZE, 0))!=0)
			send(hClntSock, message, strLen, 0);

		closesocket(hClntSock);
	}
	closesocket(hServSock);
	WSACleanup();
	return 0;
}

void ErrorHandling(char *message)
{
	fputs(message, stderr);
	fputc('\n', stderr);
	exit(1);
}
```

echo\_server\_win.c

```c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <winsock2.h>

#define BUF_SIZE 1024
void ErrorHandling(char *message);

int main(int argc, char *argv[])
{
	WSADATA wsaData;
	SOCKET hSocket;
	char message[BUF_SIZE];
	int strLen;
	SOCKADDR_IN servAdr;

	if(argc!=3) {
		printf("Usage : %s <IP> <port>\n", argv[0]);
		exit(1);
	}

	if(WSAStartup(MAKEWORD(2,2), &wsaData) != 0)
		ErrorHandling("WSAStartup() error!");

	hSocket = socket(PF_INET, SOCK_STREAM, 0);
	if(hSocket==INVALID_SOCKET)
		ErrorHandling("socket() error");

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

	if(connect(hSocket, (SOCKADDR*)&servAdr, sizeof(servAdr))==SOCKET_ERROR)
		ErrorHandling("Connect() error!");
	else
		puts("Connected........");

	while(1)
	{
		fputs("Input message(Q to quit): ", stdout);
		fgets(message, BUF_SIZE, stdin);

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

		send(hSocket, message, strlen(message), 0);
		strLen = recv(hSocket, message, BUF_SIZE-1, 0);
		message[strLen] = 0;
		printf("Message from server: %s", message);
	}
	closesocket(hSocket);
	WSACleanup();
	return 0;
}

void ErrorHandling(char *message)
{
	fputs(message, stderr);
	fputc('\n', stderr);
	exit(1);
}
```

echo\_client\_win.c

## Chapter 5 TCP 기반 서버/클라 2

### 05-1 에코 클라이언트 완벽 구현

문제가 되는 건 에코 클라

* 데이터를 한 방에 전송해버린다
* 받는 것도 한 방에 받기를 원한다
* 에코 클라 한정으로, 받을 데이터 크기 알고 있으니 해결은 쉽다

```c
str_len = write(sock, message, strlen(message))l

recv_len = 0;
while(recv_len < str_len)
{
	recv_cnt = read(sock, &message[recv_len], BUF_SIZE-1};
	if(recv_cnt==-1)
		error_handling("read() error!");
	recv_len += recv_cnt;
}
```

기존 클라에 위 코드를 추가하면 해결된다

에코 클라가 아니라면, 데이터의 끝을 파악할 수 있는 약속을 별도로 정의하거나 송수신될 데이터의 크기를 미리 알려주는 등 '어플리케이션 프로토콜'을 정의하여 해결한다

#### 계산기 프로토콜

```c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <winsock2.h>

#define BUF_SIZE 1024
void ErrorHandling(char *message);
int SumAll(int *msgBuf, int recvCount)
{
	int result = 0;
	for(int i=0; i<recvCount; i++) result += msgBuf[i];
	return result;
}

int SubtractAll(int *msgBuf, int recvCount)
{
	if(recvCount == 0) return 0;
	int result = msgBuf[0];
	for(int i=1; i<recvCount; i++) result -= msgBuf[i];
	return result;
}

int MultiplyAll(int *msgBuf, int recvCount)
{
	if(recvCount == 0) return 0;
	int result = 1;
	for(int i=0; i<recvCount; i++) result *= msgBuf[i];
	return result;
}

int main(int argc, char *argv[])
{
	WSADATA wsaData;
	SOCKET hServSock, hClntSock;
	char message[BUF_SIZE];
	int msgBuf[BUF_SIZE];
	int strLen, i, j, recvCount;

	SOCKADDR_IN servAdr, clntAdr;
	int clntAdrSize;

	if(argc!=2) {
		printf("Usage : %s <port>\n", argv[0]);
		exit(1);
	}

	if(WSAStartup(MAKEWORD(2,2), &wsaData)!=0)
		ErrorHandling("WSAStartup() error!");

	hServSock=socket(PF_INET, SOCK_STREAM, 0);
	if(hServSock==INVALID_SOCKET)
		ErrorHandling("socket() error");

	memset(&servAdr, 0, sizeof(servAdr));
	servAdr.sin_family=AF_INET;
	servAdr.sin_addr.s_addr=htonl(INADDR_ANY);
	servAdr.sin_port=htons(atoi(argv[1]));

	if(bind(hServSock, (SOCKADDR*)&servAdr, sizeof(servAdr))==SOCKET_ERROR)
		ErrorHandling("bind() error");

	if(listen(hServSock, 5)==SOCKET_ERROR)
		ErrorHandling("listen() error");

	clntAdrSize=sizeof(clntAdr);

	for(i=0; i<5; i++)
	{
		hClntSock=accept(hServSock, (SOCKADDR*)&clntAdr, &clntAdrSize);
		if(hClntSock==-1)
			ErrorHandling("accept() error");
		else
			printf("Connected client %d \n", i+1);

		recvCount = 0;
		while(1)
		{
			int msgLen = recv(hClntSock, message, BUF_SIZE, 0);
			printf("msgLen : %d\n", msgLen);
			char *s = message;
			while(*s==' '||*s=='\t'||*s=='\r'||*s=='\n') s++;
			char *e = s + strlen(s);
			while(e>s && (e[-1]==' '||e[-1]=='\t'||e[-1]=='\r'||e[-1]=='\n')) e--;
			*e = '\0';

			if(strcmp(message,"+") == 0 ||strcmp(message,"-") == 0 || strcmp(message,"*") == 0) { break; }
			else
			{
				msgBuf[recvCount] = atoi(message);
				recvCount++;
				send(hClntSock,message,strlen(message),0);
			}
		}

		int result = 0;
		if(strcmp(message,"+") == 0) result = SumAll(msgBuf, recvCount);
		else if (strcmp(message,"-") == 0) result = SubtractAll(msgBuf, recvCount);
     	else if (strcmp(message,"*") == 0) result = MultiplyAll(msgBuf, recvCount);

		char resStr[10];
		itoa(result,resStr,10);
		printf("send result\n");
		send(hClntSock, resStr, strlen(resStr), 0);
		closesocket(hClntSock);
	}
	closesocket(hServSock);
	WSACleanup();
	return 0;
}

void ErrorHandling(char *message)
{
	fputs(message, stderr);
	fputc('\n', stderr);
	exit(1);
}

```

계산기 서버

```c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <winsock2.h>

#define BUF_SIZE 1024
void ErrorHandling(char *message);

int main(int argc, char *argv[])
{
	WSADATA wsaData;
	SOCKET hSocket;
	char message[BUF_SIZE];
	int strLen;
	SOCKADDR_IN servAdr;

	if(argc!=3) {
		printf("Usage : %s <IP> <port>\n", argv[0]);
		exit(1);
	}

	if(WSAStartup(MAKEWORD(2,2), &wsaData) != 0)
		ErrorHandling("WSAStartup() error!");

	hSocket = socket(PF_INET, SOCK_STREAM, 0);
	if(hSocket==INVALID_SOCKET)
		ErrorHandling("socket() error");

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

	if(connect(hSocket, (SOCKADDR*)&servAdr, sizeof(servAdr))==SOCKET_ERROR)
		ErrorHandling("Connect() error!");
	else
		puts("Connected........");

	while(1)
	{
		fputs("Input message(Q to quit): ", stdout);
		fgets(message, BUF_SIZE, stdin);

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

		send(hSocket, message, strlen(message), 0);
		strLen = recv(hSocket, message, BUF_SIZE-1, 0);
		message[strLen] = 0;
		printf("Message from server: %s\n", message);
	}
	closesocket(hSocket);
	WSACleanup();
	return 0;
}

void ErrorHandling(char *message)
{
	fputs(message, stderr);
	fputc('\n', stderr);
	exit(1);
}
```

계산기 클라

책은 클라에서 미리 연산자까지의 숫자 개수를 보내주는 식으로 구현함.

### 05-2 TCP의 이론적인 이야기

#### TCP 소켓 입출력 버퍼

read()로 읽기 전의 데이터들은 입력 버퍼에 대기한다. write()를 호출하면 데이터 수신 전 출력 버퍼에 대기한다.

* 입출력 버퍼는 TCP 소켓 각각에 대해 별도로 존재하며, 소켓 생성시 자동 생성된다
* 소켓 닫아도 출력 버퍼 데이터(write()한거)는 계속해서 전송된다
* 소켓 닫으면 입력 버퍼(read()한거)는 소멸된다

Q. 입력 버퍼의 크기를 넘어서는 데이터가 전송되면? TCP에는 슬라이딩 윈도우라는 프로토콜이 존재한다. 이것은 입력 버퍼의 크기를 초과하는 데이터 전송을 막아준다.

#### TCP 내부 동작원리

상대 소켓과의 연결

* SYN → SYN + ACK → ACK
* `SEQ: 2000, ACK : 1001` 같은 식으로 패킷에 번호를 붙인다
  * 이 때문에 손실 데이터 확인 및 재전송 가능

상대 소켓과의 데이터 송수신

* SEQ + data → ACK → (반복)
* `SEQ 1200, 100 byte data` → `ACK 1301` (패킷 번호 1200에 데이터 크기 100 + 1) → `SEQ 1301 data ...`

상대 소켓과의 연결 종료

* FIN(연결 끊겠습니다) → ACK(알겠습니다 잠시만요) ... FIN(저도 연결 끊겠습니다) → ACK(확인했습니다)

## Chapter 06 UDP 기반 서버/클라이언트

### 06-1 UDP에 대한 이해

* TCP와 달리 흐름제어를 하지 않는다
* 호스트로 수신된 패킷을 PORT 정보를 참조하여 최종 목적지인 UDP 소켓에 전달한다
* UDP도 나름 신뢰할 만 하다. 잠깐의 화면 떨림, 잡음 정도 괜찮으면 사용.

### 06-2 UDP 기반 서버/클라이언트의 구현

* UDP의 서버와 클라는 연결돼있지 않다
* UDP에선 서버건 클라건 소켓 하나만 있으면 여러 호스트와 통신 가능

TCP는 소켓 생성 후 데이터 전송 시에 주소 정보 따로 추가 안 한다. 하지만 UDP는 연결상태 유지하지 않으므로 데이터 전송마다 목적지 주소정보 추가 필요.

* `sendto()` : 주소정보를 써 넣으면서 데이터를 전송
* `recvfrom()` : UDP 데이터 수신. 발신지 정보를 함께 반환.

```c
int main(int argc, char *argv[])
{
	int serv_sock;
	char message[BUF_SIZE];
	int str_len;
	socklen_t clnt_adr_sz;

	struct sockaddr_in serv_adr, clnt_adr;
	if(argc != 2) {
		printf("Usage : %s <port>\n", argv[0]);
		exit(1);
	}

	serv_sock=socket(PF_INET, SOCK_DGRAM, 0);
	if(serv_sock == -1)
		error_handling("UDP socket creation error");

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

	while(1)
	{
		clnt_adr_sz=sizeof(clnt_adr);
		str_len=recvfrom(serv_sock,message,BUF_SIZE,0,
		   (struct sockaddr*)&clnt_adr, &clnt_adr_sz);
		sendto(serv_sock, message, str_len, 0,
	 	(struct sockaddr*)&clnt_adr, clnt_adr_sz);
	}
	close(serv_sock);
	return 0;
}
```

`uecho_server.c`

```c
int main(int argc, char *argv[])
{
	int sock;
	char message[BUF_SIZE];
	int str_len;
	socklen_t adr_sz;

	struct sockaddr_in serv_adr, from_adr;
	if(argc!=3){
		printf("Usage : %s <IP> <port>\n", argv[0]);
		exit(1);
	}

	sock=socket(PF_INET, SOCK_DGRAM, 0);
	if(sock==-1)
		error_handling("socket() error");

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

	while(1)
	{
		fputs("Insert message(q to quit): ", stdout);
		fgets(message, sizeof(message), stdin);
		if(!strcmp(message, "q\n") || !strcmp(message, "Q\n"))
			break;

		sendto(sock, message, strlen(message), 0,
	 	(struct sockaddr*)&serv_adr, sizeof(serv_adr));
		adr_sz=sizeof(from_adr);
		str_len=recvfrom(sock,message,BUF_SIZE,0,
		   (struct sockaddr*)&from_adr, &adr_sz);
		message[str_len]=0;
		printf("Message from server: %s", message);
	}
	close(sock);
	return 0;
}
```

`uecho_clinet.c`

UDP에선 sendto() 호출 전에 bind() 호출해서 주소 정보 할당해야 한다. sendto() 호출 전까지 주소 정보 할당 안 됐다면 IP와 PORT 번호가 자동으로 할당된다. (일반적 구현법)

### 06-3 UDP의 데이터 송수신 특성과 connect 함수 호출

TCP 기반 데이터에는 경계가 존재하지 않는다.

* 데이터 송수신 과정에서 호출하는 입출력함수의 호출 횟수는 큰 의미를 갖지 않는다.

UDP 기반 데이터는 경계가 존재한다.

* 입력함수의 호출횟수와 출력함수의 호출횟수가 완벽히 일치해야 한다.
* sleep 후에 데이터가 여러 개 쌓여있다면 TCP는 한 번에 받을 수 있지만, UDP는 데이터 개수만큼 recvfrom()을 호출해야 한다.

UDP 소켓이 전송하는 패킷을 데이터그램이라고 한다.

* 데이터의 경계가 존재하기 때문에 하나의 패킷이 하나의 데이터로 간주되기 때문이다.

UDP 소켓에는 데이터를 전송할 목적지와 PORT 번호를 등록하지 않는다. 때문에 sendto 함수호출을 통한 데이터의 전송 과정은 이렇다

* 1단계: UDP 소켓에 목적지의 IP와 PORT 번호 등록
* 2단계: 데이터 전송
* 3단계: UDP 소켓에 등록된 목적지 정보 삭제
* 같은 목적지에 여러 번 데이터를 보내면 1단계와 3단계를 반복하는 것은 비효율적이다.
  * 1단계, 3단계가 데이터 전송과정의 약 1/3에 해당

목적지가 등록되면 connected 소켓, 없으면 unconnected 소켓이라 부른다

* UDP 소켓을 대상으로 connect()만 호출해주면 connected 소켓 생성
* TCP 소켓처럼 목적지의 소켓와 연결하진 않는다. 목적지 정보가 등록될 뿐이다.
* 이제 연결 대상 지정 생략하고 데이터 전송만 하면 된다.
* sendto(), recvfrom()이 아닌 write(), read() 호출로도 데이터 송수신 가능해진다.

## Chapter 07 소켓의 우아한 연결종료

### 07-1 TCP 기반의 Half-close

close()로 연결 종료하면 이후에 오는 데이터는 못 받는다. shutdown()으로 일부만 종료(Half-close)하면 전송 혹은 수신 한 쪽만 닫을 수 있다. 전송 끝났다는 의미로 EOF 보내려면, 그리고 이후에 오는 메시지 받으려면 Half-close 필요하다.

* SHUT\_RD : 입력 스트림 종료
* SHUT\_WR : 출력 스트림 종료
* SHUT\_RDWR : 입출력 스트림 종료

```c
	fp=fopen("file_server.c", "rb");

	(...)

	while(1)
	{
		read_cnt=fread((void*)buf, 1, BUF_SIZE, fp);
		if(read_cnt<BUF_SIZE)
		{
			write(clnt_sd, buf, read_cnt);
			break;
		}
		write(clnt_sd, buf, BUF_SIZE);
	}
	shutdown(clnt_sd, SHUT_WR);
	read(clnt_sd, buf, BUF_SIZE);
	printf("Message from client: %s \n", buf);
```

파일을 연 뒤 읽어서 클라에게 보내준 후 "Thank you"를 기다리는 서버 코드 shutdown()으로 발신은 끊지만 수신은 열어둔다

```c
	fp=fopen("receive.dat", "wb");

	(...)

	connect(sd, (struct sockaddr*)&serv_adr, sizeof(serv_adr));

	while((read_cnt=read(sd, buf, BUF_SIZE))!=0)
		fwrite((void*)buf, 1, read_cnt, fp);

	puts("Received file data");
	write(sd, "Thank you", 10);
```

파일을 받아서 receive.dat에 저장한 뒤 "Thank you"를 보내는 클라 코드

### 07-2 윈도우 기반으로 구현하기

shutdown()은 동일하지만 전달할 상수 이름이 약간 다르다

* SD\_RECEIVE : 입력 스트림 종료
* SD\_SEND : 출력 스트림 종료
* SD\_BOTH : 입출력 스트림 종료


---

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