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

系列文章目录


文章目录

  • 系列文章目录
  • 前言
  • 一、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中的主要流程

  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. 日志函数

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

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

  1. 初始化函数中创建线程池
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);

}
  1. 启动函数启动主线程监听端口
cpp 复制代码
void CStoreServer::Start()
{
	m_bStop = false;

	m_thNet = std::thread([&]() {
		this->DoNetWorks();
	});

	std::this_thread::sleep_for(std::chrono::milliseconds(50));

}
  1. 收到连接后创建任务交给工作线程处理
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;
				}
			}
		}
	}
}

三、调用

  1. 在上一章CStoreSCPDlg.h中定义变量
cpp 复制代码
#include "CStoreServer.h"
class CStoreSCPDlg : public CDialogEx
{
...
private:
	CStoreServer m_storeSCP;
...
}
  1. 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
}
  1. 点击按钮启动/停止服务
cpp 复制代码
void CStoreSCPDlg::OnBnClickedButtonStart()
{
	// TODO: 在此添加控件通知处理程序代码
	if (m_storeSCP.GetState() == 0) {
		m_storeSCP.Start();
	}
	else {
		m_storeSCP.Stop();
	}
}
  1. OnDestory中停止服务
cpp 复制代码
void CStoreSCPDlg::OnDestroy()
{
	if (m_storeSCP.GetState() == 1) {
		m_storeSCP.Stop();
	}

	CDialogEx::OnDestroy();
}

四、总结

下载测试StoreSCP.exe

相关推荐
rainbow_lucky010614 小时前
MFC UI控件CheckBox从专家到小白
mfc·checkbox
R-G-B1 天前
【27】MFC入门到精通——MFC 修改用户界面登录IP IP Address Control
tcp/ip·ui·mfc·mfc 用户界面登录·mfc ip登录·mfc address登录
R-G-B1 天前
【46】MFC入门到精通——MFC显示实时时间,获取系统当前时间GetCurrentTime()、获取本地时间GetLocalTime()
c++·mfc·mfc显示实时时间·mfc获取系统当前时间·getcurrenttime·getlocaltime
R-G-B2 天前
【28】MFC入门到精通——MFC串口 Combobox 控件实现串口号
c++·mfc·mfc串口控件·combobox 控件·mfc串口参数控件实现
SunkingYang2 天前
MFC/C++语言怎么比较CString类型 第一个字符
c++·mfc·方法·cstring·比较·第一个字符
R-G-B2 天前
【24】MFC入门到精通——MFC在静态框中 更改字体、颜色、大小 、背景
c++·mfc·mfc在静态框中更改字体·mfc静态框更改字体颜色·mfc静态框更改字体大小·mfc静态框更改字体背景
SunkingYang3 天前
MFC/C++语言怎么比较CString类型最后一个字符
c++·mfc·cstring·子串·最后一个字符·比较
界面开发小八哥3 天前
MFC扩展库BCGControlBar Pro v36.2新版亮点:可视化设计器升级
c++·mfc·bcg·界面控件·ui开发
R-G-B3 天前
【15】MFC入门到精通——MFC弹窗提示 MFC关闭对话框 弹窗提示 MFC按键触发 弹窗提示
c++·mfc·mfc弹窗提示·mfc关闭弹窗提示·mfc按键触发 弹窗提示