🗓️ 2023. 09. 02
⏱️ 8

Unix Domain Socket

안에선 활발한 INFP 소켓

macOS 환경에서 작성된 글입니다.

UDS

소켓하면 보통 IP 주소와 포트가 할당된 TCP나 UDP 네트워크 소켓을 생각하기 쉬우나, UDS(Unix Domain Socket)라는 소켓도 있다.
유닉스 도메인 소켓은 내부 프로세스 간 통신(IPC, Inter-Process Communication)을 위해 사용되는 소켓이다.
그래서 IPC 소켓이나 Local 소켓으로 불리기도 한다.

네트워크 소켓과 차이점

네트워크 소켓유닉스 도메인 소켓
통신 지원 범위로컬, 네트워크 상의 다른 시스템로컬
식별 요소IP 주소와 Port 번호파일 시스템 내 경로
데이터 송수신 경로네트워크로컬 메모리
접근 제어방화벽, 필터링 등파일에 대한 접근권한
지원 프로토콜 종류TCP, UDP 등로컬

네트워크 소켓과 달리 로컬 환경에서의 프로세스 간 통신이기 때문에 네트워킹에서 비롯되는 오버헤드, 라우팅, 딜레이 등의 문제를 피할 수 있어 속도와 메모리 측면에서 더 효율적이다.

유닉스 도메인 소켓에선 네트워크 문제를 피할 수 있다유닉스 도메인 소켓에선 네트워크 문제를 피할 수 있다

C언어 예시

// 많이 생략된 예시
int server_socket;
struct sockaddr_un server_address;

server_socket = socket(PF_UNIX, SOCK_STREAM, 0);

memset(&server_address, 0, sizeof(server_address));
server_address.sun_family = PF_UNIX;
strcpy(server_address.sun_path, "/tmp/ipc-test.sock");

if (bind(server_socket, (struct sockaddr *)&server_address, sizeof(server_address)) == -1) {
    perror("바인딩 실패");
    exit(0);
}

유닉스 도메인 소켓을 생성할 땐 sockaddr_un 구조체를 사용해야 한다.
(네트워크 소켓을 생성할 땐 sockaddr_in 구조체 사용)

또한 예제에선 SOCK_STREAM을 사용했지만 SOCK_DGRAM도 사용 가능하다.

(참고) 구조체 정의

/* IPv4의 주소 체계를 나타내는 구조체 */
struct in_addr {
	uint32_t s_addr; // 32비트 IPv4 주소
};

struct sockaddr_in {
	sa_family_t sin_family; // 주소 체계
	uint16_t sin_port; // 16비트 TCP/UDP 포트
	struct in_addr sin_addr; // 32비트 IPv4 주소
	char sin_zero[8]; // 사용되지 않음(단순 padding 목적)       
};

/* Local Unix 프로토콜에서 사용되는 주소 정보 구조체 */
struct sockaddr_un {
	sa_family_t sun_family; // 주소 체계
	char sun_path[108]; // 경로
};

node.js 예시

SOCK_STREAM

공식 문서에 나와있듯 net 모듈을 이용해 유닉스 도메인 소켓을 만들 수 있다 (Windows에선 named pipe).

// server.js
import net from 'node:net';
import fs from 'node:fs';

const socketPath = '/tmp/ipc-test.sock';

const server = net.createServer((socket) => {
  console.log('클라이언트 연결됨');

  socket
    .on('data', (data) => {
      const message = data.toString();
      console.log(`클라이언트가 보낸 메시지 : ${message}`);
      socket.write(message);
    })
    .on('end', () => {
      console.log('클라이언트와 연결 종료');
    });
});

fs.unlink(socketPath, () => {
  server.listen(socketPath, () => {
    console.log(`Listening on ${socketPath}`);
  });
});
// client.js
import net, { Socket } from 'node:net';

const socketPath = '/tmp/ipc-test.sock';

const client = new Socket()
  .connect(socketPath) // 혹은 net.createConnection(socketPath)
  .on('connect', () => {
    console.log('서버와 연결됨');
    client.write('Hello World');
  })
  .on('data', (data) => {
    console.log(`서버로부터 받은 메시지 : ${data.toString()}`);
    client.end();
  })
  .on('close', () => {
    console.log('서버와 연결 종료');
  });

SOCK_DGRAM

dgram 모듈은 유닉스 도메인 소켓을 지원하지 않는다.
그래서 일반적으론 net 모듈로 소켓을 구현해야 하는데, 반드시 SOCK_DGRAM으로 구현해야 한다면 node-unix-socket같은 별도 라이브러리를 사용하면 된다.

조금 더 알아보기

.sock 파일

앞선 예시에서 내부 프로세스 간 통신을 위한 소켓 파일로 /tmp/ipc-test.sock을 생성하여 사용했다.
.sock 파일은 일반적으로 /tmp 디렉토리나 시스템 전용 디렉토리 등에 위치시킨다.
소켓을 나타내기 위한 특별한 파일(일종의 채널 역할)이고 실제로 데이터가 저장되는 파일이 아니기 때문에 생성되더라도 용량은 0이다.
(데이터 송수신이 파일이 아닌 메모리를 거치기 때문)

서버-클라이언트 구조뿐만 아니라 백그라운드에서 실행되는 서비스나 데몬에서도 데이터 공유를 위해 이용된다.
대표적인 예시가 DockerMySQL에서 이용되는 docker.sock과 mysql.sock이다.

lsof -U로 컴퓨터 내 .sock 파일을 조회할 수 있다
vscode에서도 유닉스 도메인 소켓을 이용한 IPC가 이뤄진다lsof -U로 컴퓨터 내 .sock 파일을 조회할 수 있다 vscode에서도 유닉스 도메인 소켓을 이용한 IPC가 이뤄진다
파일 타입은 s(socket), 크기는 0파일 타입은 s(socket), 크기는 0

주의사항

예제에서 fs.unlink를 해줬듯이 보통 서버를 시작하기 전에 기존 소켓 파일을 제거해줘야 한다.
소켓 파일의 중복과 보안 문제를 방지해야 하기 때문인데,

  • 중복 방지
    기존 소켓 파일이 이미 존재하는 경우, 같은 경로에 새로운 소켓 파일을 생성하려고 하면 충돌이 발생할 수 있다.
    예를 들어, 기존 파일이 제거되지 않은 채로 서버를 재시작하면 EADDRINUSE 에러가 발생한다.

  • 보안 문제
    다른 프로세스에서 소켓 파일을 열 수 있으므로 보안 문제가 발생할 수 있다. 기존 소켓 파일을 삭제한 후 새로운 소켓 파일을 생성하면 다른 프로세스가 같은 경로에 대한 액세스 권한을 얻지 못하도록 보안을 강화할 수 있다.

docker.sock이나 mysql.sock 같은 소켓 파일들은 소프트웨어가 관리를 잘 해주며, 파일 접근 권한으로 보안 문제를 해결했기 때문에 삭제되지 않는다.

소켓 확인하기


# macOS
netstat -a -f unix

# Ubuntu
netstat -a -p --unix

netstat 명령어를 이용하면 현재 활성화된 유닉스 도메인 소켓 목록을 볼 수 있다.

stream과 dgram이 모두 이용되는 모습stream과 dgram이 모두 이용되는 모습

참고

돌아가기
© 2024 VERYCOSY.