系列文章目录
文章目录
- 系列文章目录
- 前言
- 一、CStoreServer类介绍
-
- [1. storescp.cc中的主要流程](#1. storescp.cc中的主要流程)
- [2. 日志函数](#2. 日志函数)
- [3. 服务初始化、启动、停止、状态查询、设置日志级别](#3. 服务初始化、启动、停止、状态查询、设置日志级别)
- [4. 主线程与工作线程](#4. 主线程与工作线程)
- 二、代码
-
- [1. CStoreServer.h](#1. CStoreServer.h)
- [2. CStoreServer.cpp](#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中的主要流程
- ASC_initializeNetwork初始化网络,整理放入到主线程函数DoNetWorks
- acceptAssociation等待连接,整理放入到主线程函数DoNetWorks
- 在acceptAssociation中调用ASC_receiveAssociation接受连接,接受到连接后,新建任务放入到线程池由工作线程处理
- 服务停止DoNetWorks中调用ASC_dropNetwork关闭网络
- 工作线程函数DcmWorkThread->DealAssociation
重点
函数DealAssociation,- 调用ASC_acceptContextsWithPreferredTransferSyntaxes,
设置接受Verification SOP Class,允许echoscu;
设置接受dcmAllStorageSOPClassUIDs中所有Storage SOP Class UIDs,允许接受dicom文件;
设置支持的传输语法 transfer Syntaxes,本项目接受所有支持的语法"we accept all supported transfer syntaxes"; - 调用acceptUnknownContextsWithPreferredTransferSyntaxes,设置接受其他未知的Storage SOP Class
- 调用ASC_getApplicationContextName获取协商结果
- 调用ASC_acknowledgeAssociation通知连接成功
- 调用processCommands处理命令,支持C-ECHO-RQ 和 C-STORE-RQ两种命令。
- processCommands中调用DIMSE_receiveCommand接受命令,根据命令类型分别调用echoSCP和storeSCP处理。
重点
storeSCP中调用DIMSE_storeProvider,DIMSE_storeProvider中输入回调函数storeSCPCallback,在storeSCPCallback中把接受到的DcmDataset保存为dicom文件- 图像接受完成调用ASC_dropSCPAssociation,ASC_destroyAssociation释放连接
- 调用ASC_acceptContextsWithPreferredTransferSyntaxes,
2. 日志函数
四个日志级别对应四个日志函数,只发消息到窗口,不保存到文件。有兴趣的可以自行添加写日志文件的功能
cpp
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. 服务初始化、启动、停止、状态查询、设置日志级别
这几个函数是对外接口,供调用者使用
cpp
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
- 初始化函数中创建线程池
cpp
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);
}
- 启动函数启动主线程监听端口
cpp
void CStoreServer::Start()
{
m_bStop = false;
m_thNet = std::thread([&]() {
this->DoNetWorks();
});
std::this_thread::sleep_for(std::chrono::milliseconds(50));
}
- 收到连接后创建任务交给工作线程处理
cpp
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 it
if (cond.bad())
{
// no matter what kind of error occurred, we need to do a cleanup
DimseCondition::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
cpp
#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 works
DUL_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 Error
int 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
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")
#endif
CStoreServer::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, // 10
NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, // 20
NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, // 30
NULL, NULL, NULL, NULL, NULL }; // +5
int 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 it
if (cond.bad())
{
// no matter what kind of error occurred, we need to do a cleanup
DimseCondition::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 command
while (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 information
if (statusDetail != NULL)
{
//statusDetail->print(COUT);
delete statusDetail;
}
// check if peer did release or abort, or if we have a valid message
if (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-RQ
switch (msg.CommandField)
{
case DIMSE_C_ECHO_RQ:
// process C-ECHO-Request
cond = echoSCP(pAssoc, &msg, presID);
break;
case DIMSE_C_STORE_RQ:
cond = storeSCP(pAssoc, &msg, presID);
break;
default:
// we cannot handle this kind of message
cond = 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 variable
req = &msg->msg.CStoreRQ;
// Dump some information if required.
log_debug("Receive C-STORE Request, MessageID:%ld", req->MessageID);
// initialize some variables
StoreCallbackData 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 metaheader
if (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 stored
DcmDataset *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 necessary
if (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 aborted
if ((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 file
OFStandard::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;
}
}
}
}
}
三、调用
- 在上一章CStoreSCPDlg.h中定义变量
cpp
#include "CStoreServer.h"
class CStoreSCPDlg : public CDialogEx
{
...
private:
CStoreServer m_storeSCP;
...
}
- OnInitDialog中初始化
cpp
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
}
- 点击按钮启动/停止服务
cpp
void CStoreSCPDlg::OnBnClickedButtonStart()
{
// TODO: 在此添加控件通知处理程序代码
if (m_storeSCP.GetState() == 0) {
m_storeSCP.Start();
}
else {
m_storeSCP.Stop();
}
}
- OnDestory中停止服务
cpp
void CStoreSCPDlg::OnDestroy()
{
if (m_storeSCP.GetState() == 1) {
m_storeSCP.Stop();
}
CDialogEx::OnDestroy();
}