C++ 代码实现局域网即时通信功能 (windows 系统 客户端)

本项目使用C++实现具备多个客户端和服务器端即时通信聊天功能软件

一:项目内容
使用C++实现一个具备多客户端和一个服务器端即时通信功能的聊天软件。
本项目的目的是
学习在windows平台下,进行C++网络开发的基本概念:TCP/IP socket通信,多线程编程,文件配置读写和通信协议制定等;
二:需求分析
这个聊天室主要有两个程序:
1.服务端:能够接受新的客户连接,并将每个客户端发来的信息,转发给对应的目标客户端。
2.客户端:能够连接服务器,并向服务器发送消息,同时可以接收服务器发来的消息。
属于C/S模型。
三:抽象与细化
服务端类需要支持:
1.支持多个客户端接入,实现聊天室基本功能。
2.启动服务,建立监听端口等待客户端连接。
3.使用epoll机制实现并发,增加效率。
4.客户端连接时,发送欢迎消息,并存储连接记录。
5.客户端发送消息时,根据消息类型,广播给所有用户(群聊)或者指定用户(私聊)。
6.客户端请求退出时,对相应连接信息进行清理。
客户端类需要支持:
1.连接服务器。
2.支持用户输入消息,发送给服务端。
3.接受并显示服务端发来的消息。
4.退出连接。
四:C/S模型
C/S模型图
五:涉及数据读写、转发等操作所以需要使用Windows 下IOCP模型:
 IOCP 全称I/O Completion Port,中文译为I/O完成端口。IOCP是一个异步I/O的Windows API,它可以高效地将I/O事件通知给应用程序,类似于Linux中的Epoll,详细信息请参考linux之epoll。
 I/O 完成端口可以充分利用 Windows 内核来进行 I/O 调度,相较于传统的Winsock模型,IOCP的优势主要体现在两方面:独特的异步I/O方式和优秀的线程调度机制。
  IOCP模型通信机制,主要过程为:1、socket关联iocp;2、在socket上投递I/O请求;3、事件完成返回完成通知封包;4、工作线程在iocp上处理事件。IOCP的这种工作模式:程序只需要把事件投递出去,事件交给操作系统完成后,工作线程在完成端口上轮询处理。该模式充分利用了异步模式高速率输入输出的优势,能够有效提高程序的工作效率。完成端口可以抽象为一个公共消息队列,当用户请求到达时,完成端口把这些请求加入其抽象出的公共消息队列。这一过程与多个工作线程轮询消息队列并从中取出消息加以处理是并发操作。这种方式很好地实现了异步通信和负载均衡,因为它使几个线程“公平地”处理多客户端的I/O,并且线程空闲时会被挂起,不会占用CPU周期。
    IOCP模型充分利用Windows系统内核,可以实现仅用少量的几个线程来处理和多个client之间的所有通信,消除了无谓的线程上下文切换,最大限度的提高了网络通信的性能。

软件运行效果如下图:效果图
客户端详细代码如下:

/******************************************************@Copyright (c) 2024, GhY, All rights reserved.*@文件    PublicDefine.h*@描述    公共数据结构定义**@作者    GhY*@日期    2024年7月24日*@版本    v1.0.0*****************************************************/
#pragma once
#include<stdio.h>
#include <iostream>
#include"winerror.h"
#define WIN32_LEAN_AND_MEAN
#include"Winsock2.h"#define OutErr(a) std::cout << "error :" << (a) << std::endl \<< "出错代码:"<< WSAGetLastError() << std::endl \<< "出错文件:"<< __FILE__ << std::endl  \<< "出错行数:"<< __LINE__ << std::endl \

#define OutMsg(a) std::cout << (a) << std::endl;#define PORT            5050            // 监听端口
#define LOCAL_HOST      "127.0.0.1"     // 本地回路地址
#define DATA_BUFSIZE    8192#define MAX_LISTEN_QUEUE    200#define MAX_CONNECT      3000#define MAX_DATA_LEN    2048        // 数据包长度#define SEND_DATA_LEN    4096        // 发送数据包长度/// 结构体定义
/**@brief    用于IOCP的特定函数*@author   GhY*@date     2024/07/24*/
typedef struct {OVERLAPPED Overlapped;WSABUF DataBuf;CHAR Buffer[DATA_BUFSIZE];
} PER_IO_OPERATION_DATA, *LPPER_IO_OPERATION_DATA;/**@brief    用于IOCP的特定结构*@author   GhY*@date     2024/07/24*/
typedef struct _PER_HANDLE_DATA {SOCKET _socket;CHAR _ip[32];int _port;_PER_HANDLE_DATA(){_socket = NULL;memset(_ip, 0, 32);_port = -1;}
} PER_HANDLE_DATA, *LPPER_HANDLE_DATA;#pragma pack(1)
/**@brief    数据包头*@author   GhY*@date     2024/07/24*/
typedef struct _DataHead {unsigned short  _type;      // 0=上传数据, 1=转发数据,2=请求数据unsigned int  _node;        // 客户端IDunsigned long   _time;_DataHead(){memset(this, 0, sizeof(_DataHead));}} TcpHead, Udp_Head;/**@brief    数据包体*@author   GhY*@date     2024/07/24*/
typedef struct _DataBody {char        _srcName[32];int         _length;char        _data[MAX_DATA_LEN];_DataBody(){memset(this, 0, sizeof(_DataBody));}} TcpBody, UdpBody;/**@brief    发送数据*@author   GhY*@date     2024/07/24*/
typedef struct _SendData {TcpHead _head;TcpBody _body;
} Tcp_SendData, Udp_SendData;#pragma pack()/**@brief    socket连接管理*@author   GhY*@date     2024/07/24*/
struct ClientManager {unsigned int _id;char _name[32];SOCKET _socket;char _addr[16];int _port;ClientManager(){memset(this, 0, sizeof(ClientManager));}};/**@brief    通信消息*@author   GhY*@date     2024/07/24*/
struct Message {unsigned int _sendId;char _send_name[32];unsigned int _receiverId;char _receiverName[32];char _data[MAX_DATA_LEN];Message(){memset(this, 0, sizeof(Message));}
};
/******************************************************@Copyright (c) 2024, GhY, All rights reserved.*@文件    application.h*@描述    app基类**@作者    GhY*@日期    2024年7月24日*@版本    v1.0.0*****************************************************/
#ifndef __APPLICATION_H__
#define __APPLICATION_H__class application
{
public:application();virtual ~application();/**@brief Do some initialize before application lanuch*/virtual bool initinstance();/**@brief Run application*/virtual int run();/**@brief Exit application*/virtual bool exitinstance();};#endif // !__APPLICATION_H__
#include "application.h"application::application()
{
}application::~application()
{
}bool application::initinstance()
{return true;
}int application::run()
{if (initinstance()) {run();}return exitinstance();
}bool application::exitinstance()
{return true;
}
/******************************************************@Copyright (c) 2024, GhY, All rights reserved.*@文件    client.h*@描述    客户端类声明**@作者    GhY*@日期    2024年7月24日*@版本    v1.0.0*****************************************************/
#ifndef  __CLIENT_H__
#define  __CLIENT_H__#include<stdio.h>
#include<iostream>
#include "MySocket.h"
#include "MyReceive.h"
#include "application.h"
#include <string>
#include <vector>
#include <list>class CCinData;/**@描述:    客户端类*@作者:    GhY*@日期:    2024/07/24*@历史:*/
class CAppClient : public application, public sigslot::has_slots<>
{
public:CAppClient();~CAppClient();/**@brief    关联信号槽*@author   GhY*@date     2024/07/24*/void InitSigslot();/**@brief    初始化*@author   GhY*@date     2024/07/24*/bool initinstance();/**@brief    退出*@author   GhY*@date     2024/07/24*/bool exitinstance();int run();/**@desc       发送数据*@param:     sdata  待传输数据*@return     void*@author     GhY*@date       2024/07/24*@version    v1.0.0*@history:*/void SendData(std::string* sdata);public:MySocket* m_mysocket;std::list<std::string*> m_sendBufs;CCinData* m_sendData;bool m_exitFlag;   // 退出标志protected:private:MyReceive* m_myrev;
};/**@描述:    获取输入数据类*@作者:    GhY*@日期:    2024/07/24*@历史:*/
class CCinData : public sigslot::has_slots<>
{
public:typedef sigslot::signal1<std::string* > SendDataEvent;SendDataEvent OnSendEvent;public:CCinData(CAppClient* app);~CCinData();void Run();private:CAppClient* m_appClient;bool m_exitFlag;
};#endif  //!__CLIENT_H__
/******************************************************@Copyright (c) 2024, GhY, All rights reserved.*@文件    client.cpp*@描述    客户端类实现**@作者    GhY*@日期    2024年7月24日*@版本    v1.0.0*****************************************************/
#include "client.h"DWORD WINAPI ClientCinProcess(LPVOID lpParam)
{CAppClient* appclient = (CAppClient*)lpParam;if (!appclient) {return 1;}if (appclient->m_sendData) {appclient->m_sendData->Run();}return 0;
}DWORD WINAPI RunSendBufProcess(LPVOID lpParam)
{CAppClient* appclient = (CAppClient*)lpParam;if (!appclient) {return 1;}while (true) {if (appclient->m_exitFlag) {break;}if (appclient->m_sendBufs.empty()) {Sleep(300);continue;}if (appclient->m_sendBufs.size() > 0) {std::string* strBuf = appclient->m_sendBufs.front();appclient->m_sendBufs.pop_front();if (appclient->m_mysocket && !strBuf->empty()) {appclient->m_mysocket->SendData(*strBuf);delete strBuf;}}}return 0;
}CAppClient::CAppClient()
{m_exitFlag = false;m_mysocket = new MySocket();std::string sIp = g_ConfigPtr.getConfigValueWithKey("net", "ip");std::string sPort = g_ConfigPtr.getConfigValueWithKey("net", "port");int iPort = sPort.empty() ? PORT : atoi(sPort.c_str());m_mysocket->InitData(sIp, iPort);m_mysocket->ClientConnect();m_myrev = new MyReceive(m_mysocket);m_sendBufs.clear();m_sendData = new CCinData(this);InitSigslot();
}CAppClient::~CAppClient()
{this->disconnect_all();if (m_myrev) {delete m_myrev;m_myrev = nullptr;}if (m_mysocket) {m_mysocket->Close();delete m_mysocket;m_mysocket = nullptr;}if (m_sendData) {delete m_sendData;m_sendData = nullptr;}
}bool CAppClient::initinstance()
{return true;
}bool CAppClient::exitinstance()
{return true;
}int CAppClient::run()
{std::cout << "使用说明:输入 quit 退出程序" << std::endl;std::string currentName = g_ConfigPtr.getConfigValueWithKey("base", "name");if (currentName.empty()) {char nameT[64] = { 0 };std::cout << "请输入名字:";std::cin >> nameT;g_ConfigPtr.SetConfigValue("base", "name", nameT);} else {std::cout << "当前用户名:" << currentName.c_str() << std::endl;}static int nCnt = 0;char sendBuf[2000] = { 0 };int recvdata = 0;HANDLE hProcessIO = CreateThread(NULL, 0, ClientCinProcess, this, 0, NULL);if (hProcessIO) {CloseHandle(hProcessIO);}HANDLE hProcessIO2 = CreateThread(NULL, 0, RunSendBufProcess, this, 0, NULL);if (hProcessIO2) {CloseHandle(hProcessIO2);}while (true) {if (m_exitFlag) {break;}recvdata = m_mysocket->ReceiveData();if (recvdata == 0) {Sleep(500);} else {recvdata = 0;}}return 0;
}void CAppClient::SendData(std::string* sdata)
{std::string* tmpdata = sdata;if (tmpdata->empty()) {return;}if (tmpdata->compare("quit") == 0) {m_exitFlag = true;m_mysocket->Close();delete tmpdata;return;}m_sendBufs.push_back(tmpdata);
}void CAppClient::InitSigslot()
{if (m_sendData) {m_sendData->OnSendEvent.connect(this, &CAppClient::SendData);}
}CCinData::CCinData(CAppClient* app): m_appClient(app), m_exitFlag(false)
{}CCinData::~CCinData()
{m_exitFlag = true;
}void CCinData::Run()
{std::cout << "please cin message: " << std::endl;std::string* sendTest = new std::string("上线");OnSendEvent.emit(sendTest);while (true) {if (m_exitFlag) {break;}std::string* sendBuf = new std::string();//std::cin >> sendBuf;getline(std::cin, *sendBuf);if (!sendBuf->empty() && sendBuf->compare("quit") == 0) {m_exitFlag = true;}if (sendBuf->size() > 0) {OnSendEvent.emit(sendBuf);} else {Sleep(500);}}
}
/******************************************************@Copyright (c) 2024, GhY, All rights reserved.*@文件    MyReceive.h*@描述    处理数据类声明**@作者    GhY*@日期    2024年7月24日*@版本    v1.0.0*****************************************************/
#ifndef  __MYRECEIVE_H__
#define __MYRECEIVE_H__
#include "MySocket.h"/**@描述:    接收数据处理类*@作者:    GhY*@日期:    2024/07/24*@历史:*/
class MyReceive : public sigslot::has_slots<>
{
public:MyReceive(MySocket* s);~MyReceive();/**@brief    关联信号槽*@author   GhY*@date     2024/07/24*/void InitSigslot();/**@desc       接收数据*@param:     sdata  待接收数据*@return     void*@author     GhY*@date       2024/07/24*@version    v1.0.0*@history:*/void ReceiveData(Tcp_SendData* sdata);private:MySocket* m_mysocket;};#endif  //!__MYRECEIVE_H__
/******************************************************@Copyright (c) 2024, GhY, All rights reserved.*@文件    MyReceive.cpp*@描述    处理数据类实现**@作者    GhY*@日期    2024年7月24日*@版本    v1.0.0*****************************************************/
#include "MyReceive.h"MyReceive::MyReceive(MySocket* s): m_mysocket(s)
{InitSigslot();
}MyReceive::~MyReceive()
{if (m_mysocket) {m_mysocket->disconnect_all();}
}void MyReceive::InitSigslot()
{if (m_mysocket) {m_mysocket->OnSelectEvent.connect(this, &MyReceive::ReceiveData);}
}void MyReceive::ReceiveData(Tcp_SendData* sdata)
{if (!sdata) {return;}do {if (sdata->_head._type == 2) {std::string tmp = sdata->_body._data;g_ConfigPtr.SetConfigValue("base", "id", tmp);} else {std::string tmpName = sdata->_body._srcName;std::string tmp = sdata->_body._data;std::cout << "send: " << tmpName.c_str() << " -- message:  " << tmp.c_str() << std::endl;}} while (0);}

注意:

文章中依赖的文件(.h,.cpp)请参见本专栏其他文章。

源代码下载地址:源代码

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.xdnf.cn/news/1487597.html

如若内容造成侵权/违法违规/事实不符,请联系一条长河网进行投诉反馈,一经查实,立即删除!

相关文章

一键解锁:科研服务器性能匹配秘籍,选择性能精准匹配科研任务和计算需求的服务器

一键解锁&#xff1a;科研服务器性能匹配秘籍 HPC科研工作站服务器集群细分领域迷途小书童 专注于HPC科研服务器细分领域kyfwq001 &#x1f3af;在当今科技飞速发展的时代&#xff0c;科研工作对计算资源的需求日益增长&#x1f61c;。选择性能精准匹配科研任务和计算需求的服…

Elasticsearch集群配置-节点职责划分 Hot Warm 架构实践

前言 本文主要讲了ES在节点部署时可以考虑的节点职责划分&#xff0c;如果不考虑节点部署&#xff0c;那么所有节点都会身兼数职&#xff08;master-eligible &#xff0c;data&#xff0c;coordinate等&#xff09;&#xff0c;这对后期的维护拓展并不利&#xff0c;所以本文…

Gitops-Argo-Cli安装与使用

一、安装Argo-Cli工具 Release v2.9.21 argoproj/argo-cd GitHub **选择合适的符合你操作系统以及CPU架构的二进制文件 #依v2.9.21-X86-64-Linux操作系统为例 wget https://github.com/argoproj/argo-cd/releases/download/v2.9.21/argocd-linux-amd64 #添加执行权限并且移…

景区AR导航营销系统:技术解决方案与实施效益分析

随着旅游市场的竞争日益激烈&#xff0c;景区需要不断创新以吸引游客。景区 AR 导航将虚拟画面与现实场景相结合&#xff0c;为游客提供了更加直观、生动的导航服务。对于景区而言&#xff0c;这一创新技术无疑是吸引游客目光、提升景区知名度的有力武器。通过独特的 AR 导航体…

计算机网络-配置路由器ACL(访问控制列表)

配置访问控制列表ACL 拓扑结构 拓扑结构如下&#xff1a; 要配置一个ACL&#xff0c;禁止PC0访问PC3&#xff0c;禁止PC4访问PC0&#xff0c;其它正常。 配置Router0 配置接口IP地址&#xff1a; interface fastethernet 0/0 ip address 192.168.1.1 255.255.255.0 no shu…

elmentui this.$confirm使用模板字符串构建HTML结构

tip(){const checkingList [];const findList[入会1,入会2,入会3] //数组const sueccList [{name:入会,suecc:1000,numcot:1000},{name:aaaaa,suecc:222,numcot:3333}] //数组对象var message// 使用模板字符串构建HTML结构if(sueccList.length>0){message <div>…

缓存穿透,缓存击穿,缓存雪崩

目录 介绍 缓存穿透 缓存击穿 缓存雪崩 原因 影响 解决方案 缓存穿透 防止缓存穿透->空值缓存案例 缓存击穿 使用互斥锁解决缓存击穿 介绍 缓存穿透 定义&#xff1a;缓存穿透是指用户查询数据&#xff0c;缓存和数据库中都不存在该数据&#xff08;一般是发起恶意…

Mac应用快速启动器:Alfred 5 for Mac 激活版

Alfred 5 是一款专为 macOS 系统设计的效率提升工具。这款软件以其快速启动和高效操作功能著称&#xff0c;通过使用快捷键来呼出输入界面&#xff0c;用户可以快速完成各种任务。 最新版本 Alfred 5.5 引入了一些新功能。其中包括整合了 ChatGPT 和 DALL-E&#xff0c;这意味…

linux root登陆,密码正确但,错误提示su: Authentication failure

初开始登陆的时候会显示失败&#xff0c;参考了很多网上的做法&#xff0c;但还是不行&#xff0c;但是&#xff0c;如果用键盘左边那一排数字按键输入&#xff0c;就可以正常登陆&#xff08;之前用的是右边的九宫格&#xff09;

Mindspore框架循环神经网络RNN模型实现情感分类|(四)损失函数与优化器

Mindspore框架循环神经网络RNN模型实现情感分类 Mindspore框架循环神经网络RNN模型实现情感分类|&#xff08;一&#xff09;IMDB影评数据集准备 Mindspore框架循环神经网络RNN模型实现情感分类|&#xff08;二&#xff09;预训练词向量 Mindspore框架循环神经网络RNN模型实现…

图论之求树的重心

文章目录 题目简要分析解题思路代码实现 题目 原题链接&#xff1a;https://www.acwing.com/problem/content/848/ 题目描述 给定一颗树&#xff0c;树中包含 n 个结点&#xff08;编号 1∼n&#xff09;和 n−1条无向边。请你找到树的重心&#xff0c;并输出将重心删除后&am…

钉钉 ai卡片 stream模式联调

sdk连接 新建卡片模板下载node.js sdkconfig.json 配置应用信息 启动项目npm i npm run build npm run start连接成功 获取卡片回调 注册卡片回调事件调用https://api.dingtalk.com/v1.0/card/instances 创建卡片实例&#xff0c;返回实例Id //参数结构 {"cardTempla…

Linux文件与相关函数的知识点1

Open函数 高频使用的Linux系统调用&#xff1a;open write read close Linux 自带的工具&#xff1a;man手册&#xff1a; man 1是普通的shell命令&#xff0c;比如ls 调用命令&#xff1a;man 1 ls man 2是系统调用函数&#xff0c;比如open&#xff0c;write 调用open…

AV1技术学习:Quantization

量化是对变换系数进行&#xff0c;并将量化索引熵编码。AV1的量化参数 QP 的取值范围是0 ~ 255。 一、Quantization Step Size 在给定的 QP 下&#xff0c;DC 系数的量化步长小于 AC 系数的量化步长。DC 系数和 AC 系数从 QP 到量化步长的映射如下图所示。当 QP 为 0 时&…

MySQL的高可用(MHA)

高可用模式下的故障切换&#xff0c;基于主从复制。 单点故障和主从复制不能切换的问题。 至少需要三台。 故障切换过程0-30秒 vip地址&#xff0c;根据vip地址所在的主机&#xff0c;确定主备。 主 vip 备 vip 主和备不是优先级确定的&#xff0c;主从复制的时候就确定…

BGP之选路MED

原理概述 当一台BGP路由器中存在多条去往同一目标网络的BGP路由时&#xff0c;BGP协议会对这些BGP路由的属性进行比较&#xff0c;以确定去往该目标网络的最优BGP路由。BGP路由属性的比较顺序为Preferred Value属性、Local Preference属性、路由生成方式、AS_Path属性、Origin属…

一款简而轻的项目运维监控软件,支持低侵入式在线构建、自动部署、日常运维(附源码)

前言 在当今快速发展的软件开发领域&#xff0c;开发团队经常面临一系列运维挑战。没有专业运维人员的支持&#xff0c;开发人员不得不承担构建和部署项目的任务。 面对不同项目的构建和部署命令&#xff0c;以及多环境的打包需求&#xff0c;开发人员需要一个能够简化这些流…

算法 day4 【双指针、快慢指针、环形链表】链表下

⚡刷题计划day4继续&#xff0c;可以点个免费的赞哦~ 下一期将会开启哈希表刷题专题&#xff0c;往期可看专栏&#xff0c;关注不迷路&#xff0c; 您的支持是我的最大动力&#x1f339;~ 目录 ⚡刷题计划day4继续&#xff0c;可以点个免费的赞哦~ 下一期将会开启哈希表刷题…

C# 匿名函数与Lambda表达式

本文仅作学习笔记与交流&#xff0c;不作任何商业用途&#xff0c;作者能力有限&#xff0c;如有不足还请斧正 1.匿名函数 在 C# 中&#xff0c;匿名函数是一种没有名称的函数&#xff0c;可以直接在代码中定义和使用 匿名函数主要有两种形式&#xff1a;匿名方法和Lambda 表…

Modbus转EtherCAT网关将Modbus协议的数据格式转换为EtherCAT协议

随着工业自动化技术的快速发展&#xff0c;不同通信协议之间的互操作性变得越来越重要。Modbus作为一种广泛使用的串行通信协议&#xff0c;与以太网为基础的EtherCAT协议之间的转换需求日益增长。本文将从网关功能、硬件设计、性能以及应用案例来介绍这款Modbus转EtherCAT网关…