总的来说,TCP通信的特点主要体现在其面向连接、可靠传输、全双工通信、面向字节流以及流量控制和拥塞控制等方面。这些特点使得TCP在需要可靠数据传输的场合得到了广泛应用,如互联网和企业网上客户端应用等。
并发(Concurrency) 是指在一个时间段内宏观上有多个程序在同时运行。这些程序在单处理机系统中实际上是交替地执行,但给人的感觉是同时执行,因而称为并发执行。并发是计算机在一段时间内同时运行或处理多个任务的能力。
但是TCP通信不支持并发通信,但可以通过一些技术或方法来实现TCP并发服务器。这是因为TCP服务器端有两个读阻塞函数,accept和recv,这两个函数需要先后运行,所以运行一个函数时另一个函数无法执行,导致无法同时连接客户端和与其他客户端通信。
有几种方法可以实现TCP并发服务器:
此次我们使用多线程实现服务器的并发:
用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)