简介:Conquest DICOM Server 是一款用于医疗影像系统联调与测试的开源 DICOM SCP 服务器工具,面向 HIS/RIS 实施工程师、医疗软件开发者及系统管理员,可模拟 DICOM 设备接收和响应请求,帮助验证 PACS 集成、工作列表(Worklist)查询及网络通信是否正常。压缩包约 22.28MB,内含 10 个主要文件,覆盖核心可执行程序 ConquestDICOMServer.exe、DgateServ.exe、CqDicom.dll 动态库,以及 .bat 启动/安装脚本、DICOM 字典文件、匿名化脚本和 7-Zip 命令行工具等,便于在本地快速搭建测试环境并处理影像收发、数据匿名化与日志监控。已有 710 人浏览学习。借助该工具,开发者可低成本完成 DICOM SCP/SCU 兼容性测试、系统性能评估和跨 PACS 数据迁移演练,有效排查通信异常并保障医疗影像数据在传输与存储中的合规安全。 手里攒了一批CT、MR的DICOM文件,需要验证自己写的PACS客户端能不能正常收发;或者刚搭好一套归档系统,想确认C-STORE、C-FIND这些服务到底通不通。这种时候手动开个软件去发图、点半天界面查图,效率实在太低。我一般直接甩一个轻量的DICOM服务器到测试环境里,用脚本和命令行工具来回打——这套东西里,ConquestDICOMServer是我用得最顺手的一个测试工具。
ConquestDICOMServer是一个开源、单机可跑的DICOM服务器,支持Windows、Linux、macOS,核心就一个几百KB的进程,装上以后可以当作迷你PACS用。它原生支持DICOM C-STORE(存储)、C-FIND(查询)、C-MOVE(拉取)、C-GET(直接取)、MPPS、Query/Retrieve这些常用服务,还能接SQLite、MySQL、PostgreSQL存储索引。写DICOM应用的开发者拿它当测试靶子,医院信息科拿它做离线阅片归档,硬件厂商拿它验证设备输出——三个场景我都干过。
这篇文章就围绕ConquestDICOMServer,把我在测试环境里从安装到对接的完整操作理一遍,包括选型原因、配置参数、常见坑和处理思路,偏向DICOM开发者和运维人员视角,如果你是刚接触DICOM协议测试的新人,这套流程可以直接照抄。
1. 为什么测试DICOM服务我会选Conquest而不是其他方案
1.1 测试DICOM应用的几个典型场景
DICOM服务端的测试,说起来无非是三四件事:
第一类是验证自己写的SCU能正常把图发给服务端。你写了个C-STORE SCU,要给影像归档发图,服务端得能正确收图、返回成功状态,最好还能把收到的文件落盘,方便回读校验。
第二类是验证自己写的SCP能正常收图。你写了个简易存档服务,需要用一个客户端源源不断发来多模态数据,验证并发、大文件、序列完整性。没有现成SCP做对象,测试就得自己造。
第三类是验证Query/Retrieve链路。客户端按患者、检查、序列查询服务端,拿到结果列表后再发起C-MOVE或者C-GET把图拉回来。这要求服务端有完整的数据索引和检索能力。
第四类是验证设备或系统的DICOM连通性。比如新到一台超声机,医院要让它能往PACS发图,那就得先有个兼容的DICOM服务端做联调,等接口确认了再接入生产PACS。
这些场景下,可以选的工具不少:DCMTK的storedsmpr可以当简易SCP,Orthanc也能做服务端,还有像dcm4chee这类重型的。但要么是功能单一不好扩展,要么是配置文件太厚,要么是部署起来要数据库、要应用服务器,测试环节根本不想碰这么重的依赖。
1.2 Conquest的优势与取舍
ConquestDICOMServer在这个定位里属于“小而全”的代表。它的优势非常明显:
- 部署极轻:Windows解压即用,Linux下编译或直接跑二进制,几乎零依赖。一个进程全搞定,不需要容器化,不需要装额外服务。
- 协议支持全:C-STORE、C-FIND、C-MOVE、C-GET、MPPS、Modality Worklist这些都是原生支持的,对测试来讲已经覆盖了90%的日常需求。
- 数据库可选:纯文件模式(索引直接存文件)可以零数据库启动,适合快速验证;要测试大批量数据时切成SQLite或者MySQL,索引检索效率会好很多。
- 配置集中:所有配置都在一个dicom.ini里,改起来直观,不需要翻GUI点半天。
当然它也有缺点:界面是真的简陋,基本没有现代化Web管理端,日志也偏原始;高并发场景下性能不如Orthanc这类为服务端场景优化的项目;部分新DICOM标准扩展(比如DICOMweb)不支持。但测试场景里这些缺点影响不大——要的是快速起一个服务端,能收到图、能查到记录、能配合流程验证。
总结:生产级归档系统我不会用Conquest,但测试环境里我不会再绕开它。是那种能一直留在工具箱里的实用工具。
2. 测试环境搭建:安装与初始配置
2.1 安装与启动
以Linux环境为例,我从源码编译的方式比较稳。Conquest代码仓库提供了Unix Makefile,依赖其实只有libpng、libjpeg、libssl和sqlite的开发包。Ubuntu/Debian下先把这些装上:
sudo apt-get install -y build-essential libpng-dev libjpeg-dev libssl-dev libsqlite3-dev git clone https://github.com/innolitics/dicom3tools.git git clone https://github.com/vincent/sftp.git注意不对,ConquestDICOMServer的源码仓库是单独的,官方原版在SourceForge上,GitHub上有一些镜像。我一般不自己编译,直接用发行版或者官方编译好的二进制,省时间。如果非要源码构建,核心步骤是:
git clone https://github.com/winfsp/dicomserver.git # 或者从SourceForge拉 cd dicomserver make linux ./dgate --version编译完启动:
./dgate如果看到类似Conquest DICOM Server, Version 1.5.0的输出,说明起来了。默认监听端口是5678,这是Conquest编译期写死的默认端口,可以后面在dicom.ini里改。
Windows下更简单,下载官方Windows安装包,解压后直接双击dgate.exe,服务默认作为控制台程序跑起来。要装成系统服务,官方包里带了install_service.bat脚本。
2.2 初始配置:dicom.ini核心参数
Conquest所有配置在dicom.ini里,位置和编译时的配置路径有关,常见的是程序同目录或者/etc/dicomserver。先看几个最关键的:
# 服务端AE Title AETitle = CONQUESTSVR # TCP监听端口 TCPPort = 5678 # 存储路径,收到的文件都会放这里 RootPath = /var/lib/dicomserver/data # 使用的数据库类型,支持 file, sqlite, mysql, postgres等 Database = sqlite # 数据库连接信息,sqlite模式下是文件路径 SQLitePath = /var/lib/dicomserver/data/dicom.db # 最大PDU大小,越大传输大文件越省事,65536是个安全值 MaxPDUSize = 65536 # 允许的peer(对端)最大PDU,测试时可以设大点 MaxPDUPeer = 65536 # 是否启用压缩传输(0=不启用,1=启用) Compression = 0几个容易忽略的点:
RootPath:这个目录是Conquest存储DICOM文件的实际位置。在文件数据库模式下,它会把收到的dcm文件按序列/实例打散存成一个复杂目录树。测试时如果你想快速找到刚发过来的原始文件,直接在这里面按时间sort是找不到的,它有自己的一套目录规则,用dgate --scan或者数据库查询反而方便。
AETitle:这是Conquest和外部通信的身份标识,任何客户端发起关联请求时,请求的Called AE Title必须和这里一致,否则关联会被拒绝。DICOM测试里大量“连接不上”“Unknown AE Title”的报错,90%是这个字段没对齐。
MaxPDUSize:PDU(Protocol Data Unit)是DICOM传输层的数据包。如果两端的PDU大小不匹配,就按小的那个走。测试时把Conquest设成65536,客户端用4096也能正常通信,但是网络利用率会差,传大的增强CT序列时会明显慢。
改完配置重启dgate,然后看日志确认配置生效。日志默认打到控制台,重定向到文件更方便:
./dgate >> /var/log/conquest/dgate.log 2>&1 &2.3 验证服务端存活
配置完了先自测一下,最简单的办法是拿DCMTK里的工具去ping一下。DCMTK里的echoscu是标准DICOM C-ECHO(验证关联)客户端,用法:
echoscu -aet TESTSCU -aec CONQUESTSVR 127.0.0.1 5678返回类似:
Requesting Association... Association Accepted (Maximum PDU: 16384) Sending Echo Request... Received Echo Response (Success) Releasing Association...只要看到Association Accepted和Echo Response (Success),说明Conquest服务端关联、C-ECHO服务都是通的,接下来可以正式测试。
这一步我每次都会做,不是效率问题,而是后续所有问题排查都需要先确认“底层链路通不通”。有个干净基线,后面出问题才不至于来回猜。
3. 核心测试能力拆解:从C-STORE到Query/Retrieve
3.1 C-STORE:数据上送与接收测试
C-STORE是最常用的DICOM操作,对应“把图发过去”。测试时用DCMTK的storescu作为SCU,Conquest作为SCP:
storescu -aet SCUTEST -aec CONQUESTSVR 127.0.0.1 5678 /path/to/ct_001.dcm一个大坑:Conquest对非标准DICOM文件兼容性远不如Orthanc那样严格。比如有些厂商导出的dcm文件头不完整或者Transfer Syntax不对,Conquest常常收下以后直接拒绝建立索引,表现为“收了文件,但查询列表里没有记录”。
我遇到一次:某型号的乳腺机导出的DICOM文件,MetaHeader里TransferSyntax写的是1.2.840.10008.1.2.1(Explicit VR Little Endian),但实际数据是压缩过的,storescu发送时其实没报错,Conquest也给了Success状态,结果查询时发现图像缺失。这种情况其实是发送端没有把文件正确转码,不是Conquest的问题。建议发送端统一先预处理成规范的Explicit Little Endian或JPEG无损格式再发。
批量发送多组文件时,storescu可以一次传多个路径:
storescu -aet SCUTEST -aec CONQUESTSVR 127.0.0.1 5678 /data/patient1/*.dcm /data/patient2/*.dcmC-STORE测完之后,检查三件事:
- 服务端返回状态:每个文件会返回对应状态码,
0000代表成功,其他如A700(out of resources)、0122(SOP Class not supported)都要排查。 - 文件是否落盘:去RootPath下的目录确认有没有新增dcm文件。
- 数据库是否建立索引:用SQLite查询确认patient/study/series/instance四个层级都建好了:
sqlite3 /var/lib/dicomserver/data/dicom.db "SELECT * FROM DICOMStudies;"3.2 C-FIND:查询服务验证
C-FIND是客户端向服务端发起查询,服务端返回匹配记录。Conquest的C-FIND响应逻辑严格按Patient Root Query/Retrieve Information Model实现,所以查询键的层级要符合DICOM标准。
DCMTK的findscu支持通过query文件指定查询条件:
# find_query.dcm (0010,0010) PN PatientName = "TEST*" (0008,0020) DA StudyDate = "" (0020,000D) UI StudyInstanceUID = ""保存成dcm格式,然后执行:
findscu -aet SCUTEST -aec CONQUESTSVR 127.0.0.1 5678 -q -f find_query.dcm或者直接命令行写查询条件,但比较复杂,我更喜欢query文件。测试C-FIND时几个典型场景:
- 按患者姓名模糊查询:测试PatientName通配符匹配逻辑。
- 按检查日期范围查询:测试StudyDate的区间条件。
- 按StudyInstanceUID精确查询:测试唯一键检索——只返回一条记录。
C-FIND最容易踩的坑是QueryRetrieveLevel填错导致的空结果。比如明明是Study级查询,但把SOPInstanceUID(属于Image级)塞进了查询键,Conquest会返回空。level的对应关系:
| QueryRetrieveLevel | 查询键示例 | 返回层级 |
|---|---|---|
| PATIENT | PatientID, PatientName | 患者 |
| STUDY | StudyInstanceUID, StudyDate | 检查 |
| SERIES | SeriesInstanceUID, Modality | 序列 |
| IMAGE | SOPInstanceUID | 图像实例 |
3.3 C-MOVE和C-GET:拉取数据验证
C-MOVE是两个服务端之间转存的操作,客户端自己不接收数据,而是指示源服务端把数据发给另一个服务端。测试时客户端就是“调度者”。
DCMTK的movescu用法:
movescu -aet SCUTEST -aec CONQUESTSVR 127.0.0.1 5678 -aem TARGETAE -k QueryRetrieveLevel=STUDY -k StudyInstanceUID="1.2.840.113619.2.55.3.2831169228.682"关键参数-aem指定目标AE Title。这里一定要保证:
- Conquest的dicom.ini里定义了TARGETAE这个对端,否则它会报Unknown AE Title并拒绝发送。
- TARGETAE对应的主机和端口可达,且那台机器上得有监听中的DICOM服务。
在dicom.ini中对端配置格式:
[TARGETAE] Host = 192.168.1.100 Port = 11112 AETitle = TARGETAEC-GET和C-MOVE的区别是,C-GET由SCP直接把数据发给SCU,不经过中间服务端,操作逻辑更直接。Conquest对C-GET的支持也不错,测试时可以用getscu:
getscu -aet SCUTEST -aec CONQUESTSVR 127.0.0.1 5678 -k QueryRetrieveLevel=SERIES -k SeriesInstanceUID="1.2.840.113619.2.55.3.2831169228.682.1"C-MOVE和C-GET的测试常用来验证网络传输、多跳场景,以及验证对端SCP是否能正确处理接收。
3.4 多模态与并发测试
Conquest作为测试靶子还常被用来做并发压力验证。比如同时开3个storescu往同一个AE Title发图,验证Conquest的并发处理能力。实测下来Conquest多线程接受C-STORE请求的能力很稳,但并发写入数据库索引时容易出现锁冲突,日志里偶尔能看到database is locked的SQLite报错。
要改善并发写入:
- 换MySQL/PostgreSQL:SQLite在写并发高时锁等待明显,换数据库后缓解很多。
- 调大
MaxSimultaneousAssociations,默认好像是4还是8,测试时可以调大。 - 确认日志路径没有挤爆磁盘:调测并发时日志量很大,不清理的话磁盘会满。
4. 常见问题与排查技巧实录
4.1 关联失败:Association Rejected
最常见的问题,症状是客户端报:
Requesting Association... Association Rejected: Result: 2, Source: 1, Reason: 1原因基本就三类:
- Called AE Title不对:客户端设置的Called AE(
-aec参数)和Conquest的AETitle不一致。检查dicom.ini里的AETitle,或者客户端参数拼写。 - 端口不通:Conquest监听端口写错,或者防火墙挡了。用
netstat -tlnp | grep 5678确认监听,nc -zv 127.0.0.1 5678测连通。 - 对端AE没注册:Conquest默认对未知的Calling AE会直接拒绝(
RequireCalledAETitle之类的配置)。可以在dicom.ini里加:
[SSSCU] Host = * Port = 0 AETitle = *这样允许任意AE接入,但生产不要这么配,只限于本机测试。
4.2 能发送成功但文件找不见
storescu返回Success,但数据库查不到记录。排查思路:
- 看日志:Conquest日志会打印每次关联、存储的明细,有没有报“Ignored”或者“Failed to store file”。
- 看RootPath:如果文件在目录下存在但数据库没有,大概率是数据库写入失败,关注SQLite锁错误。
- 看SOP Class:如
StoredPrint(打印对象)、Encapsulated PDF这类非图像SOP Class,Conquest的默认配置可能不处理。需要在[SSSCU]下的SOPClass里显式启用。
4.3 C-FIND查到数据但C-MOVE拉不回来
这个坑也很典型。C-FIND能找到Study,但发C-MOVE时报“Cannot Understand”或者“No Such Object Instance”。
最常犯的错误是C-MOVE的查询键层级和C-FIND混用。C-FIND可以用StudyDate这种通用键,但C-MOVE对QueryRetrieveLevel=STUDY而言,核心检索键实际是StudyInstanceUID和PatientID。把C-FIND的返回结果完整保留,再作为C-MOVE的输入键,一般不会错。
还有一个冷门坑:Conquest在文件数据库模式下,如果数据文件的路径损坏(比如手动改过RootPath目录),C-FIND能读到数据库索引,但C-MOVE去读文件时找不到对应实体,会报“File Not Found”。这种情况要去检查RootPath和数据库记录的路径一致性。
4.4 日志定位技巧
Conquest日志级别可以通过dicom.ini的LogLevel字段调整,从0到3,越高越详细。实际排查问题时我常直接调到2,结合系统级抓包工具对比两端的DICOM消息。配合wireshark,在dcm4che和Conquest之间的这条链路上看关联请求、P-DATA的流转,能定位绝大多数协议层问题。
搭建测试环境时留好一个常驻终端的日志窗口,能让你少走很多弯路。
4.5 常见问题速查表
| 症状 | 可能原因 | 检查/处理 |
|---|---|---|
| Association Rejected | AE Title不匹配、端口不通、对端未注册 | 核对AETitle、TCPPort、dicom.ini对端条目 |
| 发送成功但查询无记录 | 数据库写入失败/文件未正常解析 | 看日志、检查SQLite锁、确认SOPClass支持 |
| C-MOVE返回Cannot Understand | 查询键层级不对/文件缺失 | 核对QueryRetrieveLevel与查询键,确认RootPath完整 |
| 日志刷磁盘 | LogLevel过高/并发测试量大 | 调低日志级别、定期切割日志 |
| Windows下服务启动失败 | 端口被占/权限不足 | netstat查端口、管理员权限启动 |
5. 扩展玩法:自动化测试与数据回写
5.1 把Conquest接入自动化测试脚本
测试环境不只有人工点击,我更习惯把它和脚本绑定。比如用Python的pydicom先造一个最小DICOM测试文件(包含必填标签),再用子进程调用storescu发给Conquest,再查数据库验证记录。整个链路可以在CI里跑:
import pydicom from pydicom.dataset import Dataset from pydicom.uid import generate_uid ds = Dataset() ds.PatientName = "AutoTest^Patient" ds.PatientID = "PID-0001" ds.StudyInstanceUID = generate_uid() ds.SeriesInstanceUID = generate_uid() ds.SOPInstanceUID = generate_uid() ds.Modality = "CT" ds.SOPClassUID = "1.2.840.10008.5.1.4.1.1.2" ds.file_meta = pydicom.dataset.FileMetaDataset() ds.file_meta.MediaStorageSOPClassUID = ds.SOPClassUID ds.file_meta.MediaStorageSOPInstanceUID = ds.SOPInstanceUID ds.file_meta.TransferSyntaxUID = "1.2.840.10008.1.2.1" ds.is_little_endian = True ds.is_implicit_VR = False ds.save_as("test_ct.dcm", write_like_original=False)然后在同一个脚本里,用subprocess调用storescu,再用sqlite3库去读Conquest的索引库,确认新增记录。这一套可以完全无人值守。
5.2 作为Worklist模拟器
Conquest还集成了**Modality Worklist(MWL)**服务,这个在生产环境的设备联调中特别实用。CT、MR机器要向RIS系统拉取预约检查列表,开发时没有真实的RIS,就可以用Conquest模拟MWL SCP。
配置Worklist的SOP Class,然后在数据库里插入Worklist条目(patient、scheduled procedure等),设备端用MWL C-FIND就能查到。这在验证设备“预约接收”流程时非常有用。
不过Conquest的Worklist功能相对简陋,只具备基础查询,不保证对复杂排班逻辑的支持。要真实模拟复杂排班规则,建议用专用的模拟器(比如DCM4CHE的MWL工具),但Conquest作为轻量的入门版是够了。
5.3 数据回写与迁移
Conquest里的dicom文件也能往外迁移。用C-MOVE把数据从Conquest拉到另一个PACS,或者直接把RootPath里的文件用脚本转出来。要注意DICOM文件的SOPInstanceUID和数据库记录必须一致,直接拷文件到另一个系统会丢失索引,所以建议走标准的C-MOVE或C-GET流程。
6. 竞品对比:什么场景选Orthanc,什么场景选Conquest
可能你会问,现在Orthanc也很流行,为什么不直接用Orthanc?
我实际两个都用过,做个简单的对比:
| 特性 | ConquestDICOMServer | Orthanc |
|---|---|---|
| 部署复杂度和资源占用 | 极低,单进程 | 较低,但需要多进程和Web服务 |
| REST API | 基本没有 | 非常完善,主流方案 |
| DICOMweb | 不支持 | 原生支持 |
| 数据库 | file/sqlite/mysql/postgresql | SQLite或PostgreSQL |
| 高并发/大数据量 | 一般 | 更好 |
| 管理界面 | 简陋 | 自带Web UI,直观 |
| 协议扩展 | 传统DICOM全套 | 更现代,支持更广 |
| 适合场景 | 轻量测试、临时靶子、快速联调 | 服务平台、持续集成的服务端底座 |
简单说:Conquest是“轻量靶子”,Orthanc是“服务底座”。测试一个人写的小工具,发一批数据验证逻辑,用Conquest足够;但要做一套自动化集成测试的常驻服务,天天被脚本调来调去,还需要Web API去查数据、删数据,那Orthanc的REST API会让你省心很多。
我的建议是:测试机和持续集成环境里,可以两个都装,一个专门模拟“传统设备级SCP”,一个用来处理需要动态管理数据的自动化链路。反正两个都是开源工具,不冲突。
7. 测试DICOM工具链的完整落地建议
最后把整个Conquest测试环境的搭建变成一套“可复制”的流程:
- 单独准备一台测试机或者虚拟机:不建议和开发环境混跑,DICOM测试会产生大量镜像文件,磁盘用量增长很快。
- 装好Conquest并跑通C-ECHO:这是基线,一切测试都从它开始。
- 准备一批标准的测试DICOM文件:至少包含CT、MR、XA各一份,Transfer Syntax尽量覆盖无压缩和JPEG无损。来源可以从公开数据集下载,比如TCIA。
- 配置好对端AE:只加测试专有AE,不想逐个加就配通配AE。
- 写一套自动化脚本:C-STORE、C-FIND、C-MOVE、C-GET各一例,作为回归测试用例,每次代码改动后跑一遍。
- 定期清理数据:conquest数据库文件积攒得很快,测试数据不清理到后期会拖慢查询,脚本定期删库重建即可。
按这几个步骤做下来,以后再接到“帮测一下DICOM服务”的请求,基本能在一个小时内给出一个可靠的结果。测试的目的不是把环境搭得有多豪华,而是快速、可重复地验证问题。ConquestDICOMServer就是那种不起眼、但能在关键时候替你省下一整晚的测试基础设施。
本文还有配套的精品资源,点击获取