서버 유형
1. 단일 접속
- 연결을 요청하는 클라이언트를 순차적으로 연결
2. 다중 접속
- 연결을 요청하는 모든 클라이언트에게 동시에 서비스 제공
- 네트워크 프로그램은 CPU 연산이 필요치 않으며, 데이터의 송수신 시간의 비중이 높아 다중 접속이 효과적이다.
- 종류
a. 멀티프로세스 - 다수의 프로세스를 생성해 연결하는 방식
b. 멀티플렉싱 - 입출력 대상을 묶어서 관리해 연결하는 방식
c. 멀티스레딩 - 클라이언트의 수만큼 쓰레드 생성해 연결하는 방식
Procces
Process(프로세스)란?
"메모리 공간을 차지한 상태에서 실행중인 프로그램"
"OS 프로그램 관리 기본 단위"
"하나의 프로그램이 여러개의 프로세스 >> 멀티프로세스 서버"
PID - Process ID
프로세스 생성
#include <unistd.h>
pid_t fork(void);
// 성공시 PID , 실패시 -1 반환
// 실행중인, fork() 함수를 호출한 프로세스를 복사한다.
// fork() 이후의 문장을 새로운 프로세스로 실행한다.
프로세스 종료
좀비 프로세스
- fork() 호출로 생성된 프로세스가 종료되지 않고, 시스템 리소스를 차지하고 있는 상태
생성 원인
- fork() 호출로 생성된 프로세스가 종료되는 경우는 1. exit 호출 2. main의 return 이다. 운영체제는 부모 프로세스가 존재할 경우, 부모 프로세스의 exit, return 값이 전달되기 전까지 자식 프로세스를 종료시키지 않는다.
해결책
- 부모 프로세스에서 명시적으로 자식 프로세스를 종료 처리해야 한다.
1. wait()
#include .<sys/wait.h>
pid_t wait(int *statloc);
// 성공시 종료된 자식 프로세스의 PID, 실패시 -1 반환
// 이미 종료된 자식 프로세스가 있다면, 종료시 전달값 (exit 인자, return 반환 값)
// 이미 종료된 자식 프로세스가 없다면, 임의의 자식 프로세스가 종료될때까지 Blocking
WIFEXITED // Wait IF Exited
// 자식 프로세스가 정상 종료한 경우 ‘참(true)'을 반환
WEXITSTATUS // Wait Exit Status
// 자식 프로세스의 전달 값을 반환
if(WIFEXITED(status)) // 정상 종료인가?
{
puts("Normal termination!");
printf("Child pass num: %d", WEXITSTATUS(status)); // 그렇다면 반환 값은?
}
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
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); // Sleep 30 sec.
}
}
return 0;
}
2. waitpid()
#include <sys/wait.h>
pid_t waitpid(pid_t pid, int *statloc, int options);
// 성공시 종료된 자식 프로세스의 PID (or 0), 실패시 -1 반환
// pid 종료를 확인하고자 하는 자식 프로세스의 PID 전달,
// 이를 대신해 -1 경우, wait와 마찬가지로 임의의 자식 프로세스가 종료될때까지 Blocking
// statloc wait의 statloc과 동일
// options 헤더파일 sys/wait.h에 선언된 상수 WNOHANG을 인자로 전달하면,
// 종료된 자식 프로세스가 존재하지 않아도, Blocking 없이 0을 반환
#include <stdio.h>
#include <unistd.h>
#include <sys/wait.h>
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;
}
/*
root@my_linux:/home/swyoon/tcpip# gcc waitpid.c -o waitpid
root@my_linux:/home/swyoon/tcpip# ./waitpid
sleep 3sec.
sleep 3sec.
sleep 3sec.
sleep 3sec.
sleep 3sec.
Child send 24
*/
3.3 Signal Handling
wait() 함수는 자식 프로세스가 종료되었음을 확인 할 수 있다.
하지만 자식 프로세스의 종료 시점을 알 수 없기에 wait() 함수가 대기한다.
signal == 특정 상황이 발생했음을 알림
handling == 상황에 따른 메시지에 반응해 연관된 작업 진행
함수의 반복 호출 or 대기 상태가 아닌, 운영체제에게 "fork()로 생성된 프로세스가 종료되면 signal handler 실행" 명령
#include <signal.h>
void (*signal(int signo , void ( *func)(int)))(int);
// 시그널 발생시 호출되도록 이전에 등록된 함수의 포인터 반환
// 첫 번째 매개변수로 특정 상황 정보
// 두 번째 매개변수로 특정 상황에서 호출될 함수의 주소값(포인터)
// 첫번째 매개변수
SIGALRM alarm 함수호출을 통해서 등록된 시간이 된 상황
SIGINT CTRL+C가 입력된 상황
SIGCHLD 자식 프로세스가 종료된 상황
#include <stdio.h>
#include <unistd.h>
#include <signal.h>
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;
}
sigaction
#include <signal.h>
int sigaction(int signo , const struct sigaction *act, struct sigaction *oldact);
// 성공시 0, 실패시 -1 반환
// signo signal 함수와 마찬가지로 시그널 의 정보를 인자로 전달
// act 첫번째 인자로 전달된 상수에 해당는 시그널 발생시 호출될 함수(시그널 핸들러)의 정보 전달
// oldact 이전에 등록되었던 시그널 핸들러 의 함수 포인터를 얻는데 사용되는 인자, 필요 없다면 0 전달
프로세스 생성과 종료
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
#include <sys/wait.h>
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;
}
/*
root@my_linux:/home/swyoon/tcpip# gcc remove_zombie.c -o zombie
root@my_linux:/home/swyoon/tcpip# ./zombie
Hi! I'm child process
Child proc id: 9529
Hi! I'm child process
Child proc id: 9530
wait...
wait...
Removed proc id: 9530
Child send: 24
wait...
Removed proc id: 9529
Child send: 12
wait...
wait...
*/