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)!