dev-miri
[computer network] 네트워크 프로그래밍과 소켓 (2) 본문
CH 04. TCP 기반 서버/클라이언트 1
ch 04-2 TCP 기반 서버, 클라이언트의 구현
-TCP 서버의 기본적인 함수 호출 순서
socket() - 소켓 생성
bind() - 소켓에 IP/PORT 할당
listen() - 서버 소켓으로 만들어줌/연결 요청 대기상태
accept() - 연결 허용
read()/write() - 데이터 송수신
close() - 연결 종료(fin 전송)
-연결요청 대기 상태로의 진입
#include <sys/type.h>
int listen(int sock, int backlog);
-> 성공시 0, 실패시 -1qksghks
- sock : 연결요청 대기 상태에 두고자 하는 소켓의 파일 디스크립터 전달. 이 소켓이 서버 소켓이 된다.
- backlog : 연결요청 대기 큐(Queue)의 크기정보 전달. 5가 전달되면 큐의 크기가 5가 되어 클라이언트의 연결요청을 5개까지 대기시킬 수 있다.
클라이언트로부터 연결 요청을 받음(SYN) -> SYN+ACK 보낸 후 -> 다음 ACK이 올 때 까지 대기시키는 곳이 연결 요청 대기 큐이다.
연결요청도 일종의 데이터 전송이다.
따라서 연결 요청을 받아들이기 위해서도 하나의 소켓이 필요하다. 이 소켓을 가리켜 서버소켓 또는 리스닝 소켓이라고 한다.
listen 함수의 호출은 소켓을 리스닝 소켓이 되게 한다.
-클라이언트의 연결요청 수락
#include <sys/socekt.h>
int accept(int sock, struct sockaddr * addr, socklen_t * addrlen);
-> 성공 시 생성된 소켓의 파일 디스크립터, 실패 시 -1 반환
- sock : 서버 소켓의 파일 디스크립터 전달
- addr : 연결 요청 한 클라이언트의 주소정보를 담을 변수의 주소 값 전달. 함수 호출이 완료되면 인자로 전달된 주소의 변수에는 클라이언트의 주소정보가 채워진다.
- addrlen : 두 번째 매개변수 addr에 전달된 주소의 변수 크기를 바이트 단위로 전달.

순서를 정리해보면,
- client가 syn 패킷 보냄(연결요청)
- server가 응답과 연결요청 보냄 (syn+ack)
- server가 client에게 보낸 요청에 대한 응답이 올 때까지 연결요청 대기 큐에서 대기한다.
- accept 함수를 호출하여 연결요청을 허용한다. 이때 accept 함수 호출에 의해 (클라이언트)소켓이 생성되고, 그 안에는 클라이언트의 주소 정보로 채워진다. 이 클라이언트 소켓이 실제 데이터 송수신에 사용되는 소켓이다. (accept는 block 함수이다)
-TCP 클라이언트의 기본적인 함수호출 순서
socket() - 소켓 생성
connect() - 연결 요청 -> syn을 보내줌
read()/write() - 데이터 송수신
close() - 연결 종류
#include <sys/socket.h>
int connect(int sock, const struct sockaddr * servaddr, socklen_t addrlen);
-> 성공 시 생성된 소켓의 파일 디스크립터, 실패 시 -1 반환
- sock : 클라이언트 소켓의 파일 디스크립터 전달
- servaddr : 서버 정보가 담긴 구조체 값 전달
- addrlen : 두 번째 매개변수 servaddr에 전달된 주소의 변수 크기를 바이트 단위로 전달
클라이언트의 경우 소켓을 생성하고, 이 소켓을 대상으로 연결의 요청을 위해서 connect 함수를 호출하는 것이 전부이다.
그리고 connect 함수를 호출할 때 연결할 서버의 주소 정보도 함께 전달한다.
-TCP 기반 서버, 클라이언트의 함수호출 관계


connect() 함수는 S+A이 올 때까지 block 상태이다.
accept() 함수는 ack이 올때까지 block 상태이다.
ch04-3 Iterative 기반의 서버, 클라이언트의 구현
-Iterative 의 구현

만약 반복적으로 accept 함수를 호출하면, 계속해서 클라이언트의 연결 요청을 수락할 수 있다.
그러나, 동시에 둘 이상의 클라이언트에게 서비스를 제공할 수 있는 모델은 아니다.

(accept 함수 호출)마지막 ack을 보내면 -> 새로운 소켓을 생성한다 : client의 정보를 담고있는 소켓 생성
echo server : 클라이언트에서 데이터 보냄 -> 서버에서 데이터 받음 -> 서버에서 데이터 다시 보냄 -> 클라이언트에서 읽음
-에코 클라이언트의 문제점
제대로 동작은 하나 문제의 발생 소지가 있는 TCP 에코 클라이언트의 코드
write(sock, message, strlen(message));
str_len = read(sock, message, BUF_SIZE-1);
message[str_len] = 0;
printf("Message from server : %s", message);
TCP 데이터 송수신에는 경계가 존재하지 않는다.
그러나 위의 코드는 한 번의 read 함수 호출로 앞서 전송된 문자열 전체를 읽어 들일 수 있다고 가정하고 있다.
그러나, TCP에는 데이터의 경계가 존재하지 않기 때문에 서버가 전송한 문자열의 일부만 읽혀질 수 있다.
CH 10. 멀티프로세스 기반의 서버 구현
CH 10-1. 프로세스의 이해와 활용
-다중 접속 서버의 구현 방법들
다중 접속 서버란 ?
둘 이상의 클라이언트에게 동시에 접속을 허용하여, 동시에 둘 이상의 클라이언트에게 서비스를 제공하는 서버를 의미한다.
종류
- 멀티프로세스 기반 서버 : 다수의 프로세스를 생성하는 방식으로 서비스 제공
- 멀티플렉싱 기반 서버 : 입출력 대상을 묶어서 관리하는 방식으로 서비스 제공
- 멀티쓰레딩 기반 서버 :클라이언트의 수만큼 쓰레드를 생성하는 방식으로 서비스 제공
-프로세스와 프로세스의 ID
프로세스란?
- 간단하게는 실행 중인 프로그램을 뜻한다.
- 실행 중인 프로그램에 관련된 메모리, 리소스 등을 총칭하는 의미이다.
- 멀티프로세스 운영체제는 둘 이상의 프로세스를 동시에 생성 가능하다.
프로세스 ID
- 운영체제는 생성되는 모든 프로세스에 ID를 할당한다.
-fork 함수의 호출을 통한 프로세스의 생성
fork 함수가 호출되면, 호출한 프로세스가 복사되어 fork 함수 호출 이후를 각각의 프로세스가 독립적으로 실행하게 된다.
- 부모 프로세스 : fork 함수를 호출한 프로세스, 반환 값은 자식 프로세스의 ID
- 자식 프로세스 : fork 함수의 호출을 통해서 생성된 프로세스, 반환 값은 0
ch 10-2. 프로세스 & 좀비 프로세스
-좀비 프로세스의 이해
좀비 프로세스란?
- 실행이 완료되었음에도 불구하고, 소멸되지 않은 프로세스
- 소멸되지 않았다는 것은 프로세스가 사용한 리소스가 메모리 공간에 여전히 존재한다는 것이다.
좀비 프로세스의 생성 원인은?
- 자식 프로세스를 완전히 소멸시키는 일은 부모 프로세스가 해야한다.
- 자식 프로세스가 종료되면서 반환하는 상태 값이 부모 프로세스에게 전달되지 않으면 해당 프로세스틑 소멸되지 않고 좀비가 된다.
* 자식 프로세스의 종료 값이 운영체제에 전달되는 경로
- 인자를 전달하면서 exit를 호출하는 경우
- main 함수에서 return문을 실행하면서 값을 반환하는 경우
자식 프로세스의 종료 값을 반환 받을 부모 프로세스가 소멸되면, 좀비의 상태로 있던 자식 프로세스도 함께 소멸되기 때문에 부모 프로세스가 소멸되기 전에 좀비의 생성을 확인해야 한다.
-좀비 프로세스의 소멸 : wait 함수의 사용
wait(&status) : 자식 프로세스의 return 값을 쉽게 확인할 수 있다.
만약 자식 프로세스가 종료되지 않은 상황에서는 반환하지 않고 블로킹 상태(다른 일을 하지 못함)에 놓인다는 특징이 있다.
WIFEXITED : 자식 프로세스가 정상 종료한 경우 '참(true)'을 반환한다.
WEXITSTATUS : 자식 프로세스의 전달 값을 반환한다.
-좀비 프로세스의 소멸2 : waitpid 함수의 사용
wait 함수는 블로킹 상태에 빠질 수 있는 반면, waitpid 함수는 블로킹 상태에 놓이지 않게끔 할 수 있다.
pid_t waitpid(pid_t pid, int * statloc, int options)
-> 성공 시 종료된 자식 프로세스의 ID(또는 0), 실패 시 -1 반환
- pid : 종료를 확인하고자 하는 자식 프로세스의 ID 전달한다. 이를 대신해서 -1을 전달하면 wait 함수와 마찬가지로 임의의 자식 프로세스가 종료되기를 기다린다.
- statloc : wait 함수의 매개변수 statloc과 동일한 의미로 사용된다.
- options : 헤더파일 sys/wait.h에 선언된 상수 WNOHANG을 인자로 전달하면, 종료된 자식 프로세스가 존재하지 않아도 블로킹 상태에 있지 않고, 0을 반환하면서 함수를 빠져나온다.
CH10-3 . 시그널 핸들링
시그널과 시그널 등록의 이해
시그널이란?
특정 상황이 되었을 때 운영체제가 프로세스에게 해당 상황이 발생했음을 알리는 일종의 메시지를 가리켜 시그널이라고 한다.
ex) SIGCHLD : 자식 프로세스가 종료된 상황
시그널 등록이란?
특정 상황에서 운영체제로부터 프로세스가 시그널을 받기 위해서는 해당 상황에 대해서 등록의 과정을 거쳐야한다.
시그널과 시그널 함수
시그널이 등록되면, 함께 등록된 함수의 호출을 통해서 운영체제는 시그널의 발생을 알린다.
signal(SIGCHLD, mychildf); -> 자식 프로세스가 종료되면 mychild 함수를 호출해 달라!!

ch10-4. 멀티태스킹 기반의 다중접속 서버
-프로세스 기반 다중접속 서버 모델
부모 프로세스 : 연결 요청만 받는다.
자식 프로세스 : 데이터를 주고 받는 일만 감당
핵심은 연결이 하나 생성될 때마다 프로세스를 생성해서 해당 클라이언트에 대해 서비스를 제공하는 것이다.
1단계 : 에코 서버(부모 프로세스)는 accept 함수호출을 통해서 연결요청을 수락한다.
2단계 : 이때 얻게 되는 소켓의 파일 디스크립터를 자식 프로세스를 생성해서 넘겨준다.
3단계 : 자식 프로세스는 전달받은 파일 디스크립터를 바탕으로 서비스를 제공한다.

-다중접속 에코 서버의 구현
부모 프로세스의 역할 - 연결만 관리
- accept 함수로 소켓 생성
- fork로 자식 프로세스를 만들고
- 다시 위로 와서 대기(상대방이 연결 요청을 보내는지)-구현 코드에는 while 무한루프를 돌고 있다.
fork( )를 통해 자식 프로세스가 생성되면 server socket의 descripter값이 new socket으로 copy된다.
서비스 제공은 자식 프로세스에만 의해서 이루어진다.
-fork 함수 호출을 통한 디스크립터의 복사
소켓은 OS가 가지고 있다. 프로세스는 file descriptor로 OS에 있는 소켓에 접근한다.
fork 함수를 호출하면 fd값이 copy되는 것이다. 소켓이 copy 되는 것이 아니다.
프로세스가 복사되느니 경우 해당 프로세스에 의해 만들어진 소켓이 복사되는 것이 아니고, 파일 디스크립터가 복사되는 것이다!
즉, 같은 소켓을 2개의 프로세스가 연결하는 것이다.
parent process는 server socket/client socket을 가지고 있고, child process도 server socket/client socket을 가지고 있다.
즉, server socket과 client socket은 총 2개씩 존재한다.
하나의 소켓에 두 개의 파일 디스크립터가 존재하는 경우, 두 파일 디스크립터 모두 종료되어야 해당 소켓이 소멸된다.
그래서 fork 함수 호출 후에는 서로에게 상관 없는 파일 디스크립터를 종료해야 한다.
ch 10-5. TCP의 입출력 루틴 분할
-입출력 루틴 분할의 이점과 의미
소켓은 양방향 통신이 가능하다. 따라서 왼쪽 그림과 같이 입력을 담당하는 프로세스와 출력을 담당하는 프로세스를 각각 생성하면, 입력과 출력을 각각 별도로 진행시킬 수 있다.
입출력 루틴을 분할하면, 보내고 받는 구조가 아니라, 이 둘이 동시에 진행이 가능하다
