吾爱破解 - 52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 3012|回复: 9
收起左侧

[其他原创] 【原创源码】Qt框架TCP服务器测试小程序

[复制链接]
cary_云飞扬 发表于 2022-9-19 18:17
本帖最后由 cary_云飞扬 于 2022-9-19 18:24 编辑

       6月份学完C基础,写了一个控制台贪吃蛇。9月份了,学习Qt框架用来写界面程序,主要是目前工作需要一个项目管理系统,所有就着手边学习边练手。      
       tcp协议通信,我就研究了小一周,网上找的教程都是单线程通信单客户端通信,这个没问题,但我需要的是多客户端并发访问服务器,且服务器需要多线程处理数据。光服务器端多线程处理客户端通信就研究好久,今天终于搞通了,分享一下。
1、服务器初始化界面
[img][/img]

2、开启监听服务

[img][/img]
3、多个客户端与服务器建立连接
[img][/img]

4、服务器向特定客户端发信息
[img][/img]

以下是代码部分:
1、子线程头文件
[C++] 纯文本查看 复制代码
#ifndef COMTHREAD_H
#define COMTHREAD_H

#include <QThread>
#include <QTcpSocket>

class ComThread : public QThread
{
    Q_OBJECT
public:
    explicit ComThread(qintptr socketDescriptor,QObject *parent = nullptr);
    void onReadClientInfo();//读取客户端信息函数
    void onReadData();//读取客户端发送过来的数据函数
    void onWriteData(qintptr sock,QString data);//发送数据给客户端函数
    void onClientDisconnect();//客户端断开连接函数
protected:
    void run();//线程处理函数,需重写
private:
    qintptr m_sock;//通信套接字描述符
    QTcpSocket *socket;//通信套接字
signals:
    void readClientInfo();//发射读取客户端信息信号
    void clientInfo(QString info);//将客户端信息发送给调用者(主线程)
    void readData(QString data);//将客户端发过来的信息发送给调用者(主线程)
    void writeData(qintptr sock,QString data);//调用者(主线程)发送数据给客户端
    void clientDisconnect();//客户端断开连接信号,发送给调用者(主线程)

};

#endif // COMTHREAD_H

2、子线程源文件
[C++] 纯文本查看 复制代码
#include "comthread.h"
#include <QDebug>
/**
 * [url=home.php?mod=space&uid=190858]@brief[/url] ComThread::ComThread
 * [url=home.php?mod=space&uid=952169]@Param[/url] socketDescriptor
 * @param parent
 * 程序名称:多线程服务器测试小程序
 * 模块名称:线程处理通信服务
 * 作者:cary-云飞扬
 * 时间:2022-9-19
 */

ComThread::ComThread(qintptr socketDescriptor,QObject *parent)
    : QThread{parent}
{
    socket = new QTcpSocket(this);//创建通信套接字
    this->m_sock = socketDescriptor;//接收服务器检测到的通信描述符
    this->socket->setSocketDescriptor(m_sock);//将接收的描述符设置给当前通信套接字
}
//读取客户端信息函数
void ComThread::onReadClientInfo()
{
    QString ip = socket->peerAddress().toString();
    int port = socket->peerPort();
    ip = QString("[%1]:[%2]已成功连接!").arg(ip).arg(port);
    emit clientInfo(ip);
}
//读取客户端发送过来的数据信息。可以是文件,也可以是文字,本次为文字信息
void ComThread::onReadData()
{
    QByteArray array = socket->readAll();//全部读取,发过来多少读多少
    emit readData(array);//将读取到的信息发送给调用者(主线程)
}
//发送数据给客户端
void ComThread::onWriteData(qintptr sock, QString data)
{
    if(sock == this->m_sock)//判断传入的描述符和当前线程的描述符是否一致,一致才写数据
    {
        socket->write(data.toUtf8());
    }else{
        //当传入的描述符和当前线程描述符不一致,可以发送信号给调用者处理,本次空实现
        qDebug() << "发送消息失败,35行"<< "传入的描述符:"<< sock << "当前线程描述符:" << m_sock;
    }
}
//客户端断开连接函数
void ComThread::onClientDisconnect()
{
    //客户端断开连接后,发送信号给调用者(主线程),具体善后工作由调用者(主线程)处理
    emit clientDisconnect();
}
//线程处理函数
void ComThread::run()
{
    qDebug() <<"子线程号:"<< QThread::currentThread();
    //读取客户端信息,并将该信息发送给调用者(主线程)
    this->onReadClientInfo();
    //读取客户端发来的数据,并将该数据发送给调用者(主线程)
    connect(socket,&QTcpSocket::readyRead,this,&ComThread::onReadData);
    //主线程发送数据给客户端
    connect(this,&ComThread::writeData,this,&ComThread::onWriteData);
    //客户端断开连接,发送信号给调用者(主线程)
    connect(socket,&QTcpSocket::disconnected,this,&ComThread::clientDisconnect);
}


3、自定义监听套接字头文件
[C++] 纯文本查看 复制代码
#ifndef MYTCPSERVER_H
#define MYTCPSERVER_H

#include <QTcpServer>

class MyTcpServer : public QTcpServer
{
    Q_OBJECT
public:
    explicit MyTcpServer(QObject *parent = nullptr);

protected:
    virtual void incomingConnection(qintptr socketDescriptor);//多线程通信时需重写此虚函数
private:

signals:
    void newSocketDescriptor(qintptr);
};

#endif // MYTCPSERVER_H

4、自定义监听套接字源文件
[C++] 纯文本查看 复制代码
#include "mytcpserver.h"
/**
 * @brief MyTcpServer::MyTcpServer
 * @param parent
 * 程序名称:多线程服务器测试小程序
 * 模块名称:自定义监听套接字
 * 作者:cary-云飞扬
 * 时间:2022-9-19
 */

MyTcpServer::MyTcpServer(QObject *parent)
    : QTcpServer{parent}
{

}

//多线程通信时需重写此虚函数
void MyTcpServer::incomingConnection(qintptr socketDescriptor)
{
    //有新客户端连接时,Qt自动调用此虚函数。
    //本次将获得的描述符以信号形式发送给调用者使用
    emit newSocketDescriptor(socketDescriptor);
}

5、主界面头文件
[C++] 纯文本查看 复制代码
#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include <QMainWindow>
#include "mytcpserver.h"
#include "comthread.h"
#include <QMap>

QT_BEGIN_NAMESPACE
namespace Ui { class MainWindow; }
QT_END_NAMESPACE

class MainWindow : public QMainWindow
{
    Q_OBJECT

public:
    MainWindow(QWidget *parent = nullptr);
    ~MainWindow();

private slots:
    void on_startButton_clicked();
    void on_sendButton_clicked();

    void on_closeButton_clicked();

private:
    Ui::MainWindow *ui;
    MyTcpServer *server;//服务器
    int total;//连接的客户端总数
    QMap<qintptr,ComThread*> map;//存放子线程的容器
signals:
    void writeData(qintptr sock,QString data);

};
#endif // MAINWINDOW_H

6、主界面源文件
[C++] 纯文本查看 复制代码
#include "mainwindow.h"
#include "ui_mainwindow.h"
#include <QMessageBox>
#include <QDebug>
#include <QThread>
#include "comthread.h"
#include <QTcpSocket>
/**
 * @brief MainWindow::MainWindow
 * @param parent
 * 程序名称:多线程服务器测试小程序
 * 模块名称:服务器主界面
 * 作者:cary-云飞扬
 * 时间:2022-9-19
 */

MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
    , ui(new Ui::MainWindow)
{
    qDebug() <<"主线程号:"<< QThread::currentThread();
    ui->setupUi(this);
    this->setWindowTitle("TCP通信服务器");
    //设定服务器监听端口
    ui->port->setText("1986");
    //初始化客户端连接总数
    total = 0;
    ui->total->setText(QString("%1").arg(total));
    ui->sendButton->setEnabled(false);
    ui->closeButton->setEnabled(false);
    //创建服务器
    server = new MyTcpServer(this);
    /*当有新的客户端连接时,服务器发送newSocketDescriptor信号,此时创建
     子窗口,并创建独立的线程与之通信*/
    connect(server,&MyTcpServer::newSocketDescriptor,[=](qintptr sock)
    {
        //当由客户端连接时,创建子线程处理客户端对话
        ComThread *thread = new ComThread(sock);
        thread->start();//启动子线程
        total ++;//更新客户端连接数
        ui->total->setText(QString("%1").arg(total));//显示连接客户端总数
        ui->sendButton->setEnabled(true);//设置发送消息按钮可发送状态
        //显示与服务器通话的客户端,默认显示首次连接的客户端
        ui->comboBox->addItem(QString("%1").arg(sock));
        //将套接字描述符和对应的线程存放到容器中,便于后续操作
        map.insert(sock,thread);
        //读取已连接的客户端信息,并更新主界面
        connect(thread,&ComThread::clientInfo,[=](QString info)
        {
            ui->receiveEdit->append(info);
        });
        //读取客户端发来的数据,并显示在主界面
        connect(thread,&ComThread::readData,[=](QString data)
        {
            ui->receiveEdit->append(QString("客户端%1:%2").arg(sock).arg(data));
        });
        //客户端断开连接时更新主界面
        connect(thread,&ComThread::clientDisconnect,[=]()
        {
            total --;//连接总数自减
            ui->total->setText(QString("%1").arg(total));//更新客户端连接数
            if(0 == total)
            {
                ui->sendButton->setEnabled(false);
            }
            //删除已经断开连接的客户端
            ui->comboBox->removeItem(ui->comboBox->findText(QString("%1").arg(map.key(thread))));
            map.remove(map.key(thread));
            thread->quit();
            thread->wait();
            thread->deleteLater();

        });
    });
    //给指定的客户端发送数据
    connect(this,&MainWindow::writeData,[=](qintptr sock,QString data)
    {
        //发送消息给客户端
        emit map.value(sock)->writeData(sock,QString("服务器:%1").arg(data));
        //将发送的消息显示到接收消息界面
        ui->receiveEdit->append(QString("服务器:%1").arg(data));
        //发送消息界面清屏
        ui->sendEdit->clear();
    });
}

MainWindow::~MainWindow()
{
    delete ui;
}
void MainWindow::on_startButton_clicked()
{
    int port = ui->port->text().toInt();
    if(server->listen(QHostAddress::Any,port))
    {
        QMessageBox::about(this,"启动监听","开启监听服务成功!!!");
    }
    ui->startButton->setEnabled(false);
    ui->closeButton->setEnabled(true);

}
//发送数据到指定的客户端
void MainWindow::on_sendButton_clicked()
{
    QString data = ui->sendEdit->toPlainText();
    qintptr sock = ui->comboBox->currentText().toInt();
    emit writeData(sock,data);
}


void MainWindow::on_closeButton_clicked()
{
    server->close();
    QMessageBox::about(this,"关闭服务器","服务器已关闭,所有通信终止!!!");
    ui->closeButton->setEnabled(false);
    ui->startButton->setEnabled(true);
}


7、main函数源文件
[C++] 纯文本查看 复制代码
#include "mainwindow.h"

#include <QApplication>

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    MainWindow w;
    w.show();
    return a.exec();
}

      由于客户端代码比较简单,就不贴了,把服务端exe文件和客户端exe文件打包上传,但两个exe文件一定得放到Qt安装目录的bin目录下才可以运行。

服务端和客户端exe文件.rar

1.08 MB, 下载次数: 52, 下载积分: 吾爱币 -1 CB

服务端和客户端测试文件

免费评分

参与人数 1吾爱币 +1 热心值 +1 收起 理由
xyl52p + 1 + 1 感谢原创!

查看全部评分

发帖前要善用论坛搜索功能,那里可能会有你要找的答案或者已经有人发布过相同内容了,请勿重复发帖。

jjjzw 发表于 2022-9-19 20:20
好熟悉的界面感觉这种项目界面都好像
臭臭云盘 发表于 2022-9-19 20:26
ccwuax 发表于 2022-9-19 20:59
自己搞套接字实在太麻烦了,要考虑的东西也太多,再加上多线程,断点续传,想想都要崩溃了
 楼主| cary_云飞扬 发表于 2022-9-19 21:53
ccwuax 发表于 2022-9-19 20:59
自己搞套接字实在太麻烦了,要考虑的东西也太多,再加上多线程,断点续传,想想都要崩溃了

我小白一个,就当练习了
 楼主| cary_云飞扬 发表于 2022-9-19 22:05
jjjzw 发表于 2022-9-19 20:20
好熟悉的界面感觉这种项目界面都好像

我就是模仿之前前辈写的界面,我也找了好些案例,基本都是单线程,有个别多线程的,可能Qt版本不用,已经运行不起来了。这个我研究了挺长时间,还望大神指点
 楼主| cary_云飞扬 发表于 2022-9-19 22:07
臭臭云盘 发表于 2022-9-19 20:26
后续版本 希望支持 断点续传 和 客户端限速

这个我可能后续会研究,目前我能用界面访问数据库了,现在也能用客户端和服务器通讯了。下一步我打算把这俩连在一起,就是客户端通过服务器请求数据,服务器访问数据库获取数据,再将数据发给请求的 客户端,简单的项目管理系统雏形。
MarioCrane 发表于 2022-9-19 22:37
建议还是使用其他网络库,qt的网络库性能极差,毛病很多
 楼主| cary_云飞扬 发表于 2022-9-20 14:24
MarioCrane 发表于 2022-9-19 22:37
建议还是使用其他网络库,qt的网络库性能极差,毛病很多

谢谢大神提醒。我就是写一个项目管理系统用于部门信息同步,能实现功能就行了。对于我这种小白来说, 研究一个库就挺费劲的了,而且还是间断性性的。
何必等待 发表于 2023-12-17 16:14
感谢楼主分享
您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则

返回列表

RSS订阅|小黑屋|处罚记录|联系我们|吾爱破解 - LCG - LSG ( 京ICP备16042023号 | 京公网安备 11010502030087号 )

GMT+8, 2024-11-24 17:22

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表