TCP/IP Server model : 요청시 fork 시키는 pattern

서버 프로그램 중에서 서버가 기동시에 자동으로 실행하는 프로그램을 일반적으로 daemon 프로그램이라고 합니다. 예를들면, httpd, ftpd, telnetd와 같이 서버 프로그램은 명령어에 d가 붙어서 daemon 프로그램임을 표시하기도 합니다.

 

daemon프로그램의 특징은 

의도적으로 프로그램을 종료시키지 않는 이상 어떠한 경우에도 비정상 종료하면 안됩니다.

 - telnet창에서 직접 실행하더라도 terminal의 영향을 받지 않아야합니다.

 - 시스템이 부팅하지마자 실행되는 경우가 많기 때문에 표준입력/표준출력/표준오류를 무력화해야 합니다.

상세한 내용은 아래의 소스에 있는 comment를 참고하시기 바랍니다.

 

TCP/IP daemon은 동시에 여러 client의 요청에 응답을 해주어야 합니다. 따라서 일반적이고 가장 simple한 daemon은 요청시 client와 1:1로 처리해줄 프로세스를 생성(fork)시키고 처리가 완료되면 자동으로 종료하는 pattern입니다. 이 pattern은 fork(2)에 대한 overhead가 있지만, 동시 접속자가 어마어마하게 많지 않은 경우에 사용할 수 있는 pattern입니다. 동시에 접속자가 많지 않은 telnet, ftp 등과 같이 1:1로 접근하는 경우에 사용합니다. 그리고 telnet, ftp는 root 권한으로 실행되어 사용자의 id/password를 비교하고 정상적이면, process의 owner와 group을 로그인 한 user id의 owner와 group id로 변경해야 하므로 1:1 fork 방식으로 할 수 밖에 없습니다.

 


TCP/IP 서버 daemon 프로그램 (접속시 마다 fork pattern)

#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <signal.h>
#include <stdlib.h>

// tcpip_lib.c와 tcpip_lib.h는 최상단의 링크에서 다운받으세요.
#include "tcpip_lib.h"

/* 서버 port번호를 정의합니다. 업무에 따라서 정의합니다.*/
#define SERVICE_PORT    6905

/* client와 접속된 socket은 전역변수로 설정하는 것이 좋습니다. */
int  acpt_sock;

void child_main(void);

int main(int argc, char *argv[])
{
    int     idx;
    int     fd;
    int     sockfd;
    struct  sockaddr_in client;
    struct  sigaction   act_ignore;

    /* 
    * background program으로 실행과 terminal에 의한 signal 발생을 배제함. (Control-C 등) 
    * 최초실행 프로그램은 종료하여 부모 process id가 1인 child 프로세스가 됨.
    * 또한 session 그룹의 leader가 아닌 프로세스가 됨
    */
    if(fork() != 0) {
        /* child process가 아니면 종료 */
        exit(0);
    }

    /*
    * 새로운 session을 생성하여 session그룹의 leader가 됨
    * 새로운 session은 terminal의 영향을 받지 않음.
    */
    if(setsid() == -1) {
        fprintf(stderr, "NEW SESSION ERROR: %s\n", strerror(errno));
        exit(1);
    }

    /*
    * 프로세스의 home directory를 ROOT디렉토리로 변경함.
    */
    if(chdir("/") == -1) {
        fprintf(stderr, "CHDIR ERROR: %s\n", strerror(errno));
        exit(1);
    }

    /*
    * 표준입력/표준출력/표준오류 처리를 할 수 없도록 하기 위하여 /dev/null 파일을 open합니다.
    */
    if((fd = open("/dev/null", O_RDWR)) == -1) {
        exit(1);
    }

    // 표준입력의 동작을 못하도록 redirect 처리함.   
    // scanf(), fgets(..., stdin) 등의 함수는 정상동작하나 입력은 안됨.
    dup2(fd, 0); 

    // 표준출력을 출력하지 못하도록 redirect 처리함. 
    // printf() 등의 함수는 정상동작하나 출력은 안됨
    dup2(fd, 1); 

    // 표준오류를 출력하지 못하도록 redirect 처리함. 
    // fprintf(stderr, ...)등의 함수는 정상동작하나 출력은 안됨
    dup2(fd, 2);
    
    close(fd);   // 불필요한 fd는 close함

    /*
    * signal 처리 필수
    */
    memset(&act_ignore, 0x00, sizeof(act_ignore));
    act_ignore.sa_handler    = SIG_IGN;
    sigaction(SIGCLD,  &act_ignore, NULL);
    sigaction(SIGSEGV, &act_ignore, NULL);
   
    if((sockfd = TCPIPserver(SERVICE_PORT)) == -1) {
        /* TO-DO : 오류 로그 생성 */
        exit(1);
    }

    while(1) {
        if((acpt_sock = TCPIPaccept(sockfd, &client)) == -1) {
            /* TO-DO : 오류 로그 생성 */
            continue;
        }

        if(fork() == 0) {
            child_main();
            exit(0); // 반드시 종료하게 처리해야 함.
        } else {
            /* 부모 프로세스는 접속된 socket이 필요없으므로 close함. */
            close(acpt_sock);
        }
    }

    return 0;
}

void child_main(void)
{
    /* TO-DO: 이 함수는 실제 업무 로직을 구현합니다. */

    /* 
    * TO-DO: 변수는 업무에 맞게 데이터 송수신 구조체를 정의하여 사용합니다. 
    * 필요시 전역변수를 선언하여 사용합니다.
    */
    int recv_len;
    char recv_buf[4096];
    char send_buf[4096];
    
    while(1) {
        if((recv_len = TCPIPrecv(acpt_sock, recv_buf, 2048, 0)) == -1) {
            /* TO-DO : 오류 로그 생성 */
            exit(1);
        }
        /* TO-DO : 입력한 데이터로 처리하는 로직  */
        // ......

        if((TCPIPsend(acpt_sock, send_buf, 2048, 0)) == -1) {
            /* TO-DO : 오류 로그 생성 */
            exit(1);
        }
    }
}

 

<sample source file>

fork_one_by_one.c
0.00MB

 

 


see also :  TCP/IP 통신 프로그램   Socket 통신 관련 Library  

블로그 이미지

사용자 자연&사람

행복한 개발자 programmer since 1995.

Tag , , ,

댓글을 달아 주세요