第二篇,前面都是闲扯 其实正文现在才开始,这次是把压箱底的东西都拿出来了。 首先我们今天要干的事是实现一个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. C#基础篇 - 正则表达式入门

    1.基本概念 正则表达式(Regular Expression)就是用事先定义好的一些特定字符(元字符)或普通字符.及这些字符的组合,组成一个“规则字符串”,这个“规则字符串”用来判断我们给定的字符串 ...

  2. 旺财速啃H5框架之Bootstrap(三)

    好多天没有写了,继续走起 在上一篇<<旺财速啃H5框架之Bootstrap(二)>>中已经把CSS引入到页面中,接下来开始写页面. 首先有些问题要先处理了,问什么你要学boot ...

  3. 基于改进人工蜂群算法的K均值聚类算法(附MATLAB版源代码)

    其实一直以来也没有准备在园子里发这样的文章,相对来说,算法改进放在园子里还是会稍稍显得格格不入.但是最近邮箱收到的几封邮件让我觉得有必要通过我的博客把过去做过的东西分享出去更给更多需要的人.从论文刊登 ...

  4. [原] KVM 虚拟化原理探究 —— 目录

    KVM 虚拟化原理探究 -- 目录 标签(空格分隔): KVM KVM 虚拟化原理探究(1)- overview KVM 虚拟化原理探究(2)- QEMU启动过程 KVM 虚拟化原理探究(3)- CP ...

  5. 多线程条件通行工具——CountDownLatch

    CountDownLatch的作用是,线程进入等待后,需要计数器达到0才能通行. CountDownLatch(int)构造方法,指定初始计数. await()等待计数减至0. await(long, ...

  6. [转载]网站地址栏小图标favicon.ico的制作方法

    有人也许会好奇,有的网址前面有个漂亮的小图标而且有的网站图标还会动,这是怎么做到的呢? 如下图所示: 那个小图标有个名字叫favicon.ico,网站图标虽小但可以起到很好的点缀作用,尤其是当浏览者将 ...

  7. Android开发学习—— Broadcast广播接收者

    现实中:电台要发布消息,通过广播把消息广播出去,使用收音机,就可以收听广播,得知这条消息.Android中:系统在运行过程中,会产生许多事件,那么某些事件产生时,比如:电量改变.收发短信.拨打电话.屏 ...

  8. 【swift】BlockOperation和GCD实用代码块

    //BlockOperation // // ViewController.swift import UIKit class ViewController: UIViewController { @I ...

  9. EMD分析 Matlab 精华总结 附开源工具箱(全)

    前言: 本贴写于2016年12与15日,UK.最近在学习EMD(Empirical Mode Decomposition)和HHT(Hilbert-Huang Transform)多分辨信号处理,FQ ...

  10. NodeJs 开发微信公众号(五)真实环境部署

    在测试环境下开发完成代表着你离正式上线的目标不远了.接下来本章就主要谈一谈把测试环境的公众号升级为正式的公众号. 服务器和域名 目前为止我们只是在自己的电脑上完成了测试环境.真实的线上环境当然需要自己 ...