• 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的数据结构跟序列化搞了吧:

      1     struct PDVset
      2     {   
      3         //0x04
      4         public byte pduType;
      5         //pdu长度
      6         public uint pduLen;
      7         //pdv长度从作用来看他跟上面有所重复 pdv, presentation data value
      8         public uint itemLen;
      9         //这个contextID其实是指协商连接时的presentation context id 
     10         //最好判断下是否跟协商时的一致
     11         public byte contextID;
     12         //消息控制头 确定pdv类型是command 还是data 发送完成与否
     13         public byte msgControlHeader;
     14         
     15         public SortedDictionary<uint, DataElement> elements;
     16 
     17         public byte[] serial()
     18         {
     19             if ((pduLen != 0 && itemLen != 0) == false)
     20                 return null;
     21             //header
     22             MemoryStream _stream = new MemoryStream((int)pduLen + 6);
     23             WarpedStream stream = new WarpedStream(_stream);
     24 
     25             stream.writeByte(0x04);
     26             stream.skip_write(1);
     27             stream.writeUint(pduLen);
     28             stream.writeUint(itemLen);
     29             stream.writeByte(contextID);
     30             stream.writeByte(msgControlHeader);
     31             //items
     32             foreach (DataElement item in elements.Values)
     33             {
     34                 stream.writeBytes(item.serial());
     35             }
     36 
     37             _stream.Flush();
     38 
     39             byte[] data = new byte[_stream.Length];
     40             Array.Copy(_stream.GetBuffer(), data, _stream.Length);
     41             stream.close();
     42             _stream.Close();
     43             return data;
     44         }
     45     }
     46     enum pdvType
     47     {
     48         command, data, commandAndData
     49     }
     50 
     51     struct DataElement
     52     {
     53         public uint _tag;
     54         public WarpedStream.byteOrder bytOrder;
     55         public bool explicitVR;//是显式VR的 否则隐式VR
     56         public uint tag
     57         {
     58             get { return _tag; }
     59             set
     60             {
     61                 _tag = value;
     62                 VR = VRs.GetVR(value);
     63                 uint _len = VRs.getLen(VR);
     64                 if (_len != 0)
     65                     len = _len;
     66             }
     67         }
     68         public ushort VR;
     69         //虽然长度为uint 但要看情况隐式时都是4字节 显式时除ow那几个外都是2字节
     70         //如果为ow 显示不但长度为4 在之前还要跳过2字节,除ow那几个之外不用跳过
     71         public uint len;
     72         public byte[] value;
     73         public IList<DataElement> items ;//子项
     74         public bool haveItems;//是否包含子项
     75 
     76         //值的显示
     77         public string showValue()
     78         {
     79             if (haveItems )
     80                 return null;
     81 
     82             if (value != null)
     83                 return Tags.VFdecoding(VR, value, bytOrder);
     84             else
     85                 return null;
     86         }
     87         //赋值
     88         public void setValue(string valStr)
     89         {
     90             if (haveItems )
     91                 return;
     92 
     93             if (VRs.IsStringValue(VR)) {
     94                 len = (uint)valStr.Length;
     95                 value = Tags.VFencoding(VR, valStr, bytOrder, len);
     96             }
     97             else if (len != 0)//就是这个破地方 因为element的连续使用 导致会截断字符串
     98                 value = Tags.VFencoding(VR, valStr, bytOrder, len);
     99             else
    100             {
    101                 value = Tags.VFencoding(VR, valStr, bytOrder);
    102                 if (VRs.IsStringValue(VR))
    103                     len = (uint)value.Length;
    104             }
    105         }
    106         //序列化
    107         public byte[] serial()
    108         {
    109             MemoryStream data_serial = new MemoryStream();
    110             serial(this, data_serial);
    111             byte[] data = new byte[data_serial.Length];
    112             Array.Copy(data_serial.GetBuffer(), data, data.Length);
    113             data_serial.Close();
    114             return data;
    115         }
    116         //序列化的递归调用
    117         public void serial(DataElement element, MemoryStream data_serial)
    118         {
    119             //int len_serial = element.getSerialLen();
    120             //if ((VR == VRs.SQ && len_serial == UInt32.MaxValue) || (tag == 0xfffee000 && len == UInt32.MaxValue))//靠 遇到文件夹开始标签了
    121             if (element.haveItems )
    122             {
    123                 //开始标记
    124                 data_serial.WriteByte((byte)((element._tag & 0x00ff0000) >> 16));
    125                 data_serial.WriteByte((byte)((element._tag & 0xff000000) >> 24));
    126                 data_serial.WriteByte((byte)(element._tag & 0x000000ff));
    127                 data_serial.WriteByte((byte)((element._tag & 0x0000ff00) >> 8));
    128                 data_serial.WriteByte(0xff); data_serial.WriteByte(0xff); data_serial.WriteByte(0xff); data_serial.WriteByte(0xff);
    129 
    130                 foreach (DataElement item in element.items)
    131                 {
    132                     item.serial(item, data_serial);
    133                 }
    134 
    135                 //结束标记
    136                 if (element.VR == VRs.SQ)
    137                 {
    138                     data_serial.WriteByte((byte)((0xfffee0dd & 0x00ff0000) >> 16));
    139                     data_serial.WriteByte((byte)((0xfffee0dd & 0xff000000) >> 24));
    140                     data_serial.WriteByte((byte)(0xfffee0dd & 0x000000ff));
    141                     data_serial.WriteByte((byte)((0xfffee0dd & 0x0000ff00) >> 8));
    142                 }
    143                 else
    144                 {
    145                     data_serial.WriteByte((byte)((0xfffee00d & 0x00ff0000) >> 16));
    146                     data_serial.WriteByte((byte)((0xfffee00d & 0xff000000) >> 24));
    147                     data_serial.WriteByte((byte)(0xfffee00d & 0x000000ff));
    148                     data_serial.WriteByte((byte)((0xfffee00d & 0x0000ff00) >> 8));
    149                 }
    150                 data_serial.WriteByte(0x00); data_serial.WriteByte(0x00); data_serial.WriteByte(0x00); data_serial.WriteByte(0x00);
    151             }
    152             else
    153             {
    154                 byte[] data = new byte[element.getSerialLen()];
    155                 uint _len = element.len;
    156                 if (_len % 2 != 0)
    157                     _len++;
    158                 if (element.VR == VRs.SQ)
    159                     _len = 0xffffffff;
    160 
    161                 data[0] = (byte)((element._tag & 0x00ff0000) >> 16);
    162                 data[1] = (byte)((element._tag & 0xff000000) >> 24);
    163                 data[2] = (byte)(element._tag & 0x000000ff);
    164                 data[3] = (byte)((element._tag & 0x0000ff00) >> 8);
    165 
    166                 if (element.explicitVR)//显示VR
    167                 {
    168                     data[4] = 0x00;
    169                     data[5] = 0x00;
    170                     if (VRs.IsLengthField16Bit(VR))
    171                     {
    172                         if (element.bytOrder == WarpedStream.byteOrder.bigEdition)
    173                         {
    174                             data[6] = (byte)(_len & 0x000000ff);
    175                             data[7] = (byte)((_len & 0x0000ff00) >> 8);
    176                         }
    177                         else
    178                         {
    179                             data[6] = (byte)((_len & 0x0000ff00) >> 8);
    180                             data[7] = (byte)(_len & 0x000000ff);
    181                         }
    182 
    183                         for (int i = 0; i < element.value.Length; i++)
    184                             data[8 + i] = element.value[i];
    185                     }
    186                     else
    187                     {
    188                         if (element.bytOrder == WarpedStream.byteOrder.bigEdition)
    189                         {
    190                             data[6] = (byte)((_len & 0xff000000) >> 24);
    191                             data[7] = (byte)((_len & 0x00ff0000) >> 16);
    192                             data[8] = (byte)((_len & 0x0000ff00) >> 8);
    193                             data[9] = (byte)(_len & 0x000000ff);
    194 
    195                         }
    196                         else
    197                         {
    198                             data[6] = (byte)(_len & 0x000000ff);
    199                             data[7] = (byte)((_len & 0x0000ff00) >> 8);
    200                             data[8] = (byte)((_len & 0x00ff0000) >> 16);
    201                             data[9] = (byte)((_len & 0xff000000) >> 24);
    202                         }
    203                         if (element.value == null)
    204                             throw new Exception(string.Format("异常:tag{0} value未赋值 ", Tags.ToHexString(element.tag)));
    205 
    206                         for (int i = 0; i < element.value.Length; i++)
    207                             data[10 + i] = element.value[i];
    208                     }
    209                     //len_ser = (int)(4 + 2 + 4 + len);
    210                     //len_ser = (int)(4 + 2 + len);
    211                 }
    212                 else //隐式Vr
    213                 {
    214                     if (element.bytOrder == WarpedStream.byteOrder.bigEdition)
    215                     {
    216                         data[4] = (byte)((_len & 0xff000000) >> 24);
    217                         data[5] = (byte)((_len & 0x00ff0000) >> 16);
    218                         data[6] = (byte)((_len & 0x0000ff00) >> 8);
    219                         data[7] = (byte)(_len & 0x000000ff);
    220                     }
    221                     else
    222                     {
    223                         data[4] = (byte)(_len & 0x000000ff);
    224                         data[5] = (byte)((_len & 0x0000ff00) >> 8);
    225                         data[6] = (byte)((_len & 0x00ff0000) >> 16);
    226                         data[7] = (byte)((_len & 0xff000000) >> 24);
    227 
    228                     }
    229                     if (element.value == null)
    230                         throw new Exception(string.Format("异常:tag{0} value未赋值 ", Tags.ToHexString(element.tag)));
    231 
    232                     for (int i = 0; i < element.value.Length; i++)
    233                         data[8 + i] = element.value[i];
    234                 }
    235                 data_serial.Write(data, 0, data.Length);
    236             }
    237         }
    238 
    239         //获取单个元素序列化长度的递归调用
    240         public void getSerialLen_item(DataElement element, ref int len)
    241         {
    242             if (element.haveItems)
    243             {
    244                 len += element.getHeaderLen();
    245                 foreach (DataElement item in element.items)
    246                     getSerialLen_item(item, ref len);
    247                 len += 8;
    248             }
    249             else
    250             {
    251                 if (element.value != null)
    252                     len += element.getHeaderLen() + element.value.Length;
    253                 else
    254                     len += element.getHeaderLen();
    255             }
    256             if (len % 2 != 0)//文件元信息元素整体字节数一定是偶数(包括tag VR 数据长度 数据 这些一起)
    257                 len++;
    258         }
    259 
    260         //获取序列化后整个元素的长度
    261         public int getSerialLen()
    262         {
    263             int serial_len=0;
    264             getSerialLen_item(this, ref serial_len);
    265             return serial_len;
    266         }
    267 
    268         //获取item的header长度
    269         public int getHeaderLen()
    270         {
    271             int len_ser = 0;
    272             int len_tmp = 0;
    273             if (explicitVR)//显示VR
    274             {
    275                 if (tag == 0xfffee000 || tag == 0xfffee00d || tag == 0xfffee0dd)
    276                     len_ser = 4 + 4;
    277                 else if (VR == VRs.OB || VR == VRs.OW || VR == VRs.OF ||
    278                         VR == VRs.UT || VR == VRs.SQ || VR == VRs.UN)
    279                     len_ser = (int)(4 + 2 + 4 + len_tmp);
    280                 else
    281                     len_ser = (int)(4 + 2 + len_tmp);
    282             }
    283             else //隐式Vr
    284             {
    285                 len_ser = (int)(4 + 4 + len_tmp);
    286             }
    287 
    288             return len_ser;
    289         }
    290     }

    不要问我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也有 对应的 他就是 :

    1 //SOPClass: Verification SOP Class 
    2 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是非常非常非常之简单的 所以没有服务类规范 直接开动吧:

     1         //组织Verification_CECHORSP响应原语
     2         //rq端无data ,rsp端无data
     3         public void Verification_CECHORQ()
     4         {
     5             PDVset rq = new PDVset();
     6             rq.pduType = 0x04;
     7             rq.contextID = pstContextId;
     8             rq.msgControlHeader = 0x03;
     9             rq.elements = new SortedDictionary<uint, DataElement>();
    10 
    11             int len = 0;
    12 
    13             DataElement element = new DataElement();
    14             element.bytOrder = bytOrder;
    15 
    16             element.tag = 0x00000002;
    17             element.setValue(UIDs.Verification);
    18             rq.elements.Add(0x00000002, element);
    19             len += (element.getSerialLen());
    20 
    21             element.tag = 0x00000100;
    22             element.setValue(0x0030.ToString());
    23             rq.elements.Add(0x00000100, element);
    24             len += (element.getSerialLen());
    25 
    26             element.tag = 0x00000110;
    27             element.setValue(0x03.ToString());
    28             rq.elements.Add(0x00000110, element);
    29             len += (element.getSerialLen());
    30 
    31             element.tag = 0x00000800;//有无对应的数据段
    32             element.setValue(0x0101.ToString());
    33             rq.elements.Add(0x00000800, element);
    34             len += (element.getSerialLen());
    35 
    36 
    37             element.tag = 0x00000000;//消息原语数据长度
    38             element.setValue(len.ToString());
    39             rq.elements.Add(0x00000000, element);
    40             //len += (element.getSerialLen());
    41 
    42             rq.itemLen = (uint)(12 + 2 + len);
    43 
    44             rq.pduLen = rq.itemLen + 4;
    45 
    46             //进行c-echo-rsp响应
    47             stream.writeBytes(rq.serial());
    48         }


    看看代码里面特定的地方 是不是跟我上面描述的一样?就这样so easy  。看到没 其实我这些都是在dicom文档里翻的 就这样而已没什么神奇的 相信你也能。再来复习下dicom标准跟网络通讯相关的几个章节 :

    DICOM Part 4: Service Class Specifications  ,服务类规范

    DICOM Part 7: Message Exchange 消息交换

    DICOM Part 8: Network Communication Support for Message Exchange 网络通讯对消息交换的支持

    按照他们的套路来 就水到渠成 。


    这是我的测试结果 不用怀疑哥的水平 哥是拿到医院去测试过的:

  • 相关阅读:
    协程
    多进程
    多线程
    模块进阶
    内建函数
    内建属性
    属性property
    私有化
    深拷贝、浅拷贝
    ==、is
  • 原文地址:https://www.cnblogs.com/assassinx/p/3649637.html
Copyright © 2020-2023  润新知