Jupyter 通讯协议
环境设置
如果我想研究 jupyter 的服务端和客户端交互,应该怎么设置环境? 特别地,我想把 zmq 的消息打印出来供我研究 并且,我想用 nix (flake) 使得这个环境可以简单复现 用中文回复
好的,这是一个使用 Nix Flake 配置 Jupyter 环境以观察 ZMQ 消息的方案。
-
创建
flake.nix
文件:{
description = "Jupyter ZMQ inspection environment";
inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable"; # 或者指定一个稳定版本
};
outputs = { self, nixpkgs }:
let
# 支持的系统
supportedSystems = [ "x86_64-linux" "aarch64-linux" "x86_64-darwin" "aarch64-darwin" ];
# 为每个支持的系统生成 pkgs
pkgsFor = system: import nixpkgs { inherit system; };
in
{
# 为每个系统提供开发环境
devShells = nixpkgs.lib.genAttrs supportedSystems (system:
let
pkgs = pkgsFor system;
# 定义包含 jupyter 组件的 Python 环境
pythonEnv = pkgs.python3.withPackages (ps: with ps; [
ipykernel # Jupyter 内核
jupyter-client # Jupyter 客户端库
jupyter-console # 简单的文本客户端,便于观察
pyzmq # ZeroMQ 的 Python 绑定
# 如果需要 Web 界面,可以取消注释 notebook 或 jupyterlab
# notebook
# jupyterlab
]);
in
pkgs.mkShell {
name = "jupyter-zmq-dev";
# 将 Python 环境加入开发环境
packages = [
pythonEnv
];
# (可选) 进入环境时的提示信息
shellHook = ''
echo "已进入 Jupyter ZMQ 调试环境。"
echo "要查看 ZMQ 消息,请使用 '--debug' 标志启动 jupyter 组件。"
echo "例如,启动一个控制台客户端:"
echo " jupyter console --debug"
echo "注意观察输出中包含 'ZMQ' 或 'KernelClient' 相关的调试信息。"
'';
}
);
};
} -
使用方法:
- 将上述内容保存为
flake.nix
文件。 - 在包含
flake.nix
的目录中,运行nix develop
命令进入该环境。 - 进入环境后,根据
shellHook
的提示操作。
- 将上述内容保存为
-
观察 ZMQ 消息:
- 在 Nix 环境中,运行
jupyter console --debug
。 - 执行一些简单的 Python 代码(例如
a = 1
,print(a)
)。 - 在终端的输出中,查找包含
DEBUG
级别的日志,特别是那些与KernelClient
、ZMQ
、Channel
(如shell
,iopub
,control
,stdin
,hb
) 相关的日志。这些日志会显示消息的发送和接收情况。
示例输出片段(可能包含类似内容):
[D 2023-10-27 10:30:00.123 KernelClient] Connecting to: tcp://127.0.0.1:54321
[D 2023-10-27 10:30:00.456 ZMQChannelsHandler] Received message on shell channel: ...
[D 2023-10-27 10:30:00.789 KernelClient] Sending message on shell channel: ... - 在 Nix 环境中,运行
这个 Flake 配置提供了一个包含必要 Jupyter 组件(内核、客户端库、控制台)和 ZMQ 绑定的隔离环境。通过 --debug
标志运行 jupyter console
,你可以直接在终端看到客户端和内核之间通过 ZMQ 交换消息的详细日志记录。
有错误
❯ nix develop
error: flake 'path:/home/ayanamists/repo/handscript/jupyter_debug' does not provide attribute 'devShells.x86_64-linux.default', 'devShell.x86_64-linux', 'packages.x86_64-linux.default' or 'defaultPackage.x86_64-linux' Did you mean devShells?
你说得对,这个错误是因为 nix develop
默认查找名为 default
的开发环境,而之前的 flake 文件直接在系统名称下定义了 shell,没有 default
这一层。
请修改 flake.nix
文件,将 mkShell
的结果赋给 default
属性:
{
description = "Jupyter ZMQ inspection environment";
inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
};
outputs = { self, nixpkgs }:
let
supportedSystems = [ "x86_64-linux" "aarch64-linux" "x86_64-darwin" "aarch64-darwin" ];
pkgsFor = system: import nixpkgs { inherit system; };
in
{
# 修改这里:为每个系统定义一个名为 default 的 shell
devShells = nixpkgs.lib.genAttrs supportedSystems (system:
let
pkgs = pkgsFor system;
pythonEnv = pkgs.python3.withPackages (ps: with ps; [
ipykernel
jupyter-client
jupyter-console
pyzmq
# notebook
# jupyterlab
]);
in
{ # 添加这一层
default = pkgs.mkShell { # 将 mkShell 放在 default 属性下
name = "jupyter-zmq-dev";
packages = [
pythonEnv
];
shellHook = ''
echo "已进入 Jupyter ZMQ 调试环境。"
echo "要查看 ZMQ 消息,请使用 '--debug' 标志启动 jupyter 组件。"
echo "例如,启动一个控制台客户端:"
echo " jupyter console --debug"
echo "注意观察输出中包含 'ZMQ' 或 'KernelClient' 相关的调试信息。"
'';
};
} # 添加这一层
);
};
}
改动说明:
在 outputs.devShells
的定义中,对于 genAttrs
生成的每个系统属性,我们现在返回一个包含 default
属性的 Attribute Set,并将原来的 pkgs.mkShell { ... }
作为 default
的值。
这样修改后,再运行 nix develop
命令应该就能成功进入环境了。
对,你说得对。 nix develop
默认会尝试加载名为 default
的开发环境。你在 flake.nix
中创建的开发环境名为 jupyter-zmq-dev
。
你需要指定要加载的开发环境名称:
nix develop .#jupyter-zmq-dev
或者,如果你 想让 nix develop
直接工作,可以将 flake.nix
中的 devShells
部分修改为:
# ... inside outputs ...
devShells = nixpkgs.lib.genAttrs supportedSystems (system:
let
pkgs = pkgsFor system;
pythonEnv = pkgs.python3.withPackages (ps: with ps; [
ipykernel
jupyter-client
jupyter-console
pyzmq
# notebook
# jupyterlab
]);
in
{ # <-- 添加花括号
# 将 mkShell 的结果赋给 default 属性
default = pkgs.mkShell {
# name = "jupyter-zmq-dev"; # name 不再是必须的,但可以保留
packages = [
pythonEnv
];
shellHook = ''
echo "已进入 Jupyter ZMQ 调试环境 (default)。"
# ... (其余 shellHook 不变) ...
'';
};
} # <-- 添加花括号
);
# ...
修改后,devShells.<system>
的值会是一个包含 default
属性的属性集。这样,直接运行 nix develop
就可以进入环境了。
选择哪种方式取决于你的偏好:
- 指定名称 (
nix develop .#jupyter-zmq-dev
):更明确,特别是当你的 Flake 提供多个开发环境时。 - 使用
default
:方便,可以直接运行nix develop
。
过程
// User -> Kernel
�������8�������=�����������������������e�������������shell{"date":"2025-04-29T06:46:43.582Z","msg_id":"cbe66321-a40d-4a5b-b2f8-a3a4e2c41dbe","msg_type":"execute_request","session":"02991bb7-a5cd-446c-b325-4880bbd5f229","username":"","version":"5.2"}{}{"trusted":true,"deletedCells":[],"recordTiming":false,"cellId":"d5310080-727d-4769-adb5-ec201ef96d65"}{"silent":false,"store_history":true,"user_expressions":{},"allow_stdin":true,"stop_on_error":true,"code":"2 + 3"}
�������8�������=����������������������������������iopub{"msg_id": "8218c830-a736e5ef247730fd4c0f4b41_665704_111", "msg_type": "status", "username": "ayanamists", "session": "8218c830-a736e5ef247730fd4c0f4b41", "date": "2025-04-29T06:46:43.583473Z", "version": "5.3"}{"date": "2025-04-29T06:46:43.582000Z", "msg_id": "cbe66321-a40d-4a5b-b2f8-a3a4e2c41dbe", "msg_type": "execute_request", "session": "02991bb7-a5cd-446c-b325-4880bbd5f229", "username": "", "version": "5.2"}{}{"execution_state": "busy"}
�������8�������=���������������������������
������iopub{"msg_id": "8218c830-a736e5ef247730fd4c0f4b41_665704_112", "msg_type": "execute_input", "username": "ayanamists", "session": "8218c830-a736e5ef247730fd4c0f4b41", "date": "2025-04-29T06:46:43.583721Z", "version": "5.3"}{"date": "2025-04-29T06:46:43.582000Z", "msg_id": "cbe66321-a40d-4a5b-b2f8-a3a4e2c41dbe", "msg_type": "execute_request", "session": "02991bb7-a5cd-446c-b325-4880bbd5f229", "username": "", "version": "5.2"}{}{"code": "2 + 3", "execution_count": 4}
�������8�������=���������������������������*������iopub{"msg_id": "8218c830-a736e5ef247730fd4c0f4b41_665704_113", "msg_type": "execute_result", "username": "ayanamists", "session": "8218c830-a736e5ef247730fd4c0f4b41", "date": "2025-04-29T06:46:43.584374Z", "version": "5.3"}{"date": "2025-04-29T06:46:43.582000Z", "msg_id": "cbe66321-a40d-4a5b-b2f8-a3a4e2c41dbe", "msg_type": "execute_request", "session": "02991bb7-a5cd-446c-b325-4880bbd5f229", "username": "", "version": "5.2"}{}{"data": {"text/plain": "5"}, "metadata": {}, "execution_count": 4}
�������8�������=��������������������j�������������shell{"msg_id": "8218c830-a736e5ef247730fd4c0f4b41_665704_114", "msg_type": "execute_reply", "username": "ayanamists", "session": "8218c830-a736e5ef247730fd4c0f4b41", "date": "2025-04-29T06:46:43.587893Z", "version": "5.3"}{"date": "2025-04-29T06:46:43.582000Z", "msg_id": "cbe66321-a40d-4a5b-b2f8-a3a4e2c41dbe", "msg_type": "execute_request", "session": "02991bb7-a5cd-446c-b325-4880bbd5f229", "username": "", "version": "5.2"}{"started": "2025-04-29T06:46:43.583706Z", "dependencies_met": true, "engine": "b4934063-66a1-476b-8ccb-108e9bdcb03c", "status": "ok"}{"status": "ok", "execution_count": 4, "user_expressions": {}, "payload": []}
�������8�������=����������������������������������iopub{"msg_id": "8218c830-a736e5ef247730fd4c0f4b41_665704_115", "msg_type": "status", "username": "ayanamists", "session": "8218c830-a736e5ef247730fd4c0f4b41", "date": "2025-04-29T06:46:43.588689Z", "version": "5.3"}{"date": "2025-04-29T06:46:43.582000Z", "msg_id": "cbe66321-a40d-4a5b-b2f8-a3a4e2c41dbe", "msg_type": "execute_request", "session": "02991bb7-a5cd-446c-b325-4880bbd5f229", "username": "", "version": "5.2"}{}{"execution_state": "idle"}
分析一下这次的求值过程
Okay, here's an analysis of the Jupyter kernel communication for evaluating
2 + 3
:
- Client -> Kernel (shell): An
execute_request
is sent containing the code"2 + 3"
.- Kernel -> All (iopub): Kernel broadcasts its status as
"busy"
.- Kernel -> All (iopub): Kernel broadcasts the input code
"2 + 3"
andexecution_count: 4
viaexecute_input
.- Kernel -> All (iopub): Kernel broadcasts the execution result
{"text/plain": "5"}
viaexecute_result
.- Kernel -> Client (shell): Kernel sends an
execute_reply
indicating successful execution ("status": "ok"
).- Kernel -> All (iopub): Kernel broadcasts its status as
"idle"
, ready for new commands.