설비 담당자는 HSMS가 떠 있다고 한다. 호스트 로그에는 connection refused가 3ms 간격으로 쌓인다. 아니면 더 나쁜 쪽 — TCP는 붙었는데 그 뒤로 아무것도 오지 않고, 타이머만 태우다 소켓이 닫힌다. 둘 다 방화벽 문제로 오해하기 딱 좋다. 그런데 실제로는 누가 거는 쪽인지를 양쪽이 다르게 알고 있는 경우가 대부분이다. SEMI E37은 연결 모드를 Active와 Passive 둘로 나누는데, 이건 Host냐 Equipment냐와는 다른 축이다. 여기서 시간 제일 많이 버린다.
Active/Passive는 Host/Equipment와 다른 축이다
E37의 Passive entity는 정해진 포트에서 TCP 연결을 기다리는 쪽이고, Active entity는 연결을 거는 쪽이다. 그게 전부다. 어느 쪽이 Host이고 어느 쪽이 설비인지는 이 선택과 무관하다.
현장에서는 설비가 Passive, 호스트가 Active인 조합이 흔하지만 규칙이 아니다. 반대로 맞춘 라인도 있고, 같은 팹 안에서 벤더마다 다르기도 하다. 그러니 "보통 설비가 리스너"라는 가정으로 설정을 채우면 안 된다. 한 링크에서 Passive는 정확히 한쪽이어야 한다.
틀리는 방향은 두 가지뿐이고, 증상이 완전히 다르다.
- 양쪽 다 Passive — 아무도 안 건다. 포트에 리스너가 없으니 상대는 TCP 단계에서 즉시 거부당한다.
- 양쪽 다 Active — 둘 다 상대 포트로 건다. 리스너가 없으므로 역시 거부. 운 나쁘게 한쪽만 리스너를 띄워 뒀다면 TCP는 붙고, 그다음이 조용해진다.
아래 캡처는 전부 이번에 시뮬레이터 리스너를 밖에서 실제 소켓으로 두드려서 받은 것이다.
짝이 맞았을 때
설비 쪽이 Passive로 5501에서 대기하고, 호스트가 Active로 건다.
TCP connect 127.0.0.1:5501
TX Select.req 00 00 00 0A 00 0B 00 00 00 01 00 00 06 01
RX Select.rsp 00 00 00 0A 00 0B 00 00 00 02 00 00 06 01
TX Linktest.req 00 00 00 0A 00 0B 00 00 00 05 00 00 06 02
RX Linktest.rsp 00 00 00 0A 00 0B 00 00 00 06 00 00 06 02
TX Separate.req 00 00 00 0A 00 0B 00 00 00 09 00 00 06 03
(응답 없음, 상대가 소켓을 닫음)
SType는 헤더의 여섯 번째 바이트다. 01이 Select.req, 02가 Select.rsp, 05/06이 Linktest 쌍, 09가 Separate.req. Select.rsp의 네 번째 바이트 — Select Status — 는 00이고, SessionID 00 0B와 SystemBytes 00 00 06 01이 그대로 돌아왔다. 여기까지 오면 세션은 SELECTED다.
중요한 건 순서다. TCP를 건 쪽이 Select.req를 보냈다. 설비 쪽은 답만 했다.
TCP는 붙었는데 아무도 Select를 걸지 않을 때
같은 리스너에 TCP만 연결하고, 호스트 흉내를 내는 쪽에서 아무것도 보내지 않았다. 6초 기다렸다.
2026-08-26T00:02:45.034Z TCP connect 127.0.0.1 (설비 로그)
... 6.4초 동안 RX 없음, TX 없음 ...
2026-08-26T00:02:51.441Z TCP connect 127.0.0.1 (다음 캡처의 연결)
설비 쪽 패킷 로그에 TCP 연결 한 줄만 남고 그 뒤가 비어 있다. Passive 쪽은 먼저 Select.req를 보내지 않는다. 이번 캡처에서 확인한 건 이 리스너의 동작이지만, 호스트가 "연결됐으니 상대가 세션을 열어 주겠지"라고 기다리는 설정일 때 화면에 보이는 그림이 정확히 이것이다. TCP는 ESTABLISHED, HSMS 상태는 NOT SELECTED, 로그에는 아무것도 없음.
이 상태를 정리하는 건 T7(TCP 연결 후 SELECTED까지의 제한 시간)이다. T7이 만료되면 연결을 끊는 게 맞고, 그러지 않는 구현은 이 좀비 소켓을 계속 들고 있는다. 타이머 세 개를 구분하는 방법은 Linktest는 답하는데 Select가 안 끝나는 경우에 표로 정리해 뒀다.
아예 붙지 않을 때
리스너를 내리고 같은 포트로 걸었다.
connect 127.0.0.1:5501 → [Errno 111] connection refused (0.003s)
3ms. 타임아웃이 아니라 즉답이다. 그리고 설비 쪽 패킷 로그에는 이 시도가 아예 남지 않는다 — 이번 export에서도 리스너를 내린 시점과 다시 올린 시점의 SERVER 항목 두 개만 있고 그 사이는 비어 있다. TCP 세 방향 악수가 성립하지 않았으니 애플리케이션이 볼 게 없다.
이게 판별에 쓸모 있는 이유다. 설비 로그에 연결 시도가 안 보인다면 문제는 HSMS 아래층에 있다. 포트가 틀렸거나, 리스너가 안 떠 있거나, 방화벽이거나, 아니면 그쪽도 Active로 설정돼 있다.
반대로 즉시 거부가 아니라 수십 초 걸려 타임아웃이 난다면 그건 다른 이야기다. 거부는 상대 호스트까지는 갔다는 뜻이고, 조용한 타임아웃은 대개 중간에서 패킷이 버려졌다는 뜻이다.
순서대로 세 줄만 보면 된다
- 연결이 즉시 거부되는가 — 그렇다면 그 포트에 리스너가 없다. 양쪽 설정을 나란히 놓고 Passive가 한쪽뿐인지 확인한다.
- 설비 로그에 TCP 연결이 남는가 — 남지 않으면 경로 문제, 남으면 여기까지는 도달했다.
- 그 뒤에 Select.req RX가 남는가 — 남지 않으면 양쪽이 서로 기다리는 중이다. 남았는데 세션이 안 열리면 짝은 맞은 것이고, 그때부터는 Select Status를 읽을 차례다. 그 한 바이트는 Select.rsp 읽는 법에서 다뤘다.
재시도 간격도 같이 보자. 거부는 대개 즉답이라 재연결 루프가 초당 수백 번 돌 수 있다. E37의 T5(connect separation)는 정확히 이걸 막으려고 있는 값이다.
재현
세 캡처 모두 SECS/GEM 시뮬레이터의 Passive 리스너를 밖에서 실제 소켓으로 두드려 얻었다. 리스너를 띄운 상태로 호스트를 Active로 붙여 보고, 그다음 호스트도 Passive로 바꿔 보면 위의 두 번째와 세 번째 그림을 그대로 볼 수 있다.