일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | |||||
3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 | 25 | 26 | 27 | 28 | 29 | 30 |
- file read
- C API
- 티몬삼겹살데이
- meta table
- lua install
- #신혼부부 #결혼준비 #신혼부부희망타운신혼부부특별공급
- QT TCP
- 프리미어 영상변환
- FILE TRANSFER
- QTcpServer
- 국토교통부
- Lua
- TCP/IP
- 중소규모택지
- 찾다죽는줄
- #부동산전자거래 #부동산전자계약 #부동산계약 #부동산전자계약방법 #부동산전자계약하는법 #부동산계약방법 #부동산중개수수료 #부동산중개수수료아끼기 #부동산복비아끼기
- C++ API
- 월세
- file open
- lua interpreter
- 엑스퍼트2주년
- file write
- 프리미어 영상저장
- 엑스퍼트생일축하해
- 수도권주택공급
- 청량리역한양수자인192
- lua setup
- object
- 등록임대주택
- lua for windows
- Today
- Total
Value Creator의 IT(프로그래밍 / 전자제품)
Chapter 10 멀티 프로세스 기반의 서버 구현 본문
1. 프로세스란?
실행중인 프로그램을 의미한다.
멀티프로세스 운영체제는 다수의 프로세스를 동시에 실행 가능하다.
프로세스마다 프로세스 ID가 할당된다.
2. Fork 함수 : 프로세스를 복사하는 함수.
복사되어서 생긴 프로세스는 자식 프로세스, 기존의 프로세스는 부모 프로세스라고 부른다.
#include <stdio.h>
#include <unistd.h>
int gval=10; //글로벌 변수
int main(int argc, char *argv[])
{
pid_t pid; //프로세스 ID 타입의 변수 선언
int lval=20; //지역변수
gval++, lval+=5; //gval = 11, lval=25
pid=fork(); //자식 프로세스 생성
if(pid==0) // if Child Process
gval+=2, lval+=2; //자식프로세스의 변수값 변화, gval = 13, lval = 27
else // if Parent Process
gval-=2, lval-=2; //부모 프로세스의 변수값 변화, gval = 9, lval = 23
if(pid==0)
printf("Child Proc: [%d, %d] \n", gval, lval);
else
printf("Parent Proc: [%d, %d] \n", gval, lval);
return 0;
//결론 : 똑같은 프로그램이지만 다른 메모리에 할당된 자식프로세스와 부모프로세스의 동작을 알아보았다.
}
3, 좀비 프로세스 확인
자식 프로세스가 종료되었다는 것을 부모에게 알리지 않는다면, 자식 프로세스는 계속 살아있다.
그것을 좀비 프로세스라고 부른다.
(자식 프로세스가 종료되는 상황 : 인자를 전달하면서 exit를 호출하거나, main 함수에서 return 문을 실행하면서 반환)
아래 코드로 좀비 프로세스를 확인할 수 있다.
zombie.c
#include <stdio.h>
#include <unistd.h>
int main(int argc, char *argv[])
{
pid_t pid=fork();
if(pid==0) // if Child Process
{
puts("Hi I'am a child process");
}
else
{
printf("Child Process ID: %d \n", pid);
sleep(30); // Sleep 30 sec.
} // 부모 프로세스와 자식 프로세스가 각각 실행된다.
//자식 프로세스에서는 "Hi I'am a child process"가부모 프로세스에서는
if(pid==0)
puts("End child process");
else
puts("End parent process");
return 0;
}
4, 좀비 프로세스 해결 : wait 함수
wait.c
#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; //첫 번째 자식 프로세스가 3을 반환하면서 종료
}
else
{
printf("Child PID: %d \n", pid); // 첫 번째 자식 프로세스의 PID 출력
pid=fork(); //두 번째 자식 프로세스 생성
if(pid==0)
{
exit(7); //두 번째 자식 프로세스가 7을 반환하면서 종료
}
else
{
printf("Child PID: %d \n", pid); //두 번째 자식 프로세스의 PID 출력
wait(&status); //성공시 종료된 자식 프로세스의 PID, 실패시 -1 반환,,
// wait 함수를 실행함으로써 status 라는 변수에 PID 또는 -1 값이 담긴다.
// 단, wait 함수는 호출된 시점에 종료된 자식 프로세스가 없다면, 임의의 자식 프로세스가 종료될 때까지 블로킹 상태에 놓인다.
// 함수 호출시 블로킹 상태 주의
if(WIFEXITED(status)) //자식 프로세스가 정상 종료된 경우 true
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;
}
결과
Child PID: 3859
Child PID: 3860
Child send one: 3
Child send two: 7
5. 좀비 프로세스 해결 2 : waitpid 함수
wait 함수의 블로킹 문제를 해결하기 위해 waitpid 함수를 사용한다.
waitpid.c
#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
*/
6. 시그널과 프로세스
프로세스에게 특정 메세지를 전달하는 것이 시그널이다.
SIGALARM : 알람 시간이 다 되면 시그널이 호출된다.
SIGINT : Ctrl+C가 눌리면 시그널이 호출된다.
SIGCHILD : 자식 프로세스가 종료되면 시그널이 호출된다.
signal.c
#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); //실제로는 100초를 대기하지 않는다.
//시그널이 발생하면 sleep 상태가 해제되기 때문이다.
}
return 0;
}
sigaction.c
#include <stdio.h>
#include <unistd.h>
#include <signal.h>
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;
}
시그널 핸들링을 통해 좀비 프로세스를 소멸시킬 수 있다.
remove_zombie.c
#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); //프로세스가 종료될 때마다 read_childproc(int sig) 함수를 실행
pid=fork();
if(pid==0)
{
puts("Hi! I'm child process"); //첫 번째 자식프로세스가 10초 뒤 12를 반환하면서 종료함
sleep(10);
return 12;
}
else
{
printf("Child proc id: %d \n", pid); //첫 번째 자식프로세스의 PID
pid=fork();
if(pid==0)
{
puts("Hi! I'm child process"); //두 번째 자식프로세스가 10초 뒤 24를 반환하면서 종료함
sleep(10);
exit(24);
}
else
{
int i;
printf("Child proc id: %d \n", pid); //두 번째 자식프로세스의 PID
for(i=0; i<5; i++)
{
puts("wait..."); //wait메세지 5초에 한 번씩 5번 출력
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...
*/
SIGCHILD 라는 시그널을 이용하여서 자식 프로세스의 소멸을 확인할 수 있다.
7. 멀티 프로세스(멀티태스킹) 기반 다중 접속 서버
echo_mpserver.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <signal.h>
#include <sys/wait.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#define BUF_SIZE 30
void error_handling(char *message);
void read_childproc(int sig);
int main(int argc, char *argv[])
{
int serv_sock, clnt_sock;
struct sockaddr_in serv_adr, clnt_adr;
pid_t pid;
struct sigaction act; //sigaction을 이용해서 자식 프로세스의 소멸을 확인한다.
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);
}
void error_handling(char *message)
{
fputs(message, stderr);
fputc('\n', stderr);
exit(1);
}
echo_client.c
--> 이전 글의 소스코드와 똑같다.
2019/10/30 - [1. 프로그래밍/4) Network] - Chapter 5 TCP 기반 서버 클라이언트 2
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#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;
}
void error_handling(char *message)
{
fputs(message, stderr);
fputc('\n', stderr);
exit(1);
}
8. TCP의 입출력 루틴 분할
Read와 Write를 각각 부모 프로세스와 자식 프로세스가 담당하도록 분할하는 것이다.
이렇게 되면 Read와 Write를 동시에 진행할 수 있다.
상황에 따라 입출력 분할하는 방법을 사용할 수도 있고, 기존의 방법대로 구현할 수도 있다.
echo_mpclient.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#define BUF_SIZE 30
void error_handling(char *message);
void read_routine(int sock, char *buf);
void write_routine(int sock, char *buf);
int main(int argc, char *argv[])
{
int sock;
pid_t pid;
char buf[BUF_SIZE];
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);
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!");
pid=fork();
if(pid==0) //자식 프로세스는 write routine 호출
write_routine(sock, buf);
else //부모 프로세스는 read routine 호출
read_routine(sock, buf);
close(sock);
return 0;
}
void read_routine(int sock, char *buf)
{
while(1) //부모 프로세스는 읽기명령어를 호출하고, 읽을 내용이 없으면 return 한다.
{
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); //자식 프로세스는 최대 30개 크기의 문자를 입력받는다.
if(!strcmp(buf,"q\n") || !strcmp(buf,"Q\n"))
{
shutdown(sock, SHUT_WR); //쓰기 소켓(출력 소켓)을 닫고, return
return;
}
write(sock, buf, strlen(buf)); //버퍼 크기 만큼 쓰기를 진행한다.
}
}
void error_handling(char *message)
{
fputs(message, stderr);
fputc('\n', stderr);
exit(1);
}
'1. 프로그래밍 > 4) Network' 카테고리의 다른 글
Chapter 12 IO 멀티플렉싱 (0) | 2019.11.06 |
---|---|
Chapter 11 프로세스 간 통신 (0) | 2019.10.31 |
Chapter 9 소켓의 다양한 옵션 - 추후 작성 (0) | 2019.10.30 |
Chapter 8 도메인 네임과 인터넷 주소 - 추후 작성 (0) | 2019.10.30 |
Chapter 7 소켓의 우아한 종료(half-close) (0) | 2019.10.30 |