1. Unix Domain Socket(UDS)이란?
Unix Domain Socket은 같은 시스템 내(동일 로컬 머신)에서 실행 중인 프로세스 간 통신(IPC, Inter-Process Communication)을 위해 사용되는 소켓 기반 통신 방식이다. 일반적인 TCP/IP 소켓과 유사한 방식으로 동작하지만, 네트워크 스택을 통하지 않고 파일 시스템 내의 경로를 통해 통신하므로 빠르고 신뢰성이 높다.
다른 IPC 방식의 비해 Unix Domain Socket이 가지는 장점은?
TCP/IP 소켓 프로그래밍과 거의 동일한 API를 사용한다는 점이다. socket(), bind(), listen(), accept(), connect(), send(), recv() 등 동일한 시스템 콜을 사용하므로 TCP/IP 기반 네트워크 소켓을 개발해본 경험이 있다면 코드 구조나 방식을 거의 그대로 재사용할 수 있다.
SOCK_STREAM vs SOCK_DGRAM
UNIX Domain Socket은 일반 TCP/IP 소켓처럼 아래 두 타입을 모두 지원한다.
- SOCK_STREAM : 연결지향, 데이터의 순서 보장, 신뢰성 있음
- SOCK_DGRAM : 비연결지향, 메시지 간 경계 있음, 상대적으로 빠름, 재전송 없음
단, UDS에서 SOCK_DGRAM은 로컬 통신이므로 UDP 보다는 패킷 손실 가능성이 적다.
2. 주요 API 사용에 있어서 TCP/UDP 소켓과의 차이
| 함수 | 설명 | TCP/UDP | UDS |
| socket() | 소켓 생성 | AF_INET | AF_UNIX |
| bind() | 소켓 파일 경로와 소켓 연결 | struct sockaddr_in 사용 | struct sockaddr_un 사용 |
| connect() | 클라이언트가 서버에 연결 시도 | 상대 IP/PORT 지정 | 상대 파일 경로 지정(.sun_path) |
| listen() | SOCK_STREAM 전용, 서버 소켓이 연결 요청을 대기 상태로 설정 | 동일 | 동일 |
| accept() | SOCK_STREAM 전용, 클라이언트 연결 수락 | 클라이언트의 IP/PORT 정보 획득 | 클라이언트가 bind()한 경우에만 경로 획득 |
| read()/write() | 데이터 송수신 | 동일 | 동일 |
| sendto()/recvfrom() | SOCK_DGRAM 전용, 데이터 송수신 | 동일 | 동일 |
| close() | 소켓 닫기 | 동일 | 동일 |
| unlink() | 사용한 소켓 파일 삭제 | 없음 | 파일 시스템에서 만들어진 socket 파일 삭제 필요 |
3. 소스코드
다음은 UDS(Unix Domain Socket)를 기반으로, 주기적으로 상태 값을 출력하는 서버와 CLI를 통해 해당 값을 읽고 쓰는 클라이언트 간의 IPC 구조를 구현해본 것이다.
참고로 UNIX 소켓 사용시에는 클라이언트도 bind()를 통해 자신만의 소켓 경로를 가지고 있어야한다.
INET 소켓의 경우, 클라이언트는 bind()를 하지 않더라도 send()나 sendto()를 호출하면 커널이 자동으로 임의의 포트를 할당해주고, 서버는 accept()나 recvfrom() 등을 통해 알아낸 클라이언트 포트로 응답을 보낼 수 있다.
하지만 UNIX 소켓은 파일 시스템의 경로를 주소로 사용하기 때문에, 클라이언트가 bind()를 하지 않으면 고유한 주소가 없어 서버가 reply를 할 수 없기 때문에 클라이언트도 bind()를 호출하는 것이 필수이다.
uds-server.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <string.h>
#include <signal.h>
#include <sys/select.h>
#include <errno.h>
#define UNIX_SERV_SOCK_PATH "/tmp/udstest.sock"
static int running = 1;
static int value = 12;
void cleanup(int sig) {
running = 0;
}
void print_error_and_exit(const char *msg) {
perror(msg);
exit(1);
}
int main() {
signal(SIGINT, cleanup);
signal(SIGTERM, cleanup);
// 소켓 파일이 이미 존재할 경우 제거
unlink(UNIX_SERV_SOCK_PATH);
// Unix Domain Socket 생성 (SOCK_DGRAM)
int unix_serv_sock = socket(AF_UNIX, SOCK_DGRAM, 0);
if(unix_serv_sock == -1)
print_error_and_exit("socket");
// 서버 소켓 주소 설정 및 해당 소켓 경로에 바인딩
struct sockaddr_un unix_serv_addr = {0};
unix_serv_addr.sun_family = AF_UNIX;
strcpy(unix_serv_addr.sun_path, UNIX_SERV_SOCK_PATH);
if(bind(unix_serv_sock, (struct sockaddr*)&unix_serv_addr, sizeof(unix_serv_addr)) == -1)
print_error_and_exit("bind");
while(running) {
printf("[uds-server] value = %d\n", value);
fd_set rfds;
FD_ZERO(&rfds);
FD_SET(unix_serv_sock, &rfds);
struct timeval tv = {1, 0};
int ret = select(unix_serv_sock + 1, &rfds, NULL, NULL, &tv);
if(ret == -1) {
if(errno == EINTR) continue;
print_error_and_exit("select");
}
if(ret > 0 && FD_ISSET(unix_serv_sock, &rfds)) {
char recv_buf[256] = {0};
struct sockaddr_un unix_clnt_addr;
socklen_t unix_clnt_addr_len = sizeof(unix_clnt_addr);
// 클라이언트의 소켓 경로와 함께 데이터 수신
int n = recvfrom(unix_serv_sock, recv_buf, sizeof(recv_buf) - 1, 0, (struct sockaddr*)&unix_clnt_addr, &unix_clnt_addr_len);
if(n <= 0) continue;
recv_buf[n] = '\0';
char send_buf[256] = {0};
if(strncmp(recv_buf, "get val", strlen("get val")) == 0) {
snprintf(send_buf, sizeof(send_buf), "value is %d\n", value);
}
else if(strncmp(recv_buf, "set val ", strlen("set val ")) == 0) {
char *startptr = recv_buf + strlen("set val ");
char *endptr;
long temp = strtoul(startptr, &endptr, 10);
if(endptr == startptr || *endptr != '\0') {
snprintf(send_buf, sizeof(send_buf), "Error: invalid val\n");
}
else {
value = (int)temp;
snprintf(send_buf, sizeof(send_buf), "Value set to %d\n" , value);
}
}
else {
snprintf(send_buf, sizeof(send_buf), "Error: unknwon command\n");
}
sendto(unix_serv_sock, send_buf, strlen(send_buf), 0, (struct sockaddr*)&unix_clnt_addr, unix_clnt_addr_len);
}
}
printf("\n");
// 종료 시 소켓 닫고 파일 제거
close(unix_serv_sock);
unlink(UNIX_SERV_SOCK_PATH);
return 0;
}
uds-client.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <string.h>
#include <errno.h>
#define UNIX_SERV_SOCK_PATH "/tmp/udstest.sock"
int main(int argc, char *argv[]) {
if(argc < 2) {
fprintf(stderr, "Usage: %s <get/set> <val> [n]\n", argv[0]);
return 1;
}
// 클라이언트 소켓 생성 (SOCK_DGRAM)
int unix_clnt_sock = socket(AF_UNIX, SOCK_DGRAM, 0);
if(unix_clnt_sock == -1) {
perror("socket");
return 1;
}
// PID 기반으로 클라이언트의 고유 소켓 경로 생성
struct sockaddr_un unix_clnt_addr = {0};
unix_clnt_addr.sun_family = AF_UNIX;
snprintf(unix_clnt_addr.sun_path, sizeof(unix_clnt_addr.sun_path), "/tmp/udstest-%d.sock", getpid());
// 기존 소켓 파일이 있는 경우 삭제
unlink(unix_clnt_addr.sun_path);
// 클라이언트 소켓 바인딩
if(bind(unix_clnt_sock, (struct sockaddr*)&unix_clnt_addr, sizeof(unix_clnt_addr)) == -1) {
perror("bind");
return 1;
}
// 서버 소켓 주소 설정
struct sockaddr_un unix_serv_addr = {0};
unix_serv_addr.sun_family = AF_UNIX;
strcpy(unix_serv_addr.sun_path, UNIX_SERV_SOCK_PATH);
char send_buf[256] = {0};
for(int i = 1; i < argc; i++) {
strcat(send_buf, argv[i]);
if(i < argc - 1) strcat(send_buf, " ");
}
if(sendto(unix_clnt_sock, send_buf, strlen(send_buf), 0, (struct sockaddr*)&unix_serv_addr, sizeof(unix_serv_addr)) == -1) {
perror("sendto");
return 1;
}
char recv_buf[256] = {0};
int ret;
do {
ret = recvfrom(unix_clnt_sock, recv_buf, sizeof(recv_buf) - 1, 0, NULL, NULL);
if (ret > 0) {
recv_buf[ret] = '\0';
printf("%s", recv_buf);
} else if (ret == 0) {
fprintf(stderr, "recvfrom(): 0 bytes received\n");
break;
} else if (errno != EINTR) {
perror("recvfrom");
break;
}
} while (ret == -1 && errno == EINTR);
// 종료 시 소켓 닫고 파일 제거
close(unix_clnt_sock);
unlink(unix_clnt_addr.sun_path);
return 0;
}

'프로그래밍 > 리눅스 시스템 프로그래밍' 카테고리의 다른 글
| POSIX C ] root 권한 체크하기 (0) | 2025.10.21 |
|---|---|
| errno == EINTR (0) | 2025.10.15 |
| Ubuntu ] Custom MOTD(Message of the Day) 설정하기 (0) | 2025.09.18 |
| Linux ] systemd에 서비스 등록하는 방법 (0) | 2025.07.03 |
| C, Linux ] pthread 사용해 보기 (0) | 2022.08.25 |