自学内容网 自学内容网

C++ 实现局域网内即时通信服务端功能 (windows 系统)

C++ 实现局域网内即时通信服务端功能 (windows 系统)

详细描述,见客户端文章

详细代码如下:

/****************************************************
 *
 *@Copyright (c) 2024, GhY, All rights reserved.
 *@文件    server.h
 *@描述    完成端口服务器 接收到客户端的信息
 *
 *@作者    GhY
 *@日期    2024年7月17日
 *@版本    v1.0.0
 *
 ****************************************************/
#include "PublicDefine.h"
#include"windows.h"
#include "sigslot.h"
#include <map>


/*
 *@desc       创建接收线程,处理接收到的数据
 *@param:
 *@return
 *@author     GhY
 *@date       2024/07/17
 *@version    v1.0.0
 *@history:
 */
DWORD WINAPI ReceiveProcessIO(LPVOID lpParam);

/*
 *@desc       创建转发数据线程
 *@param:
 *@return
 *@author     GhY
 *@date       2024/07/24
 *@version    v1.0.0
 *@history:
 */
DWORD WINAPI TranspondMessageProcess(LPVOID lpParam);


class  CAppServer : public sigslot::has_slots<>
{
public:
    typedef sigslot::signal1<int> HandleEvent;

    HandleEvent OnCloseSocketEvent;

public:
    CAppServer();

    ~CAppServer();

    /*
     *@desc       初始化WinSocket
     *@param:
     *@return
     *@author     GhY
     *@date       2024/07/17
     *@version    v1.0.0
     *@history:
     */
    void InitWinsock();

    /*
     *@desc       关联一个已打开的文件实例和新建的或已存在的I/0完成端口
                  或者创建一个未关联任何文件的I/O完成端口
     *@author     GhY
     *@date       2024/07/24
     *@version    v1.0.0
     *@history:
     */
    void InitCompletionPort();

    /*
     *@desc       绑定端口,并返回一个 Overlapped 的ListenSocket
     *@param:     int nPort
     *@return     socket
     *@author     GhY
     *@date       2024/07/17
     *@version    v1.0.0
     *@history:
     */
    SOCKET BindServerOverlapped(int nPort);

    /*
     *@desc       根据系统的CPU来创建工作者线程
     *@author     GhY
     *@date       2024/07/17
     *@version    v1.0.0
     *@history:
     */
    void CreateMultiThread();

    /*
     *@brief    准备函数
     *@author   GhY
     *@date     2024/07/24
     */
    void Prepare();

    void Run();

    void Close();

    /*
     *@desc       关闭socket连接
     *@param:     socket 句柄
     *@return
     *@author     GhY
     *@date       2024/07/24
     *@version    v1.0.0
     *@history:
     */
    void OnCloseConnect(int);

public:
    HANDLE m_completionPort;
    std::map<unsigned int, ClientManager>  m_connectSockets;
    std::list<Message*> m_messages;

    bool m_exitFlag;      // 退出标志

protected:
private:
    SOCKET m_serverListen;  // 监听socket


};


/****************************************************
 *
 *@Copyright (c) 2024, GhY, All rights reserved.
 *@文件    server.h
 *@描述    完成端口服务器 接收到客户端的信息
 *
 *@作者    GhY
 *@日期    2024年7月17日
 *@版本    v1.0.0
 *
 ****************************************************/
#include "PublicDefine.h"
#include"windows.h"
#include "sigslot.h"
#include <map>


/*
 *@desc       创建接收线程,处理接收到的数据
 *@param:
 *@return
 *@author     GhY
 *@date       2024/07/17
 *@version    v1.0.0
 *@history:
 */
DWORD WINAPI ReceiveProcessIO(LPVOID lpParam);

/*
 *@desc       创建转发数据线程
 *@param:
 *@return
 *@author     GhY
 *@date       2024/07/24
 *@version    v1.0.0
 *@history:
 */
DWORD WINAPI TranspondMessageProcess(LPVOID lpParam);

/*
 *@描述:    服务端类
 *@作者:    GhY 
 *@日期:    2024/07/24
 *@历史:    
 */
class  CAppServer : public sigslot::has_slots<>
{
public:
    typedef sigslot::signal1<int> HandleEvent;
    HandleEvent OnCloseSocketEvent;

public:
    CAppServer();

    ~CAppServer();

    /*
     *@desc       初始化WinSocket
     *@param:
     *@return
     *@author     GhY
     *@date       2024/07/17
     *@version    v1.0.0
     *@history:
     */
    void InitWinsock();

    /*
     *@desc       关联一个已打开的文件实例和新建的或已存在的I/0完成端口
                  或者创建一个未关联任何文件的I/O完成端口
     *@author     GhY
     *@date       2024/07/24
     *@version    v1.0.0
     *@history:
     */
    void InitCompletionPort();

    /*
     *@desc       绑定端口,并返回一个 Overlapped 的ListenSocket
     *@param:     int nPort
     *@return     socket
     *@author     GhY
     *@date       2024/07/17
     *@version    v1.0.0
     *@history:
     */
    SOCKET BindServerOverlapped(int nPort);

    /*
     *@desc       根据系统的CPU来创建工作者线程
     *@author     GhY
     *@date       2024/07/17
     *@version    v1.0.0
     *@history:
     */
    void CreateMultiThread();

    /*
     *@brief    准备函数
     *@author   GhY
     *@date     2024/07/24
     */
    void Prepare();

    void Run();

    void Close();

    /*
     *@desc       关闭socket连接
     *@param:     socket 句柄
     *@return
     *@author     GhY
     *@date       2024/07/24
     *@version    v1.0.0
     *@history:
     */
    void OnCloseConnect(int);

public:
    HANDLE m_completionPort;
    std::map<unsigned int, ClientManager>  m_connectSockets;
    std::list<Message*> m_messages;
    bool m_exitFlag;      // 退出标志
private:
    SOCKET m_serverListen;  // 监听socket
};

/****************************************************
 *
 *@Copyright (c) 2024, GhY, All rights reserved.
 *@文件    server.cpp
 *@描述    完成端口服务器 接收到客户端的信息
 *
 *@作者    GhY
 *@日期    2024年7月17日
 *@版本    v1.0.0
 *
 ****************************************************/
#include "server.h"
#pragma comment(lib, "ws2_32")
#include"windows.h"
#include<iostream>
#include "MySRand.h"
#include <stdlib.h>
#include <time.h>
#include "CIniConfig.h"

using namespace std;

/************************************************************************/
/*                           全局函数定义                               */
/************************************************************************/

DWORD WINAPI ReceiveProcessIO(LPVOID lpParam)
{
    CAppServer* app = (CAppServer*)lpParam;
    if (!app) {
        return 1;
    }
    HANDLE CompletionPort = app->m_completionPort;
    DWORD BytesTransferred;
    LPPER_HANDLE_DATA PerHandleData;
    LPPER_IO_OPERATION_DATA PerIoData;

    while (true) {
        if (0 == GetQueuedCompletionStatus(CompletionPort, &BytesTransferred, (LPDWORD)&PerHandleData, (LPOVERLAPPED*)&PerIoData, INFINITE)) {
            if ((GetLastError() == WAIT_TIMEOUT) || (GetLastError() == ERROR_NETNAME_DELETED)) {
                cout << "closingsocket" << PerHandleData->_socket << endl;
                int ccH = PerHandleData->_socket;
                closesocket(PerHandleData->_socket);
                delete PerIoData;
                delete PerHandleData;
                app->OnCloseSocketEvent.emit(ccH);
                continue;
            } else {
                OutErr("GetQueuedCompletionStatus failed!");
            }
            return 0;
        }

        // 说明客户端已经退出
        if (BytesTransferred == 0) {
            cout << "closing socket" << PerHandleData->_socket << endl;
            int ccH = PerHandleData->_socket;
            closesocket(PerHandleData->_socket);
            app->OnCloseSocketEvent.emit(ccH);
            delete PerIoData;
            delete PerHandleData;
            continue;
        }

        unsigned int currentId = 0;
        // 取得数据并处理
        Tcp_SendData* pData = (Tcp_SendData*)PerIoData->Buffer;
        if (pData) {
            if (pData->_body._data != "" && pData->_body._length > 0) {
#ifdef _DEBUG
                cout << PerHandleData->_socket << "发送过来的消息:" << pData->_body._data
                     << " 信息长度:" << pData->_body._length << endl;
#endif
                currentId = pData->_head._node;
                Message* msg = new Message();
                msg->_sendId = pData->_head._node;
                memcpy(&msg->_send_name, &pData->_body._srcName, strlen(pData->_body._srcName));
                //msg->_receiverId;
                //msg->_receiverName;
                memcpy(&msg->_data, &pData->_body._data, pData->_body._length);
                app->m_messages.push_back(msg);
            }
        }

        MySRand rd;
        ClientManager cManager;
        cManager._socket = PerHandleData->_socket;
        sprintf(cManager._addr, PerHandleData->_ip);
        cManager._port = PerHandleData->_port;
        cManager._id = currentId != 0 ? currentId : atoi(rd.getNumber().c_str());

        if (app) {
            app->m_connectSockets.insert(std::make_pair(cManager._socket, cManager));

            //给客户端分配唯一id
            if (currentId == 0 && cManager._id != 0) {
                char sendBuf2[SEND_DATA_LEN];
                memset(sendBuf2, 0, sizeof(sendBuf2));
                int ihead = sizeof(TcpHead);
                int ibody = sizeof(TcpBody);
                Tcp_SendData* pData2 = (Tcp_SendData*)sendBuf2;
                pData2->_head._node = 0;
                pData2->_head._type = 2;
                pData2->_head._time = time(NULL);
                TcpBody tBody;
                std::string tmp = std::to_string(cManager._id).c_str();
                tBody._length = tmp.length();
                memcpy(tBody._data, tmp.c_str(), tmp.length());
                memcpy(&sendBuf2[ihead], &tBody, ibody);
                int sendLen = ihead + ibody;
                int ret = send(PerHandleData->_socket, sendBuf2, sendLen, 0);
            }
            //退出
            if (app->m_exitFlag) {
                break;
            }
        }
        // 继续向 socket 投递WSARecv操作
        DWORD Flags = 0;
        DWORD dwRecv = 0;
        ZeroMemory(PerIoData, sizeof(PER_IO_OPERATION_DATA));
        PerIoData->DataBuf.buf = PerIoData->Buffer;
        PerIoData->DataBuf.len = DATA_BUFSIZE;
        WSARecv(PerHandleData->_socket, &PerIoData->DataBuf, 1, &dwRecv, &Flags, &PerIoData->Overlapped, NULL)
    }
    return 0;
}

DWORD WINAPI TranspondMessageProcess(LPVOID lpParam)
{
    CAppServer* appServer = (CAppServer*)lpParam;
    if (!appServer) {
        return 0;
    }
    while (1) {
        if (appServer->m_messages.empty()) {
            Sleep(300);
            continue;
        }
        Message* msg = appServer->m_messages.front();
        for (auto con = appServer->m_connectSockets.begin(); con != appServer->m_connectSockets.end(); con++) {
            if (msg->_sendId != con->second._id) {
                char sendBuf[SEND_DATA_LEN];
                memset(sendBuf, 0, sizeof(sendBuf));
                int ihead = sizeof(TcpHead);
                int ibody = sizeof(TcpBody);
                Tcp_SendData* pData = (Tcp_SendData*)sendBuf;
                pData->_head._node = 0;
                pData->_head._type = 1;
                pData->_head._time = time(NULL);
                TcpBody tBody;
                std::string tmp = msg->_data;
                std::string tmpName = msg->_send_name;
                tBody._length = tmp.length();
                memcpy(tBody._srcName, tmpName.c_str(), tmpName.length());
                memcpy(tBody._data, tmp.c_str(), tmp.length());
                memcpy(&sendBuf[ihead], &tBody, ibody);
                int sendLen = ihead + ibody;
                int ret = send(con->second._socket, sendBuf, sendLen, 0);
            } else {
                continue;
            }
        }
        delete msg;
        msg = nullptr;
        appServer->m_messages.pop_front();

        if (appServer->m_exitFlag) {
            break;
        }
    }
    return 0;
}


CAppServer::CAppServer()
    : m_serverListen(NULL)
    , m_exitFlag(false)
{
    m_messages.clear();
    OnCloseSocketEvent.connect(this, &CAppServer::OnCloseConnect);
}

CAppServer::~CAppServer()
{
    Close();
}

void CAppServer::InitWinsock()
{
    // 初始化WINSOCK
    WSADATA wsd;
    if (WSAStartup(MAKEWORD(2, 2), &wsd) != 0) {
        OutErr("WSAStartup()");
    }
}

void CAppServer::InitCompletionPort()
{
    m_completionPort = CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0, 0);
    if (m_completionPort == NULL) {
        // 创建失败,处理错误
        OutErr(" CreateIoCompletionPort ");
        return ;
    }
}

SOCKET CAppServer::BindServerOverlapped(int nPort)
{
    // 创建socket
    m_serverListen = WSASocket(AF_INET, SOCK_STREAM, 0, NULL, 0, WSA_FLAG_OVERLAPPED);

    // 绑定端口
    char cz[8] = { 0 };
    struct sockaddr_in servAddr;
    servAddr.sin_family = AF_INET;
    servAddr.sin_port = htons(nPort);
    servAddr.sin_addr.s_addr = htonl(INADDR_ANY);

    if (bind(m_serverListen, (struct sockaddr*)&servAddr, sizeof(servAddr)) == SOCKET_ERROR) {
        OutErr("bind Failed!");
        return NULL;
    }

    // 设置监听队列为 MAX_LISTEN_QUEUE
    if (listen(m_serverListen, MAX_LISTEN_QUEUE) != 0) {
        OutErr("listen Failed!");
        return NULL;
    }
    return m_serverListen;

}

void CAppServer::CreateMultiThread()
{
    //根据系统的CPU来创建工作者线程
    SYSTEM_INFO SystemInfo;
    GetSystemInfo(&SystemInfo);
    //线程数目=系统进程数目的两倍.
    for (int i = 0; i < SystemInfo.dwNumberOfProcessors * 2; i++) {
        HANDLE hProcessIO = CreateThread(NULL, 0, ReceiveProcessIO, this, 0, NULL);
        if (hProcessIO) {
            CloseHandle(hProcessIO);
        }
    }

    HANDLE hTranProcess = CreateThread(NULL, 0, TranspondMessageProcess, this, 0, NULL);
    if (hTranProcess) {
        CloseHandle(hTranProcess);
    }
}

void CAppServer::Prepare()
{
    InitWinsock();

    InitCompletionPort();

    CreateMultiThread();

    std::string sPort = g_ConfigPtr.getConfigValueWithKey("net", "port");
    int iPort = sPort.empty() ? PORT : atoi(sPort.c_str());
    BindServerOverlapped(PORT);
}

void CAppServer::Run()
{
    SOCKET sClient;
    LPPER_HANDLE_DATA PerHandleData;
    LPPER_IO_OPERATION_DATA PerIoData;

    while (true) {
        if (!m_serverListen) {
            break;
        }
        if (m_exitFlag) {
            break;
        }
        // 等待客户端接入
        //sClient = WSAAccept(sListen, NULL, NULL, NULL, 0);
        sockaddr_in remoteAddr;
        int nAddrlen = sizeof(remoteAddr);

        sClient = accept(m_serverListen, (SOCKADDR*)&remoteAddr, &nAddrlen);
        if (sClient == INVALID_SOCKET) {
            continue;
        }

        cout << endl;
        cout << "Socket " << sClient << "连接进来" << endl;

        PerHandleData = new PER_HANDLE_DATA();
        PerHandleData->_socket = sClient;
        sprintf(PerHandleData->_ip, inet_ntoa(remoteAddr.sin_addr));
        PerHandleData->_port = remoteAddr.sin_port;

        // 将接入的客户端和完成端口联系起来
        CreateIoCompletionPort((HANDLE)sClient, m_completionPort, (DWORD)PerHandleData, 0);

        // 建立一个Overlapped,并使用这个Overlapped结构对socket投递操作
        PerIoData = new PER_IO_OPERATION_DATA();

        ZeroMemory(PerIoData, sizeof(PER_IO_OPERATION_DATA));
        PerIoData->DataBuf.buf = PerIoData->Buffer;
        PerIoData->DataBuf.len = DATA_BUFSIZE;

        // 投递一个WSARecv操作
        DWORD Flags = 0;
        DWORD dwRecv = 0;
        WSARecv(sClient, &PerIoData->DataBuf, 1, &dwRecv, &Flags, &PerIoData->Overlapped, NULL);
    }

    DWORD dwByteTrans = 0;
    //将一个已经完成的IO通知添加到IO完成端口的队列中.
    //提供了与线程池中的所有线程通信的方式.
    PostQueuedCompletionStatus(m_completionPort, dwByteTrans, 0, 0); //IO操作完成时接收的字节数.
}

void CAppServer::Close()
{
    m_exitFlag = true;
    if (m_serverListen) {
        closesocket(m_serverListen);
        m_serverListen = NULL;
    }
#ifdef _MSC_VER
    if (m_completionPort) {
        PostQueuedCompletionStatus(m_completionPort, 0, 0, 0);
        m_completionPort = NULL;
    }
#endif
}

void CAppServer::OnCloseConnect(int handle)
{
    auto tmp = m_connectSockets.find(handle);
    if (tmp != m_connectSockets.end()) {
        m_connectSockets.erase(tmp);
    }
}

/****************************************************
 *
 *@Copyright (c) 2024, GhY, All rights reserved.
 *@文件    MySRand.h
 *@描述    随机数生成类声明
 *
 *@作者    GhY
 *@日期    2024年7月24日
 *@版本    v1.0.0
 *
 ****************************************************/
#ifndef __MYSRAND_H__
#define __MYSRAND_H__
#include <string>

/*
 *@描述:    生成随机数类
 *@作者:    GhY
 *@日期:    2024/07/24
 *@历史:
 */
class MySRand
{
public:
    MySRand();
    ~MySRand();

    /*
     *@desc       获取随机数
     *@param:     length 随机数长度
     *@return     随机数
     *@author     GhY
     *@date       2024/07/17
     *@version    v1.0.0
     *@history:
     */
    std::string getNumber(int length = 6);

};

#endif // !__MYSRAND_H__

/****************************************************
 *
 *@Copyright (c) 2024, GhY, All rights reserved.
 *@文件    MySRand.cpp
 *@描述    随机数生成类实现

 *
 *@作者    GhY
 *@日期    2024年7月24日
 *@版本    v1.0.0
 *
 ****************************************************/
#include "MySRand.h"
#include <stdlib.h>
#include <time.h>



MySRand::MySRand()
{
    srand(time(NULL));
}


MySRand::~MySRand()
{
}

std::string MySRand::getNumber(int length /*= 6*/)
{
    std::string retStr = "";
    while (length--) {
        int num = rand() % 9;
        retStr.append(std::to_string(num));
    }
    return retStr;
}

主函数调用:

#include "server.h"


int main()
{
    CAppServer* appS = new CAppServer();

    appS->Prepare();

    appS->Run();

    return 0;
}

注意:

依赖的部分文件(.h,.cpp文件)见本专栏其他文章。

补充:
本文只是初步实现并发服务,支持多客户端通信,有兴趣的朋友可以自行扩展;如增加GUI交互,优化传输等;


原文地址:https://blog.csdn.net/weixin_45310411/article/details/140670434

免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!