Modbus协议是工业控制领域应用最广泛的协议,Modbus协议又分为Modbus-Rtu和Modbus-Tcp,本文分享一下在VC中实现Modbus-Tcp的线程实现方式。
通信线程线程继承自CWinThread,使用同步Socket进行数据发送和接收;在线程的类成员中定义一个被调用者指针,通过此指针进行上行数据的传递;同时在线程内申请一块大内存作为数据缓冲区,接收到的数据放到缓冲区后,调用者在外部通过线程调用线程内的数据分析接口,数据解析后通过被调用者指针传递数据。
Socket定义
#pragma once
// CClientSocket command target
class CClientSocket : public CSocket
{
public:
CClientSocket();
virtual ~CClientSocket();
virtual void OnReceive(int nErrorCode);
virtual void OnClose(int nErrorCode);
public:
CDialog* m_pMainDlg; //界面指针
CWinThread* m_pThrd; // 该CSocket所在的线程
};
Socket实现
// ClientSocket.cpp : implementation file
//
#include "stdafx.h"
#include "DustCleanHMI.h"
#include "ClientSocket.h"
#include "CommDataDef.h"
// CClientSocket
CClientSocket::CClientSocket()
{
m_hSocket = INVALID_SOCKET;
}
CClientSocket::~CClientSocket()
{
}
// CClientSocket member functions
void CClientSocket::OnReceive(int nErrorCode)
{
//发送消息由对应的线程读取数据
m_pThrd->PostThreadMessage(WM_THREAD_RECV,WP_WPARA_RECV,0);
CSocket::OnReceive(nErrorCode);
}
void CClientSocket::OnClose(int nErrorCode)
{
//发送消息由对应的线程关闭本地Socket
m_pThrd->PostThreadMessage(WM_THREAD_RECV, WP_WPARA_CLOSE, 0);
CSocket::OnClose(nErrorCode);
}
线程定义
#pragma once
#include "ClientSocket.h"
// CClientSocketThread
class CClientSocketThread : public CWinThread
{
DECLARE_DYNCREATE(CClientSocketThread)
protected:
CClientSocketThread(); // protected constructor used by dynamic creation
virtual ~CClientSocketThread();
public:
virtual BOOL InitInstance();
virtual int ExitInstance();
void AnalysisRecvData(); //数据解析接口,供外部调用
void DealFrameData(BYTE* pData);
int SendCmdData(BYTE* pSendData, WORD wDataLen,BOOL bIsTransit=FALSE); //数据发送接口
public:
//CDustCleanHMIDlg* m_pMainDlg;
CDialog* m_pMainDlg;
CString m_strSerIP;
BYTE m_bDevID;
WORD m_wTcpID;
CClientSocket m_socket;
BYTE* m_pRecvDataBuffer; //数据接收缓冲区指针
BYTE* m_pDealBuf; //数据处理缓冲区指针
int m_iWritePos;
int m_iReadPos;
__int64 m_llDataTotalLen;
__int64 m_llReadTotal;
protected:
DECLARE_MESSAGE_MAP()
afx_msg void OnCustomMsg(WPARAM wParam,LPARAM lParam);
};
socket初始化、创建接收缓冲区、连接到指定的设备IP。
BOOL CClientSocketThread::InitInstance()
{
if (!AfxSocketInit()) // 初始化CSocket必须调用的
{
return CWinThread::InitInstance(); //立刻退出
}
//创建socket
if (!m_socket.Create())
{
//创建socket失败
return CWinThread::InitInstance(); //立刻退出
}
m_socket.m_pMainDlg = m_pMainDlg;
m_socket.m_pThrd = this;
if (!m_socket.Connect(m_strSerIP,glServerPort))
{
return CWinThread::InitInstance(); //立刻退出
}
//创建数据接收缓冲区和数据解析缓存
m_pRecvDataBuffer = new BYTE[RECVDATA_BUFLEN];
ASSERT(m_pRecvDataBuffer != NULL);
m_pDealBuf = new BYTE[DATADEAL_BUFLEN];
ASSERT(m_pDealBuf != NULL);
m_iWritePos =0;
m_iReadPos =0;
m_llDataTotalLen =0;
m_llReadTotal =0;
m_wTcpID =0;
//连接成功后,通知主界面
((CDustCleanHMIDlg*)m_pMainDlg)->OnDevOnline(this,m_bDevID);
return TRUE; //线程不会立刻退出
}
收到Socket消息,接收数据放到缓冲区;收到断链消息,通知调用者。
void CClientSocketThread::OnCustomMsg(WPARAM wParam,LPARAM lParam)
{
BYTE pReadBf[DATALEN_PERTIME+1] = {0};
int iRdLen =0;
switch(wParam)
{
case WP_WPARA_RECV:
// 接收数据,先把接收数据事件关掉
m_socket.AsyncSelect(FD_CLOSE);
//读取数据
iRdLen = m_socket.Receive(pReadBf,DATALEN_PERTIME);
if (0 ==iRdLen)
{
((CDustCleanHMIDlg*)m_pMainDlg)->OnDevOffline(this,m_bDevID);
OutputDebugString(_T("CSerSocketThread:1-读数据长度为0,TCP链接断开......"));
return;
}
//缓冲区尾部不能容纳读取的数据,要分开放入缓冲区尾部、头部
if(m_iWritePos + iRdLen > RECVDATA_BUFLEN)
{
int iTailLen = RECVDATA_BUFLEN - m_iWritePos;
memcpy(m_pRecvDataBuffer +m_iWritePos,pReadBf,iTailLen);
int iHeadLen = iRdLen - iTailLen;
memcpy(m_pRecvDataBuffer,pReadBf + iTailLen,iHeadLen);
m_iWritePos = iHeadLen;
}else{
//缓冲区尾部足够容纳本次数据
memcpy(m_pRecvDataBuffer+m_iWritePos,pReadBf,iRdLen);
m_iWritePos += iRdLen;
}
//接收数据总长度
m_llDataTotalLen += iRdLen;
//重新打开接收数据事件
m_socket.AsyncSelect(FD_READ|FD_CLOSE);
break;
case WP_WPARA_CLOSE:
((CDustCleanHMIDlg*)m_pMainDlg)->OnDevOffline(this,m_bDevID);
//m_socket.Close();
OutputDebugString(_T("TCP链接断开......"));
break;
case WP_WPARA_THREAD_QUIT:
PostQuitMessage(0);
break;
}
}
发送接口
int CClientSocketThread::SendCmdData(BYTE* pSendData, WORD wDataLen,BOOL bIsTransit)
{
BYTE bData[128] ={0};
if (m_wTcpID +1 == 0xFFFF)
{
m_wTcpID =1;
}else{
m_wTcpID++;
}
ST_MODBUS_MBAP_HEADER stMbapHeader;
stMbapHeader.wTransFlag = htons(m_wTcpID);
stMbapHeader.wProtocolFlag =0;
stMbapHeader.wLen = htons(wDataLen +1);
stMbapHeader.bUnitFlag = 0x01;
int iDataLen = sizeof(ST_MODBUS_MBAP_HEADER) + wDataLen ;
memcpy(bData,&stMbapHeader,sizeof(ST_MODBUS_MBAP_HEADER));
memcpy(bData+sizeof(ST_MODBUS_MBAP_HEADER),pSendData,wDataLen);
int iRtnsend = 0;
if (!bIsTransit)
{
iRtnsend = m_socket.Send(bData,iDataLen);
}else{
iRtnsend = m_socket.Send(pSendData,wDataLen);
}
return iRtnsend;
}
代码全部贴出来有点多,数据解析代码没贴,点击下载完整代码。