半导体:Gem/Secs基本协议库的开发(4)

发布时间:2023年12月17日

继续接上篇 《半导体:Gem/Secs基本协议库的开发(3)》,本篇我们分享的比较简单,windows系统下tcp和串口通讯。这也是我们协议开发比较重要的一部分,不过我们在此把它封装程一个单独的通讯库,毕竟,它的作用也只是收发消息而已。so easy~

[codes]

// Commucation.pro

TEMPLATE = lib
DEFINES += COMMUCATION_LIBRARY
TARGET = JC_Commucation
CONFIG     += c++11 no_debug_release

win32:CONFIG(release, debug|release){
    DESTDIR     = $${PWD}/../../../deploy/lib/Release
}
else:win32:CONFIG(debug, debug|release){
    DESTDIR     = $${PWD}/../../../deploy/lib/Debug
}

OBJECTS_DIR = $${PWD}/../../../build/$${TARGET}/obj
MOC_DIR     = $${PWD}/../../../build/$${TARGET}/moc

SOURCES += \
    commucation.cpp \
    commucationbase.cpp \
    serialportobject_win.cpp \
    #stringhelper.cpp \
    tcpclientobject_win.cpp \
    tcpserverobject_win.cpp

HEADERS += \
    commucation.h \
    commucationbase.h \
    serialportobject_win.h \
    #stringhelper.h \
    tcpclientobject_win.h \
    tcpserverobject_win.h


#BEFORE_LINK_CMD_LINE = echo begin_to_compile_JC_Commucation!
#QMAKE_PRE_LINK += $$quote($$BEFORE_LINK_CMD_LINE)
#AFTER_LINK_CMD_LINE = '$$PWD/move.bat' commucation.h ../../../deploy/include/$$TARGET
#QMAKE_POST_LINK += $$quote($$AFTER_LINK_CMD_LINE)
// commucation.h
#ifndef COMMUCATION_H
#define COMMUCATION_H

#if defined(COMMUCATION_LIBRARY)
#  define _API extern "C"  __declspec(dllexport)
#else
#  define _API extern "C"  __declspec(dllimport)
#endif

#include <iostream>
#include <winsock.h>
#define JC_UNUSED(x) (void)x;


typedef enum CommucationType
{
    TcpServer,
    TcpClient,
    SerialPort
}CommType;

struct EthernetCommucationParam{

    __int32 nT3;                // Reply timeout
    __int32 nT5;                // Connect separation timeout
    __int32 nT6;                // Control transaction timeout
    __int32 nT7;                // Not selected timeout
    __int32 nT8;                // Network intercharacter timeout
    __int32 nConnectMode;		// 1=Passive, 0=Active
    __int32 nPort;              // port, set default as 5000
    __int32 nDeviceID;          // Session ID(device ID),set default as 0
    char DeviceName[50];        // Describle a device ,could be empty.
    char pIP[24];				// a string IP "127.0.0.1"
};

struct SerialCommucationParam{
    uint32_t portNo = 1;
    uint32_t baud;
    char parity;    // check byte, 'Y' or 'N'
    uint32_t databits;
    uint32_t stopsbits;
};


typedef struct CommucationParam{
    EthernetCommucationParam eParam;
    SerialCommucationParam sParam;
}CommParam;

class ICommucation;

/// rigister call back event
typedef void (*OnMsgRecivedEvent)      (ICommucation* pComm, char* message,int iRecvSize,void *pClientData);
typedef void (*OnStateChangedEvent)    (ICommucation* pComm, __int32 nState, void *pClientData);
typedef void (*OnAsyncMsgTimeoutEvent) (ICommucation* pComm, __int32 nTransfer, void *pClientData);


enum SendDirection{
    H2E , /// Host -> Equipment
    E2H   /// Equipment -> Host
};

class ICommucation
{
public:
    ICommucation(){}
    virtual ~ICommucation(){}

    virtual bool CreateCommObject() = 0 ;
    virtual void ReleaseCommObject() = 0;

    virtual void run() = 0;

    virtual int SendData(SOCKET fd ,const char *msg, int len) = 0;
    virtual bool SendSyncMessage(std::string strSendBuf, bool needReply,
                                 std::string &strRecvMsg, int iTimeOut = 5) = 0;

    virtual void setEventCallBack(OnMsgRecivedEvent eProc1,
                                  OnStateChangedEvent eProc2,
                                  OnAsyncMsgTimeoutEvent eProc3) = 0;
};


/**
 * @brief JC_CreatCommObject    创建通信对象
 * @param type
 * @param param
 * @return
 */
_API ICommucation * JC_CreatCommObject(CommucationType type,CommucationParam parm);


/**
 * @brief run  在独立的线程中执行消息监听(异步)
 * @param p
 */
_API  void  JC_RunListenThread(ICommucation* p);


/**
 * @brief JC_ReleaseCommObject  释放通信对象
 * @param p
 */
_API  void  JC_ReleaseCommObject(ICommucation* p);


/**
 * @brief JC_SetEventCallBack   注册事件回调
 * @param pObject               通信连接对象
 * @param pMsgRecivedProc       接收消息的回调函数
 * @param pStateChangedProc     状态改变的回调函数
 * @param OnAsyncMsgTimeoutProc 异步发送消息超时回调
 */
_API  void  JC_SetEventCallBack(ICommucation* pObject,
                                OnMsgRecivedEvent pMsgRecivedProc,
                                OnStateChangedEvent pStateChangedProc,
                                OnAsyncMsgTimeoutEvent OnAsyncMsgTimeoutProc);

/**
* @brief JC_SendSyncMessage  同步发送消息并接收请求数据
* @param pObject             通信连接对象
* @param direction
* @param data                发送数据
* @param needReply           是否需要回复
* @param pReplyData          接收到的回复数据
* @return
*/
_API  bool JC_SendSyncMessage( ICommucation* pObject,const SendDirection direction,
                               const std::string& data,const bool needReply,
                               std::string& pReplyData);

/*!
* \brief JC_SendAsyncMessage   异步发送消息
* \param pObject
* \param pData
* \return
*/
_API int JC_SendAsyncMessage(ICommucation* pObject, const std::string data);


/*!
 * \brief JC_Version
 * \return
 */
_API const char *JC_CommDllVersion();

#endif // COMMUCATION_H
// commucationbase.h

#ifndef COMMUCATIONBASE_H
#define COMMUCATIONBASE_H

#include "commucation.h"

class CommucationBase : public ICommucation
{
public:
    CommucationBase();

    virtual ~CommucationBase();

    virtual bool CreateCommObject();

    virtual void ReleaseCommObject();

    virtual void run();

    virtual int SendData(SOCKET fd, const char *msg, int len);

    virtual bool SendSyncMessage(std::string strSendBuf, bool needReply, std::string &strRecvMsg, int iTimeOut = 5);

    virtual void setEventCallBack(OnMsgRecivedEvent eProc1,OnStateChangedEvent eProc2,OnAsyncMsgTimeoutEvent eProc3);

};

#endif // COMMUCATIONBASE_H
// commucationbase.cpp

#include "commucationbase.h"

CommucationBase::CommucationBase()
{

}


CommucationBase::~CommucationBase()
{

}

bool CommucationBase::CreateCommObject()
{
    return true;
}

void CommucationBase::ReleaseCommObject()
{
    return;
}

void CommucationBase::run()
{
    return;
}

int CommucationBase::SendData( SOCKET fd, const char *msg, int len)
{
    fd,msg,len;
    return 0;
}


bool CommucationBase::SendSyncMessage(std::string strSendBuf, bool needReply, std::string &strRecvMsg, int iTimeOut)
{
    JC_UNUSED(strSendBuf);
    JC_UNUSED(needReply);
    JC_UNUSED(strRecvMsg);
    JC_UNUSED(iTimeOut);
    return true;
}


void CommucationBase::setEventCallBack(OnMsgRecivedEvent eProc1, OnStateChangedEvent eProc2, OnAsyncMsgTimeoutEvent eProc3)
{
    JC_UNUSED(eProc1);
    JC_UNUSED(eProc2);
    JC_UNUSED(eProc3);
    return;
}
// serialportobject_win.h
#ifndef SERIALPORTCOMMUCATIONOBJECT_H
#define SERIALPORTCOMMUCATIONOBJECT_H

#include "commucationbase.h"
#include <deque>
#include <map>

class SerialportCommucationObject : public CommucationBase
{
public:
    SerialportCommucationObject(CommucationParam param);

    bool CreateCommObject();

    void ReleaseCommObject();

    int  SendData(SOCKET fd,const char *msg, int len);

    bool SendSyncMessage(std::string strSendBuf, bool needReply,
                         std::string &strRecvMsg, int iTimeOut = 5);

    void run();

    void setEventCallBack(OnMsgRecivedEvent eProc1,
                          OnStateChangedEvent eProc2,
                          OnAsyncMsgTimeoutEvent eProc3);
private:

    /** 初始化串口函数
    *  @param:  UINT portNo 串口编号,默认值为1,即COM1,注意,尽量不要大于9
    *  @param:  UINT baud   波特率,默认为9600
    *  @param:  char parity 是否进行奇偶校验,'Y'表示需要奇偶校验,'N'表示不需要奇偶校验
    *  @param:  UINT databits 数据位的个数,默认值为8个数据位
    *  @param:  UINT stopsbits 停止位使用格式,默认值为1
    *  @param:  DWORD dwCommEvents 默认为EV_RXCHAR,即只要收发任意一个字符,则产生一个事件
    *  @return: bool  初始化是否成功
    *  @note:   在使用其他本类提供的函数前,请先调用本函数进行串口的初始化
    *        /n本函数提供了一些常用的串口参数设置,若需要自行设置详细的DCB参数,可使用重载函数
    *           /n本串口类析构时会自动关闭串口,无需额外执行关闭串口
    *  @see:
    */
    bool InitPort(UINT  portNo = 1, UINT  baud = CBR_9600, char  parity = 'N',
                  UINT  databits = 8, UINT  stopsbits = 1, DWORD dwCommEvents = EV_RXCHAR);



    /** 串口初始化函数
    *  本函数提供直接根据DCB参数设置串口参数
    *  @param:  UINT portNo
    *  @param:  const LPDCB & plDCB
    *  @return: bool  初始化是否成功
    *  @note:   本函数提供用户自定义地串口初始化参数
    *  @see:
    */
    bool InitPort(UINT  portNo, const LPDCB& plDCB);


    /** 开启监听线程
    *  本监听线程完成对串口数据的监听,并将接收到的数据打印到屏幕输出
    *  @return: bool  操作是否成功
    *  @note:   当线程已经处于开启状态时,返回flase
    *  @see:
    */
    bool OpenListenThread();


    /** 关闭监听线程
    *  @return: bool  操作是否成功
    *  @note:   调用本函数后,监听串口的线程将会被关闭
    *  @see:
    */
    bool CloseListenTread();



    /** 向串口写数据
    *  将缓冲区中的数据写入到串口
    *  @param:  unsigned char * pData 指向需要写入串口的数据缓冲区
    *  @param:  unsigned int length 需要写入的数据长度
    *  @return: bool  操作是否成功
    *  @note:   length不要大于pData所指向缓冲区的大小
    *  @see:
    */
    bool WriteData(unsigned char* pData, unsigned int length);


    /** 获取串口缓冲区中的字节数
    *  @return: UINT  操作是否成功
    *  @note:   当串口缓冲区中无数据时,返回0
    *  @see:
    */
    UINT GetBytesInCOM();


    /** 读取串口接收缓冲区中一个字节的数据
    *  @param:  char & cRecved 存放读取数据的字符变量
    *  @return: bool  读取是否成功
    *  @note:
    *  @see:
    */
    bool ReadChar(char &cRecved);


    /** 打开串口
    *  @param:  UINT portNo 串口设备号
    *  @return: bool  打开是否成功
    *  @note:
    *  @see:
    */
    bool openPort(UINT  portNo);


    /** 关闭串口
    *  @return: void  操作是否成功
    *  @note:
    *  @see:
    */
    void ClosePort();


    /** 串口监听线程 : 监听来自串口的数据和信息
    *  @param:  void * pParam 线程参数
    *  @return: UINT WINAPI 线程返回值
    *  @note:
    *  @see:
    */
    static UINT WINAPI ListenThread(void* pParam);

private:
    CommucationParam m_param;

    /** 串口句柄 */
    HANDLE  m_hComm;

    /** 线程退出标志变量 */
    static bool s_bExit;

    /** 线程句柄 */
    volatile HANDLE    m_hListenThread;

    /** 同步互斥,临界区保护 */
    CRITICAL_SECTION   m_csCommunicationSync;

    /** 事件回调  */
    OnMsgRecivedEvent msgRecivedEventProc;
    OnStateChangedEvent stateChangedEventProc;
    OnAsyncMsgTimeoutEvent asyncMsgTimeoutEvent;

};

#endif // SERIALPORTCOMMUCATIONOBJECT_H

串口

// serialportobject_win.cpp

#include "serialportobject_win.h"
#include <process.h>
#include <iostream>


/** 线程退出标志 */
bool SerialportCommucationObject::s_bExit = false;

/** 当串口无数据时,sleep至下次查询间隔的时间,单位:秒 */
const UINT SLEEP_TIME_INTERVAL = 5;


SerialportCommucationObject::SerialportCommucationObject(CommucationParam param)
    : m_param(param), m_hListenThread(INVALID_HANDLE_VALUE)
{
}


bool SerialportCommucationObject::CreateCommObject()
{
    return InitPort(m_param.sParam.portNo,m_param.sParam.baud,
                    m_param.sParam.parity,m_param.sParam.databits,m_param.sParam.stopsbits);

}

void SerialportCommucationObject::ReleaseCommObject()
{
    CloseListenTread();
    ClosePort();
    DeleteCriticalSection(&m_csCommunicationSync);
}

int SerialportCommucationObject::SendData(SOCKET fd, const char *msg, int len)
{
    JC_UNUSED(fd);
    unsigned char* umsg = new unsigned char[len]{0};
    memcpy(umsg,msg,len);
    bool ok =  WriteData(umsg,len) ;
    delete[] umsg;
    umsg = NULL;

    return ok ? len : 0;
}



void SerialportCommucationObject::run()
{
    if(!CreateCommObject()) {
        std::cout << "创建窗口通讯对象失败" << std::endl;
        return;
    }else{
        std::cout << "创建窗口通讯对象成功"  << std::endl;
    }

    OpenListenThread();
}


void SerialportCommucationObject::setEventCallBack(OnMsgRecivedEvent eProc1, OnStateChangedEvent eProc2, OnAsyncMsgTimeoutEvent eProc3)
{
    msgRecivedEventProc = eProc1;
    stateChangedEventProc = eProc2;
    asyncMsgTimeoutEvent = eProc3;

}

bool SerialportCommucationObject::SendSyncMessage(std::string strSendBuf, bool needReply, std::string &strRecvMsg, int iTimeOut)
{
    JC_UNUSED(strSendBuf);
    JC_UNUSED(iTimeOut);
    JC_UNUSED(needReply);
    JC_UNUSED(strRecvMsg);
    puts("SerialportCommucationObject::SendSyncMessage not achieved.\n");
    return false;
}


bool SerialportCommucationObject::InitPort(UINT portNo, UINT baud, char parity, UINT databits, UINT stopsbits, DWORD dwCommEvents)
{
    dwCommEvents = dwCommEvents;

    m_hComm = INVALID_HANDLE_VALUE;
    m_hListenThread = INVALID_HANDLE_VALUE;
    InitializeCriticalSection(&m_csCommunicationSync);

    /** 临时变量,将制定参数转化为字符串形式,以构造DCB结构 */
    char szDCBparam[50];
    sprintf_s(szDCBparam, "baud=%d parity=%c data=%d stop=%d", baud, parity, databits, stopsbits);

    /** 打开指定串口,该函数内部已经有临界区保护,上面请不要加保护 */
    if (!openPort(portNo))
    {
        return false;
    }

    /** 进入临界段 */
    EnterCriticalSection(&m_csCommunicationSync);


    /** 是否有错误发生 */
    BOOL bIsSuccess = TRUE;


    /** 在此可以设置输入输出的缓冲区大小,如果不设置,则系统会设置默认值.
    *  自己设置缓冲区大小时,要注意设置稍大一些,避免缓冲区溢出
    */
    /*if (bIsSuccess )
    {
    bIsSuccess = SetupComm(m_hComm,10,10);
    }*/

    /** 设置串口的超时时间,均设为0,表示不使用超时限制 */
    COMMTIMEOUTS  CommTimeouts;
    CommTimeouts.ReadIntervalTimeout = 0;
    CommTimeouts.ReadTotalTimeoutMultiplier = 0;
    CommTimeouts.ReadTotalTimeoutConstant = 0;
    CommTimeouts.WriteTotalTimeoutMultiplier = 0;
    CommTimeouts.WriteTotalTimeoutConstant = 0;

    if (bIsSuccess)
    {
        bIsSuccess = SetCommTimeouts(m_hComm, &CommTimeouts);
    }

    DCB  dcb;
    if (bIsSuccess)
    {
        // 将ANSI字符串转换为UNICODE字符串
        DWORD dwNum = MultiByteToWideChar(CP_ACP, 0, szDCBparam, -1, NULL, 0);
        wchar_t *pwText = new wchar_t[dwNum];
        if (!MultiByteToWideChar(CP_ACP, 0, szDCBparam, -1, pwText, dwNum))
        {
            bIsSuccess = TRUE;
        }

        /** 获取当前串口配置参数,并且构造串口DCB参数 */
        bIsSuccess = GetCommState(m_hComm, &dcb) && BuildCommDCB(pwText, &dcb);

        /** 开启RTS flow控制 */
        dcb.fRtsControl = RTS_CONTROL_ENABLE;

        /** 释放内存空间 */
        delete[] pwText;
    }

    if (bIsSuccess)
    {
        /** 使用DCB参数配置串口状态 */
        bIsSuccess = SetCommState(m_hComm, &dcb);
    }

    /**  清空串口缓冲区 */
    PurgeComm(m_hComm, PURGE_RXCLEAR | PURGE_TXCLEAR | PURGE_RXABORT | PURGE_TXABORT);


    /** 离开临界段 */
    LeaveCriticalSection(&m_csCommunicationSync);

    return bIsSuccess == TRUE;
}

bool SerialportCommucationObject::InitPort(UINT portNo, const LPDCB &plDCB)
{
    /** 打开指定串口,该函数内部已经有临界区保护,上面请不要加保护 */
    if (!openPort(portNo))
    {
        return false;
    }

    /** 进入临界段 */
    EnterCriticalSection(&m_csCommunicationSync);

    /** 配置串口参数 */
    if (!SetCommState(m_hComm, plDCB))
    {
        return false;
    }

    /**  清空串口缓冲区 */
    PurgeComm(m_hComm, PURGE_RXCLEAR | PURGE_TXCLEAR | PURGE_RXABORT | PURGE_TXABORT);

    /** 离开临界段 */
    LeaveCriticalSection(&m_csCommunicationSync);

    return true;
}

bool SerialportCommucationObject::OpenListenThread()
{
    /** 检测线程是否已经开启了 */
    if (m_hListenThread != INVALID_HANDLE_VALUE)
    {
        /** 线程已经开启 */
        return false;
    }

    s_bExit = false;

    /** 线程ID */
    UINT threadId;

    /** 开启串口数据监听线程 */
    m_hListenThread = (HANDLE)_beginthreadex(NULL, 0, ListenThread, this, 0, &threadId);

    if (!m_hListenThread)
    {
        return false;
    }

    /** 设置线程的优先级,高于普通线程 */
    if (!SetThreadPriority(m_hListenThread, THREAD_PRIORITY_ABOVE_NORMAL))
    {
        return false;
    }

    return true;
}

bool SerialportCommucationObject::CloseListenTread()
{
    if (m_hListenThread != INVALID_HANDLE_VALUE)
    {
        /** 通知线程退出 */
        s_bExit = true;

        /** 等待线程退出 */
        Sleep(10);

        /** 置线程句柄无效 */
        CloseHandle(m_hListenThread);

        m_hListenThread = INVALID_HANDLE_VALUE;
    }

    return true;
}

bool SerialportCommucationObject::WriteData(unsigned char *pData, unsigned int length)
{
    BOOL   bResult = TRUE;
    DWORD  BytesToSend = 0;
    if (m_hComm == INVALID_HANDLE_VALUE)
    {
        return false;
    }

    /** 临界区保护 */
    EnterCriticalSection(&m_csCommunicationSync);

    /** 向缓冲区写入指定量的数据 */
    bResult = WriteFile(m_hComm, pData, length, &BytesToSend, NULL);
    if (!bResult)
    {
        DWORD dwError = GetLastError();
        printf("error code %d\n",dwError);

        /** 清空串口缓冲区 */
        PurgeComm(m_hComm, PURGE_RXCLEAR | PURGE_RXABORT);

        LeaveCriticalSection(&m_csCommunicationSync);
        return false;
    }

    /** 离开临界区 */
    LeaveCriticalSection(&m_csCommunicationSync);

    return true;
}

UINT SerialportCommucationObject::GetBytesInCOM()
{
    DWORD dwError = 0;  /** 错误码 */
    COMSTAT  comstat;   /** COMSTAT结构体,记录通信设备的状态信息 */
    memset(&comstat, 0, sizeof(COMSTAT));

    UINT BytesInQue = 0;

    /** 在调用ReadFile和WriteFile之前,通过本函数清除以前遗留的错误标志 */
    if (ClearCommError(m_hComm, &dwError, &comstat))
    {
        BytesInQue = comstat.cbInQue; /** 获取在输入缓冲区中的字节数 */
    }

    return BytesInQue;
}

bool SerialportCommucationObject::ReadChar(char &cRecved)
{
    BOOL  bResult = TRUE;
    DWORD BytesRead = 0;

    if (m_hComm == INVALID_HANDLE_VALUE)
    {
        return false;
    }

    /** 临界区保护 */
    EnterCriticalSection(&m_csCommunicationSync);

    /** 从缓冲区读取一个字节的数据 */
    bResult = ReadFile(m_hComm, &cRecved, 1, &BytesRead, NULL);
    if ((!bResult))
    {
        /** 获取错误码,可以根据该错误码查出错误原因 */
        DWORD dwError = GetLastError();

        printf("error code %d\n",dwError);

        /** 清空串口缓冲区 */
        PurgeComm(m_hComm, PURGE_RXCLEAR | PURGE_RXABORT);

        LeaveCriticalSection(&m_csCommunicationSync);
        return false;
    }

    /** 离开临界区 */
    LeaveCriticalSection(&m_csCommunicationSync);

    return (BytesRead == 1);
}

bool SerialportCommucationObject::openPort(UINT portNo)
{
    /** 进入临界段 */
    EnterCriticalSection(&m_csCommunicationSync);

    /** 把串口的编号转换为设备名 */
    char szPort[50] = {0};
    sprintf_s(szPort, "COM%d", portNo);

    /** 打开指定的串口 */

    m_hComm = CreateFileA(szPort,                       /** 设备名,COM1,COM2等 */
                          GENERIC_READ | GENERIC_WRITE, /** 访问模式,可同时读写 */
                          0,                            /** 共享模式,0表示不共享 */
                          NULL,                         /** 安全性设置,一般使用NULL */
                          OPEN_EXISTING,                /** 该参数表示设备必须存在,否则创建失败 */
                          0,
                          0);


    /** 如果打开失败,释放资源并返回 */
    if (m_hComm == INVALID_HANDLE_VALUE)
    {
        LeaveCriticalSection(&m_csCommunicationSync);
        return false;
    }

    /** 退出临界区 */
    LeaveCriticalSection(&m_csCommunicationSync);

    return true;
}

void SerialportCommucationObject::ClosePort()
{
    /** 如果有串口被打开,关闭它 */
    if (m_hComm != INVALID_HANDLE_VALUE)
    {
        CloseHandle(m_hComm);
        m_hComm = INVALID_HANDLE_VALUE;
    }
}

UINT SerialportCommucationObject::ListenThread(void *pParam)
{
    SerialportCommucationObject *pSerialPort = reinterpret_cast<SerialportCommucationObject*>(pParam);

    // 线程循环,轮询方式读取串口数据
    while (!pSerialPort->s_bExit)
    {
        UINT BytesInQue = pSerialPort->GetBytesInCOM();

        /** 如果串口输入缓冲区中无数据,则休息一会再查询 */
        if (BytesInQue == 0)
        {

            Sleep(SLEEP_TIME_INTERVAL);
            continue;
        }

        /** 读取输入缓冲区中的数据并输出显示 */
        char cRecved = 0x00;
        do
        {
            cRecved = 0x00;
            if (pSerialPort->ReadChar(cRecved) == true)
            {
                std::cout << cRecved << std::endl;
                continue;
            }
        } while (--BytesInQue);

    }
    return 0;

}

tcp 客户端

// tcpclientobject_win.h

#ifndef JC_TCPCLIENTOBJECT_H
#define JC_TCPCLIENTOBJECT_H

#define _CRT_SECURE_NO_WARNINGS
#include <winsock.h>
#include "commucationbase.h"
#include <QByteArray>

class JcTcpClientObject : public CommucationBase
{
public:
    JcTcpClientObject(CommucationParam param);

    bool CreateCommObject();
    void ReleaseCommObject();

    void run();

    void setEventCallBack(OnMsgRecivedEvent eProc1,OnStateChangedEvent eProc2,OnAsyncMsgTimeoutEvent eProc3);

    int SendData(SOCKET fd, const char *msg, int len);
    bool SendSyncMessage(std::string strSendBuf, bool needReply,std::string &strRecvMsg, int iTimeOut = 5);

private:
    bool initialization();

private:
    CommucationParam m_param;

    int m_bufferSize = 10*1024 ;   // 10k
    char recvBuf[1024*10] = { 0 }; // per message
    QByteArray m_buffer; // cur buf;
    int m_messageLength; // cur message length

    bool m_isRunning = true;
    bool m_connected_status = false;
    std::atomic_bool m_asyncFlag = false; // 默认异步接收

    SOCKET c_client;

    /// event call back
    OnMsgRecivedEvent msgRecivedEventProc = NULL;
    OnStateChangedEvent stateChangedEventProc = NULL;
    OnAsyncMsgTimeoutEvent asyncMsgTimeoutEvent = NULL;
};

#endif // JC_TCPCLIENTOBJECT_H
// tcpclientobject_win.cpp

#include "tcpclientobject_win.h"
#include <iostream>
#include <thread>
#include "stringhelper.h"

#pragma comment(lib,"ws2_32.lib")
using namespace std;

#define BUFSIZE 1024*10


JcTcpClientObject::JcTcpClientObject(CommucationParam param): m_param(param) {
    m_connected_status = false;
}

bool JcTcpClientObject::CreateCommObject()
{

    if( !initialization() ) return false;

    c_client = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
    if (c_client == INVALID_SOCKET)
    {
        WSACleanup();
        return false;
    }

    struct sockaddr_in addr;
    addr.sin_family = AF_INET;
    addr.sin_port = htons(m_param.eParam.nPort);
    addr.sin_addr.S_un.S_addr = inet_addr(m_param.eParam.pIP);

    if (connect(c_client, (struct sockaddr*)&addr, sizeof(addr))== INVALID_SOCKET)
    {
        WSACleanup();
        return false;
    }

    return true;
}

void JcTcpClientObject::ReleaseCommObject()
{

    if(m_isRunning){
        m_isRunning = false;
    }

    closesocket(c_client);
    WSACleanup();
}

int JcTcpClientObject::SendData(SOCKET fd,const char* msg,int len)
{
    JC_UNUSED(fd);
    int sLen = 0;
    if (sLen = send( c_client /*fd*/, msg, len, 0) < 0) {
        std::cout << __FILE__ << __LINE__ << " Send message failed." << std::endl;
    }

    return sLen;
}


bool JcTcpClientObject::initialization()
{
    WORD w_req = MAKEWORD(2, 2);
    WSADATA wsadata;

    if (WSAStartup(w_req, &wsadata) != 0) {
        std::cout << "通讯库加载失败" << std::endl;
        return false;
    }
    else {
        std::cout << "通讯库加载成功" << std::endl;
        return true;
    }
}

void JcTcpClientObject::run()
{
    m_connected_status = CreateCommObject();
    if(!m_connected_status) return ;

    std::thread thrd([=](){

        while (m_isRunning) {

            if(m_asyncFlag){
                memset(recvBuf,0,BUFSIZE);

                int iRecvsize = 0;
                iRecvsize = recv(c_client, recvBuf, BUFSIZE, 0);
                if (iRecvsize <= 0){
                    continue;
                }

                /// 判断当前读取的数据包是否为完整packet
                m_buffer += QByteArray(recvBuf,iRecvsize);
                do{
                    if(m_buffer.size()  >= 4) // 前 4 个字节是 Message Length
                    {
                        m_messageLength = static_cast<uint8_t>(m_buffer.at(0));
                        m_messageLength = (m_messageLength << 8) + static_cast<uint8_t>(m_buffer.at(1));
                        m_messageLength = (m_messageLength << 8) + static_cast<uint8_t>(m_buffer.at(2));
                        m_messageLength = (m_messageLength << 8) + static_cast<uint8_t>(m_buffer.at(3));
                    }

                    if(m_buffer.size() >= m_messageLength + 4)
                    { // 到这里说明收到了一个完整的 Message

                        /// call back on message recived
                        if( msgRecivedEventProc != nullptr) {
                            msgRecivedEventProc(this,recvBuf,iRecvsize,(void*)&c_client);

                            m_buffer.clear();
                            m_messageLength = 0;
                        }
                    }
                }while(m_buffer.size() > 0);
            }
            ::Sleep(10);
        }
        closesocket(c_client);

        WSACleanup();
    });

    thrd.detach();
}

void JcTcpClientObject::setEventCallBack(OnMsgRecivedEvent eProc1, OnStateChangedEvent eProc2, OnAsyncMsgTimeoutEvent eProc3)
{
    msgRecivedEventProc = eProc1;
    stateChangedEventProc = eProc2;
    asyncMsgTimeoutEvent = eProc3;
}


bool JcTcpClientObject::SendSyncMessage(std::string strSendBuf, bool needReply,
                                        std::string& strRecvMsg, int iTimeOut)
{
    if (!m_connected_status)
        return false;

    if (needReply){
        m_asyncFlag = false;
    }

    int timeOut = iTimeOut * 1000 ;        //sec
    setsockopt(c_client, SOL_SOCKET, SO_SNDTIMEO, (char*)&timeOut, sizeof(timeOut));
    setsockopt(c_client, SOL_SOCKET, SO_RCVTIMEO, (char*)&timeOut, sizeof(timeOut));
    printf("开始发送消息\n");
    int iRet = send(c_client, strSendBuf.c_str(), strSendBuf.length(), 0);
    if (iRet == 0)
    {
        printf("发送消息超时\n");
        return false;
    }
    printf("发送消息: %s\n", strSendBuf.c_str());

    if(needReply){

        iRet = recv(c_client, recvBuf, sizeof(recvBuf), 0);
        if (iRet == -1)
        {
            printf("接受消息超时\n");
            return false;
        }

        strRecvMsg = std::string(recvBuf);
    }

    if (needReply){
        m_asyncFlag = true;
    }

    return true;
}

tcp服务端

//  tcpserverobject_win.h


#ifndef JC_TCPSERVEROBJECT_H
#define JC_TCPSERVEROBJECT_H

#define _CRT_SECURE_NO_WARNINGS
#include <winsock.h>
#include "commucationbase.h"

#ifndef BUFSIZE
#define BUFSIZE 1024*10
#endif

#include <QByteArray>

class JcTcpServerObject : public CommucationBase
{
public:
    JcTcpServerObject(CommucationParam param);
    virtual ~ JcTcpServerObject();

    bool CreateCommObject();
    void ReleaseCommObject();

    void run();

    void setEventCallBack(OnMsgRecivedEvent eProc1,OnStateChangedEvent eProc2,OnAsyncMsgTimeoutEvent eProc3);

    int  SendData(SOCKET fd, const char *msg, int len);
    bool SendSyncMessage(std::string strSendBuf, bool needReply,std::string &strRecvMsg, int iTimeOut = 5);

private:

    bool initialization();
private:
    CommucationParam m_param;

    char m_ip[20] = {0};
    UINT m_port = 5000;

    SOCKET m_socket;
    int m_bufferSize = 10*1024 ; // 10k

    QByteArray m_buffer; // cur buf;
    int m_messageLength; // cur message length

    bool m_isRunning = true;
    bool m_connected_status;
    SOCKET s_server;

    fd_set fd;

    bool needsplicing  = false;
    char m_tBuffer[10*1024] ={0};

private:
    /// eventCallBack
    OnMsgRecivedEvent msgRecivedEventProc;
    OnStateChangedEvent stateChangedEventProc;
    OnAsyncMsgTimeoutEvent asyncMsgTimeoutEvent;

};

#endif // JC_TCPSERVEROBJECT_H
//  tcpserverobject_win.cpp

#include "tcpserverobject_win.h"
#include <iostream>
#include <thread>
#include "stringhelper.h"
#include <QByteArray>

#pragma comment(lib,"ws2_32.lib")
using namespace std;

JcTcpServerObject::JcTcpServerObject(CommucationParam param)
    : m_param(param)
{
}

JcTcpServerObject::~JcTcpServerObject()
{
}

bool JcTcpServerObject::CreateCommObject()
{

    if(!initialization()) return false;

    s_server = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);

    if (s_server == INVALID_SOCKET) {
        cout << "套接字创建失败!" << endl;
        WSACleanup();
        return false;
    }
    else {
        cout << "套接字创建成功!" << endl;
    }

    /// 设置端口复用
    bool bReuseaddr = true;
    setsockopt(s_server,SOL_SOCKET,SO_REUSEADDR,(const char*)&bReuseaddr,sizeof(bool));

    /// 设置超时
    // int nNetTimeout=1000;//1秒
    // setsockopt(s_server,SOL_SOCKET,SO_SNDTIMEO,(char *)&nNetTimeout,sizeof(int));
    // setsockopt(s_server,SOL_SOCKET,SO_RCVTIMEO,(char *)&nNetTimeout,sizeof(int));


    struct sockaddr_in server_addr;
    server_addr.sin_family = AF_INET;
    server_addr.sin_port = htons(m_param.eParam.nPort);
    server_addr.sin_addr.S_un.S_addr = inet_addr(m_param.eParam.pIP);

    if (bind(s_server, (SOCKADDR*)&server_addr, sizeof(SOCKADDR)) == SOCKET_ERROR) {
        cout << "套接字绑定失败!" << endl;
        WSACleanup();
        return false;
    }
    else {
        cout << "套接字绑定成功!" << endl;
    }

    //3.设置套接字为监听状态  SOMAXCONN 监听的端口数 右键转到定义为5
    if (listen(s_server, SOMAXCONN) < 0) {
        cout << "设置监听状态失败!" << endl;
        WSACleanup();
        return false;

    }
    else {
        cout << "设置监听状态成功!" << endl;
    }
    return true;

}

void JcTcpServerObject::ReleaseCommObject()
{
    if(m_isRunning){
        m_isRunning = false;
    }

    closesocket(s_server);
    WSACleanup();
}

int JcTcpServerObject::SendData(SOCKET fd, const char *msg, int len)
{
    int sLen = send(fd, msg, len, 0);
    if (sLen <= 0) {
        std::cout << "Send message failed." << std::endl;
    }
    return sLen;
}


bool JcTcpServerObject::initialization()
{
    WORD w_req = MAKEWORD(2, 2);
    WSADATA wsadata;

    if (WSAStartup(w_req, &wsadata) != 0) {
        std::cout << "通讯库加载失败" << std::endl;
        return false;
    }
    else {
        std::cout << "通讯库加载成功" << std::endl;
        return true;
    }
}


void JcTcpServerObject::run()
{

    std::thread thrd( [=](){

        // 初始化启动套接字
        if(!CreateCommObject()){
            return;
        }

        std::cout << "等待Host连接到设备" << std::endl;

        // 定义接受请求套接字
        SOCKET s_accept;

        char szDataBuff[BUFSIZE] = {0};
        int iResult = 0;

        sockaddr_in addrAccept;
        int iAcceptLen = sizeof(addrAccept);

        int iRecvSize = 0;

        sockaddr_in addrTemp;
        int iTempLen;


        FD_ZERO(&fd);
        FD_SET(s_server,&fd);

        // timeval tm;
        // tm.tv_sec = 0;
        // tm.tv_usec = 1000;

        while(m_isRunning) {

            fd_set fdOld = fd;
            iResult = select(0,&fdOld,NULL,NULL,/*&tm*/NULL);
            if (0 <= iResult){

                for(UINT i = 0;i < fd.fd_count; i++){

                    if (FD_ISSET(fd.fd_array[i],&fdOld)){

                        /// 如果socket是服务器,则接收连接
                        if (fd.fd_array[i] == s_server){

                            memset(&addrAccept,0,sizeof(addrTemp));
                            s_accept = accept(s_server,(sockaddr *)&addrAccept,&iAcceptLen);
                            if ( INVALID_SOCKET != s_accept){

                                /// 客户端连接
                                if(stateChangedEventProc){
                                    stateChangedEventProc(this,0,(void*)&fd.fd_array[i]);
                                }

                                FD_SET(s_accept,&fd);
                                printf("%s:%d has connected to server!\n",inet_ntoa(addrAccept.sin_addr),
                                       ntohs(addrAccept.sin_port));
                            }
                        }
                        else { /// 非服务器,接收数据(因为fd是读数据集)

                            memset(szDataBuff,0,BUFSIZE);
                            iRecvSize = recv(fd.fd_array[i],szDataBuff,BUFSIZE,0);

                            memset(&addrTemp,0,sizeof(addrTemp));
                            iTempLen = sizeof(addrTemp);
                            getpeername(fd.fd_array[i],(sockaddr *)&addrTemp,&iTempLen);


                            if (SOCKET_ERROR == iRecvSize){

                                /// 触发客户端关闭的回调函数 param2: 0 表示正常连接;1表示断开连接
                                if(stateChangedEventProc){
                                    stateChangedEventProc(this,1,(void*)&fd.fd_array[i]);
                                }

                                closesocket(fd.fd_array[i]);
                                FD_CLR(fd.fd_array[i],&fd);
                                i--;
                                printf("Failed to recv data ,%s:%d errorcode:%d.\n",
                                       inet_ntoa(addrTemp.sin_addr),ntohs(addrTemp.sin_port),WSAGetLastError());
                                continue;
                            }

                            if (0 == iRecvSize){

                                /// 客户端socket关闭
                                printf("%s:%d has closed!\n",inet_ntoa(addrTemp.sin_addr),ntohs(addrTemp.sin_port));

                                /// 触发客户端关闭的回调函数 param2: 0 表示正常连接;1表示断开连接
                                if(stateChangedEventProc){
                                    stateChangedEventProc(this,1,(void*)&fd.fd_array[i]);
                                }

                                closesocket(fd.fd_array[i]);
                                FD_CLR(fd.fd_array[i],&fd);
                                i--;
                            }

                            if (0 < iRecvSize){

                                /// 打印接收的数据
                                printf("recv len=%d from %s:%d \n",iRecvSize,inet_ntoa(addrTemp.sin_addr),ntohs(addrTemp.sin_port));

                                /// 判断当前读取的数据包是否为完整packet
                                m_buffer += QByteArray(szDataBuff,iRecvSize);
                                do
                                {
                                    if(m_buffer.size()  >= 4) // 前 4 个字节是 Message Length
                                    {
                                        m_messageLength = static_cast<uint8_t>(m_buffer.at(0));
                                        m_messageLength = (m_messageLength << 8) + static_cast<uint8_t>(m_buffer.at(1));
                                        m_messageLength = (m_messageLength << 8) + static_cast<uint8_t>(m_buffer.at(2));
                                        m_messageLength = (m_messageLength << 8) + static_cast<uint8_t>(m_buffer.at(3));
                                    }

                                    if(m_buffer.size() >= m_messageLength + 4)
                                    { /// 到这里说明收到了一个完整的 Message

                                        /// call back on message recived
                                        if( msgRecivedEventProc != nullptr) {

                                            msgRecivedEventProc(this,szDataBuff,iRecvSize,(void*)&fd.fd_array[i]);
                                            m_buffer.clear();
                                            m_messageLength = 0;
                                        }
                                    }
                                }while(m_buffer.size() > 0);

                            }
                        }
                    }

                    /// it's import here,don't remove
                    Sleep(30);
                }
            }
            else if (SOCKET_ERROR == iResult)
            {
                Sleep(100);
            }
        }
        // WSACleanup();

    });

    thrd.detach();
}

void JcTcpServerObject::setEventCallBack(OnMsgRecivedEvent eProc1, OnStateChangedEvent eProc2, OnAsyncMsgTimeoutEvent eProc3)
{
    msgRecivedEventProc = eProc1;
    stateChangedEventProc = eProc2;
    asyncMsgTimeoutEvent = eProc3;
}

bool JcTcpServerObject::SendSyncMessage(std::string strSendBuf, bool needReply, std::string &strRecvMsg, int iTimeOut)
{
    JC_UNUSED(strSendBuf);
    JC_UNUSED(needReply);
    JC_UNUSED(strRecvMsg);
    JC_UNUSED(iTimeOut);
    return true;
}

🆗,此通讯库暂时如此,其实考虑IOCP会有更高效率~~ 有机会再进一步优化吧。

文章来源:https://blog.csdn.net/weixin_39568531/article/details/135021377
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。