图1 Echo SCP源码动态流程图

1 创建过程

首先,创建一个DcmStorageSCPProxy的代理类,此类继承于 OFThread。整个StoreSCP服务的链接过程,可以简单的描述为在主线程中(上图中的蓝色区域)初始化链接对象(包括创建socket);主要的创建代码在dul.cc文件中,这里需要注意的是,在服务端绑定的ip地址的时候,INADDR_ANY表示本机所有的网卡,都要进行监听。这里和127.0.0.1还有有很大区别的,127.0.0.1表示只有本机客户端可以连接此监听端口。然后,开始监听此端口。整个过程比较简单。

static OFCondition initializeNetworkTCP(PRIVATE_NETWORKKEY ** key, void *parameter)
{struct linger sockarg;int reuse = 1;(*key)->networkSpecific.TCP.tLayer = NULL;(*key)->networkSpecific.TCP.tLayerOwned = 0;(*key)->networkSpecific.TCP.port = -1;(*key)->networkSpecific.TCP.listenSocket = -1;int sock;struct sockaddr_in server;(*key)->networkSpecific.TCP.port = *(int *) parameter;(*key)->networkSpecific.TCP.listenSocket = socket(AF_INET, SOCK_STREAM, 0); // 表示创建ip4 stream类型的socketsock = (*key)->networkSpecific.TCP.listenSocket;setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (char *) &reuse, sizeof(reuse));server.sin_family = AF_INET;server.sin_addr.s_addr = INADDR_ANY;server.sin_port = (unsigned short) htons((*key)->networkSpecific.TCP.port);bind(sock, (struct sockaddr *) & server, sizeof(server)); // 这里要注意,绑定的ip地址,INADDR_ANY表示本机所有的网卡,都要进行监听。这里和127.0.0.1还有有很大区别的,127.0.0.1表示只有本机客户端可以连接此监听端口/* Listen on the socket */listen(sock, PRV_LISTENBACKLOG);return EC_Normal;
}

2 监听线程

其次,启动监听线程(上图中绿色区域), 等待SCU来连接。 服务端一旦监听到SCU的连接,就会对比请求的Presetation Context,是否SCP端支持,如果支持,就会向SCU发送
A-ASSOCIATE-AC消息。这里要注意,在和客户端交互的过程中,有一个很重要的概念,就是dicom通讯中的状态机流程。在交互的过程中,有13个状态

Sta1 - Idle
Sta2 - Transport connection open
Sta3 - Awaiting local A-ASSOCIATE response primitive
Sta4 - Awaiting transport connection opening to complete
Sta5 - Awaiting A-ASSOCIATE-AC or A-ASSOCIATE-RJ PDU
Sta6 - Association established and ready for data transfer
Sta7 - Awaiting A-RELEASE-RP PDU
Sta8 - Awaiting local A-RELEASE response primitive
Sta9 - Release collision requestor side; awaiting A-RELEASE response
Sta10 - Release collision acceptor side; awaiting A-RELEASE-RP PDU
Sta11 - Release collision requestor side; awaiting A-RELEASE-RP PDU
Sta12 - Release collision acceptor side; awaiting A-RELEASE response primitive
Sta13 - Awaiting Transport Connection Close Indication

另外,还有19中事件Event,来触发这些状态的跃迁。

#define    A_ASSOCIATE_REQ_LOCAL_USER    0
#define    TRANS_CONN_CONFIRM_LOCAL_USER    1
#define    A_ASSOCIATE_AC_PDU_RCV        2
#define    A_ASSOCIATE_RJ_PDU_RCV        3
#define    TRANS_CONN_INDICATION        4
#define    A_ASSOCIATE_RQ_PDU_RCV        5
#define    A_ASSOCIATE_RESPONSE_ACCEPT    6
#define    A_ASSOCIATE_RESPONSE_REJECT    7
#define    P_DATA_REQ            8
#define    P_DATA_TF_PDU_RCV        9
#define    A_RELEASE_REQ            10
#define    A_RELEASE_RQ_PDU_RCV        11
#define    A_RELEASE_RP_PDU_RCV        12
#define    A_RELEASE_RESP            13
#define    A_ABORT_REQ            14
#define    A_ABORT_PDU_RCV            15
#define    TRANS_CONN_CLOSED        16
#define    ARTIM_TIMER_EXPIRED        17
#define    INVALID_PDU             18
#define DUL_NUMBER_OF_EVENTS        19

在图1中的绿色区域,首先是TRANS_CONN_INDICATION=4事件下,将SCP当前的连接的状态从state = 1(Idle) next state = 2(Transport connection open)的转换,马上又通过A_ASSOCIATE_RQ_PDU_RCV=5  事件,将current state = 2 (Transport connection open)next state = 3(Awaiting local A-ASSOCIATE response primitive) 。然后,Scp代码通过本地的一些配置,判断当前服务是否支持SCU提交的PC,是否是合法的AE,如果所有的验证都通过后,将发送A-ASSOCIATE-AC信息,通过事件A_ASSOCIATE_RESPONSE_ACCEPT=6 将本地状态从  current state = 3 ( Awaiting local A-ASSOCIATE response primitive)转化为next state = 6(Association established and ready for data transfer)的状态。

3 创建DIMSE处理线程

ASC连接成功后,启动第三个线程,处理P-DATA-TF 格式的PDU,也就是DIMSE层来处理具体的请求(在图1中的黄色区域)。

整个过程是通过一个循环调用DIMSE_readNextPDV函数来获取PDV包。

for (last = OFFalse, bytesRead = 0, type = DUL_COMMANDPDV;type == DUL_COMMANDPDV && !last;){/* get next PDV (in detail, in order to get this PDV, a *//* PDU has to be read from the incoming socket stream) */cond = DIMSE_readNextPDV(assoc, blocking, timeout, &pdv);/* if this is the first loop iteration, get the presentation context ID which is captured in the *//* current PDV. If this is not the first loop iteration, check if the presentation context IDs in *//* the current PDV and in the last PDV are identical. If they are not, return an error. */// 如果是PDV的第一个包,将表示上下文记录下来;后续的PDV如果表达上下的ID和第一个PDV的表达上下文不同的话,也不符合规范,返回;if (pdvCount == 0){pid = pdv.presentationContextID;}else if (pdv.presentationContextID != pid){delete cmdSet;return makeDcmnetSubCondition(DIMSEC_RECEIVEFAILED, OF_error, "DIMSE Failed to receive message", subCond);}/* check if the fragment length of the current PDV is odd. This should *//* never happen (see DICOM standard (year 2000) part 7, annex F) (or *//* the corresponding section in a later version of the standard.) *///保证PDV的长度是2的整数倍  ... ... 可以查看第7章的 附件F/* if information is contained the PDVs fragment, we want to insert this information into the buffer */if (pdv.fragmentLength > 0) {cmdBuf.setBuffer(pdv.data, pdv.fragmentLength);}/* if this fragment contains the last fragment of the DIMSE command, set the end of the stream *///如果是最后一个PDV,那么,将命令buffer设置结束标志量if (pdv.lastPDV) {cmdBuf.setEos();}/* insert the information which is contained in the buffer into the DcmDataset *//* variable. Mind that DIMSE commands are always specified in the little endian *//* implicit transfer syntax. Additionally, we want to remove group length tags. */econd = cmdSet->read(cmdBuf, EXS_LittleEndianImplicit, EGL_withoutGL);if (econd != EC_Normal && econd != EC_StreamNotifyClient){delete cmdSet;return makeDcmnetSubCondition(DIMSEC_RECEIVEFAILED, OF_error, "DIMSE: receiveCommand: cmdSet->read() Failed", econd);}/* update the counter that counts how many bytes were read from the incoming socket *//* stream. This variable will only be used for dumping general information. */bytesRead += pdv.fragmentLength;/* update the following variables which will be evaluated at the beginning of each loop iteration. */last = pdv.lastPDV;type = pdv.pdvType;/* update the counter that counts how many PDVs were received on the incoming *//* socket stream. This variable will be used for determining the first *//* loop iteration and dumping general information. */pdvCount++;}

当通过DIMSE_readNextPDV函数接收到从SCU发送的A-ASSOCIATE-RELEASE-RQ的请求, SCP内部发生Event:A_RELEASE_RQ_PDU_RCV=11的事件,将 current state = 6(Association established and ready for data transfer) next state = 8  (Awaiting local A-RELEASE response primitive) 状态。

最后程序调用ASC_acknowledgeRelease(assoc)和DUL_AcknowledgeRelease函数,将A-ASSOCIATE-RESPONSE发送会SCU端。这时,内部状态机发生A_RELEASE_RESP=13 事件,将将 current state = 8(Awaiting local A-RELEASE response primitive)改变为 next state = 13  (Awaiting Transport Connection Close Indication) 状态。

源代码中,注意文件dulfsm.cc中的FSM_ENTRY,有限状态机类,和dcm4che中的dcm4che-net.jar的State类作用相同,这里的实现过程,设计都很巧妙,有兴趣的可以研读源代码。

Dcmtk 源码解读SCP交互过程- CEcho Scp相关推荐

  1. MyBatis 源码解读-配置解析过程

    首先我们要清楚的是配置解析的过程全部只解析了两种文件.一个是mybatis-config.xml 全局配置文件.另外就是可能有很多个的Mapper.xml 文件,也包括在Mapper 接口类上面定义的 ...

  2. MyBatis 源码解读-会话创建过程

    这是第二步, 我们跟数据库的每一次连接, 都需要创建一个会话, 我们用openSession()方法来创建. DefaultSqlSessionFactory -- openSessionFromDa ...

  3. Feflow 源码解读

    Feflow 源码解读 Feflow(Front-end flow)是腾讯IVWEB团队的前端工程化解决方案,致力于改善多类型项目的开发流程中的规范和非业务相关的问题,可以让开发者将绝大部分精力集中在 ...

  4. PackageManagerService Android 8.1 源码解读 02

    接上文:PackageManagerService Android 8.1 源码解读 01 d.第三步细节:PKMS.main(),main函数主要工作: [检查]Package编译相关系统属性 [调 ...

  5. tomcat源码解读(一)

    tomcat源码解读(一) 什么是 tomcat ? Tomcat是由Apache软件基金会下属的Jakarta项目开发的一个Servlet容器,按照Sun Microsystems提供的技术 规范, ...

  6. 【Deformable DETR 论文+源码解读】Deformable Transformers for End-to-End Object Detection

    目录 前言 一.背景和改进思路 二.细节原理和源码讲解 2.1.多尺度特征 2.1.1.backbone生成多尺度特征 2.1.2.多尺度位置编码 2.2.多尺度可变形注意力 2.2.1.普通多头注意 ...

  7. BitXHub 跨链插件(Fabric)源码解读

    前言 趣链科技的BitXHub跨链平台是业界较为完善的跨链开源解决方案,主要通过中继链.网关和插件机制对跨链流程中的功能.安全性和灵活性等进行了优化.本文对BitXHub的meshplus/pier- ...

  8. PostgreSQL 源码解读(153)- 后台进程#5(walsender#1)

    本节简单介绍了PostgreSQL的后台进程walsender,该进程实质上是streaming replication环境中master节点上普通的backend进程,在standby节点启动时,s ...

  9. x264源码解读(二)- VCL和NAL那些事

    目录 ANNEXB vs. AVCC VCL vs. NAL signal函数响应键盘事件 收尾清理工作 x264的编码 ANNEXB vs. AVCC 今天我们继续来说一下x264结构中非常重要的属 ...

最新文章

  1. 虚方法(virtual)和抽象方法(abstract)的区别
  2. 【大话存储】学习笔记(7章), OSI模型
  3. 理解ROS Navigation Stack,看完这篇你就知道啦!
  4. 简述mysql的事务_请简述为什么要使用数据库的事务
  5. spring提供的线程池
  6. Factstone Benchmark
  7. win7误删计算机,Win7系统下文件数据被误删了怎么办
  8. Servlet 环境设置
  9. 【优化算法】粒子群优化多目标搜索算法【含Matlab源码 1124期】
  10. matlab怎么画lnx图像,inx图像(lnx的图像函数)
  11. 向往的生活之鸿蒙传承,《向往的生活5》播放量破6.43亿,张艺兴功劳大,《跑男》比不了...
  12. Scrapy报错之:Request object has no attribute dont_filter
  13. 护理和母乳喂养文胸的全球与中国市场2022-2028年:技术、参与者、趋势、市场规模及占有率研究报告
  14. matlab如何打开dcm_MatLab 与 visual studio 混合编程环境配置
  15. 键盘上哪个键是ESCAPE键?
  16. 一起来看看SpringBoot蓝天幼儿园管理系统(详解)
  17. 嵌入式系统大作业——基于QT的3D模型展示
  18. coreldraw的线条怎么变成圆头_如何PS包装盒平面图改为立体图
  19. 兼职币圈代言人?特斯拉CEO:加密货币是我的安全词
  20. 2021年05月软件设计师真题透析

热门文章

  1. matlab科学计算的应用,精通MATLAB科学计算与数据统计应用 高清版pdf[12MB]
  2. 【清华大学-郑莉教授】C++语言程序设计 函数的参数函数的内联、重载和系统函数的调用
  3. Android下的多线程下Handler的使用
  4. 【转发】RDV/IDV/VDI三种模式
  5. Visual Studio 2015 未响应/已停止工作的问题解决
  6. 1.每天进步一点点------爬虫应用场景
  7. 学校运动会主题的微信公众号图文排版有哪些技巧?
  8. 前端最全面试指南,从简历到谈薪
  9. 网络分流器(Network Tap)是什么?
  10. 微信跳一跳java辅助