QNX RTOS: 5-4. How a Client Finds a Server

2025. 12. 2. 17:17운영체제/QNX

1. 기본 문제: 클라이언트는 서버를 어떻게 찾나?

QNX 메시지 패싱에서 클라이언트가 MsgSend()를 하려면:

  1. coid( connection ID )가 필요하고
  2. coid를 만들려면 ConnectAttach()를 호출해야 하고
  3. ConnectAttach()를 하려면 서버의 pid(프로세스 ID)와 chid(채널 ID) 를 알아야 함.

실습에서는:

  • 서버가 printf("pid=%d, chid=%d\n") 찍고
  • 클라이언트가 그걸 커맨드라인 인자로 직접 입력했음.

→ 하지만 이건 “실습용”일 뿐, 실제 시스템에서 쓸 수 있는 구조는 아님.

그래서 QNX는 이름 기반(Name-based) 서버 찾기 방식을 제공함.


2. 두 가지 방식: Resource Manager vs. 단순 MsgReceive 서버

2.1 Resource Manager인 경우

  • 서버가 resmgr_attach()로 이름을 등록:
resmgr_attach(..., "/dev/sound", ...);
 
  • 클라이언트는 그냥:
int fd = open("/dev/sound", O_RDWR);
 
  • QNX에서 file descriptor = 사실상 coid
    → read(fd, ...), write(fd, ...)는 내부적으로:
    • 메시지 포맷 만들고
    • MsgSend(fd, ...) 호출하고
    • MsgReply() 결과 처리하는, 10줄 내외짜리 래퍼 함수일 뿐.

즉, Resource Manager를 쓰면 open/read/write만으로 IPC가 다 되는 구조.


2.2 단순 MsgReceive 루프 서버인 경우 (우리가 지금 보고 있는 케이스)

Resource Manager처럼 거창하게 안 쓰고, 그냥:

while (1) {
    rcvid = MsgReceive(chid, &msg, sizeof(msg), &info);
    ...
}
 

이런 서버라면?

  • 서버는 name_attach()로 이름을 등록
  • 클라이언트는 name_open()으로 그 이름을 찾아서 coid 얻기

3. name_attach() / name_open() 메커니즘

3.1 서버: name_attach()

name_attach_t *attach;
attach = name_attach(NULL, "my_server", 0);
if (attach == NULL) { /* error */ }

int chid = attach->chid;   // MsgReceive할 때 사용할 채널 ID
 
  • name_attach()가 내부적으로:
    • 채널을 생성 (ChannelCreate() 호출)
    • 그 채널에 이름을 pathname space에 등록
    • name_attach_t*를 반환 (안에 chid 포함)
  • 서버는 이후 MsgReceive()할 때:
MsgReceive(attach->chid, &msg, sizeof(msg), &info);
 
  • 서버 종료 & 이름 제거:
name_detach(attach, 0);   // 채널 파괴 + 이름 제거
 

 


3.2 클라이언트: name_open()

int coid = name_open("my_server", 0);
if (coid == -1) { /* error */ }

// 이후에는 그냥 MsgSend에 coid 사용
MsgSend(coid, &msg, sizeof(msg), &reply, sizeof(reply));
 
  • name_open()이 내부적으로:
    • 서버를 lookup해서
    • ConnectAttach()를 호출
    • coid(연결 ID)를 반환
  • 클라이언트가 더 이상 사용 안 하면:
name_close(coid);
 

요약:

  • 서버: name_attach() → MsgReceive(attach->chid, ...)
  • 클라이언트: name_open() → MsgSend(coid, ...)
    → pid/chid를 직접 알 필요가 없음.

4. name_attach()가 내부에서 설정하는 Channel Flags (중요)

단순히 채널만 만드는 게 아니라, ChannelCreate()를 호출할 때 몇 가지 플래그를 자동으로 세팅합니다. 그래서 서버는 이런 Pulse들을 받을 수 있다는 걸 알고 있어야 함.

name_attach()가 설정하는 플래그:

  1. _NTO_CHF_DISCONNECT
  2. _NTO_CHF_COID_DISCONNECT
  3. _NTO_CHF_UNBLOCK

각각 의미는:

4.1 _NTO_CHF_DISCONNECT — “내 클라이언트가 죽으면 알려줘”

  • 의미: 서버의 클라이언트가 사라질 때(프로세스 종료, name_close, ConnectDetach 등)
  • 커널이 서버에게 pulse를 보냄
    • pulse code: _PULSE_CODE_DISCONNECT
    • pulse 안의 scoid로 어떤 클라이언트인지 알려줌

서버는 이걸 받아서:

  • 클라이언트 리스트에서 해당 클라이언트 제거
  • 할당된 버퍼/리소스 해제
  • 마지막에 반드시 ConnectDetach(scoid) 호출해야 함 (매우 중요)

 


4.2 _NTO_CHF_COID_DISCONNECT — “내가 클라이언트로 붙어 있는 서버가 죽으면 알려줘”

  • 여기서 이 프로세스는 서버이면서 동시에 다른 서버의 클라이언트일 수 있음.
  • 이 플래그는:
    • 내가 사용 중인 coid(=내가 다른 서버에 붙은 연결)가 죽을 때 pulse로 알려달라”는 의미
  • 커널은 해당 프로세스에 pulse 전송:
    • pulse code: _PULSE_CODE_COIDDEATH
    • pulse의 value에 어느 coid/FD가 죽었는지 전달

예:

  • 내 서버는 클라이언트들로부터도 메시지를 받고,
  • 동시에 다른 IPC 서버에 붙어서 I/O를 요청할 수도 있음.
  • 상대 서버가 죽거나 채널을 닫으면 → _PULSE_CODE_COIDDEATH 로 알려줌 → 정리 필요.

4.3 _NTO_CHF_UNBLOCK — “Reply 기다리는 클라이언트가 타임아웃/시그널로 Unblock 되면 알려줘”

  • 상황:
    • 클라이언트가 MsgSend() → 서버가 MsgReceive()로 받고 처리 중
    • 클라이언트는 REPLY blocked 상태로 서버의 답장을 기다림
    • 그 사이에 클라이언트가 타임아웃 / signal / 기타 이유로 unblock을 요청할 수 있음
  • 이 플래그 설정 시:
    • 그런 일이 발생하면 서버 채널로 pulse 전송
    • pulse code: _PULSE_CODE_UNBLOCK

서버는 이걸 보고:

  • “아, 이 클라이언트는 더 이상 reply를 기다리지 않겠구나”
  • 해당 클라이언트에 대해 진행 중이던 작업을 중지하거나 정리할 수 있음.

5. 서버가 클라이언트를 “어떻게 추적”하는가? (Client Table & scoid)

서버는 보통 클라이언트별 상태를 관리하는 테이블/리스트를 갖습니다.

예시:

  • 각 클라이언트가:
    • 어떤 요청을 했는지
    • 얼마나 많은 데이터를 요청했고, 지금까지 몇 바이트 처리했는지
    • 어떤 하드웨어와 연결되어 있는지
    • 어떤 이벤트에 대해 notify를 요청했는지
  • 이런 정보를 client_list[] / linked list 등으로 관리.

5.1 클라이언트 ID로 scoid 사용

  • 최초로 특정 클라이언트로부터 메시지를 받으면:
struct _msg_info info;
rcvid = MsgReceive(chid, &msg, sizeof(msg), &info);

if (is_new_client(info.scoid)) {
    add_client_to_list(info.scoid, ...);
}
 
  • _msg_info 안의 scoid는:
    • 서버 입장에서 이 클라이언트를 식별하는 ID
    • 클라이언트가 살아 있는 동안에는 동일하게 유지됨.

5.2 클라이언트 종료 시: disconnect pulse 처리

  • 클라이언트가 종료되거나 연결을 끊으면:
    • 서버는 다음과 같은 pulse를 받게 됨:
rcvid = MsgReceive(chid, &msg, sizeof(msg), NULL);

if (rcvid == 0) {  // pulse
    switch (msg.pulse.code) {
    case _PULSE_CODE_DISCONNECT:
        {
            int dead_scoid = msg.pulse.scoid;
            client_t *c = find_client(dead_scoid);
            cleanup_client(c);              // 버퍼, 하드웨어, pending 작업 정리
            ConnectDetach(dead_scoid);      // ★ 필수!
        }
        break;
    ...
    }
}
 

핵심 포인트

  • _PULSE_CODE_DISCONNECT 수신
  • pulse 안의 scoid로 클라이언트 탐색
  • 리소스 정리 + 무조건 ConnectDetach(scoid) 호출
    (disconnect pulse를 요청했으면, detach는 서버 책임)

6. 전체 흐름 요약

  1. 서버 시작
    • name_attach(NULL, "my_server", 0)
    • 내부적으로 ChannelCreate() + 이름 등록 + _NTO_CHF_* 플래그 설정
    • attach->chid로 MsgReceive() 루프 시작
  2. 클라이언트 시작
    • coid = name_open("my_server", 0)
    • MsgSend(coid, ...)로 서버와 동기 메시지 통신
  3. 서버 측 메시지 처리
    • MsgReceive(chid, &msg, sizeof(msg), &info)
    • rcvid > 0: 정상 메시지 → _msg_info로 scoid 확인 → client list 관리
    • rcvid == 0: pulse
      • _PULSE_CODE_DISCONNECT: 클라이언트 종료 → client list 정리 + ConnectDetach(scoid)
      • _PULSE_CODE_UNBLOCK: REPLY 기다리던 클라이언트가 unblock → 작업 취소/정리
      • _PULSE_CODE_COIDDEATH: 이 서버가 의존하던 다른 서버의 coid가 죽음 → 재연결 등 처리
  4. 종료
    • 서버: name_detach(attach, 0)
    • 클라이언트: name_close(coid)

'운영체제 > QNX' 카테고리의 다른 글

QNX RTOS: 5-6. Issues Related to Priorities  (0) 2025.12.02
QNX RTOS: 5-5. Multi-Part Messages  (0) 2025.12.02
QNX RTOS: 5-3. Client Information Structure  (0) 2025.12.02
QNX RTOS: 5-2. Pulses  (0) 2025.12.02
QNX RTOS: 5-1. Message Passing  (0) 2025.11.27