Qt/qt creator实现TCP通信,多线程实现服务器的并发(server/client)(一)服务器端(包含文件传输功能)
创始人
2025-01-20 18:06:43
0

概述特点 

TCP(Transmission Control Protocol)通信的特点主要体现在以下几个方面:

  1. 面向连接的传输协议:TCP在应用程序使用它之前,必须先建立TCP连接。在传送数据完毕后,必须释放已经建立的TCP连接。这种连接是一对一的,即每一条TCP连接只能有两个端点。
  2. 可靠传输:TCP提供可靠交付的服务。通过TCP连接传送的数据,无差错、不丢失、不重复,并且按序到达。它采用序号、确认和重传机制来确保数据的可靠性。如果发现数据包丢失或损坏,TCP会重新传输数据。
  3. 全双工通信:TCP允许通信双方的应用进程在任何时候都能发送数据。TCP连接的两端都设有发送缓存和接收缓存,用来临时存放双向通信的数据。
  4. 面向字节流:TCP中的“流”指的是流入到进程或从进程流出的字节序列。TCP不关心应用层的消息边界,而是将数据视为一连续的字节流进行传输。发送方将数据划分为小的数据块,而接收方会根据需要重组这些数据块。
  5. 流量控制和拥塞控制:TCP使用流量控制机制来防止快速发送方导致慢速接收方无法处理的情况。通过接收方发送的窗口大小,TCP调整发送方的发送速率,以适应网络状况和接收方的处理能力。同时,TCP还通过拥塞控制机制来防止网络拥塞。当网络拥塞时,TCP会降低发送速率以减轻网络负担,从而保持整体网络的稳定性。

总的来说,TCP通信的特点主要体现在其面向连接、可靠传输、全双工通信、面向字节流以及流量控制和拥塞控制等方面。这些特点使得TCP在需要可靠数据传输的场合得到了广泛应用,如互联网和企业网上客户端应用等。

并发

概念:

并发(Concurrency) 是指在一个时间段内宏观上有多个程序在同时运行。这些程序在单处理机系统中实际上是交替地执行,但给人的感觉是同时执行,因而称为并发执行。并发是计算机在一段时间内同时运行或处理多个任务的能力。

但是TCP通信不支持并发通信,但可以通过一些技术或方法来实现TCP并发服务器。这是因为TCP服务器端有两个读阻塞函数,accept和recv,这两个函数需要先后运行,所以运行一个函数时另一个函数无法执行,导致无法同时连接客户端和与其他客户端通信。

有几种方法可以实现TCP并发服务器:

  1. 使用多线程:每个线程可以独立处理一个客户端连接,从而实现并发处理多个连接。
  2. 使用多进程:每个进程可以独立运行,处理不同的客户端连接,实现并发。
  3. 使用多路IO复用:如select、poll、epoll等机制,允许一个进程或线程同时监听多个socket,实现并发处理多个连接。

案例: 

此次我们使用多线程实现服务器的并发:

用QT写一个小demo:

服务器端:

首先加入网络模块:network再.pro中

加入头文件 

 

我们采用多线程实现服务器的并发,每次客户端请求连接后,我们都要分配一个子线程去处理客户端的请求,使用重写QTHread类的run函数,实现子线程的处理。 

首先使用重写incomingConnection函数

当一个新的客户端连接到服务器时,QTcpServer 会自动调用 incomingConnection 函数。这个函数提供了一个标识符(通常是一个文件描述符或套接字描述符),该标识符唯一地标识了新建立的连接。在 incomingConnection 函数中,服务器通常会创建一个新的 QTcpSocket 对象,并使用传入的 socketDescriptor 来初始化它。这个新创建的 QTcpSocket 对象代表了一个与客户端的连接,并允许服务器与客户端进行通信。一旦有了 QTcpSocket 对象,服务器就可以开始处理这个连接了。这通常涉及到读取从客户端发送过来的数据、处理这些数据,以及向客户端发送响应。这里我们将封装一个类,将连接成功获得的可通信的标识符传入子线程,去进行处理具体的逻辑业务

 QTcpSocket *clientSocket = pendingConnections.takeFirst();
ClientHandler *thread = new ClientHandler(clientSocket,dir);

封装好的ClientHandler如下

clienthandler.h
#ifndef CLIENTHANDLER_H #define CLIENTHANDLER_H  #include  #include  #include  #include  #include  #include  #include  #include  #include  #include  #include  #include "jsondata.h" #include  struct FileDate {     int readCnt;     int lastSize;     int type;     int data;     QString fileName; }; class ClientHandler : public QThread {     Q_OBJECT public:     explicit ClientHandler(QTcpSocket *socket,QString dir,QObject *parent = nullptr);     ~ClientHandler();     void ClientDismsg();     qint64 Mins(qint64 a,qint64 b);     void selectFile(QString filePath) ;     void SendFile();     void GetDirFileName(); public:     void run() override; signals:     void threadStarted(QThread *thread);     void threadended(QThread *thread);     void threadsIdadd(QString);     void threadsIddel(QString);  private:     QTcpSocket *m_socket;     QFile file;     QString fileName;     qint64 fileSize;     qint64 sendSize;     JsonData jsondaga;     mutable QMutex mutex;     QString dirs; }; #endif // CLIENTHANDLER_H 

 clienthandler.cpp

#include "clienthandler.h"   ClientHandler::ClientHandler(QTcpSocket *socket,QString dir, QObject *parent)     : QThread(parent), m_socket(socket),dirs(dir) {  }  ClientHandler::~ClientHandler() {     m_socket->deleteLater(); }  void ClientHandler::run() {     // 获取当前线程的ID     Qt::HANDLE threadId = this->currentThreadId();     // 将线程ID转换为quintptr,然后转换为QString     quintptr threadIdValue = reinterpret_cast(threadId);     QString threadIdStr = QString::number(threadIdValue);     //qDebug()<readAll();         // 处理数据...         qDebug()<write(head.toUtf8().data());             if(len<=0)             {                 qDebug()<<"头部信息发送失败 ";                 file.close();             }         }         else if(str == fileName)         {             SendFile();         }else         {             m_socket->write(data);         }     });      // 保持线程运行,直到连接关闭     connect(m_socket, &QTcpSocket::disconnected, this, &ClientHandler::ClientDismsg);     exec(); } void ClientHandler::ClientDismsg() {     QHostAddress clientaddress = m_socket->peerAddress();     quint16 clientPort = m_socket->peerPort();     qDebug() << "client:" <quit();      // 获取当前线程的ID     Qt::HANDLE threadId = this->currentThreadId();     // 将线程ID转换为quintptr,然后转换为QString     quintptr threadIdValue = reinterpret_cast(threadId);     QString threadIdStr = QString::number(threadIdValue);     // qDebug()<write(buf,len);         sendSize += len;         qDebug()< 0);     if(sendSize == fileSize)     {         file.close();         mutex.unlock();     } } void ClientHandler::GetDirFileName() {     // 假设你有一个文件夹路径     //QString folderPath = "D:/ZKJ_SHAREDIR";     // 创建一个 QDir 对象     QDir dir(dirs);     // 获取文件夹中所有文件的名称列表     QStringList fileNames = dir.entryList(QDir::Files);     // 创建一个 QVector 来保存文件名     QVector fileVector;     // 将 QStringList 中的文件名转换到 QVector 中     fileVector.reserve(fileNames.size());     for (const QString &fileName : fileNames) {         fileVector.push_back(fileName);     }     QString str= jsondaga.ToJsonMSG(fileVector);     m_socket->write(str.toUtf8().data()); } 

 封装TcpServer的接口如下代码

tcpserver.h
#ifndef TCPSERVER_H #define TCPSERVER_H  #include  #include  #include "clienthandler.h"  class TcpServer : public QTcpServer {     Q_OBJECT public:     explicit TcpServer(QString dir,QObject *parent = nullptr); protected:     void incomingConnection(qintptr socketDescriptor) override;  private:     QList pendingConnections; // 管理待处理的连接     QList threads;     QList threadsid;     QString dir; };   #endif // TCPSERVER_H 

tcpserver.cpp 

#include "tcpserver.h"  TcpServer::TcpServer(QString dir,QObject *parent) : QTcpServer(parent),dir(dir) {  }     void TcpServer::incomingConnection(qintptr socketDescriptor)  {     QTcpSocket *socket = new QTcpSocket();     socket->setSocketDescriptor(socketDescriptor);     pendingConnections.append(socket);     QHostAddress clientAddress = socket->peerAddress();     quint16 clientPort = socket->peerPort();     qDebug() << "client:" <wait(); // 等待线程退出         delete t; // 删除线程对象     });     connect(thread, &ClientHandler::threadsIdadd, this, [=](QString id) {         threadsid.append(id);         for(int i=0;istart();//开启线程  };  
mainwindow.cpp中
#include "mainwindow.h" #include "ui_mainwindow.h" MainWindow::MainWindow(QWidget *parent)     : QMainWindow(parent)     , ui(new Ui::MainWindow) {     ui->setupUi(this);     this->setFixedSize(830,125);     ui->pushButton->setHidden(true); }  MainWindow::~MainWindow() {     delete ui; }   void MainWindow::on_pushButton_clicked() {      if(!ui->lineEditPort->text().isEmpty())     {         if(Listens==false)         {              if (!server->listen(QHostAddress::Any, ui->lineEditPort->text().toInt()))             {                 qDebug() << "Server could not start";                 return ;             }else             {                 qDebug() << "Server started";                 ui->pushButton->setText("监听中……");                  Listens=true;             }         }else         {             server->close();             ui->pushButton->setText("监听");             qDebug() << "Server ended";             Listens=false;         }     }  }  void MainWindow::on_pushButton_2_clicked() {     QString  dir = QFileDialog::getExistingDirectory(this,"请选择本地数据 ","./");     server = new TcpServer(dir);     ui->pushButton->setHidden(false);     ui->pushButton_2->setHidden(true); } 

【end】

备注:下一篇我将把client的demo以及整个完成的案例项目上传到资源中。下一篇(client)

相关内容

热门资讯

透视能赢!aapoker ai... 透视能赢!aapoker ai插件(透视)透视软件(切实真的是有挂)1、很好的工具软件,可以解锁游戏...
透视黑科技"wepo... 透视黑科技"wepoker破解器"确实真的有挂(透视)辅助教程(有挂解密)1、进入到wepoker破...
透视app!智星德州插件,真是... 透视app!智星德州插件,真是是有挂(透视)透明挂教程(有挂规律)1、全新机制【智星德州插件软件透明...
透视辅助!aapoker ai... 透视辅助!aapoker ai插件(透视)辅助插件工具(总是存在有挂)该软件可以轻松地帮助玩家将aa...
透视中牌率"wepo... 透视中牌率"wepoker有用吗"竟然是有挂(透视)扑克教程(有挂方法)1.wepoker有用吗 a...
透视系统!德扑圈透视挂,最初真... 透视系统!德扑圈透视挂,最初真的有挂(透视)透牌教程(有挂解说)1、实时德扑圈透视挂开挂更新:用户可...
透视了解!aapoker脚本怎... 透视了解!aapoker脚本怎么用(透视)万能辅助器(本来存在有挂)1、每一步都需要思考,不同水平的...
透视透视"xpoke... 透视透视"xpoker辅助"本来是真的有挂(透视)分享教程(有挂插件);1、xpoker辅助机器人多...
透视软件!poker worl... 透视软件!poker world辅助器,起初有挂(透视)必备教程(有挂方法)1、下载好poker w...
透视私人局!aapoker怎么... 透视私人局!aapoker怎么设置提高好牌几率(透视)辅助(果然是有挂)aapoker怎么设置提高好...