Qt实现简易的多线程TCP服务器(附源码)

这篇具有很好参考价值的文章主要介绍了Qt实现简易的多线程TCP服务器(附源码)。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

目录

一.UI界面的设计

二.服务器的启动

三.实现自定义的TcpServer类

1.在widget中声明自定义TcpServer类的成员变量

2.在TcpServer的构造函数中对于我们声明的m_widget进行初始化,m_widget我们用于后续的显示消息等,说白了就是主界面的更新显示等

四.实现自定义的TcpSocket类

1.TcpSocket.h   先忽略掉信号与槽函数,关注构造函数与qintptr类型的 m_sockDesc

五.实现自定义线程类

1.主要关注run函数,其中run函数是继承QThread中的虚函数,需要我们进行重写

2.实现某个客户端断开连接时通过信号与槽让主界面改变

3.实现有新的客户端连接时主界面更新

六.服务器收到多客户端消息进行显示的流程实现

七.服务器发送消息给某个客户端流程

八.服务器发送信息后,要在主页面信息消息更新显示的流程

注意:

效果演示:

源码下载地址:


在初学Qt 中Tcp服务器与客户端的时候,发现每次服务器只能和最后一个连接的客户端进行通信,因为没有用到多线程以及TcpServer中虚函数incomingConnection(),当新的客户端连接的时候,会自动调用incomingConnection函数,在里面产生新的线程来处理通信。

以下来讲讲这个简易的多线程Tcp服务器的实现

一.UI界面的设计

qtcpsocket服务器搭建多线程,Qt,服务器,qt,tcp/ip

其中包括2个Label,一个LineEdit,两个pushbutton,上面是一个TextBrowser用于服务器显示通信记录,下面一个TextEdit用于发送信息。这样一个简单的界面就搭建完成了~~~

二.服务器的启动

首先我们肯定需要实现点击启动服务器按钮来启动服务器

1.在界面中右击启动按钮 ----> 转到槽

qtcpsocket服务器搭建多线程,Qt,服务器,qt,tcp/ip

2.实现逻辑,这里直接放代码,其中serverisworking 是我在widget.h 中声明的一个bool类型,用于判断服务器是否启动,同时更改按钮的文本显示内容,以及弹出对话框提示。

//点击开始启动服务器
void Widget::on_pushButton_StartServer_clicked()
{
    //如果服务器没有启动
     if (!this->serverisworking) {
        if(this->m_tcpserver->listen(QHostAddress::Any,ui->lineEdit_Port->text().toUShort())){
            QMessageBox::information(this,"成功!","启动成功!");
            ui->pushButton_StartServer->setText("关闭服务器");
            this->serverisworking = true;
        }
        else {
            QMessageBox::critical(this,"失败!","服务器启动失败请检查设置!");
        }
    }
    //如果服务器正在启动
     else if(this->serverisworking) {
         m_tcpserver->close();
         if(!m_tcpserver->isListening()){
             ui->pushButton_StartServer->setText("启动服务器");
             this->serverisworking = false;
             QMessageBox::information(this,"提示","关闭成功!");
         }
         else {
             QMessageBox::critical(this,"错误","关闭失败,请重试!");
             return;
         }

     }
}

三.实现自定义的TcpServer类

1.首先我们搞清楚,这个类是负责干嘛的?这个类我们要继承QTcpServer,重写虚函数incomingConnetion

2.这是头文件,如果有报错,注意看使用#pragma once没有,因为可能涉及到头文件重复包含

//tcpserver.h
#pragma once
#ifndef TCPSERVER_H
#define TCPSERVER_H

#include <QObject>
#include<QTcpServer>

#include <widget.h>
#include"serverthread.h"

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

private:

    //重写虚函数
    void incomingConnection(qintptr sockDesc);

private:
    
    //用来保存连接进来的套接字,存到ComboBox中
    QList<qintptr> m_socketList;
    
    //再包含一个widget对象
    Widget *m_widget;

};

#endif // TCPSERVER_H

1.在widget中声明自定义TcpServer类的成员变量

//widget.h
#pragma once
#ifndef WIDGET_H
#define WIDGET_H

#include <QWidget>
#include<QMessageBox>
#include"tcpserver.h"

namespace Ui {
class Widget;
}

class TcpServer;

class Widget : public QWidget
{
    Q_OBJECT

public:
    explicit Widget(QWidget *parent = nullptr);
    ~Widget();

public:
    //当有新连接的时候在页面上显示
    void showNewConnection(qintptr sockDesc);

    //断开时显示
    void showDisconnection(qintptr sockDesc);

    //当服务器发送消息后,通知窗口更新消息
    void UpdateServerMsg(qintptr Desc,const QByteArray &msg);


private slots:

    //按钮被触发
    void on_pushButton_StartServer_clicked();

    void on_pushButton_Trans_clicked();


public slots:

    //当服务器收到客户端发送的消息后,更新消息
    void RecvMsg(QString Desc,const QByteArray &msg);


signals :
   void  sendData(qintptr Desc ,const QByteArray &msg);

private:
    Ui::Widget *ui;

    TcpServer *m_tcpserver;

    bool serverisworking;
};

#endif // WIDGET_H

其他的我们先不去讨论,这里我们就声明一个TcpServer类型的m_tcpserver 以及一个bool类型的serverisworking用来判断服务器是否在工作

2.在TcpServer的构造函数中对于我们声明的m_widget进行初始化,m_widget我们用于后续的显示消息等,说白了就是主界面的更新显示等

TcpServer::TcpServer(QObject *parent) : QTcpServer(parent)
{
     m_widget = dynamic_cast<Widget *>(parent);

}

那么,问题来了,我们要重写TcpServer的incomingConnection函数,里面要涉及到线程,那么我们需要去写一个自定义的线程类

//当有新的连接进来的时候会自动调用这个函数,不需要你去绑定信号槽
void TcpServer::incomingConnection(qintptr sockDesc)
{
    //将标识符保存进list
    m_socketList.append(sockDesc);

    //产生线程用于通信
    ServerThread *thread = new ServerThread(sockDesc);

    //窗口中显示有新的连接
    m_widget->showNewConnection(sockDesc);

    //线程中发出断开tcp连接,触发widget中显示断开
    connect(thread, &ServerThread::disconnectTCP, this,[=]{
         m_widget->showDisconnection(sockDesc);
    });



    //当socket 底层有readyread信号的时候  -> 发送socket_getmsg信号  -> 发送socket_getmsg_thread
    //将socket_getmsg_thread 与 widget中 RecvMsg 绑定,RecvMsg 用于处理将收到的消息进行显示
    connect(thread,&ServerThread::socket_getmsg_thread,this->m_widget,&Widget::RecvMsg);

    //当点击发送的时候-> 产生一个SendData 信号  -> 调用线程中SendDataSlot函数用于发送sendData信号来使socket来发送消息
    connect(this->m_widget,&Widget::sendData,thread,&ServerThread::sendDataSlot);

    //当服务器给客户端发送下消息后,会产生一个writeover信号-> 触发线程发送writeover信号给 Tcpserver -> Tcpserver中widget更新消息
    connect(thread,&ServerThread::writeover,[=](qintptr Desc,const QByteArray &msg){
           m_widget->UpdateServerMsg(Desc,msg);
    });


    thread->start();
}

我们要实现线程类,线程的工作是需要每个线程里面都有一个不同的Tcpsocket类实例,但是我们要在不同的线程里面 工作不同的socket,那么我们可以在一个线程中 去使用套接字标识符(socketDesc)去区分不同的套接字,然后服务器也可以通过不同的套接字标识符去进行与不同的套接字进行通信,所以我们需要先去实现一个自定义的TcpSocket类

四.实现自定义的TcpSocket类

1.TcpSocket.h   先忽略掉信号与槽函数,关注构造函数与qintptr类型的 m_sockDesc

为什么是qintptr类型呢,我们查看官方文档,使用qintpt类型作为参数更方便,qtcpsocket服务器搭建多线程,Qt,服务器,qt,tcp/ip

#pragma once
#ifndef SERVERSOCKET_H
#define SERVERSOCKET_H

#include <QObject>
#include<QTcpSocket>



class ServerSocket : public QTcpSocket
{
    Q_OBJECT
public:
    explicit ServerSocket(qintptr socketDesc,QObject *parent = nullptr);

signals:
    void socket_getmsg(QString Desc, const QByteArray &msg);

     void writeover(qintptr Desc,const QByteArray &msg);

public slots:
    void sendData(qintptr Desc, const QByteArray &data);

private:
        qintptr m_sockDesc;
};

#endif // SERVERSOCKET_H

五.实现自定义线程类

1.主要关注run函数,其中run函数是继承QThread中的虚函数,需要我们进行重写

//serverthread.h
#pragma once
#ifndef SERVERTHREAD_H
#define SERVERTHREAD_H

#include <QObject>

#include <QThread>
#include<serversocket.h>

class ServerThread : public QThread
{
    Q_OBJECT
public:

    //构造函数初始化套接字标识符
    explicit ServerThread(qintptr sockDesc,QObject *parent = nullptr);

    void run() override;

    ~ServerThread();

signals:
    void disconnectTCP(qintptr m_sockDesc);

    void sendData(qintptr Desc, const QByteArray& msg);

    void socket_getmsg_thread(QString Desc,const QByteArray &msg);

    void  writeover(qintptr Desc,const QByteArray &msg);

public  slots:
    void sendDataSlot(qintptr Desc, const QByteArray& msg);



private:
    qintptr m_socketDesc;

    ServerSocket *m_socket;

};



#endif // SERVERTHREAD_H

2.实现某个客户端断开连接时通过信号与槽让主界面改变

1)我们在run函数中,其实就是对某个对应的用来通信套接字运行一个线程,所以我们在run中,先对m_socket进行初始化,将自身的m_socketDesc 作为参数传给m_socket的有参构造。并且使用TcpSocket的setSocketDescriptor方法对m_socket进行绑定标识符,这样我们每个线程内工作的套接字都是不同的

  m_socket = new ServerSocket(this->m_socketDesc);

    //绑定套接字标识符绑定给自定义套接字对象
    if (!m_socket->setSocketDescriptor(this->m_socketDesc)) {
        return ;
    }

2)在线程中,当该线程中的套接字断开时,底层会发射出disconnected信号,我们线程可以此信号与一个用来发射信号的槽函数绑定起来,实现当套接字发送disconnect信号的时候,线程发射出一个disconnectTcp这样一个自定义信号通知服务器套接字断开,server在调用widget成员的方法实现在主界面中显示断开连接

qtcpsocket服务器搭建多线程,Qt,服务器,qt,tcp/ip

 //run()中:
    //当套接字断开时,发送底层的disconnected信号
    connect(m_socket, &ServerSocket::disconnected, this, [=]{

        //此信号可以出发server的槽函数然后再调用widget中combobox清除该socketDesc
        emit disconnectTCP(this->m_socketDesc);

        //让该线程中的套接字断开连接
        m_socket->disconnectFromHost();//断开连接

        //线程退出
        this->quit();
    
//incommingConnection中
  //线程中发出断开tcp连接,触发widget中显示断开
    connect(thread, &ServerThread::disconnectTCP, this,[=]{
         m_widget->showDisconnection(sockDesc);
    });
//widget.cpp中
//用以显示连接断开
void Widget::showDisconnection(qintptr sockDesc)
{
    ui->textBrowser_ServerMess->append(QString::number(sockDesc)+"断开了连接");
    
    //通过信号传递的标识符,将其删除
    int index = ui->comboBox_CilentID->findData(sockDesc);

    ui->comboBox_CilentID->removeItem(index);
}

3.实现有新的客户端连接时主界面更新

当有新的客户端连接的时候,会自动调用server中的incommingConnect函数,直接在此函数中调用widget->showNewconnection函数

//incomingConnection函数中:
    //窗口中显示有新的连接
    m_widget->showNewConnection(sockDesc);
//widget.cpp
void Widget::showNewConnection(qintptr sockDesc)
{
    ui->textBrowser_ServerMess->append("有新的连接!,新接入"+QString::number(sockDesc));
    ui->comboBox_CilentID->addItem(QString("%1").arg(sockDesc), sockDesc);
}

 通过这两个连接就可以直接实现有新的客户端连接时主界面更新。

六.服务器收到多客户端消息进行显示的流程实现

qtcpsocket服务器搭建多线程,Qt,服务器,qt,tcp/ip

//serversocket.cpp
ServerSocket::ServerSocket(qintptr socketDesc,QObject *parent) : QTcpSocket(parent)
{
    this->m_sockDesc = socketDesc;


    connect(this,&ServerSocket::readyRead,this,[=]{

        QString name = QString::number(m_sockDesc);

        QByteArray msg = readAll();

        emit socket_getmsg(name,msg);

    });
}
//serverthread::run()中
    //套接字发出有消息的信号,然后触发线程中发出有消息的信号
    connect(m_socket, &ServerSocket::socket_getmsg, this,[=](QString Desc,const QByteArray &msg){


                emit socket_getmsg_thread(Desc,msg);

    });
//server.cpp
   //当socket 底层有readyread信号的时候  -> 发送socket_getmsg信号  -> 发送socket_getmsg_thread
    //将socket_getmsg_thread 与 widget中 RecvMsg 绑定,RecvMsg 用于处理将收到的消息进行显示
    connect(thread,&ServerThread::socket_getmsg_thread,this->m_widget,&Widget::RecvMsg);
//widget.cpp
//当客户端发送消息,服务器收到后,显示消息
void Widget::RecvMsg(QString Desc,const QByteArray &msg)
{
    ui->textBrowser_ServerMess->append(Desc+":"+msg);
}

实现收到客户端消息进行显示

七.服务器发送消息给某个客户端流程

qtcpsocket服务器搭建多线程,Qt,服务器,qt,tcp/ip

void Widget::on_pushButton_Trans_clicked()
{
    if(serverisworking){

        //如果连接个数大于0,发送发送消息的信号
        if(ui->comboBox_CilentID->count() >0)
        {
            //发射 发送信号
            emit sendData( ui->comboBox_CilentID->currentText().toInt(), ui->textEdit_SendMess->toPlainText().toUtf8());
            qDebug()<<"发送了sendData信号"<<endl;
            ui->textEdit_SendMess->clear();
        }

    }
    else {
        QMessageBox::critical(this,"错误","请检查连接");
        return;
    }

}
//Tcpserver.cpp  incomingConnection中
    //当点击发送的时候-> 产生一个SendData 信号  -> 调用线程中SendDataSlot函数用于发送sendData信号来使socket来发送消息
    connect(this->m_widget,&Widget::sendData,thread,&ServerThread::sendDataSlot);
void ServerThread::sendDataSlot(qintptr Desc, const QByteArray &msg)
{

     emit sendData(Desc, msg);
}
//run()中
   connect(this,&ServerThread::sendData,m_socket,&ServerSocket::sendData);
void ServerSocket::sendData(qintptr Desc, const QByteArray &msg)
{
    if (Desc == m_sockDesc && !msg.isEmpty()) {
        this->write(msg);

        //发送完毕,发出信号,通知主页面更新聊天框
        emit writeover(Desc,msg);
    }
}

八.服务器发送信息后,要在主页面信息消息更新显示的流程

qtcpsocket服务器搭建多线程,Qt,服务器,qt,tcp/ip

void ServerSocket::sendData(qintptr Desc, const QByteArray &msg)
{
    if (Desc == m_sockDesc && !msg.isEmpty()) {
        this->write(msg);

        //发送完毕,发出信号,通知主页面更新聊天框
        emit writeover(Desc,msg);
    }
}
//serverthread.cpp
    //socket 发送 writeorver 通知线程发送writeover 用来提醒server中的widget更新消息
    connect(m_socket,&ServerSocket::writeover,this,[=](qintptr Desc, const QByteArray& msg){
            emit writeover(Desc,msg);
    });
//server.cpp
    //当服务器给客户端发送下消息后,会产生一个writeover信号-> 触发线程发送writeover信号给 Tcpserver -> Tcpserver中widget更新消息
    connect(thread,&ServerThread::writeover,[=](qintptr Desc,const QByteArray &msg){
           m_widget->UpdateServerMsg(Desc,msg);
    });
//widget.cpp
//当服务器发送消息后,通知主窗口更新信号
void Widget::UpdateServerMsg(qintptr Desc, const QByteArray &msg)
{
    ui->textBrowser_ServerMess->append("服务器:"+msg+" to "+QString::number(Desc));
}

注意:

注册自定义信号参数,否则信号槽机制使用时会出现保存

#include "widget.h"
#include <QApplication>

int main(int argc, char *argv[])
{
    qRegisterMetaType<qintptr>("qintptr");
    QApplication a(argc, argv);
    Widget w;
    w.show();

    return a.exec();
}

效果演示:

qtcpsocket服务器搭建多线程,Qt,服务器,qt,tcp/ip

源码下载地址:

yuanzhaoyi/My_project at master (github.com)https://github.com/yuanzhaoyi/My_project/tree/master文章来源地址https://www.toymoban.com/news/detail-854530.html

到了这里,关于Qt实现简易的多线程TCP服务器(附源码)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • QT实现TCP服务器客户端的实现

    ser: widget.cpp: widget.h widget.ui cli: widget.cpp widget.h widget.ui:

    2024年02月08日
    浏览(33)
  • 【Linux后端服务器开发】封装线程池实现TCP多线程通信

    目录 一、线程池模块 Thread.h LockGuard.h ThreadPool.h 二、任务模块模块 Task.h 三、日志模块 Log.h 四、守护进程模块 Deamon.h  五、TCP通信模块 Server.h Client.h server.cpp client.cpp 关于TCP通信协议的封装,此篇博客有详述: 【Linux后端服务器开发】TCP通信设计_命运on-9的博客-CSDN博客 线程池

    2024年02月16日
    浏览(35)
  • QT实现tcp服务器客户端

    2024年02月07日
    浏览(42)
  • 基于Qt的多线程TCP即时通讯软件的设计与实现

    本文将从涉及到主要技术开始,讲解使用Qt来实现一个支持多客户端链接的 多线程TCP服务器 及其 客户端 的设计与实现的解决方案。 注:本文使用的开发环境为Qt5.15.2, 使用MSVC2019_64编译器, C++11及以上 接下来我将会详细讲解客户端和服务端的设计与实现的关键细节。完整的源

    2024年01月16日
    浏览(40)
  • C/S架构学习之多线程实现TCP并发服务器

    并发概念: 并发是指两个或多个事件在 同一时间间隔 发生; 多线程实现TCP并发服务器的实现流程: 一、创建套接字(socket函数): 通信域选择IPV4网络协议、套接字类型选择流式; 二、填充服务器的网络信息结构体: 1.定义网络信息结构体变量; 2.求出结构体变量的内存

    2024年02月06日
    浏览(44)
  • 【网络编程】实现一个简单多线程版本TCP服务器(附源码)

    accept 函数是在服务器端用于接受客户端连接请求的函数,它在监听套接字上等待客户端的连接,并在有新的连接请求到来时创建一个新的套接字用于与该客户端通信。 下面是 accept 函数的详细介绍以及各个参数的意义: sockfd: 是服务器监听套接字的文件描述符,通常是使用

    2024年02月13日
    浏览(41)
  • QT实现TCP通信(服务器与客户端搭建)

    创建一个QTcpServer类对象,该类对象就是一个服务器 调用listen函数将该对象设置为被动监听状态,监听时,可以监听指定的ip地址,也可以监听所有主机地址,可以通过指定端口号,也可以让服务器自动选择 当有客户端发来连接请求时,该服务器会自动发射一个newConnection信号

    2024年02月09日
    浏览(41)
  • 简易TCP服务器搭建

    目录 一、套接字及其分类 二、相关接口函数 三、TCP服务器搭建流程 1、创建套接字socket() 2、保存服务器信息 3、套接字绑定 4、监听客户端连接请求 5、接收客户端连接请求 6、数据收发 7、关闭套接字 四、运行结果         所谓套接字(Socket),就是对网络中不同主机上的

    2023年04月18日
    浏览(47)
  • [Linux] 网络编程 - 初见TCP套接字编程: 实现简单的单进程、多进程、多线程、线程池tcp服务器

    网络的上一篇文章, 我们介绍了网络变成的一些重要的概念, 以及 UDP套接字的编程演示. 还实现了一个简单更简陋的UDP公共聊天室. [Linux] 网络编程 - 初见UDP套接字编程: 网络编程部分相关概念、TCP、UDP协议基本特点、网络字节序、socket接口使用、简单的UDP网络及聊天室实现…

    2024年02月16日
    浏览(52)
  • C# Tcplistener,Tcp服务端,Tcp心跳包服务器简易封装

    我最近有个需求要写Tcp服务端,我发现Tcp服务端的回调函数比较麻烦,简化Tcp的服务,我打算自己封装一个简单的Tcp服务端。 C# TCP应用编程三 异步TCP应用编程 C# Tcpclient Tcplistener 服务器接收多个客户端消息通讯 关于C#Socket断开重连问题 我最近有个Tcp服务端的项目,发现TcpL

    2024年01月19日
    浏览(27)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包