首页 > 其他分享 >Fdog系列(六):利用Qt通过服务端进行客户端与客户端通信(资料少,建议收藏)

Fdog系列(六):利用Qt通过服务端进行客户端与客户端通信(资料少,建议收藏)

时间:2023-05-25 15:36:47浏览次数:54  
标签:tcpSocket Fdog Qt ui QString MainWindow 服务端 客户端


Fdog系列(六):利用Qt通过服务端进行客户端与客户端通信(资料少,建议收藏)_C


文章目录

  • 一. 前言
  • 二. 正文
  • 1. 客户端与客户端通信思路
  • 2. 代码实现
  • (1)服务端的实现
  • (2)客户端的实现



一. 前言

Fdog已写文章目录:

Fdog系列(一):思来想去,不如写一个聊天软件,那就从仿QQ注册页面开始吧。

Fdog系列(二):html写完注册页面之后怎么办,用java写后台响应呀。

Fdog系列(三):使用腾讯云短信接口发送短信,数据库写入,部署到服务器,web收尾篇。

Fdog系列(四):使用Qt框架模仿QQ实现登录界面,界面篇。

Fdog系列(五):使用Qt模仿QQ实现登录界面到主界面,功能篇

Fdog系列(六):Qt实现客户端与客户端通过服务端进行互相通信 当前篇

所有文章源码已整体打包上传至github,求星星!


先上图

Fdog系列(六):利用Qt通过服务端进行客户端与客户端通信(资料少,建议收藏)_网络_02


关于QT通信网上能搜到的,基本都是清一色的客户端到服务端的通信,至于客户端与客户端的通信,少之又少,我反正是基本找不到,要有也只是给一张图,博主也是想了很长时间,也算是想出来了,所以当你看到这里,我再次强烈建议你收藏。

关于客户端与服务端之间的通信,这个在倒是有很多资料的,基本内容都一样,如果不了解客户端到服务端之间的通信,可以先去复习一下。


二. 正文

1. 客户端与客户端通信思路

先简单叙述一下单纯的客户端到服务端通信的流程

第一步:服务端:

设置ip
设置端口
开始监听

第二步:客户端:

设置ip
设置端口
发送TCP请求

经过三次握手之后,客户端建立与服务端的通信,这就是单纯的客户端和服务端建立通信过程。

现在我们要做的就是要多个用户可以连接服务端,并且通过服务端进行客户端与客户端的通信。

这里就大大加大了难度,单纯的客户端与服务端通信,无需考虑或者说是识别是那个用户,因为只是作为一个例子出现。

以QQ举例

  1. 一个QQ就是一个客户端,服务端就要考虑识别是哪个用户,要知道一条信息是来自哪个客户端并发送给哪一个客户端的。
  2. 一个QQ又有N个好友,一个客户端收到的消息又要考虑如何到达正确的好友聊天窗口。

这都是本篇接下来将要表达的,但是这篇只考虑双方在线的情况下进行通信,不考虑不在线的情况,关于不在线的情况,客户端如何在上线后继续接受消息,这一功能可以交给数据库来做,本篇暂不在讨论。


在一个简单的客户端与服务端通信例子中,服务端无需识别用户,因为用户唯一,那么多个客户端登录如何识别?
可以使用ip吗,不行,想一想,当两个客户端在同一台电脑登录时,ip将是相同,如何做到唯一值?
使用 账号+IP+端口 确定唯一值,来说一说为什么需要三个组合值才能确定客户端。

A,B作为客户端,S作为服务端

A,B在同一台电脑登录,假设IP值都是10.13.128.122

仅使用IP无法识别,再加一个端口号呢?

这个端口号并不是客户端与服务端通信时设置的端口号,而是服务端为每一个请求连接的客户端分配的闲置端口号。

Fdog系列(六):利用Qt通过服务端进行客户端与客户端通信(资料少,建议收藏)_网络_03


IP+端口号还是不能识别?接着往下看

要让服务器知道一条信息是来自哪个客户端并发送给哪一个客户端的,可以在客户端发送信息之前给消息加上前缀。

A的账号是12345678,B的账号是11111111

A要给B发一句”你好“,这条信息在发送给服务端之前,被加工成为“1111111112345678你好”。

前8位为目的地账号,再往后8位为发送者账号。

服务端分配的随机端口号只有服务器知道,客户端是无法获取,或者只能让服务器传回去。

所以服务端收到的信息是”1111111112345678你好“,服务端通过解析知道了要发生给11111111这个账号,也就是B,并将前8位截掉。

但是问题来了,服务端怎么知道11111111对应哪个客户端,哪个IP,哪个端口号,所以除了IP,端口,还需要一个账号才能确定唯一用户。

客户端第一次连接服务端,将自己的账号发送,表明身份,服务端将账号,IP,生成的端口保存在数据库,服务端知道要发生给11111111这个账号,就去数据库查询该账号对应的端口号,因为IP可能相同,但是端口号不会相同,至此服务端正确的将信息发送给B。

所以B接受到的信息为“12345678你好”。

B与服务端的连接是存在于主界面的,所以是B的主界面收到一条信息“12345678你好”。

但是B有很多好友,每一个好友都是一个聊天窗口,如何将消息显示在正确对应的窗口,就要用到信息的前8位,表示这条信息要显示在标记为12345678的窗口。

至此整个流程完成。

读完之后读者可能有疑问,服务器怎么连接多个客户端,这里是单纯的连接,普通的连接是服务端监听请求,有请求就创建套接字,所以有几个请求,就创建几个套接字就可以实现多个客户端连接。


2. 代码实现

(1)服务端的实现

省略ui变量操作,头文件

#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include"usersql.h"
#include<QMainWindow>
#include<QTcpServer>
#include<QTcpSocket>
#include<QList>
namespace Ui {
class MainWindow;
}

class MainWindow : public QMainWindow
{
    Q_OBJECT
private:
    usersql sqconn;             //连接数据库
    QTcpServer * tcpServer;     //TCP服务器
    QList <QTcpSocket *> tcpSocket;//TCP通信的Socket
    QList <bool> isfrist;       //判断是否第一条消息
    QString getLocalIP();       //获取本机IP地址
public:
    explicit MainWindow(QWidget *parent = 0);
    ~MainWindow();

private slots:
    void onNewConnection();  //有新的请求会调用
    
    //void onSocketStateChange(QAbstractSocket::SocketState socketState); 
    //显示连接状态 暂不需要,可通过stateChanged信号调用
    
    void onClientConnected(int);	//连接成功调用
    void onClientDisconnected(int);	//连接断开调用
    void onSocketReadyRead(int);	//有消息调用

private:
    Ui::MainWindow *ui;
};

#endif // MAINWINDOW_H

再来看cpp文件

//构造函数
MainWindow::MainWindow(QWidget *parent) :
    QMainWindow(parent),
    ui(new Ui::MainWindow)
{
	ui->setupUi(this);
	tcpServer = new QTcpServer(this);
    connect(tcpServer,SIGNAL(newConnection()),this,SLOT(onNewConnection()));//绑定槽函数,当有新的连接请求就会调用该函数
	//开始监听
    QString IP = getLocalIP();//服务器IP
    quint16 port = ui->spinBox->value();//获取界面端口 也就是60
    QHostAddress addr(IP);
    tcpServer->listen(addr,port);
    ui->plainTextEdit->appendPlainText("**开始监听...");
    ui->plainTextEdit->appendPlainText("**服务器地址:"
                   +tcpServer->serverAddress().toString());
    ui->plainTextEdit->appendPlainText("**服务器端口:"
                   +QString::number(tcpServer->serverPort()));
    ui->pushButton->setEnabled(false);
    ui->pushButton_2->setEnabled(true);
    LabListen->setText("监听状态:正在监听");
};


void MainWindow::onNewConnection()
{
    QTcpSocket * tcpSocket = new QTcpSocket();
    tcpSocket = tcpServer->nextPendingConnection();
    this->tcpSocket.append(tcpSocket);
    this->isfrist.append(false);
    onClientConnected(this->tcpSocket.length()-1);

    QSignalMapper * myMapper1 = new QSignalMapper(this);
    connect(tcpSocket, SIGNAL(disconnected()), myMapper1, SLOT(map()));
    myMapper1->setMapping(tcpSocket,this->tcpSocket.length()-1);
    connect(myMapper1, SIGNAL(mapped(int)), this, SLOT(onClientDisconnected(int)));
    
    //connect(tcpSocket,SIGNAL(stateChanged(QAbstractSocket::SocketState)),
    //        this,SLOT(onSocketStateChange(QAbstractSocket::SocketState)));
            
    QSignalMapper * myMapper2 = new QSignalMapper(this);
    connect(tcpSocket, SIGNAL(readyRead()), myMapper2, SLOT(map()));
    myMapper2->setMapping(tcpSocket,this->tcpSocket.length()-1);
    connect(myMapper2, SIGNAL(mapped(int)), this, SLOT(onSocketReadyRead(int)));
}

//void MainWindow::onSocketStateChange(QAbstractSocket::SocketState socketState)
//{
//    switch (socketState) {
//    case QAbstractSocket::UnconnectedState:
//        LabSocketState->setText("scoket状态:UnconnectedState");
//        break;
//    case QAbstractSocket::HostLookupState:
//        LabSocketState->setText("scoket状态:HostLookupState");
//        break;
//    case QAbstractSocket::ConnectingState:
//        LabSocketState->setText("scoket状态:ConnectingState");
//        break;
//    case QAbstractSocket::ConnectedState:
//        LabSocketState->setText("scoket状态:ConnectedState");
//        break;
//    case QAbstractSocket::BoundState:
//        LabSocketState->setText("scoket状态:BoundState");
//        break;
//    case QAbstractSocket::ClosingState:
//        LabSocketState->setText("scoket状态:ClosingState");
//        break;
//    case QAbstractSocket::ListeningState:
//        LabSocketState->setText("scoket状态:ListeningState");
//        break;
//    default:
//        break;
//    }
//}


void MainWindow::onClientConnected(int i)
{
    ui->plainTextEdit->appendPlainText("**clinet socket connected");
    ui->plainTextEdit->appendPlainText("**peer address"+
                                       tcpSocket[i]->peerAddress().toString());
    ui->plainTextEdit->appendPlainText("**peer port:"+
                                       QString::number(tcpSocket[i]->peerPort()));
    ui->listWidget->addItem(tcpSocket[i]->peerAddress().toString()+"  "+QString::number(tcpSocket[i]->peerPort()));
}

void MainWindow::onClientDisconnected(int i)
{
    ui->plainTextEdit->appendPlainText("**client socket disconnected");
    tcpSocket[i]->deleteLater();
    this->tcpSocket.removeAt(i);
    this->isfrist.removeAt(i);
    //对列表做删除操作
    ui->listWidget->takeItem(i);
}

void MainWindow::onSocketReadyRead(int i)
{
    if(this->isfrist[i]==false)
    {
        //获取连接账号
        QString account = tcpSocket[i]->readLine();
        //写入数据库
        qDebug()<<"写入数据库";
        sqconn.update(account,QString::number(tcpSocket[i]->peerPort()));
        this->isfrist[i]=true;
    }
    else
    {
        while(tcpSocket[i]->canReadLine())
        {
            //获取内容
            QString data = tcpSocket[i]->readLine();
            //data.mid(0,8);为到达方 data.mid(8,8);为发送方
            //遍历列表ip是否登录
            //遍历数据库查询该帐户
            QString account = data.mid(0,8);
            qDebug()<<"目标帐户为:"<<account;
            //通过该帐户找到ip地址,然后发送信息
            QString port = sqconn.AccountIP(account); //查询数据库
            for(int i =0;i<this->tcpSocket.length();i++)
            {
                if(QString::number(tcpSocket[i]->peerPort())==port)//说明在线
                {   //将数据转发给到达方,并保留发送方帐户,用于窗口识别
                    QByteArray str = data.mid(8).toUtf8();
                    tcpSocket[i]->write(str);
                    qDebug()<<"目标端口号:"<<tcpSocket[i]->peerPort();
                }
            }
        }
    }
}

QString MainWindow::getLocalIP()
{
    //获取本机IPv4地址
    QString hostName = QHostInfo::localHostName();//本机主机名
    QHostInfo hostInfo = QHostInfo::fromName(hostName);
    QString localIP="";
    QList<QHostAddress> addList = hostInfo.addresses();
    if(!addList.isEmpty())
    {
        for(int i = 0;i<addList.count();i++)
        {
            QHostAddress aHost = addList.at(i);
            if(QAbstractSocket::IPv4Protocol==aHost.protocol())
            {
                localIP = aHost.toString();
                break;
            }
        }
    }
   return localIP;
}

(2)客户端的实现

主界面头文件

class MainWindow : public QMainWindow
{
    Q_OBJECT

private:
    QList<Chat *>  listchat; //自定义的聊天窗口类
    QTcpSocket * tcpClient;//连接网络
};

主界面cpp文件

MainWindow::MainWindow(QString account,QWidget *parent) :
    QMainWindow(parent),
    ui(new Ui::MainWindow)
{
    ui->setupUi(this);
	tcpClient=new QTcpSocket(this);
	//连接到服务器
    QString addr =getLocalIP();
    quint16 port = 60;
    tcpClient->connectToHost(addr,port);

    connect(tcpClient,SIGNAL(connected()),
            this,SLOT(onConnected()));
    connect(tcpClient,SIGNAL(disconnected()),
            this,SLOT(onDisconnected()));
    //connect(tcpClient,SIGNAL(stateChanged(QAbstractSocket::SocketState)),
    //        this,SLOT(onSocketStateChange(QAbstractSocket::SocketState)));
    connect(tcpClient,SIGNAL(readyRead()),
            this,SLOT(onSocketReadyRead()));
}

void MainWindow::onConnected()
{
    //连接成功之后第一次发送自己的帐户
    QByteArray straccount = this->account.toUtf8();
    tcpClient->write(straccount);
}

void MainWindow::onDisconnected() //断开连接
{
    //ui->plainTextEdit->appendPlainText("已断开服务器的连接");

}

void MainWindow::onSocketReadyRead()//收到消息
{
    while (tcpClient->canReadLine()) {
        QString data = tcpClient->readLine();
        for(int i = 0;i<listchat.length();i++)
        {
        	//传给聊天界面,这里要确定传给哪个界面
            if(listchat[i]->getOtheraccount()==data.mid(0,8))
            {
                listchat[i]->setIsread(true); //给聊天界面一个值 其他了解界面根据这个值的真假来判断是否在该窗口显示信息
                emit sendChatData(data);//这个信号的绑定在聊天窗口
            }
        }
    }
}

聊天窗口

//构造函数
Chat::Chat(QString account,QString name,MainWindow * main,QWidget *parent) :
    QWidget(parent),
    ui(new Ui::Chat)
{
    ui->setupUi(this);
    this->otheraccount=account;
    this->name=name;
    tcpClient=new QTcpSocket(this);
    //接收主窗口的消息信号
    mainwindow = main;
    connect(mainwindow,SIGNAL(sendChatData(QString)),this,SLOT(onSocketReadyRead(QString)));
}

void Chat::onSocketReadyRead(QString data)
{
    if(this->isread==true) //开始判断isread值
    {
        ui->plainTextEdit->appendPlainText(data);
        this->isread=false;
    }
}

//聊天窗口发送消息
void Chat::on_pushButton_3_clicked()
{
    QDateTime curDateTime=QDateTime::currentDateTime();
    //数据格式为: 到达方账号,发送方账号,内容
    QString msg = this->getOtheraccount()+this->getAccount()+ui->lineEdit->text();
    //ui->plainTextEdit->appendPlainText(msg); 文本框显示发送消息
    //ui->lineEdit->clear();  清空文本框
    //ui->lineEdit->setFocus();//设置文本框焦点
    QByteArray str = msg.toUtf8();
    //发送信号
    emit sendData(str); //发送信息给主窗口
}

主窗口绑定信号槽

connect(a,SIGNAL(sendData(QString)),this,SLOT(MainSendData(QString)));

void MainWindow::MainSendData(QString str)
{
    QByteArray strdata = str.toUtf8();
    strdata.append('\n');
    tcpClient->write(strdata);
}

最后说一下代码中的信号槽

无论是父子窗口还是两个独立窗口,信号槽都是可以使用的,

A想调用B的函数,就在A里面发送信息,在B里面连接信号槽即可。


所有文章源码已整体打包上传至github,求星星!

重要的东西说两遍,资料少,建议收藏!

最后IU镇楼

Fdog系列(六):利用Qt通过服务端进行客户端与客户端通信(资料少,建议收藏)_数据库_04



标签:tcpSocket,Fdog,Qt,ui,QString,MainWindow,服务端,客户端
From: https://blog.51cto.com/u_14770531/6348226

相关文章

  • 基于Qt的网络音乐播放器(五)实现歌词滚动显示
    文章目录1.思路和效果图2.歌词的解析与存储3.onDurationChanged()4.总结网络播放器系列:qt布局和样式表基于Qt的网络音乐播放器(一)添加音频文件,播放音乐,更新进度条基于Qt的网络音乐播放器(二)切换歌曲,调节音量,调节语速,暂停基于Qt的网络音乐播放器(三)通过酷狗音乐的api接口,返回json格......
  • Qt读取qss文件失败或qss不生效解决方案
            最近在写qt加载样式表的博文,发现qss文件要么打开失败,要么加载成功,但是不生效,经过一番搜索也是算解决了这个问题。读取qss失败:读取文件的方式有两种,一种是绝对路径,一种是相对路径://绝对路径C:\\Users\\fdog\\Desktop\\sheet.qss//相对路径./lib/sheet.qss出现错......
  • Qt父窗口与子窗口数据交互(用拾色器举例)
    文章目录一.效果图二.实现1.在子窗口中声明信号2.在主窗口中声明并实现槽函数,并进行信号与槽的绑定3.在子窗口中发送信号(emit),并完成其控件的相应4.在子窗口的构造函数中传入需要的主窗口数据一.效果图二.实现首先我们创建主窗口和子窗口,并拖动控件,完成基本界面。这里的布局以及......
  • Qt正则表达式类QRegExp(附检验小程序)
           在许多场景中,我们需要验证用户输入的数据是否有效,或者是查找并修改文本,或者是提取指定数据,为此,相对于Qstring的一些函数,QT提供了一个更加强大的类——QRegExp,使用函数配合正则表达式来操作字符串,QRegExp可以进行下面的操作,并附带检验小程序,可在文末下载。一.正则表达......
  • 基于Qt的音乐播放器(三)通过酷狗音乐的api接口,返回json格式歌曲信息(播放地址,歌词,图片)
    2020博客之星年度总评选进行中:请为74号的狗子投上宝贵的一票!我的投票地址:点击为我投票文章目录前言1.获取歌曲搜索列表api接口2.获取单个歌曲详细信息包括歌词3.总结前言首先说明,本教程仅供个人学习,研究使用,禁止用于任何的商业和非法用途。(手动狗头)之所以要研究这个,是因为我想......
  • Fdog系列(三):使用腾讯云短信接口发送短信,数据库写入,部署到服务器,web收尾篇。
    文章目录1.前言2.使用腾讯云短信接口发送短信3.java连接数据库4.部署到服务器(如果你有的话)目录Fdog系列(一):思来想去,不如写一个聊天软件,那就从仿QQ注册页面开始吧。Fdog系列(二):html写完注册页面之后怎么办,用java写后台响应呀。文章中出现的源码获取方式:评论区留下邮箱地址。创作......
  • Fdog系列(二):html写完注册页面之后怎么办,用java写后台响应呀。
    文章目录1.前言2.创建javaweb项目3.创建Server4.解决中文乱码问题5.响应后台数据目录Fdog系列(一):思来想去,不如写一个聊天软件,那就从仿QQ注册页面开始吧。所有文章源码已整体打包上传至github,求星星!1.前言接着第一篇用html完成了注册页面,第二篇,我们来完成后台数据处理,需要用......
  • 【转载】vs设置qt应用程序logo
    1、下载一个ico,拷贝到主函数工程目录下 2、新建一个文件如logo.rc,内容如下:IDI_ICON1ICONDISCARDABLE“logo.ico”3、修改主函数工程文件Main.vcxproj在文件最后添加<ItemGroup><ResourceCompileInclude="logo.rc"/></ItemGroup> 4、生成工程后即可看到ex......
  • MQTT入门DEMO(Java语言)
    目录快速开始准备下载及安装第一次安装EMQX第一次运行EMQX客户端代码快速开始准备MQTT简介EMQX简介下载及安装第一次安装EMQX版本选择EMQX支持多种操作系统,请选择合适您的版本下载。下载地址:https://www.emqx.io/cn/downloads#broker在MicrosoftWindows下安装目前EMQX......
  • 宝塔面板登录 phpMyAdmin 提示服务器和客户端上指示的HTTPS之间不匹配
    宝塔面板登录phpMyAdmin提示服务器和客户端上指示的HTTPS之间不匹配https://www.niuqi360.com/btpanel/mismatch-between-https-indicated-on-server-and-client/......