Modbus Tcp 通信线程的实现

文章介绍了如何在VisualC++中使用CWinThread实现Modbus-Tcp协议的通信,包括创建通信线程、使用Socket进行数据收发、设置缓冲区以及数据解析和传递的过程。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

        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;
}

代码全部贴出来有点多,数据解析代码没贴,点击下载完整代码

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

previewer1024

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值