第二篇,前面都是闲扯 其实正文现在才开始,这次是把压箱底的东西都拿出来了。 首先我们今天要干的事是实现一个echo响应测试工具 也就是echo 的scu,不是实现打印作业管理么。同学我告诉你还早着呢。本来标题取的就是《dicomviewer 第二弹 之 实现打印管理》名字多霸气,最后我又改回来了。
 
首先你得把数据组织方式搞懂 那就是pdu  和dimse  元素  数据元素。然后基于这之上你得把协商连接这块搞懂 ,协商连接都没通过不用说后面的了。然后你得把实现一个功能 比如打印 ,scu跟scp之间你来我往的 过程和概念搞懂 也就是dimse 然后才是服务类。最够你全都理解了 并且写出东西来了能跟医院的设备正常 连接和操作了,那么恭喜你 差不多了。

最后要说的是: 解析dicom文件那篇你们都已经看过了,dicom网络通讯跟解析文件是一样的 只不过解析的是socket数据流里的 元素 数据结构本身是一样的,然后他有一些规范和标准 ,这就是dimse 和服务类 这些好像都在dicom标准的第四章  第八章 第七章 。

实现这一大坨的东西 有点望而却步了吧,其实总结起来就一句话 概括 按照dicom标准 封装数据 处理数据 ,然后根据特殊的参数和应用场景 依规范响应数据,。
 
好废话少说,开工 看过 标准简介那篇博客 的都知道:
PDU是一种数据结构 dataElement是一种数据结构
pdu结构总共7种 其中用于连接控制的就占了6种
A-Associate—RQ PDU
连接请求协议数据单元,用于关联请求。
A-Associate.AC PDU
基于DICOM标准的医学图像通信过程的实现
连接接受协议数据单元,
A.Associate—RJ PDU
连接拒绝协议数据单元,
A-Release-RQ PDU
用于对关联请求的应答。
用于拒绝关联请求。
连接释放请求协议数据单元,
A-Release.RSP PDU
连接释放响应协议数据单元,
A.Abort PDU
传输内容的pdu只有一种P.DATA.TF PDU,
当通讯双方建立了关联之后,就可以使用P.DATA-TF所提供的传输服务来实现不同的通信功能了。
 总之你在进行后面的dimse发送之前先得建立连接,否则你什么也搞不了。
好下面就协商连接的pdu进行分析:

你问这图是怎么来的 dicom 第八章 31页。是不是跟上面说的是一样的 开始两字节 然后4字节表示长度,只不过这个更详细了。协商连接pdu说起有6种 其实有好多是大同小异 比如Associate pdu, 他分rq 和ac  rq是请求 ac是响应。
我把协商连接概述一下

概述之前,什么是通讯:
还是炒剩饭 我又得把以前说过的话像背书一样的背一遍了 其实他确实是那么回事。什么是通讯 :
命令tag +数据tag  一起组成sop ,就好象说一句这样的话:“把这根萝卜拿去给我切了”    “喏  ,萝卜” 。其实这就是通讯  跟人与人之间传达意思一样。说话的时候太熟练了  没察觉到  你要仔细去想  你自己是一台电脑   ,会是一个什么样的步骤。
网络传输 跟文件组织 是一样的格式 。不过有命令tag 。很多命令tag组合到一堆这称之为dimse。 echo n-create c-find c-store 这些都称之为dimse ,记住没有c-print  大哥
打印管理是由很多组dimse 包括n-create 那些 你来我往的一套组成 比如 先n-create 什么东西 再 n-set什么东西,他有一种逻辑规范 什么参数错误则不进行n-set。这很多组dimse称之为服务类 ,比如打印管理 就是一个服务类。这些规范在dicom标准的第八章有说明。总之在dimse传递之前 你必须得协商连接。
dicom标准的地址是这个http://medical.nema.org/standard.html,英文的 。看也比较困难 装装面子,主要是理解就行了 我看的也是别人翻译的中文的。不过官方的就是官方的 没办法 某些地方你找不到原因 想参照最标准的指示 你还是得硬着头皮去看英文文档。
  
Associate pdu 协商连接的过程:
又说多了 不论如何在进行dimse之前必须得进行连接协商 因为你与别人进行通讯首先你得确定几个东西。 ,谈话的主题是什么,你是用哪国语言。这两个东西一个称之为虚拟语法 abssyntax  一个称之为传输语法 transfersyntax 传输语法其实主要确定两个东西 字节序 和 vr表示方式 ,如果你不知道字节序是什么 请自己百度 vr表示方式 跟文件解析一样的,他们两个一起被称之为表达上下文。注意表达上下文有多个 每个都有id。如果你是scp端 那么连接协商响应 也就是association-ac的时候你要告知 以你scp程序的服务能力可以完成哪些表达上下文的服务 传输语法语法是什么,如果服务不了也要给出对应的上下文id 并进行告知。这样的话scu端知道你服务不了就知难而退 主动断开连接。 其次还有些其他东西比如pdu最大数据长度 一般是0x4000。好了讲完了 这就是协商连接的过程 对照上面的图理解了否。
这是官方的解释:

官方的解释 网络协议是分层的,Dicom ul p ,称之为dicom上层协议。  也就是上图的dicom ul service provider。 反正要按照osi的标准来, 也就是说要定义一个associate-rq 或者 ac的数据结构来,一切的数据序列化或者反序列化都由 dicom ul service provider 来进行,反正只怕忽悠不死你。反正他说是那样说 我们自己按照自己的方式来。
 
好终于要动代码了 ,我喜欢的事情来了 噢啦啦啦。其实这是一个抽象化的过程,把你的想法付诸行动 代码化.就像某人说过的 主要的不是技术 而是思路。分成两步 根据文档定义 associate Pdu的数据结构,遵循上面说的原则 一个associate pdu有多高 pst Item,我们把pst Item定义为子项,然后serial()是associate pdu的网络序列化函数:

 public enum PDUTypes
{
AssociateRQ = 0x01,
AssociateAC = 0x02,
AssociateRJ = 0x03,
DataTransfer = 0x04,
AssociateReleaseRQ = 0x05,
AssociateReleaseRP = 0x06,
AssociateAbort = 0x07, ApplicationContext = 0x10,
PresentationContext = 0x20,
UserInformation = 0x50,
} struct PDUAssociate {
//header
public byte pduType;
public uint length;
public ushort ProteocalVersion;
public string CallEdAE ;//length=16
public string CallingAE ; //
public byte appType;
public ushort appLength;
public string appName; //
public IList<PstItem> pstItems;
//50 userinfo
public byte userinfoType;
public ushort userinfoLength;
public byte maxnumType;
public ushort maxnumLength;
public uint maxnum;//DATA-TF PDU的可变字段的最大长度 一般为0x400 即1024
public byte impType;//关于实现类的
public ushort impLength;
public string impUID;
public byte impVersionType;
public ushort impVersionLength;
public string impVersion; public byte[] serial()
{
if (length == )
return null;
MemoryStream _stream = new MemoryStream((int)length + );
WarpedStream stream = new WarpedStream(_stream);
#region 序列化aassociateAC PDU
//header
stream.writeByte(pduType);
stream.skip_write();
stream.writeUint(length);//最低要94 我去 这是为什么呢
stream.writeUshort(ProteocalVersion);
stream.skip_write();
stream.writeString(CallEdAE, );
stream.writeString(CallingAE, );
stream.skip_write(); //
stream.writeByte(appType);
stream.skip_write();
stream.writeUshort(appLength);
stream.writeString(appName, );
// for (int i = ; i < pstItems.Count; i++)
{
if (pstItems[i].used)
{
stream.writeByte(pstItems[i].pstType);
stream.skip_write();
stream.writeUshort(pstItems[i].pstLength);
stream.writeByte(pstItems[i].pstID);
stream.skip_write();
//if (pstItems[i].used)
//stream.writeBytes(new byte[] { 0x00, 0x00, 0x00 });
//else
//
if (pstItems[i].pstType == 0x20)//如果是20则为printSCU
{
stream.writeByte(pstItems[i].absType);
stream.skip_write();
stream.writeUshort(pstItems[i].absLength);
stream.writeString(pstItems[i].absStr, );
} stream.writeByte(pstItems[i].tsfType);
stream.skip_write();
if (pstItems[i].used)
stream.writeUshort(pstItems[i].tsfLeghth);
else
stream.writeUshort();
if (pstItems[i].used)
stream.writeString(pstItems[i].tsfStr, );
}
else
{
stream.writeByte(pstItems[i].pstType);
stream.skip_write();
stream.writeUshort(0x08);
stream.writeByte(pstItems[i].pstID);
stream.writeBytes(new byte[] { 0x00, 0x04, 0x00 });
stream.writeBytes(new byte[] { 0x40, 0x00, 0x00, 0x00 });
}
} //
stream.writeByte(userinfoType);
stream.skip_write();
stream.writeUshort(userinfoLength); stream.writeByte(maxnumType);
stream.skip_write();
stream.writeUshort(maxnumLength);
stream.writeUint(maxnum); stream.writeByte(impType);
stream.skip_write();
stream.writeUshort(impLength);
stream.writeString(impUID, ); stream.writeByte(impVersionType);
stream.skip_write();
stream.writeUshort(impVersionLength);
stream.writeString(impVersion, );
#endregion _stream.Flush();
byte[] data = _stream.GetBuffer();
stream.close();
_stream.Close();
return data;
}
} struct PstItem
{
//20 abstractsyntax transfersyntax传输语法
public byte pstType;
public ushort pstLength;
public byte pstID;
public bool used;
//public byte pstRec; //保留字节 有效项是00 00 00 无效项是00 04 00
public byte absType;//20的子项 30 40 读取的时候应该跟20一并读出来
public ushort absLength;
public string absStr;
public byte tsfType; //传输语法项 本来也有多个 为了方便只写一个
public ushort tsfLeghth;
public string tsfStr;
}

构建一个associate-rq的pdu 并发送:

 public bool associateRQ()//请求建立连接
{
PDUAssociate pdu_ac = new PDUAssociate();
#region 构造associateAC PDU
//
pdu_ac.appType = 0x10;
pdu_ac.appLength = (ushort)UIDs.DICOMApplicationContextName.Length;//pdu_associate_rq.appLength
pdu_ac.appName = UIDs.DICOMApplicationContextName;//pdu_associate_rq.appName //20
//30 abs
//40 transfer syntax
pdu_ac.pstItems = new List<PstItem>(); PstItem pst_ac = new PstItem(); pst_ac.absType = 0x30;
pst_ac.absLength = (ushort)UIDs.Verification.Length;
pst_ac.absStr = UIDs.Verification; pst_ac.tsfType = 0x40;
pst_ac.tsfLeghth = (ushort)UIDs.ImplicitVRLittleEndian.Length;//pdu_associate_rq.pstItems[i].tsfLeghth;
pst_ac.tsfStr = UIDs.ImplicitVRLittleEndian;//pdu_associate_rq.pstItems[i].tsfStr; pst_ac.pstType = 0x20;
pst_ac.pstLength = (ushort)( + ( + pst_ac.tsfLeghth) + ( + pst_ac.absLength));
pst_ac.pstID = 0x01;//表达上下文ID,多个表达上下文的时候以作区分。这里我们为发送方 主动控制为01
pst_ac.used = true;
pdu_ac.pstItems.Add(pst_ac); //
pdu_ac.userinfoType = 0x50;
pdu_ac.maxnumType = 0x51;
pdu_ac.maxnumLength = 0x04;
pdu_ac.maxnum = 0X4000;// pdu_ac.impType = 0x52;
pdu_ac.impLength = (ushort)UIDs.ImplementionUid.Length;
pdu_ac.impUID = UIDs.ImplementionUid; pdu_ac.impVersionType = 0x55;
pdu_ac.impVersionLength = ;
pdu_ac.impVersion = "ASSASSMedic"; pdu_ac.userinfoLength = (ushort)( * + pdu_ac.maxnumLength + pdu_ac.impVersionLength + pdu_ac.impLength); //header
pdu_ac.pduType = 0x01;
pdu_ac.ProteocalVersion = 0x01;
pdu_ac.CallEdAE = calledAET;
pdu_ac.CallingAE = callingAET;
pdu_ac.length = (uint)(( - ) + (pdu_ac.appLength + ) +
(pdu_ac.userinfoLength + )); for (int i = ; i < pdu_ac.pstItems.Count; i++)
{
if (pdu_ac.pstItems[i].used)
pdu_ac.length += (ushort)(pdu_ac.pstItems[i].pstLength + );
else
pdu_ac.length += ;
} #endregion
//序列化
stream.writeBytes(pdu_ac.serial()); Console.WriteLine(string.Format("associate create success,CalledAET:{0}", calledAET)); return false;
}

在这之前你还是得连接到SCP端:

 public void run(string ipStr, int port)
{
TcpClient _client = new TcpClient();
IPAddress ipdrs = IPAddress.Parse(ipStr);
_client.Connect(ipdrs, port); if (_client.Connected == false)
{
Console.WriteLine("与所指定主机连接失败");
Console.WriteLine("连接断开");
return;
}
WarpedStream stream = new WarpedStream(_client.GetStream()); echo(stream);
stream.close();
_client.Close();
} public void echo(WarpedStream _stream)
{
stream = _stream;
//第一步协商连接
associateRQ();
PDUTypes PduType = (PDUTypes)stream.readByte();
stream.skip();
uint pduLen = stream.readUint();
stream.skip((int)pduLen);
Console.WriteLine("协商连接成功");
//第二步 进行echo 请求
Verification_CECHORQ();
PduType = (PDUTypes)stream.readByte();
stream.skip();
pduLen = stream.readUint();
stream.skip((int)pduLen);
release();
Console.WriteLine("echo测试成功");
}

注意我们并没有对收到的associate-ac数据进行解码验证,直接偷懒略过了 。我们默认对方都是好人 都是按照套路来的 并且能够承担我们所请求的echo服务。

dicom网络通讯入门(2)的更多相关文章

  1. dicom网络通讯入门(3)

    接下来可以进行消息传递了 ,也就是dimse ,再来复习下 什么是dimse .n-set  n-create c-echo 这些都是dimse  他们都是属于一种结构的pdu 那就是tf-pdu(传 ...

  2. dicom网络通讯入门(1)

    看标准 越看越糊,根本原因:dicom抽象得非常严重,是“专家”弄的.没办法. 又是什么服务类 又是什么sop,相信你把dicom标准看到头大 都不知如何下手. 不就是 socket么 这有何难. 首 ...

  3. Python网络爬虫入门篇

    1.  预备知识 学习者需要预先掌握Python的数字类型.字符串类型.分支.循环.函数.列表类型.字典类型.文件和第三方库使用等概念和编程方法. 2. Python爬虫基本流程 a. 发送请求 使用 ...

  4. 脑残式网络编程入门(六):什么是公网IP和内网IP?NAT转换又是什么鬼?

    本文引用了“帅地”发表于公众号苦逼的码农的技术分享. 1.引言 搞网络通信应用开发的程序员,可能会经常听到外网IP(即互联网IP地址)和内网IP(即局域网IP地址),但他们的区别是什么?又有什么关系呢 ...

  5. 脑残式网络编程入门(五):每天都在用的Ping命令,它到底是什么?

    本文引用了公众号纯洁的微笑作者奎哥的技术文章,感谢原作者的分享. 1.前言   老于网络编程熟手来说,在测试和部署网络通信应用(比如IM聊天.实时音视频等)时,如果发现网络连接超时,第一时间想到的就是 ...

  6. 脑残式网络编程入门(四):快速理解HTTP/2的服务器推送(Server Push)

    本文原作者阮一峰,作者博客:ruanyifeng.com. 1.前言 新一代HTTP/2 协议的主要目的是为了提高网页性能(有关HTTP/2的介绍,请见<从HTTP/0.9到HTTP/2:一文读 ...

  7. 脑残式网络编程入门(三):HTTP协议必知必会的一些知识

    本文原作者:“竹千代”,原文由“玉刚说”写作平台提供写作赞助,原文版权归“玉刚说”微信公众号所有,即时通讯网收录时有改动. 1.前言 无论是即时通讯应用还是传统的信息系统,Http协议都是我们最常打交 ...

  8. 脑残式网络编程入门(二):我们在读写Socket时,究竟在读写什么?

    1.引言 本文接上篇<脑残式网络编程入门(一):跟着动画来学TCP三次握手和四次挥手>,继续脑残式的网络编程知识学习 ^_^. 套接字socket是大多数程序员都非常熟悉的概念,它是计算机 ...

  9. 脑残式网络编程入门(一):跟着动画来学TCP三次握手和四次挥手

    .引言 网络编程中TCP协议的三次握手和四次挥手的问题,在面试中是最为常见的知识点之一.很多读者都知道“三次”和“四次”,但是如果问深入一点,他们往往都无法作出准确回答. 本篇文章尝试使用动画图片的方 ...

随机推荐

  1. AFNetworking 3.0 源码解读(六)之 AFHTTPSessionManager

    AFHTTPSessionManager相对来说比较好理解,代码也比较短.但却是我们平时可能使用最多的类. AFNetworking 3.0 源码解读(一)之 AFNetworkReachabilit ...

  2. iOS微信里打开app,Universal Links

    这两天在弄分享,从第三方应用或者浏览器打开自己app的东西 传统的方式是通过URL Scheme的方式,但是iOS9以后又出了新的更完美的方式Universal Links. 传统的URL Schem ...

  3. App 审核由于 IPv6 网络问题被拒

    昨天 提交App Store 的时候被拒了 We discovered one or more bugs in your app when reviewed on iPhone running iOS ...

  4. Android开发学习——动画

    帧动画> 一张张图片不断的切换,形成动画效果* 在drawable目录下定义xml文件,子节点为animation-list,在这里定义要显示的图片和每张图片的显示时长              ...

  5. Open-Test 测试驱动模式与版本号管理机制

    以测试用例驱动项目开发,coding/case俩条线并走模式.   1.开发人员只负责功能实现:   2.测试人员提供自测用例,研发人员jenkins持续集成项目后自动化执行自测用例,通过后方可转测试 ...

  6. 理解Storm并发

    作者:Jack47 PS:如果喜欢我写的文章,欢迎关注我的微信公众账号程序员杰克,两边的文章会同步,也可以添加我的RSS订阅源. 注:本文主要内容翻译自understanding-the-parall ...

  7. JS高级前端开发群加群说明及如何晋级

    JS高级前端开发群加群说明 一.文章背景: 二. 高级群: 三. 加入方式: 四. 说明:   一.文章背景: 去年年初建了几个群,在不经意间火了,一直排在“前端开发”关键字搜索结果第一名.当然取得这 ...

  8. CentOS 6.3下 安装 Mono 3.2 和Jexus 5.4

    最新更新参看: Centos 7.0 安装Mono 3.4 和 Jexus 5.6 2012年初写过一篇<32和64位的CentOS 6.0下 安装 Mono 2.10.8 和Jexus 5.0 ...

  9. 用SSH访问内网主机的方法

    如今的互联网公司通常不会直接自己直接配主机搭建服务器了,而是采用了类似阿里云的这种云主机,当应用变得越来越大了之后,就不可避免地增加主机,而出于成本考虑,不可能给每一台主机都分配公网带宽,所以实际的情 ...

  10. MapReduce剖析笔记之七:Child子进程处理Map和Reduce任务的主要流程

    在上一节我们分析了TaskTracker如何对JobTracker分配过来的任务进行初始化,并创建各类JVM启动所需的信息,最终创建JVM的整个过程,本节我们继续来看,JVM启动后,执行的是Child ...