📄 modbusclient.cpp
字号:
#include "StdAfx.h"
#include "ModbusClient.h"
/*
* ClientThread() callback function
*
* Called as the client handler thread.
* Handle of the CONNECTION to be used by thread is passed-in as thread's argument.
*/
UINT ClientThread( LPVOID lpParam )
{
HANDLE hConn = (HANDLE) lpParam;
BYTE buffer[LEN_MAXADU];
REQUEST request;
WORD wRcvd;
WORD wSend;
BYTE idError;
// Get connection's socket ID
SOCKET socket = CModbusConnection::GetConnSocket( hConn );
// Get connection's 'stop' event object
HANDLE hStop = CModbusConnection::GetConnStopEvent( hConn );
// Setup WSAOVERLAPPED for overlapped read operations
WSAOVERLAPPED wsaover;
wsaover.hEvent = WSACreateEvent();
// Continue to receive client's requests until disconnect, stop signal, or error
while( (wRcvd = CModbusClient::ReceiveRequest( socket, buffer, &wsaover, hStop )) > 0 )
{
// Parse the request
idError = CModbusClient::ParseRequest( buffer, wRcvd, &request );
if( idError == 0 )
{
// Process the request (transfer values to/from 'Registers' data area)
idError = CModbusClient::ProcessRequest( &request );
}
if( idError == 0 )
{
// Make a positive response to send to the client
wSend = CModbusClient::MakePositiveResponse( &request, buffer );
}
else
{
// Make an error response to send to the client
wSend = CModbusClient::MakeErrorResponse( idError, request.bFnCode, buffer );
}
// Send response to client
if( !CModbusClient::SendResponse( socket, buffer, wSend ) )
{
// Error on socket while sending response -- terminate connection
break;
}
// Keep statistics
CModbusConnection::IncrementConnCounters( hConn, (idError == 0), wRcvd, wSend );
}
// Close the connection being used by this client thread
CModbusConnection::CloseConnection( hConn );
// Clean up
WSACloseEvent( wsaover.hEvent );
return 0;
}
// The array of words that constitutes the 'Registers' data area
WORD awRegs[N_REGISTERS] = { 0 };
CRITICAL_SECTION csRegs;
CModbusClient::CModbusClient(void)
{
InitializeCriticalSection( &csRegs );
}
CModbusClient::~CModbusClient(void)
{
DeleteCriticalSection( &csRegs );
}
void CModbusClient::StartClientThread( HANDLE hConn )
{
HANDLE hClientThread;
DWORD dwThreadAddr;
// Start a client thread to handle this connection
hClientThread = AfxBeginThread(ClientThread, (LPVOID)hConn);
//hClientThread = CreateThread( NULL, // Security
// 0, // Stack size
// ClientThread, // Function address
// hConn, // Argument
// 0, // Init flag
// &dwThreadAddr); // Thread address
if( hClientThread == NULL )
{
CLogger::GetInstance()->Log( LOG_ERROR, _T("Unable to create client thread"), GetLastError() );
CModbusConnection::CloseConnection( hConn );
}
else
{
// Won't be using the thread handle, so close it now. (Thread will continue to run)
CloseHandle( hClientThread );
}
}
/*
* ReceiveRequest() function
*
* Receives the client's Modbus/TCP request. Calling thread will be blocked
* until the complete request is received or until 'stop' signal is set.
*
* Parameters:
* [in] socket The socket to be used for receiving request
* [out] buffer The buffer for receiving the request
* [in] lpOverlap Pointer to WSAOVERLAPPED struct (used for overlapped input)
* [in] hStop The 'stop' event object
*
* Returns zero if error, disconnect or stop signal; otherwise,
* returns the total length (in bytes) of the request
*/
WORD CModbusClient::ReceiveRequest( SOCKET socket, LPBYTE buffer, LPWSAOVERLAPPED lpOverlap, HANDLE hStop )
{
WORD wLenPDU;
WSABUF wsabuf;
// Receive request's Modbus/TCP Header
wsabuf.buf = (char*)buffer;
wsabuf.len = LEN_HEADER;
if( !ReceiveBytes( socket, &wsabuf, lpOverlap, hStop ) )
return 0;
// Get the length of request's PDU from the MBAP Header
wLenPDU = ParseHeader( buffer );
if( wLenPDU == 0 )
return 0;
// Receive request's PDU (filling buffer immediately following the header)
wsabuf.buf = (char*)buffer + LEN_HEADER;
wsabuf.len = wLenPDU;
if( !ReceiveBytes( socket, &wsabuf, lpOverlap, hStop ) )
return 0;
// Return total length of request
return LEN_HEADER + wLenPDU;
}
/*
* ReceiveBytes() function
*
* Receives a specified number of bytes from a client. Calling thread will be blocked
* until the number of requested bytes is received or until 'stop' signal is set.
*
* Parameters:
* [in] socket The socket to be used for receiving the bytes
* [in] lpBuffer Pointer to WSABUF struct (preset with number of bytes to receive)
* [in] lpOverlap Pointer to WSAOVERLAPPED struct (used for overlapped input)
* [in] hStop The 'stop' event object
*
* Returns FALSE if error, disconnect or stop signal; otherwise, returns TRUE
*/
BOOL CModbusClient::ReceiveBytes( SOCKET socket, LPWSABUF lpBuffer, LPWSAOVERLAPPED lpOverlap, HANDLE hStop )
{
INT nRet;
DWORD dwRcvd;
DWORD dwFlags;
HANDLE ahEvents[2];
// Loop until all bytes are received from client
while( lpBuffer->len > 0 )
{
dwFlags = 0;
nRet = WSARecv( socket, // Socket
lpBuffer, // WSABUF
1, // Number of buffers
&dwRcvd, // Bytes received
&dwFlags, // Flags
lpOverlap, // WSAOVERLAPPED
NULL ); // Completion function
if( nRet != 0 )
{
if( WSAGetLastError() == WSA_IO_PENDING )
{
// Wait for the receive to complete or stop signal
ahEvents[0] = hStop;
ahEvents[1] = lpOverlap->hEvent;
if( WaitForMultipleObjects( 2, ahEvents, FALSE, INFINITE ) == WAIT_OBJECT_0 )
{
// Stop signal received
return FALSE;
}
// Get I/O result
if( !WSAGetOverlappedResult( socket, lpOverlap, &dwRcvd, FALSE, &dwFlags ) )
{
CLogger::GetInstance()->Log(LOG_ERROR, _T("WSAGetOverlappedResult()"), WSAGetLastError() );
return FALSE;
}
}
else
{
CLogger::GetInstance()->Log(LOG_ERROR, _T("WSARecv()"), WSAGetLastError() );
return FALSE;
}
}
// Check for client disconnect (zero-length request received without error)
if( dwRcvd == 0 )
{
CLogger::GetInstance()->Log( LOG_INFO, _T("Client on socket %hu disconnected"), socket );
return FALSE;
}
// Adjust WSABUF for actual number of bytes read
lpBuffer->len -= dwRcvd;
lpBuffer->buf += dwRcvd;
}
return TRUE;
}
/*
* ParseHeader() function
*
* Parses and validates the MBAP Header of the client's request.
*
* Parameters:
* [in] buffer The buffer containing the client's request
*
* Returns zero if invalid header; otherwise,
* returns the length (in bytes) of the request's PDU
*/
WORD CModbusClient::ParseHeader( LPBYTE buffer )
{
int iLenPDU;
// Check that Protocol ID (second word of header) indicates Modbus (zero)
if( MAKEWORD(buffer[3], buffer[2]) != 0 )
{
CLogger::GetInstance()->Log(LOG_INFO, _T("The request from client on socket had invalid Protocol ID"));
return 0;
}
// Use Command Length (third word of header) to compute length of request's PDU
iLenPDU = MAKEWORD(buffer[5], buffer[4]) - LEN_DESTID;
// Check that Command Length is an acceptable value
if( iLenPDU < LEN_MINPDU || iLenPDU > LEN_MAXPDU )
{
CLogger::GetInstance()->Log(LOG_INFO, _T("The request from client on socket had invalid Command Length"));
return 0;
}
return (WORD) iLenPDU;
}
/*
* ParseRequest() function
*
* Parses and validates the PDU of the client's request.
*
* Parameters:
* [in] buffer The buffer containing the client's request
* [in] wRcvd The length (in bytes) of the client's request
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -