dicom网络通讯入门(3)
接下来可以进行消息传递了 ,也就是dimse ,再来复习下 什么是dimse 。n-set n-create c-echo 这些都是dimse 他们都是属于一种结构的pdu 那就是tf-pdu(传输数据和命令的都称之为tf-pdu 或者transfer pdu ,协商连接的都称之为associcate pdu) 。dimse 由 许多tag组成,就像文件解析那篇博文一样。
tf-pdu数据结构分析如下:

如果你又要问此图是怎么来的 ,dicom标准第八章 33页。你可能又要问 dimse又是什么 ,dimse全称 dicom 消息服务元素DIMSE(DICOM Message Service Element)
为什么你就说echo属于dimse 。请看dicom标准第七章 的目录: 9 DIMSE-C 9.1.5 C-ECHO SERVICE 。不用我多说了噻。
在这之前还是像以前一样先把tf-pdu的数据结构跟序列化搞了吧:
struct PDVset
{
//0x04
public byte pduType;
//pdu长度
public uint pduLen;
//pdv长度从作用来看他跟上面有所重复 pdv, presentation data value
public uint itemLen;
//这个contextID其实是指协商连接时的presentation context id
//最好判断下是否跟协商时的一致
public byte contextID;
//消息控制头 确定pdv类型是command 还是data 发送完成与否
public byte msgControlHeader; public SortedDictionary<uint, DataElement> elements; public byte[] serial()
{
if ((pduLen != && itemLen != ) == false)
return null;
//header
MemoryStream _stream = new MemoryStream((int)pduLen + );
WarpedStream stream = new WarpedStream(_stream); stream.writeByte(0x04);
stream.skip_write();
stream.writeUint(pduLen);
stream.writeUint(itemLen);
stream.writeByte(contextID);
stream.writeByte(msgControlHeader);
//items
foreach (DataElement item in elements.Values)
{
stream.writeBytes(item.serial());
} _stream.Flush(); byte[] data = new byte[_stream.Length];
Array.Copy(_stream.GetBuffer(), data, _stream.Length);
stream.close();
_stream.Close();
return data;
}
}
enum pdvType
{
command, data, commandAndData
} struct DataElement
{
public uint _tag;
public WarpedStream.byteOrder bytOrder;
public bool explicitVR;//是显式VR的 否则隐式VR
public uint tag
{
get { return _tag; }
set
{
_tag = value;
VR = VRs.GetVR(value);
uint _len = VRs.getLen(VR);
if (_len != )
len = _len;
}
}
public ushort VR;
//虽然长度为uint 但要看情况隐式时都是4字节 显式时除ow那几个外都是2字节
//如果为ow 显示不但长度为4 在之前还要跳过2字节,除ow那几个之外不用跳过
public uint len;
public byte[] value;
public IList<DataElement> items ;//子项
public bool haveItems;//是否包含子项 //值的显示
public string showValue()
{
if (haveItems )
return null; if (value != null)
return Tags.VFdecoding(VR, value, bytOrder);
else
return null;
}
//赋值
public void setValue(string valStr)
{
if (haveItems )
return; if (VRs.IsStringValue(VR)) {
len = (uint)valStr.Length;
value = Tags.VFencoding(VR, valStr, bytOrder, len);
}
else if (len != )//就是这个破地方 因为element的连续使用 导致会截断字符串
value = Tags.VFencoding(VR, valStr, bytOrder, len);
else
{
value = Tags.VFencoding(VR, valStr, bytOrder);
if (VRs.IsStringValue(VR))
len = (uint)value.Length;
}
}
//序列化
public byte[] serial()
{
MemoryStream data_serial = new MemoryStream();
serial(this, data_serial);
byte[] data = new byte[data_serial.Length];
Array.Copy(data_serial.GetBuffer(), data, data.Length);
data_serial.Close();
return data;
}
//序列化的递归调用
public void serial(DataElement element, MemoryStream data_serial)
{
//int len_serial = element.getSerialLen();
//if ((VR == VRs.SQ && len_serial == UInt32.MaxValue) || (tag == 0xfffee000 && len == UInt32.MaxValue))//靠 遇到文件夹开始标签了
if (element.haveItems )
{
//开始标记
data_serial.WriteByte((byte)((element._tag & 0x00ff0000) >> ));
data_serial.WriteByte((byte)((element._tag & 0xff000000) >> ));
data_serial.WriteByte((byte)(element._tag & 0x000000ff));
data_serial.WriteByte((byte)((element._tag & 0x0000ff00) >> ));
data_serial.WriteByte(0xff); data_serial.WriteByte(0xff); data_serial.WriteByte(0xff); data_serial.WriteByte(0xff); foreach (DataElement item in element.items)
{
item.serial(item, data_serial);
} //结束标记
if (element.VR == VRs.SQ)
{
data_serial.WriteByte((byte)((0xfffee0dd & 0x00ff0000) >> ));
data_serial.WriteByte((byte)((0xfffee0dd & 0xff000000) >> ));
data_serial.WriteByte((byte)(0xfffee0dd & 0x000000ff));
data_serial.WriteByte((byte)((0xfffee0dd & 0x0000ff00) >> ));
}
else
{
data_serial.WriteByte((byte)((0xfffee00d & 0x00ff0000) >> ));
data_serial.WriteByte((byte)((0xfffee00d & 0xff000000) >> ));
data_serial.WriteByte((byte)(0xfffee00d & 0x000000ff));
data_serial.WriteByte((byte)((0xfffee00d & 0x0000ff00) >> ));
}
data_serial.WriteByte(0x00); data_serial.WriteByte(0x00); data_serial.WriteByte(0x00); data_serial.WriteByte(0x00);
}
else
{
byte[] data = new byte[element.getSerialLen()];
uint _len = element.len;
if (_len % != )
_len++;
if (element.VR == VRs.SQ)
_len = 0xffffffff; data[] = (byte)((element._tag & 0x00ff0000) >> );
data[] = (byte)((element._tag & 0xff000000) >> );
data[] = (byte)(element._tag & 0x000000ff);
data[] = (byte)((element._tag & 0x0000ff00) >> ); if (element.explicitVR)//显示VR
{
data[] = 0x00;
data[] = 0x00;
if (VRs.IsLengthField16Bit(VR))
{
if (element.bytOrder == WarpedStream.byteOrder.bigEdition)
{
data[] = (byte)(_len & 0x000000ff);
data[] = (byte)((_len & 0x0000ff00) >> );
}
else
{
data[] = (byte)((_len & 0x0000ff00) >> );
data[] = (byte)(_len & 0x000000ff);
} for (int i = ; i < element.value.Length; i++)
data[ + i] = element.value[i];
}
else
{
if (element.bytOrder == WarpedStream.byteOrder.bigEdition)
{
data[] = (byte)((_len & 0xff000000) >> );
data[] = (byte)((_len & 0x00ff0000) >> );
data[] = (byte)((_len & 0x0000ff00) >> );
data[] = (byte)(_len & 0x000000ff); }
else
{
data[] = (byte)(_len & 0x000000ff);
data[] = (byte)((_len & 0x0000ff00) >> );
data[] = (byte)((_len & 0x00ff0000) >> );
data[] = (byte)((_len & 0xff000000) >> );
}
if (element.value == null)
throw new Exception(string.Format("异常:tag{0} value未赋值 ", Tags.ToHexString(element.tag))); for (int i = ; i < element.value.Length; i++)
data[ + i] = element.value[i];
}
//len_ser = (int)(4 + 2 + 4 + len);
//len_ser = (int)(4 + 2 + len);
}
else //隐式Vr
{
if (element.bytOrder == WarpedStream.byteOrder.bigEdition)
{
data[] = (byte)((_len & 0xff000000) >> );
data[] = (byte)((_len & 0x00ff0000) >> );
data[] = (byte)((_len & 0x0000ff00) >> );
data[] = (byte)(_len & 0x000000ff);
}
else
{
data[] = (byte)(_len & 0x000000ff);
data[] = (byte)((_len & 0x0000ff00) >> );
data[] = (byte)((_len & 0x00ff0000) >> );
data[] = (byte)((_len & 0xff000000) >> ); }
if (element.value == null)
throw new Exception(string.Format("异常:tag{0} value未赋值 ", Tags.ToHexString(element.tag))); for (int i = ; i < element.value.Length; i++)
data[ + i] = element.value[i];
}
data_serial.Write(data, , data.Length);
}
} //获取单个元素序列化长度的递归调用
public void getSerialLen_item(DataElement element, ref int len)
{
if (element.haveItems)
{
len += element.getHeaderLen();
foreach (DataElement item in element.items)
getSerialLen_item(item, ref len);
len += ;
}
else
{
if (element.value != null)
len += element.getHeaderLen() + element.value.Length;
else
len += element.getHeaderLen();
}
if (len % != )//文件元信息元素整体字节数一定是偶数(包括tag VR 数据长度 数据 这些一起)
len++;
} //获取序列化后整个元素的长度
public int getSerialLen()
{
int serial_len=;
getSerialLen_item(this, ref serial_len);
return serial_len;
} //获取item的header长度
public int getHeaderLen()
{
int len_ser = ;
int len_tmp = ;
if (explicitVR)//显示VR
{
if (tag == 0xfffee000 || tag == 0xfffee00d || tag == 0xfffee0dd)
len_ser = + ;
else if (VR == VRs.OB || VR == VRs.OW || VR == VRs.OF ||
VR == VRs.UT || VR == VRs.SQ || VR == VRs.UN)
len_ser = (int)( + + + len_tmp);
else
len_ser = (int)( + + len_tmp);
}
else //隐式Vr
{
len_ser = (int)( + + len_tmp);
} return len_ser;
}
}
不要问我pdv是啥 第一篇就出现过,pdv 即p data value ,它包括许多的data element 也就是俗称tag。一个元素接一个元素 直到结束 跟文件解析的时候一样 ,他的vr方式 以及字节序 在协商连接的时候就已确定 你只管读就是了。那么新的问题又来了 echo这个dimse到底包含哪些tag 他们的值又应该各是多少?为了解决你这个疑问我又要翻一个表出来:
你又要问这个表是怎么来的 ,dicom第七章 53页。具体每个tag的作用各是什么 请参照右边的说明,有三个地方我要提一下:
affected sop class uid
受影响的sop uid ,看过第一篇里图的筒子都知道 打印有打印sop uid ,filmbox 有filmboxuid,那么这里echo也有 对应的 他就是 :
//SOPClass: Verification SOP Class
public const String Verification = "1.2.840.10008.1.1";
为啥是这个 我不会再说了 你懂的。
command field
这个是作甚的,他的作用是用来区分不同的dimse 比如 c-create c-find ,不用我多讲了 旁边的说明已经很明显了 echo时他的值应设置成0x0030 。
command data set type
数据集选项 ,说白了就是给个标识 后面有无数据集,我们这里自然是没有 那么应设置成0x0101 。
好了开工吧,打住 不是说还有服务类规范么 ,只有复杂的才有服务类规范 我们这个echo是非常非常非常之简单的 所以没有服务类规范 直接开动吧:
//组织Verification_CECHORSP响应原语
//rq端无data ,rsp端无data
public void Verification_CECHORQ()
{
PDVset rq = new PDVset();
rq.pduType = 0x04;
rq.contextID = pstContextId;
rq.msgControlHeader = 0x03;
rq.elements = new SortedDictionary<uint, DataElement>(); int len = ; DataElement element = new DataElement();
element.bytOrder = bytOrder; element.tag = 0x00000002;
element.setValue(UIDs.Verification);
rq.elements.Add(0x00000002, element);
len += (element.getSerialLen()); element.tag = 0x00000100;
element.setValue(0x0030.ToString());
rq.elements.Add(0x00000100, element);
len += (element.getSerialLen()); element.tag = 0x00000110;
element.setValue(0x03.ToString());
rq.elements.Add(0x00000110, element);
len += (element.getSerialLen()); element.tag = 0x00000800;//有无对应的数据段
element.setValue(0x0101.ToString());
rq.elements.Add(0x00000800, element);
len += (element.getSerialLen()); element.tag = 0x00000000;//消息原语数据长度
element.setValue(len.ToString());
rq.elements.Add(0x00000000, element);
//len += (element.getSerialLen()); rq.itemLen = (uint)( + + len); rq.pduLen = rq.itemLen + ; //进行c-echo-rsp响应
stream.writeBytes(rq.serial());
}
看看代码里面特定的地方 是不是跟我上面描述的一样?就这样so easy 。看到没 其实我这些都是在dicom文档里翻的 就这样而已没什么神奇的 相信你也能。再来复习下dicom标准跟网络通讯相关的几个章节 :
DICOM Part 4: Service Class Specifications ,服务类规范
DICOM Part 7: Message Exchange 消息交换
DICOM Part 8: Network Communication Support for Message Exchange 网络通讯对消息交换的支持
按照他们的套路来 就水到渠成 。
这是我的测试结果 不用怀疑哥的水平 哥是拿到医院去测试过的:

dicom网络通讯入门(3)的更多相关文章
- dicom网络通讯入门(2)
第二篇,前面都是闲扯 其实正文现在才开始,这次是把压箱底的东西都拿出来了. 首先我们今天要干的事是实现一个echo响应测试工具 也就是echo 的scu,不是实现打印作业管理么.同学我告诉你还早着呢. ...
- dicom网络通讯入门(1)
看标准 越看越糊,根本原因:dicom抽象得非常严重,是“专家”弄的.没办法. 又是什么服务类 又是什么sop,相信你把dicom标准看到头大 都不知如何下手. 不就是 socket么 这有何难. 首 ...
- Python网络爬虫入门篇
1. 预备知识 学习者需要预先掌握Python的数字类型.字符串类型.分支.循环.函数.列表类型.字典类型.文件和第三方库使用等概念和编程方法. 2. Python爬虫基本流程 a. 发送请求 使用 ...
- 脑残式网络编程入门(六):什么是公网IP和内网IP?NAT转换又是什么鬼?
本文引用了“帅地”发表于公众号苦逼的码农的技术分享. 1.引言 搞网络通信应用开发的程序员,可能会经常听到外网IP(即互联网IP地址)和内网IP(即局域网IP地址),但他们的区别是什么?又有什么关系呢 ...
- 脑残式网络编程入门(五):每天都在用的Ping命令,它到底是什么?
本文引用了公众号纯洁的微笑作者奎哥的技术文章,感谢原作者的分享. 1.前言 老于网络编程熟手来说,在测试和部署网络通信应用(比如IM聊天.实时音视频等)时,如果发现网络连接超时,第一时间想到的就是 ...
- 脑残式网络编程入门(四):快速理解HTTP/2的服务器推送(Server Push)
本文原作者阮一峰,作者博客:ruanyifeng.com. 1.前言 新一代HTTP/2 协议的主要目的是为了提高网页性能(有关HTTP/2的介绍,请见<从HTTP/0.9到HTTP/2:一文读 ...
- 脑残式网络编程入门(三):HTTP协议必知必会的一些知识
本文原作者:“竹千代”,原文由“玉刚说”写作平台提供写作赞助,原文版权归“玉刚说”微信公众号所有,即时通讯网收录时有改动. 1.前言 无论是即时通讯应用还是传统的信息系统,Http协议都是我们最常打交 ...
- 脑残式网络编程入门(二):我们在读写Socket时,究竟在读写什么?
1.引言 本文接上篇<脑残式网络编程入门(一):跟着动画来学TCP三次握手和四次挥手>,继续脑残式的网络编程知识学习 ^_^. 套接字socket是大多数程序员都非常熟悉的概念,它是计算机 ...
- 脑残式网络编程入门(一):跟着动画来学TCP三次握手和四次挥手
.引言 网络编程中TCP协议的三次握手和四次挥手的问题,在面试中是最为常见的知识点之一.很多读者都知道“三次”和“四次”,但是如果问深入一点,他们往往都无法作出准确回答. 本篇文章尝试使用动画图片的方 ...
随机推荐
- HTML 事件(二) 事件的注册与注销
本篇主要介绍HTML元素事件的注册.注销的方式. 其他事件文章 1. HTML 事件(一) 事件的介绍 2. HTML 事件(二) 事件的注册与注销 3. HTML 事件(三) 事件流.事件委托 4. ...
- 一道返回num值的小题目
题目描述: 实现fizzBuzz函数,参数num与返回值的关系如下: .如果num能同时被3和5整除,返回字符串fizzbuzz .如果num能被3整除,返回字符串fizz .如果num能被5整除,返 ...
- jQuery学习之路(2)-DOM操作
▓▓▓▓▓▓ 大致介绍 jQuery作为JavaScript库,继承并发扬了JavaScript对DOM对象操作的特性,使开发人员能方便的操作DOM对象. ▓▓▓▓▓▓ jQuery中的DOM操作 看 ...
- 28个你必须知道的HTML5的新特性,技巧以及技术
崭新新的页面布局 传统的: HTML5: 1. 新的Doctype 尽管使用<!DOCTYPE html>,即使浏览器不懂这句话也会按照标准模式去渲染 2. Figure元素 用<f ...
- Mach-O 的动态链接(Lazy Bind 机制)
➠更多技术干货请戳:听云博客 动态链接 要解决空间浪费和更新困难这两个问题最简单的方法就是把程序的模块相互分割开来,形成独立的文件,而不再将它们静态的链接在一起.简单地讲,就是不对那些组成程序的目标文 ...
- 高仿it之家新闻客户端源码
仿it之家新闻客户端界面,数据为本地假数据.仅实现了新闻模块的功能. 源码下载:http://code.662p.com/list/11_1.html 详细说明:http://android.662p ...
- [开源]QuickSwitchSVNClient,快速完成SVN Switch的工具
在实际的开发中,我们一般使用SVN工具进行源代码的管理.在实际的产品开发中,根据项目的一些定制要求,往往需要对某一些代码的修改,但是又不想影响主要的开发,这个时候需要对当前的主分支做一些分支处理(br ...
- 论C#逼格手册
水文.如何让自己的代码看起来,更有逼格? 要想让自己的代码,看起来更优雅,更有逼格,更高大上,就一定要写出晦涩难懂,而又简洁的代码来. 对于类自身的全局变量,一定要加this,对于基类的,一定要加ba ...
- 写自己的Socket框架(一)
本系列仅介绍可用于生产环境的C#异步Socket框架,如果您在其他地方看到类似的代码,不要惊讶,那可能就是我在参考开源代码时,直接“剽窃”过来的. 1.在脑海里思考一下整个socket的链接的处理流程 ...
- 简单例子了解View的事件分发
什么是事件分发 我们在写自定义ViewGroup或者自定义View的时候经常要处理用户的点击事件,如果我们的View在最底层,他在很多ViewGroup里面,我们如何让我们的点击事件准确传递到View ...