基础通信实战
约 1403 字大约 5 分钟
PACSDICOMfo-dicomC-ECHO
2026-03-20
如果只是分别理解 6.C-ECHO、7.C-FIND、8.C-MOVE、9.C-STORE 的语义,还不算真正能落地。实际联调时,更常见的问题是“不知道第一步跑什么、第二步验证什么、失败后该回头查哪里”。
这一篇把基础 C 系列通信整理成一条最小实战链路,目标不是覆盖所有协议细节,而是给出一个能真正跑起来的排查顺序。
1. 实战目标
这里假设你要完成一个最常见的场景:
- 先确认本地程序可以连通 PACS。
- 再查询某个患者或检查的元数据。
- 最后把目标影像取回本地,或者把本地影像发送给 PACS。
也就是说,这篇文档覆盖的是基础 C 系列命令里最常见的两条链路:
- 查询并取回:C-ECHO -> C-FIND -> C-MOVE 或 C-GET。
- 本地上传:C-ECHO -> C-STORE。
2. 开始前先准备好这些参数
无论是哪条链路,都建议先准备一份明确的节点配置:
- PACS 地址。
- PACS 端口。
- 本地 Calling AE Title。
- 远端 Called AE Title。
- 如果要做 C-MOVE,本地接收端的 AE Title 和监听端口。
如果这些参数还没对齐,先回看 2.AE Title 与节点配置 和 3.SCU 与 SCP 角色说明。
3. 第一步:先跑 C-ECHO
第一步永远建议先做 6.C-ECHO,因为它能快速回答一个最关键的问题:
当前这组 IP、端口、AE Title 组合,是否至少能建立基本 DICOM 通信。
最小写法可以是:
var client = new DicomClient(pacsIp, pacsPort, false, localAet, remoteAet);
var request = new DicomCEchoRequest();
request.OnResponseReceived += (req, response) =>
{
Console.WriteLine($"C-ECHO status: {response.Status}");
};
await client.AddRequestAsync(request);
await client.SendAsync();如果这一步失败,就先不要继续调 C-FIND 或 C-MOVE,优先排查:
- 地址和端口是否正确。
- AE Title 是否写反或写错。
- PACS 是否真的开放了对应服务。
4. 第二步:用 C-FIND 验证业务查询
链路打通后,再进入 7.C-FIND。这一步的目的不是马上取回影像,而是先确认:
- 你是否真的能查到目标对象。
- 查询级别和条件是否写对。
- 后续检索要用的 UID 是否能拿到。
一个常见的 Study 级查询示意:
var request = new DicomCFindRequest(DicomQueryRetrieveLevel.Study);
request.Dataset.Add(DicomTag.PatientID, patientId);
request.Dataset.Add(DicomTag.StudyInstanceUID, string.Empty);
request.Dataset.Add(DicomTag.StudyDate, string.Empty);
request.OnResponseReceived += (req, response) =>
{
if (response.Dataset == null) return;
Console.WriteLine(response.Dataset.GetSingleValueOrDefault(DicomTag.StudyInstanceUID, string.Empty));
};
await client.AddRequestAsync(request);
await client.SendAsync();这一步真正重要的产物,通常是:
StudyInstanceUIDSeriesInstanceUID- 患者和检查层面的关键元数据
这些结果会直接决定后面的 C-MOVE 或 C-GET 能不能发对。
5. 第三步:按场景选择取回方式
查到目标 UID 后,接下来要判断是用 8.C-MOVE 还是 10.C-GET。
适合用 C-MOVE 的情况
- 你有独立的本地接收端。
- PACS 可以反向连接这个接收端。
- 想让 PACS 主动把影像推送给指定 AE。
适合用 C-GET 的情况
- 不能让 PACS 反向连接本地。
- 希望在当前关联上直接接收对象。
- 本地已具备处理 C-STORE 子操作的能力。
如果是最传统的 PACS 联调流程,一般还是优先 C-MOVE。
6. 第四步:为 C-MOVE 准备接收端
如果你选择 C-MOVE,那么在发请求前,本地必须先准备好 9.C-STORE 接收端。最小思路通常是:
var server = DicomServer.Create<PacsCStoreSCP>(port: 104);然后再构造 MOVE 请求:
var request = new DicomCMoveRequest(destinationAeTitle, studyInstanceUid);
await client.AddRequestAsync(request);
await client.SendAsync();这一步最容易犯的错误有两个:
- 本地接收端没启动,就先发了 MOVE。
destinationAeTitle在 PACS 侧根本没有正确注册。
7. 如果你的场景是本地上传
不是所有基础通信都是“从 PACS 取影像”。另一条很常见的链路,是本地把对象发送给 PACS:
- 先跑 C-ECHO。
- 再直接走 9.C-STORE。
一个最小上传示意:
var request = new DicomCStoreRequest(filePath);
request.OnCStoreRequestResponseReceived += (req, resp) =>
{
Console.WriteLine($"C-STORE status: {resp.Status}");
};
await client.AddRequestAsync(request);
await client.SendAsync();如果业务要求“确认对端已经承诺保存”,那就不能停在 C-STORE,需要继续进入 14.Storage Commitment 流程总览 那组文档。
8. 一个推荐的联调顺序
如果你是第一次联调 PACS,可以按下面顺序推进:
- 跑 C-ECHO,验证链路。
- 跑 C-FIND,验证查询条件和返回字段。
- 选择 C-MOVE 或 C-GET,验证检索链路。
- 如果要接收影像,单独验证本地 C-STORE SCP。
- 如果要上传影像,单独验证本地作为 C-STORE SCU 的发送链路。
不要一开始就把所有命令堆在一起调。先把每一步拆开验证,问题会清楚得多。
9. 实战排查建议
基础通信最常见的失败点通常集中在下面几类:
- AE Title 不一致。
- 查询级别不对,导致拿不到后续检索需要的 UID。
- C-MOVE 的目标接收端没有配置好。
- 本地接收端能启动,但没有正确处理 C-STORE。
- 把 C-STORE 成功误认为整个业务流程完成。
如果你已经能把这篇里的流程跑通,后面再看扩展 DIMSE 或 Storage Commitment,会轻松很多。