背景:

前一篇博文通过扩展JMeter的java请求,结合dcm4che2现有的工具包dcmsnd.bat实现了简单的测试DICOM服务器C-STORE SCP性能的尝试。由于借用了现有的dcmsnd.bat命令行工具,会有诸多的局限性,比如:

1)必须构造命令行中的参数,才能调用dcmsnd.bat,操作多此一举
2)无法准确跟踪一张图像上传完成后的准确时间
3)既然要模拟海量用户并发,需要准备对应的数量的文件,无法通过自动生成dcm的三级UID来自动生成海量测试文件。

针对上述情况,本篇博文在剖析dcm4che2的dcmsnd工具源码的基础上,给出了自己的DcmSend类,通过该类可自由控制dcm文件发送,与此同时可通过修改单一文件的各级UID来自动模拟出海量dcm文件,提高了测试效率和准确性。下面就给出具体过程。

dcm4che2介绍:

相较于之前常用的dcmtk和fo-dicom,在dcm4che中对于DICOM标准的实现更全面一些,出现了部分之前少有提及的概念,但不必担心,这些新的概念都是对以往概念的总结和归纳 。在dcmsnd.bat工具包源码中用到的几个新概念主要是DICOM3.0标准第15部分,这里主要介绍附录H中的几个核心概念:
1)Device:这里指的是从服务概念来划分的,并不一定指的是物理上的一台设备,有可能是多台设备(比如PET-CT,我也不是很确定?)。但是通常Device可以认为是物理上的一台设备。
2)Transfer Capability:这个在DICOM3.0标准的附录H中被划分成了NetworkAE、NetworkConnection和Transfer Capability三部分,三者的具体关系如下图:

简单来说,Transfer Capability类同于我们之前介绍的Presentation Context(即描述上下文),两者含有的内容是一样的,都是AbstractSyntax+TransferSyntax,即服务类别+编码格式。但两者所描述的范围不同,Transfer Capability是用于描述设备(暂且认为Device对应一台物理设备)的功能,是设备说明书的一部分;而Presentation Context是我们在讲解DICOM网络传输时用到的概念,是在连接建立的握手过程中用于表明连接双方请求的服务和编码格式的,因此从概念上来说Transfer Capability用于宏观,Presentation Context用于微观;Transfer Capability对应于实体(即设备),而PresentationContext对应于服务(即通讯)
总之简单来说,在编码时,可以通过Transfer Capability来设置连接具体交互时的PresentationContext。这一点在DcmSnd.java类的configureTransferCapability,以及的NetworkApplicationEntity.javamakeAAssociateRQ函数中表现的很明显,在makeAAssociateRQ函数中就是通过TransferCapability来构造AssociateRQ的PresentationContext的。剩下的NetworkApplicationEntity和NetworkConnection两个概念就比较好理解了。
3)NetworkApplicationEntity:代表提供服务的主体,这里要与Device区别开来。举个例子,一台安装Window 7的电脑我们可以称之为Device,而电脑中我们安装了Office、PhotoShop、VisualStudio,分别提供不同的服务,这些安装在Windows7下的软件可以类比做NetworkApplicationEntity,即提供单项服务的主体(即软件)。
4)NetworkConnection:指的就是DICOM中Association对应的底层TCP连接。

编写自己的DcmSnd类:

关于DICOM中Associaton建立的过程,之前我在专栏中介绍过多次DICOM医学图像处理:DICOM网络传输、DICOM医学图像处理:全面分析DICOM3.0标准中的通讯服务模块。dcm4che2与之前使用的dcmtk和fo-dicom相同,只不过开发语言换成了java而已。下面就直奔主题,直接介绍如何抽取DcmSnd.java现有类库中的相关代码,构造自己的DcmSnd类,我这里称之为ZSDcmSend

DcmSnd.java类分析:

具体流程跟之前博文中对于DICOM Association建立完全一样,知道了DcmSnd.java具体构造流程后,我们就直接裁剪掉关于cmd命令行解析的代码,构造我们自己的精简版ZSDcmSend.java。

核心部分代码粘贴如下,流程就是按照上图所示:

public void SendDicomFile()
{try {//set parameters for SCP, remotesetCalledAET("PACS_SCP");setRemoteHost("192.168.24.1");setRemotePort(11110);//set parameters for SCU, localsetCalling("ZSSURE");setLocalHost("127.0.0.1");setLocalPort(11112);DimseRSPHandler rspHandler = new DimseRSPHandler() {@Overridepublic void onDimseRSP(Association as, DicomObject cmd,DicomObject data) {ZSDcmSend.this.onDimseRSP(cmd);}};FileInfo info = new FileInfo(dcmFile);DicomObject dcmObj = new BasicDicomObject();DicomInputStream in = null;try {in = new DicomInputStream(dcmFile);in.setHandler(new StopTagInputHandler(Tag.StudyDate));in.readDicomObject(dcmObj, PEEK_LEN);info.tsuid = in.getTransferSyntax().uid();info.fmiEndPos = in.getEndOfFileMetaInfoPosition();} catch (IOException e) {e.printStackTrace();System.err.println("WARNING: Failed to parse " + dcmFile + " - skipped.");System.out.print("Failure");return;} finally {CloseUtils.safeClose(in);}info.cuid = dcmObj.getString(Tag.MediaStorageSOPClassUID,dcmObj.getString(Tag.SOPClassUID));if (info.cuid == null) {System.err.println("WARNING: Missing SOP Class UID in " + dcmFile+ " - skipped.");System.out.print("Failure");return;}info.iuid = dcmObj.getString(Tag.MediaStorageSOPInstanceUID,dcmObj.getString(Tag.SOPInstanceUID));if (info.iuid == null) {System.err.println("WARNING: Missing SOP Instance UID in " + dcmFile+ " - skipped.");System.out.print("Failure");return;}addTransferSyntaxs(info.cuid,info.tsuid);configureTransferCapability();try {assoc=ae.connect(remoteAE, executor);} catch (ConfigurationException e1) {// TODO Auto-generated catch blocke1.printStackTrace();} catch (InterruptedException e1) {// TODO Auto-generated catch blocke1.printStackTrace();}DataWriter data=new DataWriter(info);String test=new DicomInputStream(info.f).readDicomObject().getString(Tag.SOPInstanceUID);System.out.println(test);try {assoc.cstore(info.cuid, info.iuid, 0, data, info.tsuid,rspHandler);} catch (NoPresentationContextException e) {System.err.println("WARNING: " + e.getMessage()+ " - cannot send ");System.out.print('F');} catch (IOException e) {e.printStackTrace();System.err.println("ERROR: Failed to send - " + ": "+ e.getMessage());System.out.print('F');} catch (InterruptedException e) {// should not happene.printStackTrace();}catch(NullPointerException e){e.printStackTrace();}} catch (IOException e) {// TODO Auto-generated catch blocke.printStackTrace();}}

自定义修改DCM文件UID:

在dcm4che2官网的cookbook中有关于DICOM文件读写的示例,这里有一个“坑”需要注意一下,——之前我一直认为跟dcmtk和fo-dicom操作类似,加载完DicomObject对象后,先remove掉指定Tag元素,然后根据类型采用putString、putInt、putDouble等在添加即可。但是下面一段代码一直没有发生变化:

        DicomInputStream temp=new DicomInputStream(info.f);DicomObject tempObj=temp.readDicomObject();tempObj.remove(Tag.StudyInstanceUID);tempObj.putString(Tag.StudyInstanceUID, VR.UI,"123.123.123.123.1.1");tempObj.remove(Tag.SeriesInstanceUID);tempObj.putString(Tag.SeriesInstanceUID,VR.UI,"123.123.123.123.1.1.1");tempObj.remove(Tag.SOPInstanceUID);tempObj.putString(Tag.SOPInstanceUID,VR.UI,"123.123.123.123.1.1.1.1");temp.close();info.iuid=tempObj.getString(Tag.SOPInstanceUID);FileOutputStream fos=new FileOutputStream(info.f);BufferedOutputStream bos=new BufferedOutputStream(fos);DicomOutputStream dos=new DicomOutputStream(bos);dos.writeDicomFile(tempObj);

后来自己浏览了dcm4che2的Cookbook,发现Java中有FilterInputStream和FilterOutputStream两个字节流,对于DICOM文件修改要想生效应该采用派生自FilterOutputStream的DicomOutputStream。官网实例如下图:

然后我添加了一个修改UID的函数changeTagbyString即可解决自定义修改DICOM文件中UID的目的

    public int changeTagbyString(int[] tag,VR[] vr,String[] strValue)
{if(tag.length!=vr.length || tag.length!=strValue.length ||strValue.length!=vr.length){System.out.println("parameters set error!");return -1;}if(tag==null || vr==null ||strValue==null || dcmFile==null){System.out.println("parameter set error!");return -2;}try {DicomInputStream temp=new DicomInputStream(dcmFile);DicomObject tempObj=temp.readDicomObject();for(int i=0;i<tag.length;++i){tempObj.remove(tag[i]);tempObj.putString(tag[i], vr[i],strValue[i]);}temp.close();//save your changesFileOutputStream fos=new FileOutputStream(dcmFile);BufferedOutputStream bos=new BufferedOutputStream(fos);DicomOutputStream dos=new DicomOutputStream(bos);dos.writeDicomFile(tempObj);dos.close();} catch (FileNotFoundException e) {// TODO Auto-generated catch blocke.printStackTrace();} catch (IOException e) {// TODO Auto-generated catch blocke.printStackTrace();}return 0;
}

至此,自定义ZSDcmSend.java类就算完成了,配置一个PACS服务器测试一下看看结果吧。

后记:

写好了自定义的ZSDcmSend.java类还只是扩展JMeter的Java请求的第一步,后续好需要配合AbstractJavaSamplerClient类在合适的地方添加采样开关,同时需要统计相关数据才能具体测算出每幅图像传输的时间和服务器响应时间,进而实现测试PACS服务器的性能的目标。后续还会继续更新。
(未完……)

作者:zssure@163.com
时间:2015-05-24

DICOM:基于JMeter+dcm4che2测试PACS服务器性能的解决方案(续篇)相关推荐

  1. DICOM:基于JMeter+dcm4che2测试PACS服务器性能的解决方案(前篇)

    背景: 目前对于传统WEB网站性能(压力/负载)的测试工具有很多,loadrunner.iperf.siege等,操作都比较简单,这里就不介绍了.然而对于医疗领域内的服务器,通常指的是DICOM服务器 ...

  2. jmeter压力测试及服务器性能监控

    1.jmeter 下载 Apache JMeter - Download Apache JMeter 2.  下载完成之后进行解压. 下载之后得到ZIP文件 解压之后程序文件夹,jmeter为解压程序 ...

  3. Jmeter工具使用-分布式架构和服务器性能监控解决方案

    Jmeter工具使用-分布式架构和服务器性能监控解决方案 参考文章: (1)Jmeter工具使用-分布式架构和服务器性能监控解决方案 (2)https://www.cnblogs.com/zhengs ...

  4. 如何测试web服务器性能,如何执行Web服务器性能基准测试?

    本文概述 你知道你网站的平均响应时间吗?你知道你的网站可以处理多少个并发用户吗? 负载测试对于Web应用程序了解网站容量至关重要.如果要选择Web服务器, 那么要做的第一件事就是执行负载测试, 然后看 ...

  5. mqtt服务器性能H3,运用 MQTT-JMeter 插件测试 MQTT 服务器性能

    MQTT 简介 MQTT(Message Queuing Telemetry Transport,消息队列遥测传输)是 IBM 开发的一个即时通讯协议,它比较适合于在低带宽.不可靠的网络的进行远程传感 ...

  6. java pacs上传服务_有没有办法将DICOM数据发送到远程PACS服务器上的特定目录?

    我得到了SCU和SCP之间的DICOM服务器和图像通信的诀窍.我正在使用一个ClearCanas PACS服务器,并且可以访问webgui.使用以下代码,我可以将DICOM dt从SCU(我的计算机) ...

  7. SPEC测试arm服务器性能,SPECJVM2008测试处理器性能_服务器评测与技术-中关村在线...

    首先,我们使用SPECJVM2008测试最新至强E5处理器的虚拟化性能. SPECJVM2008是一种通用的多线程Java基准测试工具,它能够反映JRE(Java Runtime Environmen ...

  8. 转载:运用MQTT-JMeter插件测试MQTT服务器性能

    今天我们介绍XMeter团队带来的新版MQTT-JMeter插件,您可以更为方便地添加MQTT连接.发布.订阅取样器,构造组合的应用场景,例如背景连接.多发少收.少发多收,计算消息转发时延等.利用该插 ...

  9. 测试pppoe服务器性能,PPPOE服务器测试

    PPPOE服务器测试 说明一下,最好找另外一台在同一广播域上的计算机来测试,因为有很小的几率你在本机上测试成功了,但是网络上的计算机却不能连接.这可能是由于物理层的问题导致的,也可能是其他的问题,这里 ...

最新文章

  1. 一分钟AI | 特斯拉发布电动卡车,扫地机器人被曝窥探个人隐私
  2. IE8 chrome 中 table隔行换色解决办法
  3. 没学c语言可以学python_学了Python,但是没有学c,直接去学c++是可行的吗?
  4. hexo+githup搭建属于自己的博客
  5. python统计提取数量_python中统计计数的几种方法和Counter的介绍
  6. 干货 | C语言系列3——常量,运算符,常用数学函数......
  7. 一文讲清,MySQL数据库一行数据在磁盘上是怎么存储的?
  8. su 切换,提示:“密码不正确”;
  9. html5 datepicker ios,iOS DatePicker日期时间选择器【组件】
  10. javaul材质包下载_我的世界:7月商城作品下载量再创新高 TOP10中有哪些你喜欢的?...
  11. Clover Configurator 5.16.0.0 黑苹果引导四叶草配置工具
  12. 东风谷早苗 简单的水题
  13. Android以太网卡配置启动流程和双网卡同时支持的实现
  14. 祝萍:后疫情时代,医美运营既要走心也要反套路
  15. 第三方登录数据库用户表结构设计
  16. WIFI无线传输,图传。视频提节
  17. 2019年_BATJ大厂面试题总结-华为篇
  18. PHP做大转盘抽奖的思路,jQuery实现幸运大转盘(php抽奖程序)抽奖程序
  19. 从零开始学习Java设计模式 | 设计模式入门篇:设计模式概述
  20. RTL8189ES/ETV/FTV系列模块定频软件操作手册

热门文章

  1. python爬虫 爬取JD商城快消品的保质期
  2. 最佳适应算法(best fit)
  3. 无线运动耳机品牌排行榜前十名,目前最火爆的六款运动耳机推荐
  4. 为何要从用户角度出发来思考问题
  5. 猫捉老鼠java_一个用java程序写的猫捉老鼠程序
  6. 大数据核心技术与应用实战峰会(上):大数据在多行业内大放异彩
  7. Cycle-Consistent Inverse GAN for Text-to-Image Synthesis
  8. 微信小程序反编译及源码抓取(2021最新)
  9. 直播网站程序源码,搜索框实现快速搜索功能
  10. html网页课件,HTML网页制作-教学课件.ppt