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模型
五:涉及数据读写、转发等操作所以需要使用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)请参见本专栏其他文章。
源代码下载地址:源代码
相关文章:

C++ 代码实现局域网即时通信功能 (windows 系统 客户端)
本项目使用C实现具备多个客户端和服务器端即时通信聊天功能软件 一:项目内容 使用C实现一个具备多客户端和一个服务器端即时通信功能的聊天软件。 本项目的目的是 学习在windows平台下,进行C网络开发的基本概念:TCP/IP socket通信࿰…...
机器人阻抗控制实现方法及其存在的科学问题
一、机器人阻抗控制的实现方法 机器人阻抗控制主要分为两种方法:基于位置的阻抗控制和基于力的阻抗控制。 基于位置的阻抗控制: 工作原理:让机器人电机在位置模式下工作,通过发送目标位置和速度实现阻抗特性。主要目的:控制机器人的位置精度和运动轨迹。特点:该方法侧重…...
解决:xxx.xxx/res/modules/.ds_store: error: the file name must end with .xml 问题
解决:xxx.xxx/res/modules/.ds_store: error: the file name must end with .xml 问题 该问题是由于Android Studio校验到布局文件中存在不以.xml后缀名结尾的文件,这个文件就是.DS_store,它是Mac上系统自动创造的隐藏文件,把该文…...

EEtrade:区块链技术的五大应用场景
区块链技术,作为近年来备受瞩目的颠覆性技术,其去中心化、透明化、安全性和可追溯性等特性,为各行各业带来了前所未有的机遇。从数字货币到金融资产交易结算,从数字政务到存证防伪,再到数据服务,区块链正逐…...
DAO、DPO、DTO、POJO、VO、BO、EBO
目录 1. DAO (Data Access Object) 2. DPO (Data Persistence Object) 3. DTO (Data Transfer Object) 4. POJO (Plain Old Java Object) 5. VO (Value Object) 6. BO (Business Object) 7. EBO (Entity Bean Object) 在Java开发中,尤其是与数据访问和对象映…...

数据库期末复习
数据库期末复习 分析题 1 (1)使用数据库系统可以大大提高应用开发的效率,方便用户的使用减轻数据库系统管理人员维护的负担,请回答数据库系统有哪些部分组成?什么是数据库管理系统,其主要功能包括哪些方而&…...

pyinstaller带浏览器一起打包playwright 独立运行exe
前置条件 没有安装自带环境,则 playwright install 安装了自带的浏览器 查看playwright的浏览器的位置 playwright install --dry-run 打开此文件夹可以看到 新建一个多层级目录playwright\driver\package.local-browsers 然后复制chromium-1124到playwright\dr…...

docker添加容器服务所需字体
1、在宿主机新建chinese目录 [rootHS-AP-application ~]#mkdir /usr/share/fonts/chinese 2、上传字体 把windows c盘下的Windows/Fonts下的所有字段上传至/usr/shared/fonts/chinese 3、授权chinese目录 chmod -R 755 /usr/share/fonts/chinese 4、生成fonts.scale文件 …...

Java面试八股之Spring AOP 和 AspectJ AOP 的区别
Spring AOP 和 AspectJ AOP 的区别 Spring AOP 和 AspectJ AOP 是两种不同的面向切面编程(Aspect-Oriented Programming, AOP)实现。它们各有特点,适用于不同的场景。下面是一些主要的区别: 1. 实现机制 Spring AOP: 基于代理…...

Java人力资源招聘社会校招类型招聘系统PC端
🔍【揭秘】人力资源新利器!社会校招一站式PC端招聘系统全攻略🚀 🌈 开篇引言:招聘新纪元,效率为王! Hey小伙伴们,你是否还在为繁琐的招聘流程头疼不已?🤯 面…...

C# 知识点总结
入门 C#程序在.NET上运行,.NET framework包含两个部分: ①:.NET framework类库 ②:公共语言运行库CLR(.NET虚拟机) CLS(公共语言规范) CTS(通用类型系统) .N…...
【ffmpeg命令入门】视频的旋转与翻转
文章目录 前言什么时候需要使用旋转与翻转1. 视频拍摄方向不正确2. 视频编辑特效使用什么参数1. 旋转视频 - transpose2. 水平翻转视频 - hflip3. 垂直翻转视频 - vflip 总结 前言 在视频编辑的过程中,我们经常会遇到需要旋转或翻转视频的情况。无论是因为拍摄时相…...
学懂C语言(二十五):深入理解 C语言结构体 位域 的概念
目录 一、位域的基本概念 二、位域的定义 三、位域的内存分配和大小计算 示例1:简单位域 示例2:跨越多个存储单元 注意事项 结构体对齐控制 总结 C语言中的位域(Bit-Field)是一种特殊的数据结构,允许在结构体中…...

LLM推理优化——KV Cache篇(百倍提速)
LLM推理优化——KV Cache篇(百倍提速) 注意:KV Cache本质上是空间换时间的技术。与计算机组成原理中的cache不同,它不涉及访存优化。 不知道大家在用LLM的时候,有没有注意到一个问题:我们在输入我们的问题…...

Linux进程--system
...

[Office] Word 特殊字符
0 打开“特殊字符集” 依次选择:Insert -> Symbol -> More Symbol 1 带圈编号 字体Font选择Wingdings...

联想电脑怎么重装系统_联想电脑U盘重装win10详细图文教程
联想电脑怎么重装系统?在当今科技发展迅猛的时代,联想电脑已经成为了人们生活中不可或缺的一部分。然而,随着时间的推移,我们可能会遇到一些问题,例如系统崩溃或者需要更换操作系统。这时,使用U盘来重新安装…...

前端开发者必备:揭秘谷歌F12调试的隐藏技巧!
前言 使用断点(breakpoint)是调试 JavaScript 代码的一种非常有效的方式。通过在代码的关键位置设置断点,可以阻止页面的状态变化,从而方便地检查和修改页面的当前状态。 1. 使用 setTimeout 配合 debugger 和 console.log setTi…...
vivado IP_REPO_PATHS
此属性允许您创建自定义IP目录,以与Vivado Design Suite一起使用。 IP_REPO_PATHS属性定义了一个或多个目录的路径,这些目录包含 第三方或用户定义的IP。指定的目录和任何子目录是 搜索要添加到Vivado Design Suite IP目录以用于设计的IP定义 进入或与IP…...

前端代码混淆加密(使用Terser、WebpackObfuscator)
零、相关技术及版本号 "vue": "2.6.12", "vue/cli-service": "4.4.6", "javascript-obfuscator": "^4.1.1", "terser-webpack-plugin": "^4.2.3", "vue-template-compiler": &quo…...

(LeetCode 每日一题) 3442. 奇偶频次间的最大差值 I (哈希、字符串)
题目:3442. 奇偶频次间的最大差值 I 思路 :哈希,时间复杂度0(n)。 用哈希表来记录每个字符串中字符的分布情况,哈希表这里用数组即可实现。 C版本: class Solution { public:int maxDifference(string s) {int a[26]…...
[2025CVPR]DeepVideo-R1:基于难度感知回归GRPO的视频强化微调框架详解
突破视频大语言模型推理瓶颈,在多个视频基准上实现SOTA性能 一、核心问题与创新亮点 1.1 GRPO在视频任务中的两大挑战 安全措施依赖问题 GRPO使用min和clip函数限制策略更新幅度,导致: 梯度抑制:当新旧策略差异过大时梯度消失收敛困难:策略无法充分优化# 传统GRPO的梯…...

微软PowerBI考试 PL300-选择 Power BI 模型框架【附练习数据】
微软PowerBI考试 PL300-选择 Power BI 模型框架 20 多年来,Microsoft 持续对企业商业智能 (BI) 进行大量投资。 Azure Analysis Services (AAS) 和 SQL Server Analysis Services (SSAS) 基于无数企业使用的成熟的 BI 数据建模技术。 同样的技术也是 Power BI 数据…...

练习(含atoi的模拟实现,自定义类型等练习)
一、结构体大小的计算及位段 (结构体大小计算及位段 详解请看:自定义类型:结构体进阶-CSDN博客) 1.在32位系统环境,编译选项为4字节对齐,那么sizeof(A)和sizeof(B)是多少? #pragma pack(4)st…...

聊聊 Pulsar:Producer 源码解析
一、前言 Apache Pulsar 是一个企业级的开源分布式消息传递平台,以其高性能、可扩展性和存储计算分离架构在消息队列和流处理领域独树一帜。在 Pulsar 的核心架构中,Producer(生产者) 是连接客户端应用与消息队列的第一步。生产者…...
django filter 统计数量 按属性去重
在Django中,如果你想要根据某个属性对查询集进行去重并统计数量,你可以使用values()方法配合annotate()方法来实现。这里有两种常见的方法来完成这个需求: 方法1:使用annotate()和Count 假设你有一个模型Item,并且你想…...

《用户共鸣指数(E)驱动品牌大模型种草:如何抢占大模型搜索结果情感高地》
在注意力分散、内容高度同质化的时代,情感连接已成为品牌破圈的关键通道。我们在服务大量品牌客户的过程中发现,消费者对内容的“有感”程度,正日益成为影响品牌传播效率与转化率的核心变量。在生成式AI驱动的内容生成与推荐环境中࿰…...
Rust 异步编程
Rust 异步编程 引言 Rust 是一种系统编程语言,以其高性能、安全性以及零成本抽象而著称。在多核处理器成为主流的今天,异步编程成为了一种提高应用性能、优化资源利用的有效手段。本文将深入探讨 Rust 异步编程的核心概念、常用库以及最佳实践。 异步编程基础 什么是异步…...
LLM基础1_语言模型如何处理文本
基于GitHub项目:https://github.com/datawhalechina/llms-from-scratch-cn 工具介绍 tiktoken:OpenAI开发的专业"分词器" torch:Facebook开发的强力计算引擎,相当于超级计算器 理解词嵌入:给词语画"…...

智能分布式爬虫的数据处理流水线优化:基于深度强化学习的数据质量控制
在数字化浪潮席卷全球的今天,数据已成为企业和研究机构的核心资产。智能分布式爬虫作为高效的数据采集工具,在大规模数据获取中发挥着关键作用。然而,传统的数据处理流水线在面对复杂多变的网络环境和海量异构数据时,常出现数据质…...