Skip to content

소켓 프로토콜 ​

서버 소켓의 JSON 프로토콜이에요. 서버 사이에 세션을 옮기는 부분도 있어요.

다른 프로그램에서: 소켓 ​

서버 소켓(~/.zeropty/mux.sock, ZEROPTY_SOCK으로 바꿔요)에 JSON 한 줄을 보내면 한 줄로 답해요. 연결 하나로 여러 명령을 보낼 수 있고, 답은 순서대로 와요. 소켓은 0600 권한이라 같은 사용자(와 root)만 접속할 수 있어요.

→ {"argv":["capture-pane","-p","-t","=work:"],"cwd":"/"}
← {"code":0,"out":"...화면...","err":""}
  • cwd는 상대 경로(new-session -c, load-buffer)의 기준 폴더예요.
  • 요청에 "raw":true를 넣으면 답이 JSON 대신 <code> <out 바이트 수> <err 바이트 수> 한 줄과 그 뒤의 out, err 바이트 그대로 와요. JSON을 풀기 어려운 클라이언트(zsh 함수)용이에요.
  • attach 연결은 첫 답 뒤로 프레임을 주고받아요: [종류 u8][길이 u32 LE][바이트]. 서버 → 클라이언트는 0(출력), 1(세션 끝), 클라이언트 → 서버는 0(키), 1(크기: 열·행 u16 LE), 2(떼기)예요.
  • 첫 요청이 export-sessions나 import-sessions인 연결은 세션을 넘기는 데 써요(내부용): 소켓 프로토콜.
sh
# 아무 언어에서나: 예를 들어 nc로
printf '{"argv":["list-sessions"],"cwd":"/"}\n' | nc -U ~/.zeropty/mux.sock

세션 옮기기 ​

세션을 다른 프로세스로 옮겨요. 실행 중인 프로그램은 그대로예요(pid도 같아요). 화면·스크롤백은 바이트로, 세션의 fd는 fd 전달(SCM_RIGHTS)로 넘겨요.

sh
zeropty pull-sessions -s ~/.zeropty/other.sock [이름...]   # 다른 서버의 세션을 이 서버로 (이름 없으면 전부)
zeropty push-sessions -s ~/.zeropty/other.sock [이름...]   # 이 서버의 세션을 다른 서버로
zeropty restart-server                                  # 새 서버가 세션을 전부 넘겨받고 소켓 자리를 차지
  • 임베디드 엔진에서도 같은 명령이 돼요. 예를 들어 Node 앱이 시작할 때 서버의 세션을 pull-sessions로 가져오고, 끝날 때 push-sessions로 돌려주면 앱을 재시작해도 세션이 안 끊겨요.
  • restart-server는 지금 실행한 zeropty 바이너리로 새 서버를 띄워요. 그래서 세션을 잃지 않고 업그레이드하는 방법이기도 해요. 전환하는 몇 ms 사이에 들어온 명령은 옛 서버로 가요.
  • 받는 쪽이 "받았다"고 답한 세션만 보낸 쪽이 놓아요. 중간에 실패하면 세션은 원래 쪽에 그대로 있어요. 이름이 이미 있는 세션은 옮기지 않아요.
  • 한계: 옮기는 순간 이스케이프 시퀀스가 반쯤 들어와 있었다면, 그 나머지는 글자로 찍혀요.

소켓 프로토콜: export-sessions, import-sessions ​

pull-sessions, push-sessions, restart-server가 서버 소켓에서 쓰는 내부용 요청이에요. 직접 보낼 일은 거의 없고, 세션 레코드 형식은 버전마다 바뀔 수 있어요(첫 바이트가 레코드 버전이라, 다른 버전의 레코드는 session record of another version으로 거절해요). 사람이 읽을 레퍼런스로 남겨 둬요. 구현: src/engine/handover.rs.

  • 연결의 첫 요청 줄이 {"argv":["export-sessions", 이름...],"cwd":""} 또는 {"argv":["import-sessions"],"cwd":""}이면, 그 연결은 세션을 넘기는 데만 쓰고 끝나면 닫혀요. 그동안 서버는 다른 일을 하지 않아요(보통 몇 ms).
  • 양쪽 모두 한 단계에 10초까지 기다려요.
  • 세션 하나는 [레코드 길이 u32 LE][레코드]로 가요. 세션의 fd(입력, 출력 중 열려 있는 것, 0~2개)는 길이 4바이트에 SCM_RIGHTS로 붙여 보내요. 길이 0이 끝 표시예요. 레코드는 256MB를 넘을 수 없어요.
  • 나머지 줄은 모두 JSON 한 줄이에요.
export-sessions (가져가는 쪽 → 서버)
→ {"argv":["export-sessions","a","b"],"cwd":""}     이름이 없으면 전부
← [길이][레코드] ... [0]                              세션마다 fd와 함께
← ["b"]                                             서버에 없는 이름
→ {"taken":["a"],"failed":[]}                       받은 세션. 서버는 이것만 놓아요

import-sessions (주는 쪽 → 서버)
→ {"argv":["import-sessions"],"cwd":""}
← "ready"
→ [길이][레코드] ... [0]
← {"taken":["a"],"failed":["duplicate session: b\n"]}   주는 쪽은 taken만 놓아요

failed에는 받지 못한 이유가 들어가요(같은 이름이 이미 있음, 레코드 버전이 다름 등). 어느 단계에서든 연결이 끊기거나 형식이 틀리면 받은 쪽이 받은 세션을 놓고, 세션은 원래 쪽에 남아요.

MIT 또는 Apache-2.0 라이선스로 배포해요.