개발일기/Spring + Unix Domain Socket

[Spring + UDS] 3. C demo Server

ignuy 2024. 8. 8.

지금부터 Spring을 Client, C Daemon을 server로 생각한다.

Spring에서 UDS를 통해 C Daemon으로 특정 요청을 보내면 C Daemon이 이에 대한 적절한 응답을 하는 간단한 Demo 앱을 만들어보며 Spring + UDS를 테스트해보자. 그래서 구상한 구조는 C ‘echo’ Server이다. Spring에서 request로 보낸 메시지를 C echo server가 받고 로그 파일에 기록함과 동시에 UDS를 통해 Spring 앱으로 다시 해당 msg를 전송할 것이다.

전체 코드

이를 위해 구현한 간단한 C daemon 코드는 아래와 같다.

// server.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h> // only for UNIX
#include <sys/un.h> // only for UNIX
#include <unistd.h>

#define SOCKET_PATH "/tmp/my_unix_socket.sock"
#define BUFFER_SIZE 128
#define BACKLOG 5

int main(int argc, char *argv[]) {
    int server_socket, client_socket;
    struct sockaddr_un server_addr;
    ssize_t numRead;
    char buf[BUFFER_SIZE];

    // If there are socket file which has same name already
    if(access(SOCKET_PATH, F_OK) == 0) {
        unlink(SOCKET_PATH);
    }

    server_socket = socket(AF_UNIX, SOCK_STREAM, 0);
    if(server_socket < 0) {
        perror("ERROR opening socket");
        exit(1);
    }

    memset(&server_addr, 0, sizeof(struct sockaddr_un));
    server_addr.sun_family = AF_UNIX;
    strncpy(server_addr.sun_path, SOCKET_PATH, sizeof(server_addr.sun_path) - 1);

    if(bind(server_socket, (struct sockaddr *) &server_addr, sizeof(struct sockaddr_un)) < 0) {
        perror("ERROR on binding");
        exit(1);
    }

    if(listen(server_socket, BACKLOG) < 0) {
        perror("ERROR on listening");
        exit(1);
    }

    printf("Server is listening on %s\\n", SOCKET_PATH);

    while(1) {
        client_socket = accept(server_socket, NULL, NULL);
        if(client_socket < 0) {
            perror("ERROR on accept");
            exit(1);
        }

        while((numRead = read(client_socket, buf, BUFFER_SIZE)) > 0) {
            buf[numRead] = '\\0';

            // For write on log file
            if(write(STDOUT_FILENO, buf, numRead) != numRead) {
                break;
            }

            // For Echo response
            if(write(client_socket, buf, numRead) != numRead) {
                break;
            }
        }

        if(numRead < 0) {
            perror("ERROR on reading");
        }

        close(client_socket);
    }

    printf("Server is unconected on %s\\n", SOCKET_PATH);

    close(server_socket);
    unlink(SOCKET_PATH);
    return 0;
}

하나하나 뜯어보자.

include

include 목록에 있는 sys/socket.h 와 sys/un.h 는 UNIX 계열에서 사용하는 header로 window에서 활용이 불가능하다. window에서는 실행은 커녕 컴파일도 안되니 방법이 없다. UNIX계열 환경을 사전에 준비하자.

기존 socket이 남아있는 경우

UNIX domain socket은 TCP나 UDP같은 네트워크 소켓이 아니라 UNIX 계열에서 활용하는 파일 기반 소켓이다. 따라서 기존에 떠있던 프로세스가 socket을 정리하지 않았고 종료되었다면 socket 파일이 남아있을 수 있다.

이때 기존 socket 파일이 존재하고 있다면, 같은 경로에 새로운 소켓 파일을 생성하려고 시도한다면 충돌이 발생한다. 따라서 Daemon이 뜬 후 처음으로 해야 할 작업은 기존 소켓 파일을 정리하는 것이다.

    // If there are socket file which has same name already
    if(access(SOCKET_PATH, F_OK) == 0) {
        unlink(SOCKET_PATH);
    }

int unlink(const char* pathname) 시스템 콜은 인자인 경로명 pathname에서 지정한 파일을 삭제함을 의미한다.

socket 설정 초기화

이제 UNIX domain socket을 생성하자. domain socket은 network socket과 다르게 sockaddr_un 구조체를 사용한다. 따라서 sockaddr_un server_addr 에 소켓 종류와 소켓 경로를 초기화 해주고 바인딩(bind())한다.

    server_socket = socket(AF_UNIX, SOCK_STREAM, 0);
    if(server_socket < 0) {
        perror("ERROR opening socket");
        exit(1);
    }

    memset(&server_addr, 0, sizeof(struct sockaddr_un));
    server_addr.sun_family = AF_UNIX;
    strncpy(server_addr.sun_path, SOCKET_PATH, sizeof(server_addr.sun_path) - 1);
    
    if(bind(server_socket, (struct sockaddr *) &server_addr, sizeof(struct sockaddr_un)) < 0) {
        perror("ERROR on binding");
        exit(1);
    }

 

Client 연결 대기

클라이언트 연결을 대기 상태로 설정한다.

    if(listen(server_socket, BACKLOG) < 0) {
        perror("ERROR on listening");
        exit(1);
    }

    printf("Server is listening on %s\\\\n", SOCKET_PATH);

클라이언트 연결 수락 및 데이터 처리

클라이언트 연결을 수락한 후 데이터를 읽고, 로그 파일에 파일 기반 출력을 한 후 클라이언트에게 되돌려준다(echo).

    while(1) {
        client_socket = accept(server_socket, NULL, NULL);
        if(client_socket < 0) {
            perror("ERROR on accept");
            exit(1);
        }

        while((numRead = read(client_socket, buf, BUFFER_SIZE)) > 0) {
            buf[numRead] = '\\\\0';

            // For write on log file
            if(write(STDOUT_FILENO, buf, numRead) != numRead) {
                break;
            }

            // For Echo response
            if(write(client_socket, buf, numRead) != numRead) {
                break;
            }
        }

        if(numRead < 0) {
            perror("ERROR on reading");
        }

        close(client_socket);
    }

실행 로그 관찰

댓글