Qt5.14.2 P2P聊天系统开发实战,跨平台通话零距离


在日益发达的互联网时代,即时通讯已经成为人与人之间沟通的重要渠道之一。无论是办公场合还是生活闲聊,一款优秀的聊天软件都能为我们提供高效、便捷的沟通体验。今天,我们就来一探Qt5构建P2P聊天系统的奥秘,亲手打造一款跨平台的实时通话应用!


一、系统架构


我们将构建一个基于C/S架构的P2P聊天系统。其中,服务端负责管理在线用户并转发数据,客户端则通过服务端建立直连后进行音视频通话。整体架构设计如下:

+---------------+
|    Server     |
|  - User Mgmt  |
|  - Data Relay |
+---------------+|
+---------------+
|    Client     |
|  - P2P Module |
|  - Media I/O  |
|  - UI         |  
+---------------+

服务端和客户端将分别作为独立的Qt项目开发和部署,双方通过TCP套接字进行数据交换,支持文本、语音和视频等多媒体通话。

二、服务端开发


首先从服务端开始,它主要包含用户管理(UserManager)和数据转发(DataRelay)两部分功能。


1、用户管理模块


UserManager用于维护在线用户列表,处理登录、注册及查询请求。它的核心是一个QMap<QString,QTcpSocket*>类型的成员,作为用户名到套接字描述符的映射表:

// usermanager.h
class UserManager : public QObject
{Q_OBJECTpublic:// 获取单例static UserManager* instance();// 注册新用户void registerUser(const QString& name, QTcpSocket* sock);// 查询在线用户列表  QStringList onlineUsers() const;signals:// 用户上线信号void userOnline(const QString& name);// 用户下线信号 void userOffline(const QString& name);private:// 用户映射表QMap<QString, QTcpSocket*> m_users;
};

新用户上线时,服务端会接收其注册请求,然后由registerUser函数处理并更新用户映射表。下线用户时,服务端通过socket的disconnected信号移除对应的映射项。


2、数据转发模块


DataRelay负责在两个通话方之间转发信令和媒体数据,实现消息中继。我们可以在服务端创建一个QTcpServer,监听客户端连接请求。对每个新连接,DataRelay会先行验证身份,建立QTcpSocket并加入对应的Client派生类实例:

// datarelay.h
class DataRelay : public QTcpServer
{Q_OBJECTpublic:DataRelay(QObject* parent = nullptr);protected:// 处理新连接void incomingConnection(qintptr handle) override;private:// 客户端实例表QVector<Client*> m_clients;
};// datarelay.cpp
void DataRelay::incomingConnection(qintptr handle)
{QTcpSocket* socket = new QTcpSocket(this);socket->setSocketDescriptor(handle);Client* client = new Client(this, socket);m_clients.append(client);connect(socket, &QTcpSocket::readyRead, [=]() {client->onReadyRead();});connect(socket, &QTcpSocket::disconnected, [=]() {m_clients.removeOne(client);client->deleteLater();});
}

Client类维护单个客户端的通话会话,完成协议解析、数据转发等功能。它是服务端和客户端的桥梁,承担着核心的消息路由职责:

// client.h 
class Client : public QObject
{Q_OBJECTpublic:Client(QObject* parent, QTcpSocket* socket);// 处理读取的数据void onReadyRead();// 发送数据void send(const QByteArray& data);private:QTcpSocket* m_socket;QString m_name;QString m_peer;
};// client.cpp
void Client::onReadyRead()
{// 解析协议...QByteArray data = m_socket->readAll();// 判断消息类型if (isCallRequest(data)) {// 处理呼叫请求...QString peer = parsePeer(data);if (havePeer(peer)) {// 转发给对端findPeer(peer)->send(data);}} else {// 转发给对端findPeer(m_peer)->send(data);}
}void Client::send(const QByteArray& data)
{m_socket->write(data);
}

可以看到,onReadyRead函数用于读取和解析客户端发来的请求数据包。Client会先判断数据包是呼叫请求还是普通数据,如果是呼叫请求,则查找被叫方是否在线,若在线就转发该请求;如果是普通数据,则直接将其转发给对端。


3、服务端总览


现在,我们就可以完成服务端的总体架构了:

// server.h
class Server : public QObject 
{Q_OBJECTpublic:Server(QObject* parent = nullptr);void start();private:UserManager* m_userManager;DataRelay* m_relay;
};// server.cpp
Server::Server(QObject* parent): QObject(parent), m_userManager(UserManager::instance()),m_relay(new DataRelay(this))
{// 连接信号与槽connect(m_relay, &DataRelay::newConnection, [=](){// 新连接时提示});// 其他初始化...
}void Server::start()
{m_relay->listen(QHostAddress::Any, 6666);
}

在QObject派生类Server中,我们组合了UserManager和DataRelay两个关键部件。当启动监听后,服务端就能响应客户端的连接请求,交换信令和媒体数据,完成信令和数据的转发工作。


三、客户端开发


客户端除了网络模块和UI之外,还需要集成媒体I/O(audio/video)处理能力,从而实现多媒体通话功能。


1、P2P网络模块


P2PClient类主要负责与服务端进行通信,发起呼叫、接听呼叫和传输数据等底层网络操作:

// p2pclient.h
class P2PClient : public QObject
{Q_OBJECTpublic:P2PClient(QObject* parent = nullptr);// 连接服务器void connectToServer(const QString& server, quint16 port);// 登录 void login(const QString& username);// 呼叫void call(const QString& peer);// 发送数据void sendData(const QByteArray& data);signals:// 连接服务器成功void serverConnected();// 登录成功void loginSuccess(const QString& username);// 收到新呼叫void newCall(const QString& caller);// 收到对端数据void dataReceived(const QByteArray& data);private slots:// 读取服务器数据void onServerData();// 连接断开处理   void onDisconnected();private:QTcpSocket* m_socket;QString m_username;QString m_peer;
};

P2PClient通过QTcpSocket与服务端进行数据交互,实现登录、呼叫、发送数据等功能,同时定义了相应的信号,以便将网络事件传递给UI层。

具体的网络操作细节比如协议解析、状态维护等,这里不再赘述。读者可以自行根据需求进行实现。


2、媒体I/O模块


为了支持语音和视频通话,我们需要集成媒体I/O功能,包括音视频采集、编解码、渲染等。Qt多媒体模块为我们提供了现成的跨平台接口,例如QAudioInput/QAudioOutput用于音频I/O,QCamera/QAbstractVideoSurface用于视频I/O。

// audioinput.h
class AudioInput : public QAudioInput
{Q_OBJECTpublic:AudioInput(const QAudioFormat &format, QObject* parent = nullptr);// 开始录音bool startRecording();protected:// 重载收到音频数据处理函数qint64 readData(char* data, qint64 maxSize) override;signals:// 音频数据就绪信号void audioReady(const QByteArray& audioData);private:// 音频缓冲区QByteArray m_buffer;
};

通过继承QAudioInput并重载readData函数,我们就可以从系统音频设备持续获取PCM原始数据流,并通过信号将其传递出去。视频部分的VideoInput同理。

这些音视频数据经过编码后就可以通过P2PClient模块发送给对方,对方收到后再进行解码和渲染,最终呈现在屏幕和扬声器上。


3、UI实现


最后是Qt客户端程序的UI实现,我们可以在这个阶段将上面的网络和媒体模块组装起来,完成最终的交互逻辑。

UI层主要包括一个登录界面和一个通话界面。登录界面比较简单,就是获取用户名并连接服务器:

// loginview.cpp
LoginView::LoginView(P2PClient* client): m_client(client)
{// 初始化UI...// 连接login按钮信号connect(m_loginBtn, &QPushButton::clicked, [=]() {QString username = m_usernameEdit->text();m_client->login(username);});// 连接P2PClient信号connect(m_client, &P2PClient::loginSuccess, this, &LoginView::onLoginSuccess);connect(m_client, &P2PClient::serverConnected, this, &LoginView::onServerConnected);
}void LoginView::onLoginSuccess(const QString& username)
{// 隐藏登录界面,显示通话界面hide();CallView* view = new CallView(m_client, username);view->show();
}

通话界面就相对复杂一些了,它需要集成各种UI控件和交互逻辑,并与网络和媒体模块紧密配合,最终构建起完整的通话流程。比如:


// callview.cpp
CallView::CallView(P2PClient* client, const QString& selfId): m_client(client), m_selfId(selfId)
{// 初始化UI...// 设置本地视频渲染m_localVideoLbl->setVideoSurface(m_videoInput.videoSurface());// 设置音频输入和输出m_audioInput.startRecording();m_audioOutput.start(); // 连接信号与槽connect(m_client, &P2PClient::newCall, this, &CallView::onNewCall);connect(m_client, &P2PClient::dataReceived, this, &CallView::onDataReceived);connect(m_audioInput, &AudioInput::audioReady, this, &CallView::onAudioReady); // ...
}void CallView::onNewCall(const QString& caller)
{// 新呼叫到来,弹出接听选择框QMessageBox::StandardButton res = QMessageBox::question(this, "Incoming Call", QString("%1 is calling you, answer?").arg(caller));if (res == QMessageBox::Yes) {m_client->call(caller); // 接听// 开始通话...} else {// 拒绝}
}void CallView::onDataReceived(const QByteArray& data) 
{// 收到对端数据,判断类型并作出响应if (isAudioData(data)) {m_audioOutput.writeData(data);} else if (isVideoData(data)) {m_remoteVideoLbl->updateFrame(data); }// ...
}void CallView::onAudioReady(const QByteArray& audioData)
{// 获取音频输入数据,编码并发送m_client->sendData(encodeAudio(audioData));
}

可以看到,在CallView中我们综合应用了网络、音视频等各模块的功能,最终构建出了一个完整的通话场景。用户可以发起或接听呼叫,对端数据到来时自动进行音视频渲染,本地设备数据则通过编码后发送给对方。

这只是一个简单的示例,在实战项目中,我们可能还需要处理更多的UI交互细节,比如呼叫转移、音视频设置、会话管理等。但只要理解了基本原理,运用Qt强大的跨平台支持和丰富的技术栈,我们就能打造出媲美主流IM的通讯应用。


四、结语


至此,我们已经学习了如何利用Qt5高效开发一个P2P聊天系统。在实际项目中,我们还需要进一步考虑安全性、可靠性、用户认证、断线重连、数据缓存和重传、数据冗余与纠错、负载均衡等,下一期博文,我将为您深度解析以上功能,让我们共同攀登P2P聊天系统编程的巅峰!


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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【leetcode面试经典150题】4.删除有序数组中的重复项 II(C++)

【leetcode面试经典150题】专栏系列将为准备暑期实习生以及秋招的同学们提高在面试时的经典面试算法题的思路和想法。本专栏将以一题多解和精简算法思路为主&#xff0c;题解使用C语言。&#xff08;若有使用其他语言的同学也可了解题解思路&#xff0c;本质上语法内容一致&…

2024.3.14力扣每日一题——合并后数组中的最大元素

2024.3.14 题目来源我的题解方法一 贪心倒序遍历 题目来源 力扣每日一题&#xff1b;题序&#xff1a;2789 我的题解 方法一 贪心倒序遍历 个人思想&#xff1a;要想满足条件的基础上得到最大值&#xff0c;则需要贪心倒序遍历&#xff0c;从右往左只要满足条件就相加&#…

我与C++的爱恋:类与对象(一)

​ ​ &#x1f525;个人主页&#xff1a;guoguoqiang. &#x1f525;专栏&#xff1a;我与C的爱恋 ​C语言是面向过程的&#xff0c;关注的是过程&#xff0c;分析出求解问题的步骤&#xff0c;通过函数调用逐步解决问题。 C是基于面向对象的&#xff0c;关注的是对象&…

单例模式的多种写法

目录 正文&#xff1a; 1.概念 2.饿汉式 3.懒汉式 3.1线程不安全的懒汉式 3.2线程安全的懒汉式 4.双重检查式 总结&#xff1a; 正文&#xff1a; 1.概念 单例模式&#xff08;Singleton Pattern&#xff09;是软件设计中常见的设计模式之一。它确保一个类只有一个实例…

Tomcat安装部署及JavaEE项目创建

一.Tomcat下载 官网链接 选择自己需要的版本&#xff08;本次采用Tomcat10&#xff09;下载 二.文件结构 解压下载的文件&#xff0c;其主要文件目录如下&#xff1a; 三.启动Tomcat 找到bin目录下的startup.bat文件&#xff0c;双击运行 启动后出现弹窗&#xff0…

【数据结构】初识数据结构与复杂度总结

前言 C语言这块算是总结完了&#xff0c;那从本篇开始就是步入一个新的大章——数据结构&#xff0c;这篇我们先来认识一下数据结构有关知识&#xff0c;以及复杂度的相关知识 个人主页&#xff1a;小张同学zkf 若有问题 评论区见 感兴趣就关注一下吧 目录 1.什么是数据结构 2.…

Java 面向对象(基础)

1、面向对象的概述及两大要素&#xff1a;类与对象 1. 面向对象内容的三条主线&#xff1a; - Java类及类的成员&#xff1a;&#xff08;重点&#xff09;属性、方法、构造器&#xff1b;&#xff08;熟悉&#xff09;代码块、内部类 - 面向对象的特征&#xff1a;封装、继承…

洛谷 1331.海战

这道题其实对于那个船的相邻问题说的相当不清楚&#xff0c;因为既然不是一条船&#xff0c;为什么还相邻呢&#xff1f;让人有点摸不到头脑。 总之可以用dfs来解决。你也可以选择用bfs&#xff0c;这个模型本质上就是flood fill。 至于判断条件&#xff0c;也就是在一个#为中…

JMeter+Grafana+influxdb 配置出现transaction无数据情况解决办法

JMeterGrafanainfluxdb 配置出现transaction无数据情况解决办法 一、问题描述二、解决方法 一、问题描述 如下图所示出现application有数据但是transaction无数据情况 二、解决方法 需要做如下设置 打开变量设置如下图打开两个选项 然后再进行后端监听器的设置 如下图所…

Ansible批量更新远程主机用户密码 (包括Ansible批量做ssh互信)

按照集团运维信息安全制度, 需要每个一段时间对线上服务器密码进行一次变更&#xff0c;通过shell脚本部署比较繁琐&#xff0c;所以决定采用ansible脚本对远程主机root密码进行批量重置&#xff0c;该脚本已经在稳定运行在正式环境下。具体方法如下: 1) 在服务端安装ansible …

数据结构进阶篇 之 【交换排序】(冒泡排序,快速排序递归、非递归实现)

当你觉的自己不行时&#xff0c;你就走到斑马线上&#xff0c;这样你就会成为一个行人 一、交换排序 1.冒泡排序 BubbleSort 1.1 基本思想 1.2 实现原理 1.3 代码实现 1.4 冒泡排序的特性总结 2.快速排序 QuickSort 2.1 基本思想 2.2 递归实现 2.2.1 hoare版 2.2.2 …

软件设计原则:里氏替换原则

定义 里氏替换原则&#xff08;Liskov Substitution Principle, LSP&#xff09;确保继承表现为一种类型扩展而非类型的重定义。具体而言&#xff0c;如果类型 S 是类型 T 的子类型&#xff0c;则类型 T 的对象可以在程序中被类型 S 的对象替换&#xff08;即&#xff0c;类型…

NoSQL之Redis

目录 一、关系型数据库与非关系型数据库 1.关系数据库 2.非关系数据库 2.1非关系型数据库产生背景 3.关系型数据库与非关系型数据区别 &#xff08;1&#xff09;数据存储方式不同 &#xff08;2&#xff09;扩展方式不同 &#xff08;3&#xff09;对事物性的支持不同 …

关于VueCli项目中如何加载调试Worker和SharedWorker

安装Webpack插件 VueCli 项目中默认是没有加载 worker 的配置&#xff0c;需要额外安装 webpack 插件来实现&#xff0c;让我们开始安装 worker-loader 插件 # npm npm install worker-loader # pnpm pnpm install worker-loader # yarn yarn add worker-loader配置Webpack插…

微服务(基础篇-008-es、kibana安装)

目录 05-初识ES-安装es_哔哩哔哩_bilibilihttps://www.bilibili.com/video/BV1LQ4y127n4?p81&vd_source60a35a11f813c6dff0b76089e5e138cc 1.部署单点es 1.1.创建网络 1.2.加载镜像 1.3.运行 2.部署kibana 2.1.部署 2.2.DevTools 3.安装IK分词器 3.1.在线安装ik…

装修避坑指南 | 定制家具你遇到过哪些坑?福州中宅装饰,福州装修

定制家具时可能会遇到以下一些常见问题&#xff1a; 尺寸不准确&#xff1a;由于定制家具需要按需定制&#xff0c;对尺寸的要求很高。如果尺寸不准确&#xff0c;很可能会导致安装困难或者家具不符合空间需求。 材料质量差&#xff1a;有些厂家可能会使用质量较差的材料来降…

[AutoSar]BSW_Memory_Stack_003 NVM与APP的显式和隐式同步

目录 关键词平台说明背景一、implicit synchronization1.1 Write requests 流程 (NvM_WriteBlock)1.2 Read requests 流程 (NvM_ReadBlock)1.3 Restore default requests 流程 (NvM_RestoreBlockDefaults)1.4 Multi block read requests 流程 (NvM_ReadAll)1.5 Multi block wri…

C# BitConverter

BitConverter大端小端转16进制 BitConverter BitConverter 是 C# 中的一个类&#xff0c;它提供了用于字节顺序操作的方法&#xff0c;包括在基本数据类型&#xff08;如 int、float、double 等&#xff09;和它们的字节表示之间转换的方法。这个类在处理二进制数据、网络编程…

【Python系列】 yaml中写入数据

&#x1f49d;&#x1f49d;&#x1f49d;欢迎来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 推荐:kwan 的首页,持续学…

WEB漏洞-文件上传之基础及过滤方式

目录 案例1&#xff1a;百度搜索关键词&#xff0c;找到可能存在漏洞的网页 案例2&#xff1a;不同格式下的文件类型后门测试 案例3&#xff1a;配合解析漏洞下的文件类型后门测试 案例4&#xff1a;本地文件上传漏洞下的文件类型后门测试 案例5&#xff1a;某CVE漏洞利用…