当前位置: 首页 > news >正文

基于dcmtk的dicom工具 第二章 图像接受StoreSCP(2)

系列文章目录


文章目录

  • 系列文章目录
  • 前言
  • 一、CStoreServer类介绍
    • 1. storescp.cc中的主要流程
    • 2. 日志函数
    • 3. 服务初始化、启动、停止、状态查询、设置日志级别
    • 4. 主线程与工作线程
  • 二、代码
    • 1. CStoreServer.h
    • 2. CStoreServer.cpp
  • 三、调用
  • 四、总结


前言

前一章完成了图像接受的界面,本章把dcmtk storescp.cc中的接受流程封装成类CStoreServer。
并在CStoreSCPDlg中调用CStoreServer对象,完成图像接受项目。用第一章中编译dcmtk得到的
echoscu.exe、storescu.exe进行测试,
测试命令:
echoscu.exe -v -d -aet xg-storescu -aec xg-storescp 127.0.0.1 3000
storescu.exe -aec xg-storescp 127.0.0.1 3000 -v +sd E:\Test\webdicom-test-dcm\yyl
效果如下:
在这里插入图片描述


一、CStoreServer类介绍

1. storescp.cc中的主要流程

  1. ASC_initializeNetwork初始化网络,整理放入到主线程函数DoNetWorks
  2. acceptAssociation等待连接,整理放入到主线程函数DoNetWorks
  3. 在acceptAssociation中调用ASC_receiveAssociation接受连接,接受到连接后,新建任务放入到线程池由工作线程处理
  4. 服务停止DoNetWorks中调用ASC_dropNetwork关闭网络
  5. 工作线程函数DcmWorkThread->DealAssociation
  6. 重点函数DealAssociation,
    1. 调用ASC_acceptContextsWithPreferredTransferSyntaxes,
      设置接受Verification SOP Class,允许echoscu;
      设置接受dcmAllStorageSOPClassUIDs中所有Storage SOP Class UIDs,允许接受dicom文件;
      设置支持的传输语法 transfer Syntaxes,本项目接受所有支持的语法"we accept all supported transfer syntaxes";
    2. 调用acceptUnknownContextsWithPreferredTransferSyntaxes,设置接受其他未知的Storage SOP Class
    3. 调用ASC_getApplicationContextName获取协商结果
    4. 调用ASC_acknowledgeAssociation通知连接成功
    5. 调用processCommands处理命令,支持C-ECHO-RQ 和 C-STORE-RQ两种命令。
    6. processCommands中调用DIMSE_receiveCommand接受命令,根据命令类型分别调用echoSCP和storeSCP处理。
    7. 重点storeSCP中调用DIMSE_storeProvider,DIMSE_storeProvider中输入回调函数storeSCPCallback,在storeSCPCallback中把接受到的DcmDataset保存为dicom文件
    8. 图像接受完成调用ASC_dropSCPAssociation,ASC_destroyAssociation释放连接

2. 日志函数

四个日志级别对应四个日志函数,只发消息到窗口,不保存到文件。有兴趣的可以自行添加写日志文件的功能

class CStoreServer
{
public:CStoreServer();~CStoreServer();
...
protected:void log_debug(const char* fmt, ...);void log_info(const char* fmt, ...);void log_warn(const char* fmt, ...);void log_error(const char* fmt, ...);
...
}

3. 服务初始化、启动、停止、状态查询、设置日志级别

这几个函数是对外接口,供调用者使用

class CStoreServer
{
public:CStoreServer();~CStoreServer();void Init(std::string aet, int port, HWND hLogWnd, std::string rootDir); // 初始化void Start();  // 启动服务void Stop();   // 停止服务void SetLogLevel(int level);  // 设置日志级别int GetState() {  // 查询服务状态return m_state;}
...
}

4. 主线程与工作线程

服务采用多线程模式。主线程监听服务端口,等待客户端连接。有连接进来则新建任务放入线程池,由工作线程处理。
线程池使用开源的ThreadPool,只有一个头文件,非常方便。点此查看ThreadPool

  1. 初始化函数中创建线程池
void CStoreServer::Init(std::string aet, int port, HWND hLogWnd, std::string rootDir)
{OFStandard::initializeNetwork();dcmDisableGethostbyaddr.set(OFTrue);m_localAET = aet;m_port = port;m_hLogWnd = hLogWnd;m_rootDir = rootDir;// 线程池SYSTEM_INFO sysInfo;GetSystemInfo(&sysInfo);int nWorkThread = sysInfo.dwNumberOfProcessors;//m_threadPool = std::make_unique<ThreadPool>(nWorkThread);m_threadPool = new ThreadPool(nWorkThread);}
  1. 启动函数启动主线程监听端口
void CStoreServer::Start()
{m_bStop = false;m_thNet = std::thread([&]() {this->DoNetWorks();});std::this_thread::sleep_for(std::chrono::milliseconds(50));}
  1. 收到连接后创建任务交给工作线程处理
OFCondition CStoreServer::acceptAssociation(DcmAssociationConfiguration& asccfg)
{OFCondition cond = EC_Normal;int nAssocTimeOut = 1;T_ASC_Association* pAssoc = NULL;// try to receive an association. Here we either want to use blocking or// non-blocking, depending on if the option --eostudy-timeout is set.do{if (m_bStop){break;}pAssoc = NULL;cond = ASC_receiveAssociation(m_pNet, &pAssoc, ASC_DEFAULTMAXPDU, NULL, NULL, OFFalse, DUL_NOBLOCK, nAssocTimeOut);if (cond.code() == DULC_NOASSOCIATIONREQUEST && pAssoc){ASC_destroyAssociationParameters(&pAssoc->params);free(pAssoc);pAssoc = NULL;}} while (cond.code() == DULC_NOASSOCIATIONREQUEST);OFString temp_str;if (m_bStop){ASC_dropAssociation(pAssoc);ASC_destroyAssociation(&pAssoc);return EC_Normal;}// if some kind of error occurred, take care of itif (cond.bad()){// no matter what kind of error occurred, we need to do a cleanupDimseCondition::dump(temp_str, cond);Replace(temp_str, "\n", "\r\n");log_error("Receive Association error,code:0x%04x,%s", cond.code(), temp_str.c_str());ASC_dropAssociation(pAssoc);ASC_destroyAssociation(&pAssoc);return EC_Normal;}// 收到客户端连接,放入线程池处理DealAssocParam* dap = new DealAssocParam;dap->pServer = this;dap->pAssoc = pAssoc;m_threadPool->enqueue(DcmWorkThread, dap);return EC_Normal;
}

二、代码

1. CStoreServer.h

#pragma once
#include "ThreadPool.h"#include "dcmtk/config/osconfig.h"
#include "dcmtk/dcmdata/dcfilefo.h"
#include "dcmtk/dcmdata/dctk.h" 
#include "dcmtk/ofstd/ofstd.h"
#include "dcmtk/dcmnet/dimse.h"
#include "dcmtk/dcmnet/diutil.h"
#include "dcmtk/dcmnet/dcmtrans.h"      /* for dcmSocketSend/ReceiveTimeout */
#include "dcmtk/dcmnet/dcasccfg.h"      /* for class DcmAssociationConfiguration */
#include "dcmtk/dcmnet/dcasccff.h"      /* for class DcmAssociationConfigurationFile */
#include "dcmtk/dcmdata/dcuid.h"        /* for dcmtk version name */struct DealAssocParam
{void* pServer;T_ASC_Association* pAssoc;
};struct StoreCallbackData
{std::string dcmPath;std::string jpgPath;std::string filePath;DcmFileFormat* dcmff;T_ASC_Association* assoc;void *pServer;
};class CStoreServer
{
public:CStoreServer();~CStoreServer();void Init(std::string aet, int port, HWND hLogWnd, std::string rootDir);void Start();void Stop();void SetLogLevel(int level);int GetState() {return m_state;}protected:void log_debug(const char* fmt, ...);void log_info(const char* fmt, ...);void log_warn(const char* fmt, ...);void log_error(const char* fmt, ...);private:void DoNetWorks();void DealAssociation(T_ASC_Association* pAssoc);static void DcmWorkThread(void* param);static std::string GetArchivePath(DcmDataset* dset);OFCondition	acceptAssociation(DcmAssociationConfiguration& asccfg);// dicom worksDUL_PRESENTATIONCONTEXT * findPresentationContextID(LST_HEAD * head,T_ASC_PresentationContextID presentationContextID);OFCondition acceptUnknownContextsWithTransferSyntax(T_ASC_Parameters * params,const char* transferSyntax,T_ASC_SC_ROLE acceptedRole);OFCondition acceptUnknownContextsWithPreferredTransferSyntaxes(T_ASC_Parameters * params,const char* transferSyntaxes[],int transferSyntaxCount,T_ASC_SC_ROLE acceptedRole);OFCondition processCommands(T_ASC_Association* pAssoc);OFCondition echoSCP(T_ASC_Association* pAssoc,T_DIMSE_Message * msg,T_ASC_PresentationContextID presID);OFCondition storeSCP(T_ASC_Association* pAssoc,T_DIMSE_Message *msg,T_ASC_PresentationContextID presID);static void storeSCPCallback(void *callbackData,T_DIMSE_StoreProgress *progress,T_DIMSE_C_StoreRQ *req,char * /*imageFileName*/,DcmDataset **imageDataSet,T_DIMSE_C_StoreRSP *rsp,DcmDataset **statusDetail);private://std::unique_ptr<ThreadPool> m_threadPool;ThreadPool* m_threadPool;std::thread m_thNet;bool m_bStop;int m_loglevel; // 0 Debug, 1 Info, 2 Warn, 3 Errorint m_port;std::string m_localAET;HWND m_hLogWnd;std::string m_rootDir;int m_state; // 0 停止, 1 运行T_ASC_Network*	m_pNet;
};

2. CStoreServer.cpp

#include "pch.h"
#include "CStoreServer.h"
#include "Utilities.h"
#include "StoreSCPDlg.h"#pragma comment(lib, "Iphlpapi.lib")
#pragma comment(lib, "ws2_32.lib")
#pragma comment(lib, "netapi32.lib")#pragma comment(lib, "dcmnet.lib")
#pragma comment(lib, "dcmdata.lib")
#pragma comment(lib, "oflog.lib")
#pragma comment(lib, "ofstd.lib")
#pragma comment(lib, "dcmtls.lib")
#pragma comment(lib, "oficonv.lib")#ifdef _DEBUG
#pragma comment(lib,"zlib_d.lib")
#else
#pragma comment(lib,"zlib_o.lib")
#endifCStoreServer::CStoreServer(): m_hLogWnd(nullptr), m_threadPool(nullptr), m_loglevel(3), m_state(0), m_bStop(false), m_Logger(OFLog::getLogger("xg.storescp"))
{
}CStoreServer::~CStoreServer()
{if (m_threadPool) {delete m_threadPool;m_threadPool = nullptr;}OFStandard::shutdownNetwork();
}void CStoreServer::Init(std::string aet, int port, HWND hLogWnd, std::string rootDir)
{OFStandard::initializeNetwork();dcmDisableGethostbyaddr.set(OFTrue);m_localAET = aet;m_port = port;m_hLogWnd = hLogWnd;m_rootDir = rootDir;// 线程池SYSTEM_INFO sysInfo;GetSystemInfo(&sysInfo);int nWorkThread = sysInfo.dwNumberOfProcessors;m_threadPool = new ThreadPool(nWorkThread);}void CStoreServer::Start()
{m_bStop = false;m_thNet = std::thread([&]() {this->DoNetWorks();});std::this_thread::sleep_for(std::chrono::milliseconds(50));}void CStoreServer::Stop()
{m_bStop = true;if (m_thNet.joinable()) {m_thNet.join();}
}void CStoreServer::SetLogLevel(int level)
{m_loglevel = level;
}void CStoreServer::log_debug(const char* fmt, ...)
{std::string str;va_list args;va_start(args, fmt);{int nLength = _vscprintf(fmt, args);nLength += 1; std::vector<char> vectorChars(nLength);_vsnprintf(vectorChars.data(), nLength, fmt, args);str.assign(vectorChars.data());}va_end(args);std::string timeStr = GetTimeStr(5);std::string logStr = "[" + timeStr + "][DEBUG] " + str + "\r\n";if (0 >= m_loglevel) {::SendMessage(m_hLogWnd, UM_ADD_LOG, (WPARAM)logStr.c_str(), 0);}}void CStoreServer::log_info(const char* fmt, ...)
{std::string str;va_list args;va_start(args, fmt);{int nLength = _vscprintf(fmt, args);nLength += 1;std::vector<char> vectorChars(nLength);_vsnprintf(vectorChars.data(), nLength, fmt, args);str.assign(vectorChars.data());}va_end(args);std::string timeStr = GetTimeStr(5);std::string logStr = "[" + timeStr + "][INFO] " + str + "\r\n";if (1 >= m_loglevel) {::SendMessage(m_hLogWnd, UM_ADD_LOG, (WPARAM)logStr.c_str(), 0);}
}void CStoreServer::log_warn(const char* fmt, ...)
{std::string str;va_list args;va_start(args, fmt);{int nLength = _vscprintf(fmt, args);nLength += 1;std::vector<char> vectorChars(nLength);_vsnprintf(vectorChars.data(), nLength, fmt, args);str.assign(vectorChars.data());}va_end(args);std::string timeStr = GetTimeStr(5);std::string logStr = "[" + timeStr + "][WARN] " + str + "\r\n";if (2 >= m_loglevel) {::SendMessage(m_hLogWnd, UM_ADD_LOG, (WPARAM)logStr.c_str(), 0);}
}void CStoreServer::log_error(const char* fmt, ...)
{std::string str;va_list args;va_start(args, fmt);{int nLength = _vscprintf(fmt, args);nLength += 1;std::vector<char> vectorChars(nLength);_vsnprintf(vectorChars.data(), nLength, fmt, args);str.assign(vectorChars.data());}va_end(args);std::string timeStr = GetTimeStr(5);std::string logStr = "[" + timeStr + "][ERROR] " + str + "\r\n";if (3 >= m_loglevel) {::SendMessage(m_hLogWnd, UM_ADD_LOG, (WPARAM)logStr.c_str(), 0);}
}void CStoreServer::DoNetWorks()
{OFCondition cond;DcmAssociationConfiguration asccfg;/* initialize network, i.e. create an instance of T_ASC_Network*. */cond = ASC_initializeNetwork(NET_ACCEPTOR, OFstatic_cast(int, m_port), 600, (T_ASC_Network **)&m_pNet);if (cond.bad()){DimseCondition::dump(cond);log_error("初始化网络失败,请检查端口[%d]是否被占用.", m_port);return;}m_state = 1;if (m_hLogWnd) {::SendMessage(m_hLogWnd, UM_SERVER_START, 0, 0);}while (cond.good()){/* receive an association and acknowledge or reject it. If the association was *//* acknowledged, offer corresponding services and invoke one or more if required. */log_info("等待连接...");cond = acceptAssociation(asccfg);if (m_bStop){break;}}m_state = 0;if (m_hLogWnd) {::PostMessage(m_hLogWnd, UM_SERVER_STOP, 0, 0);}/* drop the network, i.e. free memory of T_ASC_Network* structure. This call *//* is the counterpart of ASC_initializeNetwork(...) which was called above. */ASC_dropNetwork((T_ASC_Network **)&m_pNet);}void CStoreServer::DealAssociation(T_ASC_Association* pAssoc)
{char buf[BUFSIZ];OFCondition cond;OFString temp_str;std::string ourAET = m_localAET;int	 numberOfSupportedAbstractSyntaxes = 0;const char* knownAbstractSyntaxes[] ={UID_VerificationSOPClass};const char* transferSyntaxes[] = { NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,   // 10NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,   // 20NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,   // 30NULL, NULL, NULL, NULL, NULL };                               // +5int numTransferSyntaxes = 0;transferSyntaxes[0] = UID_JPEG2000TransferSyntax;transferSyntaxes[1] = UID_JPEG2000LosslessOnlyTransferSyntax;transferSyntaxes[2] = UID_JPEGProcess2_4TransferSyntax;transferSyntaxes[3] = UID_JPEGProcess1TransferSyntax;transferSyntaxes[4] = UID_JPEGProcess14SV1TransferSyntax;transferSyntaxes[5] = UID_JPEGLSLossyTransferSyntax;transferSyntaxes[6] = UID_JPEGLSLosslessTransferSyntax;transferSyntaxes[7] = UID_RLELosslessTransferSyntax;transferSyntaxes[8] = UID_MPEG2MainProfileAtMainLevelTransferSyntax;transferSyntaxes[9] = UID_FragmentableMPEG2MainProfileMainLevelTransferSyntax;transferSyntaxes[10] = UID_MPEG2MainProfileAtHighLevelTransferSyntax;transferSyntaxes[11] = UID_FragmentableMPEG2MainProfileHighLevelTransferSyntax;transferSyntaxes[12] = UID_MPEG4HighProfileLevel4_1TransferSyntax;transferSyntaxes[13] = UID_FragmentableMPEG4HighProfileLevel4_1TransferSyntax;transferSyntaxes[14] = UID_MPEG4BDcompatibleHighProfileLevel4_1TransferSyntax;transferSyntaxes[15] = UID_FragmentableMPEG4BDcompatibleHighProfileLevel4_1TransferSyntax;transferSyntaxes[16] = UID_MPEG4HighProfileLevel4_2_For2DVideoTransferSyntax;transferSyntaxes[17] = UID_FragmentableMPEG4HighProfileLevel4_2_For2DVideoTransferSyntax;transferSyntaxes[18] = UID_MPEG4HighProfileLevel4_2_For3DVideoTransferSyntax;transferSyntaxes[19] = UID_FragmentableMPEG4HighProfileLevel4_2_For3DVideoTransferSyntax;transferSyntaxes[20] = UID_MPEG4StereoHighProfileLevel4_2TransferSyntax;transferSyntaxes[21] = UID_FragmentableMPEG4StereoHighProfileLevel4_2TransferSyntax;transferSyntaxes[22] = UID_HEVCMainProfileLevel5_1TransferSyntax;transferSyntaxes[23] = UID_HEVCMain10ProfileLevel5_1TransferSyntax;transferSyntaxes[24] = UID_HighThroughputJPEG2000ImageCompressionLosslessOnlyTransferSyntax;transferSyntaxes[25] = UID_HighThroughputJPEG2000RPCLImageCompressionLosslessOnlyTransferSyntax;transferSyntaxes[26] = UID_HighThroughputJPEG2000ImageCompressionTransferSyntax;transferSyntaxes[27] = UID_JPEGXLLosslessTransferSyntax;transferSyntaxes[28] = UID_JPEGXLJPEGRecompressionTransferSyntax;transferSyntaxes[29] = UID_JPEGXLTransferSyntax;transferSyntaxes[30] = UID_DeflatedExplicitVRLittleEndianTransferSyntax;transferSyntaxes[31] = UID_EncapsulatedUncompressedExplicitVRLittleEndianTransferSyntax;if (gLocalByteOrder == EBO_LittleEndian){transferSyntaxes[32] = UID_LittleEndianExplicitTransferSyntax;transferSyntaxes[33] = UID_BigEndianExplicitTransferSyntax;}else {transferSyntaxes[32] = UID_BigEndianExplicitTransferSyntax;transferSyntaxes[33] = UID_LittleEndianExplicitTransferSyntax;}transferSyntaxes[34] = UID_LittleEndianImplicitTransferSyntax;numTransferSyntaxes = 35;/* dump presentation contexts if required */char callingAddress[100];char calledAddress[100];ASC_getPresentationAddresses(pAssoc->params, callingAddress, sizeof(callingAddress), calledAddress, sizeof(calledAddress));OFString callingAET = pAssoc->params->DULparams.callingAPTitle;log_info("收到连接 [AETitle:%s,IP:%s]", pAssoc->params->DULparams.callingAPTitle, callingAddress);/* accept the Verification SOP Class if presented */cond = ASC_acceptContextsWithPreferredTransferSyntaxes(pAssoc->params,knownAbstractSyntaxes,DIM_OF(knownAbstractSyntaxes),transferSyntaxes,numTransferSyntaxes);if (cond.bad()){DimseCondition::dump(temp_str, cond);Replace(temp_str, "\n", "\r\n");log_error("%s, line:%d", temp_str.c_str(), __LINE__);goto cleanup;}/* the array of Storage SOP Class UIDs comes from dcuid.h */cond = ASC_acceptContextsWithPreferredTransferSyntaxes(pAssoc->params,dcmAllStorageSOPClassUIDs, numberOfDcmAllStorageSOPClassUIDs,transferSyntaxes,numTransferSyntaxes);if (cond.bad()){DimseCondition::dump(temp_str, cond);Replace(temp_str, "\n", "\r\n");log_error("%s, line:%d", temp_str.c_str(), __LINE__);goto cleanup;}/* accept everything not known not to be a storage SOP class */cond = acceptUnknownContextsWithPreferredTransferSyntaxes(pAssoc->params, transferSyntaxes, numTransferSyntaxes, ASC_SC_ROLE_DEFAULT);if (cond.bad()){DimseCondition::dump(temp_str, cond);Replace(temp_str, "\n", "\r\n");log_error("%s,line:%d", temp_str.c_str(), __LINE__);goto cleanup;}/* set our app title */ASC_setAPTitles(pAssoc->params, NULL, NULL, ourAET.c_str());/* acknowledge or reject this association */cond = ASC_getApplicationContextName(pAssoc->params, buf, sizeof(buf));if ((cond.bad()) || strcmp(buf, UID_StandardApplicationContext) != 0){/* reject: the application context name is not supported */T_ASC_RejectParameters rej ={ASC_RESULT_REJECTEDPERMANENT,ASC_SOURCE_SERVICEUSER,ASC_REASON_SU_APPCONTEXTNAMENOTSUPPORTED};log_error("Association Rejected: Bad Application Context Name: %s", buf);cond = ASC_rejectAssociation(pAssoc, &rej);if (cond.bad()){DimseCondition::dump(temp_str, cond);Replace(temp_str, "\n", "\r\n");log_error("%s,line:%d", temp_str.c_str(), __LINE__);}goto cleanup;}if (strlen(pAssoc->params->theirImplementationClassUID) == 0){/* reject: the no implementation Class UID provided */T_ASC_RejectParameters rej ={ASC_RESULT_REJECTEDPERMANENT,ASC_SOURCE_SERVICEUSER,ASC_REASON_SU_NOREASON};log_error("Association Rejected: No Implementation Class UID provided.");cond = ASC_rejectAssociation(pAssoc, &rej);if (cond.bad()){DimseCondition::dump(temp_str, cond);Replace(temp_str, "\n","\r\n");log_error("%s,line:%d",temp_str.c_str(),__LINE__);}goto cleanup;}log_debug("Implementation Class UID:[%s]", pAssoc->params->theirImplementationClassUID);log_debug("Implementation VersionName:[%s]", pAssoc->params->theirImplementationVersionName);cond = ASC_acknowledgeAssociation(pAssoc);if (cond.bad()){DimseCondition::dump(temp_str, cond);Replace(temp_str, "\n", "\r\n");log_error("%s,line:%d", temp_str.c_str(), __LINE__);goto cleanup;}log_info("Association Acknowledged (Max Send PDV: %d)", pAssoc->sendPDVLength);if (ASC_countAcceptedPresentationContexts(pAssoc->params) == 0)log_debug(",but no valid presentation contexts");/* dump the presentation contexts which have been accepted/refused */ASC_dumpParameters(temp_str, pAssoc->params, ASC_ASSOC_AC);Replace(temp_str, "\n", "\r\n");log_debug("Request Parameters:\r\n%s", temp_str.c_str());/* now do the real work, i.e. receive DIMSE commands over the network connection *//* which was established and handle these commands correspondingly. In case of *//* storescp only C-ECHO-RQ and C-STORE-RQ commands can be processed. */cond = processCommands(pAssoc);if (cond == DUL_PEERREQUESTEDRELEASE){log_info("%s Release Association,done.", callingAET.c_str());cond = ASC_acknowledgeRelease(pAssoc);}else if (cond == DUL_PEERABORTEDASSOCIATION){log_info("%s Abort Association", callingAET.c_str());}else if (cond.code() != 100){log_error("DIMSE failure (aborting association).");/* some kind of error so abort the association *///cond = ASC_releaseAssociation(pAssoc);ASC_abortAssociation(pAssoc);}else{DimseCondition::dump(temp_str, cond);Replace(temp_str, "\n", "\r\n");log_error("processCommands error, %s", temp_str.c_str());}cleanup:ASC_dropSCPAssociation(pAssoc);ASC_destroyAssociation(&pAssoc);}void CStoreServer::DcmWorkThread(void* param)
{DealAssocParam *dap = (DealAssocParam*)param;CStoreServer* pServer = (CStoreServer*)dap->pServer;pServer->DealAssociation(dap->pAssoc);delete dap;dap = nullptr;DWORD dwThread = GetCurrentThreadId();pServer->log_info("NetWork Thread %d exit, tasksize: %d", (DWORD)dwThread, pServer->m_threadPool->TaskNumber());
}std::string CStoreServer::GetArchivePath(DcmDataset* dset)
{std::string patName, seriesInsUID;dset->findAndGetOFString(DCM_PatientName, patName);dset->findAndGetOFString(DCM_SeriesInstanceUID, seriesInsUID);return patName + "\\" + seriesInsUID + "\\";
}OFCondition CStoreServer::acceptAssociation(DcmAssociationConfiguration& asccfg)
{OFCondition cond = EC_Normal;int nAssocTimeOut = 1;T_ASC_Association* pAssoc = NULL;// try to receive an association. Here we either want to use blocking or// non-blocking, depending on if the option --eostudy-timeout is set.do{if (m_bStop){break;}pAssoc = NULL;cond = ASC_receiveAssociation(m_pNet, &pAssoc, ASC_DEFAULTMAXPDU, NULL, NULL, OFFalse, DUL_NOBLOCK, nAssocTimeOut);} while (cond.code() == DULC_NOASSOCIATIONREQUEST);OFString temp_str;if (m_bStop){//log_info("User Cancel receive association.");ASC_dropAssociation(pAssoc);ASC_destroyAssociation(&pAssoc);return EC_Normal;}// if some kind of error occurred, take care of itif (cond.bad()){// no matter what kind of error occurred, we need to do a cleanupDimseCondition::dump(temp_str, cond);Replace(temp_str, "\n", "\r\n");log_error("Receive Association error,code:0x%04x,%s", cond.code(), temp_str.c_str());ASC_dropAssociation(pAssoc);ASC_destroyAssociation(&pAssoc);return EC_Normal;}DealAssocParam* dap = new DealAssocParam;dap->pServer = this;dap->pAssoc = pAssoc;m_threadPool->enqueue(DcmWorkThread, dap);return EC_Normal;
}DUL_PRESENTATIONCONTEXT * CStoreServer::findPresentationContextID(LST_HEAD * head, T_ASC_PresentationContextID presentationContextID)
{DUL_PRESENTATIONCONTEXT *pc;LST_HEAD **l;OFBool found = OFFalse;if (head == NULL)return NULL;l = &head;if (*l == NULL)return NULL;pc = OFstatic_cast(DUL_PRESENTATIONCONTEXT *, LST_Head(l));(void)LST_Position(l, OFstatic_cast(LST_NODE *, pc));while (pc && !found) {if (pc->presentationContextID == presentationContextID) {found = OFTrue;}else {pc = OFstatic_cast(DUL_PRESENTATIONCONTEXT *, LST_Next(l));}}return pc;
}OFCondition CStoreServer::acceptUnknownContextsWithTransferSyntax(T_ASC_Parameters * params, const char * transferSyntax, T_ASC_SC_ROLE acceptedRole)
{OFCondition cond = EC_Normal;int n, i, k;DUL_PRESENTATIONCONTEXT *dpc;T_ASC_PresentationContext pc;OFBool accepted = OFFalse;OFBool abstractOK = OFFalse;n = ASC_countPresentationContexts(params);for (i = 0; i < n; i++){cond = ASC_getPresentationContext(params, i, &pc);if (cond.bad()) return cond;abstractOK = OFFalse;accepted = OFFalse;if (dcmFindNameOfUID(pc.abstractSyntax) == NULL){abstractOK = OFTrue;/* check the transfer syntax */for (k = 0; (k < OFstatic_cast(int, pc.transferSyntaxCount)) && !accepted; k++){if (strcmp(pc.proposedTransferSyntaxes[k], transferSyntax) == 0){accepted = OFTrue;}}}if (accepted){cond = ASC_acceptPresentationContext(params, pc.presentationContextID,transferSyntax, acceptedRole);if (cond.bad()) return cond;}else {T_ASC_P_ResultReason reason;/* do not refuse if already accepted */dpc = findPresentationContextID(params->DULparams.acceptedPresentationContext,pc.presentationContextID);if ((dpc == NULL) ||((dpc != NULL) && (dpc->result != ASC_P_ACCEPTANCE))){if (abstractOK) {reason = ASC_P_TRANSFERSYNTAXESNOTSUPPORTED;}else {reason = ASC_P_ABSTRACTSYNTAXNOTSUPPORTED;}/** If previously this presentation context was refused* because of bad transfer syntax let it stay that way.*/if ((dpc != NULL) &&(dpc->result == ASC_P_TRANSFERSYNTAXESNOTSUPPORTED))reason = ASC_P_TRANSFERSYNTAXESNOTSUPPORTED;cond = ASC_refusePresentationContext(params,pc.presentationContextID,reason);if (cond.bad()) return cond;}}}return EC_Normal;
}OFCondition CStoreServer::acceptUnknownContextsWithPreferredTransferSyntaxes(T_ASC_Parameters * params, const char * transferSyntaxes[], int transferSyntaxCount, T_ASC_SC_ROLE acceptedRole)
{OFCondition cond = EC_Normal;/*** Accept in the order "least wanted" to "most wanted" transfer** syntax.  Accepting a transfer syntax will override previously** accepted transfer syntaxes.*/for (int i = transferSyntaxCount - 1; i >= 0; i--){cond = acceptUnknownContextsWithTransferSyntax(params, transferSyntaxes[i], acceptedRole);if (cond.bad()) return cond;}return cond;
}OFCondition CStoreServer::processCommands(T_ASC_Association* pAssoc)
{OFCondition cond = EC_Normal;T_DIMSE_Message msg;T_ASC_PresentationContextID presID = 0;DcmDataset *statusDetail = NULL;int	 nImgCount = 0;DWORD start = GetTickCount();DWORD end;int timeout = 20;// start a loop to be able to receive more than one DIMSE commandwhile (cond == EC_Normal || cond == DIMSE_NODATAAVAILABLE || cond == DIMSE_OUTOFRESOURCES){// receive a DIMSE command over the network//cond = DIMSE_receiveCommand(pAssoc, DIMSE_NONBLOCKING, 5, &presID, &msg, &statusDetail);cond = DIMSE_receiveCommand(pAssoc, DIMSE_BLOCKING, 0, &presID, &msg, &statusDetail);end = GetTickCount();DWORD time = (end - start) / 1000;if (time >= timeout){cond = DUL_READTIMEOUT;break;}if (m_bStop){cond = makeOFCondition(0, 100, OF_error, "User forced to stop Dicom server,after DIMSE_receiveCommand");break;}// if the command which was received has extra status// detail information, dump this informationif (statusDetail != NULL){//statusDetail->print(COUT);delete statusDetail;}// check if peer did release or abort, or if we have a valid messageif (cond == EC_Normal){// in case we received a valid message, process this command// note that storescp can only process a C-ECHO-RQ and a C-STORE-RQswitch (msg.CommandField){case DIMSE_C_ECHO_RQ:// process C-ECHO-Requestcond = echoSCP(pAssoc, &msg, presID);break;case DIMSE_C_STORE_RQ:cond = storeSCP(pAssoc, &msg, presID);break;default:// we cannot handle this kind of messagecond = DIMSE_BADCOMMANDTYPE;log_error("DcmNet Cannot handle command: 0x%x", OFstatic_cast(unsigned, msg.CommandField));break;}start = GetTickCount();}}return cond;
}OFCondition CStoreServer::echoSCP(T_ASC_Association* pAssoc, T_DIMSE_Message * msg, T_ASC_PresentationContextID presID)
{OFCondition cond = DIMSE_sendEchoResponse(pAssoc, presID, &msg->msg.CEchoRQ, STATUS_Success, NULL);if (cond.bad()){OFString temp_str;DimseCondition::dump(temp_str, cond);log_info("Echo SCP Failed: %s", temp_str.c_str());} else {log_info("process C-ECHO-Request Success.");}return cond;
}OFCondition CStoreServer::storeSCP(T_ASC_Association* pAssoc, T_DIMSE_Message *msg, T_ASC_PresentationContextID presID)
{OFCondition cond = EC_Normal;T_DIMSE_C_StoreRQ *req;std::string logMsg;// assign the actual information of the C-STORE-RQ command to a local variablereq = &msg->msg.CStoreRQ;// Dump some information if required.log_debug("Receive C-STORE Request, MessageID:%ld", req->MessageID);// initialize some variablesStoreCallbackData callbackData;callbackData.assoc = pAssoc;//callbackData.dcmPath = imgDir;DcmFileFormat dcmff;callbackData.dcmff = &dcmff;callbackData.pServer = this;const char *callingaet = pAssoc->params->DULparams.callingAPTitle;// store SourceApplicationEntityTitle in metaheaderif (pAssoc && pAssoc->params){if (callingaet) dcmff.getMetaInfo()->putAndInsertString(DCM_SourceApplicationEntityTitle, callingaet);}// define an address where the information which will be received over the network will be storedDcmDataset *dset = dcmff.getDataset();DcmMetaInfo* mpMetaInfo = dcmff.getMetaInfo();cond = DIMSE_storeProvider(pAssoc, presID, req, NULL, OFTrue, &dset,storeSCPCallback, &callbackData, DIMSE_BLOCKING, 0);// if some error occurred, dump corresponding information and remove the outfile if necessaryif (cond.bad()){OFString temp_str;DimseCondition::dump(temp_str, cond);logMsg = temp_str;Replace(logMsg, "\n", "\r\n");OFString patId;dset->findAndGetOFString(DCM_PatientID, patId);log_error("接收图像失败,[%s>>%s],[UID:%s,PID:%s][%s]", callingaet, m_localAET.c_str(), req->AffectedSOPInstanceUID, patId.c_str(), logMsg.c_str());return cond;}OFString strDcmName = callbackData.dcmPath.c_str();log_info("收到图像,[%s>>%s],%s", callingaet, m_localAET.c_str(), strDcmName.c_str());return cond;
}void CStoreServer::storeSCPCallback(void *callbackData, T_DIMSE_StoreProgress *progress, T_DIMSE_C_StoreRQ *req, char * /*imageFileName*/, DcmDataset **imageDataSet, T_DIMSE_C_StoreRSP *rsp, DcmDataset **statusDetail)
{DIC_UI sopClass;DIC_UI sopInstance;StoreCallbackData *cbdata = OFstatic_cast(StoreCallbackData *, callbackData);CStoreServer* pServer = (CStoreServer*)cbdata->pServer;// determine if the association shall be abortedif ((progress->state != DIMSE_StoreBegin || progress->state == DIMSE_StoreEnd) && pServer->m_bStop){pServer->log_info("Abort initiated (due to user operation)");ASC_abortAssociation((OFstatic_cast(StoreCallbackData*, callbackData))->assoc);rsp->DimseStatus = STATUS_STORE_Refused_OutOfResources;return;}if (pServer->m_bStop){pServer->log_info("Abort initiated (user force to exit)");ASC_abortAssociation((OFstatic_cast(StoreCallbackData*, callbackData))->assoc);rsp->DimseStatus = STATUS_STORE_Refused_OutOfResources;return;}// if this is the final call of this function, save the data which was received to a file// (note that we could also save the image somewhere else, put it in database, etc.)if (progress->state == DIMSE_StoreEnd){// do not send status detail information*statusDetail = NULL;// we want to write the received information to a file only if this information// is present and the options opt_bitPreserving and opt_ignore are not set.if ((imageDataSet != NULL) && (*imageDataSet != NULL)){OFCondition cond = EC_Normal;OFString fileName;std::string rootDir = pServer->m_rootDir;char lastChar = rootDir.at(rootDir.length() - 1);if (lastChar != '\\' && lastChar != '/') {rootDir += "\\";}std::string relaPath = GetArchivePath(*imageDataSet);OFString sopInsUid;(*imageDataSet)->findAndGetOFString(DCM_SOPInstanceUID, sopInsUid);fileName = rootDir + relaPath + sopInsUid + ".dcm";Replace(fileName, "/", "\\");cbdata->dcmPath = fileName.c_str();// determine the transfer syntax which shall be used to write the information to the file// 根据设置压缩图像, 未实现E_TransferSyntax xfer = (*imageDataSet)->getOriginalXfer();DcmXfer newXfer = xfer;std::string imgDir = rootDir + relaPath;if (!OFStandard::dirExists(imgDir.c_str()))MakePath(imgDir);cond = cbdata->dcmff->saveFile(fileName.c_str(), xfer);if (cond.bad()){pServer->log_error("cannot write DICOM file: %s:%s", fileName.c_str(), cond.text());rsp->DimseStatus = STATUS_STORE_Refused_OutOfResources;// delete incomplete fileOFStandard::deleteFile(fileName);}if (cond.bad()){pServer->log_error("DcmNet StoreScp write to %s failed,\r\n%s", fileName.c_str(), cond.text());rsp->DimseStatus = STATUS_STORE_Refused_OutOfResources;}// check the image to make sure it is consistent, i.e. that its sopClass and sopInstance correspond// to those mentioned in the request. If not, set the status in the response message variable.if ((rsp->DimseStatus == STATUS_Success)){// which SOP class and SOP instance ?if (!DU_findSOPClassAndInstanceInDataSet(*imageDataSet, sopClass, sizeof(sopClass), sopInstance, sizeof(sopInstance), OFFalse)){pServer->log_error("DcmNet StoreScp: Bad image file %s", fileName.c_str());rsp->DimseStatus = STATUS_STORE_Error_CannotUnderstand;}if (strcmp(sopClass, req->AffectedSOPClassUID) != 0){rsp->DimseStatus = STATUS_STORE_Error_DataSetDoesNotMatchSOPClass;}else if (strcmp(sopInstance, req->AffectedSOPInstanceUID) != 0){rsp->DimseStatus = STATUS_STORE_Error_DataSetDoesNotMatchSOPClass;}}}}
}

三、调用

  1. 在上一章CStoreSCPDlg.h中定义变量
#include "CStoreServer.h"
class CStoreSCPDlg : public CDialogEx
{
...
private:CStoreServer m_storeSCP;
...
}
  1. OnInitDialog中初始化
BOOL CStoreSCPDlg::OnInitDialog()
{CDialogEx::OnInitDialog();...// TODO: 在此添加额外的初始化代码CString msg;msg.Format(_T("程序启动, AET:%s, 端口:%d"), m_aet, m_port);addLog(msg);m_rootDir = GetAppPath().c_str();m_rootDir += _T("RecvFiles");...// 初始化服务m_storeSCP.Init(std::string(m_aet), m_port, this->m_hWnd, std::string(m_rootDir));m_storeSCP.SetLogLevel(1);...return TRUE;  // 除非将焦点设置到控件,否则返回 TRUE
}
  1. 点击按钮启动/停止服务
void CStoreSCPDlg::OnBnClickedButtonStart()
{// TODO: 在此添加控件通知处理程序代码if (m_storeSCP.GetState() == 0) {m_storeSCP.Start();}else {m_storeSCP.Stop();}
}
  1. OnDestory中停止服务
void CStoreSCPDlg::OnDestroy()
{if (m_storeSCP.GetState() == 1) {m_storeSCP.Stop();}CDialogEx::OnDestroy();
}

四、总结

下载测试StoreSCP.exe

http://www.dtcms.com/a/284414.html

相关文章:

  • Python Day16
  • Java行为型模式---备忘录模式
  • 从零开始的云计算生活——第三十三天,关山阻隔,ELK日志分析
  • rtp传输推流h265
  • Unity使用GTCRN实现流式语音增强
  • SpringBoot一Web Flux、函数式Web请求的使用、和传统注解@Controller + @RequestMapping的区别
  • 探微“元宇宙”:概念内涵、形态发展与演变机理
  • CSS面试题及详细答案140道之(41-60)
  • Kiro AI IDE上手初体验!亚马逊出品,能否撼动Cursor的王座?
  • Amazon S3成本优化完全指南:从入门到精通
  • 8 几何叠加分析
  • 系统设计时平衡超时时间与多因素认证(MFA)带来的用户体验下降
  • 量子计算的安全与伦理:当技术革命叩击数字时代的潘多拉魔盒
  • sqli-labs靶场通关笔记:第25-26a关 and、or、空格和注释符多重过滤
  • 4G模块 A7680通过MQTT协议连接到腾讯云
  • AI赋能Baklib,重塑企业知识管理与客户支持方式
  • Curr. Res. Food Sci.|福州大学吕旭聪团队:富硒鼠李糖乳杆菌GG重塑肠-肝轴,显著缓解酒精性肝损伤
  • 网络通信之基础知识
  • deep learning(李宏毅)--(六)--loss
  • day19-四剑客与正则-特殊符号正则-awk
  • [yotroy.cool] 记一次 Git 移除某个不该提交的文件
  • iOS WebView 调试与性能优化 跨平台团队高效协作方法解析
  • PyTorch生成式人工智能(18)——循环神经网络详解与实现
  • 可视化图解算法56:岛屿数量
  • Word 中为什么我的图片一拖就乱跑,怎么精确定位?
  • python使用pymysql库
  • modbus 校验
  • 泛型与类型安全深度解析及响应式API实战
  • Java 集合框架详解:Collection 接口全解析,从基础到实战
  • 7月17日日记