Sends a C-STORE-REQ message to a peer member of a connection. This function is available in the PACS Imaging Toolkit.
#include "ltdic.h"
L_INT LDicomNet::SendCStoreRequest(nPresentationID, nMessageID, pszClass, pszInstance, nPriority, pszMoveAE, nMoveMessageID, pDS)
Presentation ID. The presentation ID provides information about both the class type of the data and the transfer syntax to use when transferring the data.
Message ID. Each message sent by a member of a connection should have a unique ID. Since a member of a connection may send several messages, this ID allows that member to identify when a specific request has been completed.
Class affected by the request. This will be an SOP Class or an SOP MetaClass.
The instance of the class. A server may, for example, have three instances of the Nuclear Medicine Class. This value identifies the data with a specific instance.
The priority level of the message. The Service Class Provider may or may not support priority. Therefore, setting this parameter may or may not have any effect. Possible values are:
Value | Meaning |
---|---|
COMMAND_PRIORITY_LOW | [0x0002] Low priority message. |
COMMAND_PRIORITY_MEDIUM | [0x0000] Medium priority message. |
COMMAND_PRIORITY_HIGH | [0x0001] High priority message. |
Character string that contains the name of the application entity that originally requested the move. For a simple storage request from a client to a server, this should be ". When the request is a sub-operation of a C-MOVE, this will contain the name of the AE that requested the move.
The ID of the original move request message. For a simple storage request from a client to a server, this should be 0. When the request is a sub-operation of a C-MOVE, this will contain the original message ID of the C-MOVE request.
Pointer to the data set to be stored.
Value | Meaning |
---|---|
0 | SUCCESS |
>0 | An error occurred. Refer to Return Codes. |
When an SCU requests a Move (C-MOVE-REQ), the SCP may have to call this function to request one or more C-STORE-REQ sub-operations to complete the storage. The series of calls and information transfer in a C-MOVE-REQ is complicated. For more information, refer to Moving Composite Data.
Calling this function generates a call to LDicomNet::OnReceiveCStoreRequest on the SCP. The SCP should respond by calling LDicomNet::SendCStoreResponse which will generate a call to LDicomNet::OnReceiveCStoreResponse.
Win32, x64
This is a basic, but complete example that shows a DICOM client sending a C-Store-REQ to a server, and the server processing the request.
namespace LDicomNet_SendCStoreRequest_Namespace
{
// Logs a message
// This implementation logs to the console, and the debug window
L_VOID LogMessage(TCHAR *szMsg)
{
wprintf(TEXT("\n"));
wprintf(szMsg);
OutputDebugStringW(TEXT("\n"));
OutputDebugStringW(szMsg);
}
L_VOID LogMessage(TCHAR *s, L_INT n)
{
TCHAR szLog[200] = { 0 };
wsprintf(szLog, TEXT("%s [%d]"), s, n);
LogMessage(szLog);
}
L_VOID LogMessage(TCHAR *s, TCHAR *s2)
{
TCHAR szLog[200] = { 0 };
wsprintf(szLog, TEXT("%s [%s]"), s, s2);
LogMessage(szLog);
}
// *******************************************************************************************
// Client Class
//
// Class that is used to connect to the server
// *******************************************************************************************
class CMyClient : public LDicomNet
{
public:
CMyClient(L_INT32 nMode) : LDicomNet(NULL, nMode)
{
m_waitEvent = CreateEvent(NULL, TRUE, TRUE, TEXT("ClientEvent"));
ResetEvent(m_waitEvent);
}
~CMyClient(void)
{
CloseHandle(m_waitEvent);
}
// Client
L_VOID OnConnect(L_INT nError);
L_VOID OnReceiveAssociateAccept(LDicomAssociate *pPDU);
L_VOID OnReceiveReleaseResponse();
L_VOID OnReceiveCStoreResponse(L_UCHAR nPresentationID, L_UINT16 nMessageID, L_TCHAR *pszClass, L_TCHAR *pszInstance, L_UINT16 nStatus);
L_BOOL Wait(DWORD timeout = 5000);
private:
HANDLE m_waitEvent;
};
// Continues dispatching messages until hEvent is signaled, our timeout
// Returns TRUE if hEvent is signaled
// Returns FALSE if timeout
L_BOOL MessageLoop(
HANDLE hEvent, // handles that need to be waited on
DWORD timeout // timeout in milliseconds
)
{
DWORD dwStart = GetTickCount();
MSG msg = { 0 };
volatile L_BOOL bRunForever = TRUE;
while (bRunForever)
{
if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
if (WaitForSingleObject(hEvent, 0) == WAIT_OBJECT_0)
{
ResetEvent(hEvent);
return TRUE;
}
DWORD dwCurrent = GetTickCount();
if ((dwCurrent - dwStart) > timeout)
{
return FALSE;
}
}
return TRUE;
}
L_VOID CMyClient::OnConnect(L_INT nError)
{
L_TCHAR szMsg[200] = { 0 };
wsprintf(szMsg, TEXT("CMyClient::OnConnect: nError[%d]"), nError);
LogMessage(szMsg);
}
L_VOID CMyClient::OnReceiveAssociateAccept(LDicomAssociate *pPDU)
{
UNREFERENCED_PARAMETER(pPDU);
LogMessage(TEXT("CMyClient::OnReceiveAssociateAccept"));
SetEvent(m_waitEvent);
}
L_VOID CMyClient::OnReceiveCStoreResponse(L_UCHAR nPresentationID, L_UINT16 nMessageID, L_TCHAR *pszClass, L_TCHAR *pszInstance, L_UINT16 nStatus)
{
if (pszClass == NULL)
{
pszClass = TEXT("");
}
LogMessage(TEXT("CMyClient::OnReceiveCStoreResponse"));
LogMessage(TEXT("\t nPresentationID"), nPresentationID);
LogMessage(TEXT("\t nMessageID"), nMessageID);
LogMessage(TEXT("\t pszClass"), pszClass);
LogMessage(TEXT("\t pszInstance"), pszInstance);
switch (nStatus)
{
case COMMAND_STATUS_WARNING:
LogMessage(TEXT("\t nStatus: COMMAND_STATUS_WARNING"));
break;
case COMMAND_STATUS_PENDING_WARNING:
LogMessage(TEXT("\t nStatus: COMMAND_STATUS_PENDING_WARNING"));
break;
case COMMAND_STATUS_PENDING:
LogMessage(TEXT("\t nStatus: COMMAND_STATUS_PENDING"));
break;
case COMMAND_STATUS_SUCCESS:
LogMessage(TEXT("\t nStatus: COMMAND_STATUS_SUCCESS"));
SetEvent(m_waitEvent);
break;
case COMMAND_STATUS_REFUSED_OUT_OF_RESOURCES:
{
LogMessage(TEXT("\t nStatus: COMMAND_STATUS_REFUSED_OUT_OF_RESOURCES"));
// Retrieve additional extra elements to the command set
LDicomDS *pCS = GetCommandSet();
if (pCS != NULL)
{
pDICOMELEMENT pElement = pCS->FindFirstElement(NULL, TAG_ERROR_COMMENT, TRUE);
if (pElement != NULL)
{
L_TCHAR *pszErrorComment = pCS->GetStringValue(pElement, 0, 1);
LogMessage(TEXT("\t ErrorComment: "), pszErrorComment);
}
}
}
SetEvent(m_waitEvent);
break;
}
SetEvent(m_waitEvent);
}
L_VOID CMyClient::OnReceiveReleaseResponse()
{
LogMessage(TEXT("CMyClient::OnReceiveReleaseResponse"));
SetEvent(m_waitEvent);
}
L_BOOL CMyClient::Wait(DWORD timeout)
{
L_BOOL bRet = MessageLoop(m_waitEvent, timeout);
return bRet;
}
// *******************************************************************************************
// Server Connection Class
//
// When a client connects, CMyServer creates a new instance of the CMyServerConnection class
// and accepts the connection.
// *******************************************************************************************
class CMyServerConnection : public LDicomNet
{
public:
CMyServerConnection(L_INT32 nMode) : LDicomNet(NULL, nMode)
{
}
~CMyServerConnection(void)
{
}
// Server
L_VOID OnReceiveAssociateRequest(LDicomAssociate *pPDU);
L_VOID OnReceiveCStoreRequest(L_UCHAR nPresentationID, L_UINT16 nMessageID, L_TCHAR *pszClass, L_TCHAR *pszInstance, L_UINT16 nPriority, L_TCHAR *pszMoveAE, L_UINT16 nMoveMessageID, LDicomDS *pDS);
L_VOID OnReceiveReleaseRequest();
};
#define SIZEINWORD(p) sizeof(p)/sizeof(L_TCHAR)
L_VOID CMyServerConnection::OnReceiveAssociateRequest(LDicomAssociate *pPDU)
{
LogMessage(TEXT("\tCMyServerConnection::OnReceiveAssociateRequest"));
LDicomAssociate DicomAssociate(FALSE);
L_TCHAR clientAE[20] = { 0 };
pPDU->GetCalling(clientAE, 20);
//Copy presentation objects from received
//Reply that we only support the first Transfer Syntax from the received hPDU
L_TCHAR szTransfer[PDU_MAX_UID_SIZE + 1] = { 0 };
L_TCHAR szAbstract[PDU_MAX_UID_SIZE + 1] = { 0 };
L_INT iPresentationCount = pPDU->GetPresentationCount();
for (L_UCHAR i = 0; i < iPresentationCount; i++)
{
L_UCHAR nId = pPDU->GetPresentation(i);
pPDU->GetTransfer(nId, 0, szTransfer, PDU_MAX_UID_SIZE + 1);
L_UCHAR nResult = PDU_ACCEPT_RESULT_SUCCESS;
pPDU->GetAbstract(nId, szAbstract, PDU_MAX_UID_SIZE + 1);
DicomAssociate.AddPresentation(nId, nResult, szAbstract);
DicomAssociate.AddTransfer(nId, szTransfer);
}
LogMessage(TEXT("\tCMyServerConnection::SendAssociateAccept"));
SendAssociateAccept(&DicomAssociate);
}
L_VOID CMyServerConnection::OnReceiveCStoreRequest(L_UCHAR nPresentationID, L_UINT16 nMessageID, L_TCHAR *pszClass, L_TCHAR *pszInstance, L_UINT16 nPriority, L_TCHAR *pszMoveAE, L_UINT16 nMoveMessageID, LDicomDS *pDS)
{
UNREFERENCED_PARAMETER(pDS);
UNREFERENCED_PARAMETER(pszMoveAE);
LogMessage(TEXT("\tCMyServerConnection::OnReceiveCStoreRequest"));
LogMessage(TEXT("\t nMoveMessageID"), nMoveMessageID);
LogMessage(TEXT("\t nPriority"), nPriority);
//...
//...Do the store here
//...nStatus = status of the store
LogMessage(TEXT("\t\t Do the store here"));
// Send C-Store-RSP
LogMessage(TEXT("\tCMyServerConnection::SendCStoreResponse"));
SendCStoreResponse(nPresentationID, nMessageID, pszClass, pszInstance, COMMAND_STATUS_SUCCESS);
}
L_VOID CMyServerConnection::OnReceiveReleaseRequest()
{
LogMessage(TEXT("\tCMyServerConnection::OnReceiveReleaseRequest"));
LogMessage(TEXT("\tCMyServerConnection::SendReleaseResponse"));
SendReleaseResponse();
}
// *******************************************************************************************
// Server Class
//
// Listens for connections
// When a client connects, this class creates a CMyServerConnection and accepts the connection
// *******************************************************************************************
class CMyServer : public LDicomNet
{
public:
CMyServer(L_INT32 nMode) : LDicomNet(NULL, nMode)
{
m_pServerConnection = NULL;
}
~CMyServer(void)
{
if (m_pServerConnection != NULL)
{
delete m_pServerConnection;
}
}
L_VOID OnAccept(L_INT nError);
L_VOID OnClose(L_INT nError, LDicomNet *pServerConnection);
CMyServerConnection *m_pServerConnection;
};
L_VOID CMyServer::OnAccept(L_INT nError)
{
LogMessage(TEXT("\tCMyServer::OnAccept"));
if (nError != DICOM_SUCCESS)
{
return;
}
if (m_pServerConnection != NULL)
{
delete m_pServerConnection;
m_pServerConnection = NULL;
}
m_pServerConnection = new CMyServerConnection(DICOM_SECURE_NONE);
if (m_pServerConnection == NULL)
{
return;
}
// m_pServerConnection->EnableOptimizedSend(TRUE);
nError = LDicomNet::Accept(m_pServerConnection);
if (nError != DICOM_SUCCESS)
{
delete m_pServerConnection;
return;
}
}
L_VOID CMyServer::OnClose(L_INT nError, LDicomNet *pServerConnection)
{
UNREFERENCED_PARAMETER(nError);
LogMessage(TEXT("\tCMyServer::OnClose"));
if (m_pServerConnection == pServerConnection)
{
m_pServerConnection = NULL;
}
delete (CMyServerConnection *)pServerConnection;
}
// *******************************************************************************************
// Sample starts here
// *******************************************************************************************
#define WaitForProcessing() \
{ \
if (!client.Wait()) \
{ \
LogMessage(TEXT("Timeout: client.Connect")); \
nRet = DICOM_ERROR_NET_TIME_OUT; \
goto Cleanup; \
} \
}
L_INT LDicomNet_SendCStoreRequestExample()
{
LogMessage(TEXT("\n\n *** SendCStoreRequestExample ***"));
L_TCHAR *pszServerAddress = TEXT("127.0.0.1");
L_UINT uServerPort = 504;
L_INT nRet = DICOM_SUCCESS;
// Load the DICOM dataset that the client will store
LDicomDS ds;
// ds.LoadDS(MAKE_IMAGE_PATH(TEXT("IMAGE1.dcm")), DS_LOAD_CLOSE);
ds.LoadDS((TEXT("d:\\images\\image3.dcm")), DS_LOAD_CLOSE);
L_TCHAR *pszStorageClass = NULL;
pDICOMELEMENT pElement = ds.FindFirstElement(NULL, TAG_MEDIA_STORAGE_SOP_CLASS_UID, TRUE);
if (pElement != NULL)
{
pszStorageClass = ds.GetStringValue(pElement, 0, 1);
}
if (pszStorageClass == NULL || _tcslen(pszStorageClass) == 0)
{
pElement = ds.FindFirstElement(NULL, TAG_SOP_CLASS_UID, TRUE);
if (pElement != NULL)
{
pszStorageClass = ds.GetStringValue(pElement, 0, 1);
}
}
if (pszStorageClass == NULL || _tcslen(pszStorageClass) == 0)
{
pszStorageClass = UID_CT_IMAGE_STORAGE; // Default to CT Image Storage
}
//
// Get Image transfer syntax
//
L_TCHAR *pszTransferSyntax = NULL;
L_TCHAR *pszStorageInstance = NULL;
pElement = ds.FindFirstElement(NULL, TAG_TRANSFER_SYNTAX_UID, TRUE);
if (pElement != NULL)
{
pszTransferSyntax = ds.GetStringValue(pElement, 0, 1);
}
pElement = ds.FindFirstElement(NULL, TAG_SOP_INSTANCE_UID, TRUE);
if (pElement != NULL)
{
pszStorageInstance = ds.GetStringValue(pElement, 0, 1);
}
LDicomNet::StartUp();
CMyClient client(DICOM_SECURE_NONE);
CMyServer server(DICOM_SECURE_NONE);
LogMessage(TEXT("\tCMyServer::Listen"));
nRet = server.Listen(pszServerAddress, uServerPort, 5);
LogMessage(TEXT("CMyClient::Connect"));
client.Connect(NULL, 0, pszServerAddress, uServerPort);
if (!client.Wait(2000))
{
if (!client.IsConnected())
{
LogMessage(TEXT("Timeout: client.Connect"));
nRet = DICOM_ERROR_NET_TIME_OUT;
goto Cleanup;
}
}
if (nRet == DICOM_SUCCESS)
{
//create the Associate Class as Request
LDicomAssociate dicomAssociateRequest(TRUE);
dicomAssociateRequest.SetCalled(TEXT("L20_PACS_SCP32"));
dicomAssociateRequest.SetCalling(TEXT("LEAD_CLIENT"));
dicomAssociateRequest.SetImplementClass(TRUE, TEXT("1.2.840.114257.1"));
dicomAssociateRequest.SetImplementVersion(TRUE, TEXT("1"));
dicomAssociateRequest.SetMaxLength(TRUE, 0x100000);
dicomAssociateRequest.AddPresentation(1, 0, UID_VERIFICATION_CLASS);
dicomAssociateRequest.AddTransfer(1, UID_IMPLICIT_VR_LITTLE_ENDIAN);
dicomAssociateRequest.AddPresentation(3, 0, pszStorageClass);
dicomAssociateRequest.AddTransfer(3, UID_IMPLICIT_VR_LITTLE_ENDIAN);
// Send A-Associate-RQ message
LogMessage(TEXT("CMyClient::SendAssociateRequest"));
nRet = client.SendAssociateRequest(&dicomAssociateRequest);
if (!client.Wait(5000))
{
LogMessage(TEXT("Timeout: client.Connect"));
nRet = DICOM_ERROR_NET_TIME_OUT;
goto Cleanup;
}
}
if (nRet == DICOM_SUCCESS)
{
L_UCHAR nPresentationID = client.GetAssociate()->FindAbstract(pszStorageClass);
L_UINT16 uUniqueID = 99;
LogMessage(TEXT("CMyClient::SendCStoreRequest"));
client.SendCStoreRequest(nPresentationID, uUniqueID, pszStorageClass, pszStorageInstance, COMMAND_PRIORITY_MEDIUM, TEXT("NONE"), 1, &ds);
WaitForProcessing();
}
LogMessage(TEXT("CMyClient::SendReleaseRequest"));
client.SendReleaseRequest();
WaitForProcessing();
Cleanup:
LogMessage(TEXT("CMyClient::Close"));
client.Close();
client.Wait(1000);
LogMessage(TEXT("\tCMyServer::Close"));
server.Close();
LDicomNet::ShutDown();
return nRet;
}
}