Dcmtk 源码解读SCP交互过程- CEcho Scp
Posted 恒哥的爸爸
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Dcmtk 源码解读SCP交互过程- CEcho 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类型的socket
sock = (*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的主要内容,如果未能解决你的问题,请参考以下文章
[Java随笔]冰蝎2.0-jsp马交互部分源码解读及其特征检测