Develop

[c] Unix Domain Socket 을 이용한 IPC

by hooni posted Apr 23, 2013
?

단축키

Prev이전 문서

Next다음 문서

ESC닫기

크게 작게 위로 아래로 댓글로 가기 인쇄
Unix Domain Socket(이하 UDS) 는 socket API를 수정없이 이용며, port 기반의 Inernet Domain Socket에 비해서 로컬 시스템의 파일시스템을 이용해서 내부프로세스간의 통신을 위해 사용한다는 점이 다르다고 할수 있다. 

ls 를 이용해서 통신을 위해서 만들어진 파일을 보면 다음과 같은 모습을 보인다.
[yundream@localhost tmp]$ ls -al
srwx------    1 root     nobody          0 12월 14 21:16 .fam_socket

보면 파일타입에 "s" 가 붙어 있는걸 알수 있으며, 파일사이즈가 0으로 되어 있는 걸 알수 있다. 왜냐하면 FIFO와 마찬가지로 메시지가 파일로 쌓이지 않고 커널로 전달되어서 커널에서 처리하기 때문이다.

파일을 통해서 통신을 하며, 커널내부에서 메시지를 관리한다는 점에서 FIFO와 매우 유사한면을 보여주지만, FIFO와는 달리 양방향 통신이 가능하다는 특징을 가지고 있다. 그러므로 다중의 클라이언트를 받아들이는 서버/클라이언트 모델을 만들기가 매우 쉽다.
또한 Inet 소켓을 통한 외부통신에 비해서 2배 이상의 효율을 보여준다라는 장점을 가지고 있다. 

많은 경우 약간 복잡한 내부프로세스간 통신을 해야된다고 했을때 UDS을 많이 사용한다. INET 계층에서의 통신이 TCP/IP 4계층을 모두 거치는것과는 다르게, UDS 은 어플리케이션 계층에서 TCP 계층까지만 메시지가 전달되고, 다시 곧바로 어플리케이션 계층으로 메시지가 올라가게 된다. TCP/IP 계층에 대한 자세한 내용은 TCP/IP 개요(2)를 참고 하기 바란다.

위에서 INET 소켓보다 2배이상의 효율을 가진다고 했는데, 4계층의 레이어를 모두 거쳐야하는 INET 소켓에 비해서 단지 2개의 레이어만 사용한다는 점도 그 이유중 하나로 작용한다.

쏘쓰 코드는 다중연결서버 만들기(1)의 zipcode_multi.c 와 셈플로 알아보는 소켓프로그래밍(1)의 zipcode_cli.c 를 사용하도록할것이다.

예제: zipcode_local.c
#include <sys/types.h> 
#include <sys/stat.h> 
#include <sys/socket.h> 
#include <sys/un.h> 
#include <unistd.h> 
#include <stdio.h> 
#include <stdlib.h> 
#include <string.h> 

int main(int argc, char **argv)
{
    int server_sockfd, client_sockfd;
    int state, client_len;
    pid_t pid;

    FILE *fp;
    struct sockaddr_un clientaddr, serveraddr;

    char buf[255];
    char line[255];

    if (argc != 2)
    {
        printf("Usage : ./zipcode [file_name]\n");
        printf("예    : ./zipcode /tmp/mysocket\n"); 
        exit(0);
    }

    memset(line, '0', 255);
    state = 0;

    if (access(argv[1], F_OK) == 0)
    {
        unlink(argv[1]);
    }
    // 주소 파일을 읽어들인다. 
    client_len = sizeof(clientaddr);
    if((fp = fopen("zipcode.txt", "r")) == NULL)
    {
        perror("file open error : ");
        exit(0);
    }

    // internet 기반의 스트림 소켓을 만들도록 한다. 
    if ((server_sockfd = socket(AF_UNIX, SOCK_STREAM, 0)) < 0)
    {
        perror("socket error : ");
        exit(0);
    }
    bzero(&serveraddr, sizeof(serveraddr));
    serveraddr.sun_family = AF_UNIX;
    strcpy(serveraddr.sun_path, argv[1]);


    state = bind(server_sockfd , (struct sockaddr *)&serveraddr, 
            sizeof(serveraddr));
    if (state == -1)
    {
        perror("bind error : ");
        exit(0);
    }

    state = listen(server_sockfd, 5);
    if (state == -1)
    {
        perror("listen error : ");
        exit(0);
    }


    printf("accept : \n");
    while(1)
    {
        client_sockfd = accept(server_sockfd,
            (struct sockaddr *)&clientaddr, &client_len);
        printf("test test\n");
        pid = fork();
        if (pid == 0)
        {
            if (client_sockfd == -1)
            {
                perror("Accept error : ");
                exit(0);
            }
            while(1)
            {
                memset(buf, '0', 255);
                if (read(client_sockfd, buf, 255) <= 0)
                {
                    close(client_sockfd);
                    fclose(fp);
                    exit(0);
                }

                if (strncmp(buf, "quit",4) == 0)
                {
                    write(client_sockfd, "bye bye\n", 8);  
                    close(client_sockfd);
                    fclose(fp);
                    break;
                }

                while(fgets(line,255,fp) != NULL)
                {
                    if (strstr(line, buf) != NULL)
                        write(client_sockfd, line, 255);
                    memset(line, '0', 255);
                }
                write(client_sockfd, "end", 255);
                printf("send end\n");
                rewind(fp);
            }
        }
    }
    close(client_sockfd);
}


다음은 클라이언트 프로그램이다.
예제: zipcode_cli_local.c
#include <sys/types.h> 
#include <sys/stat.h> 
#include <sys/socket.h> 
#include <unistd.h> 
#include <sys/un.h> 
#include <stdio.h> 
#include <stdlib.h> 
#include <string.h>

int main(int argc, char **argv)
{
    int client_len;
    int client_sockfd;

    FILE *fp_in;
    char buf_in[255];
    char buf_get[255];

    struct sockaddr_un clientaddr;

    if (argc != 2)
    {       
        printf("Usage : ./zipcode_cl [file_name]\n");
        printf("예    : ./zipcode_cl /tmp/mysocket\n");
        exit(0);
    }       


    client_sockfd = socket(AF_UNIX, SOCK_STREAM, 0);
    if (client_sockfd == -1)
    {
        perror("error : ");
        exit(0);
    }
    bzero(&clientaddr, sizeof(clientaddr));
    clientaddr.sun_family = AF_UNIX;
    strcpy(clientaddr.sun_path, argv[1]);
    client_len = sizeof(clientaddr);
    if (connect(client_sockfd,
        (struct sockaddr *)&clientaddr, client_len) < 0)
    {
        perror("Connect error: ");
        exit(0);
    }
    while(1)
    {
        printf("지역이름 입력 : ");
        fgets(buf_in, 255,stdin);

        buf_in[strlen(buf_in) - 1] = '0';
        write(client_sockfd, buf_in, 255);
        if (strncmp(buf_in, "quit", 4) == 0)
        {
            close(client_sockfd);
            exit(0);
        }
        while(1)
        {
            read(client_sockfd, buf_get, 255); 
            if (strncmp(buf_get, "end", 3) == 0)
                break;

            printf("%s", buf_get);
        }
    }

    close(client_sockfd);
    exit(0);
}

기존의 INET 버젼의 프로그램과 비교해 보면 고작 3줄 정도만 수정되었음을 알수 있을것이다. 단지 소켓 구조체가 sockaddr_un 으로 바뀌고, AF_INET 대신 AF_UNIX 를 그리고 port 번호대신에 파일명을 사용했음을 알수 있다.

나머지의 모든 코드는 INET 코드와 완전히 같다. 그러므로 Unix Domain Socket 를 사용하면 Inet Domain Socket 와 코드 일관성을 유지할수 있으며, 동일한 기술을 사용해서 프로그래밍을 할수 있다. 

또한 다른 대부분의 IPC 설비들이, 범용적으로 사용하기에는 부족한 여러가지 단점들을 가진반면(단방향 이거나, 읽기만 가능하다거나, 제어하기가 어려운) UDS는 매우 범용적인 IPC 로써 사용가능하다라는 장점을 가지고 있다.

실제로 X 서버 같은경우에 외부에서의 접근시에는 INET 연결을 내부에서의 연결을 위해서는 UDS 를 사용한다. 이밖에도 mysql, pgsql, KDE, Gnome 과 같은 대부분의 서버프로그램이 내부통신을 위해서 UDS 를 사용한다.