• QT学习笔记(12) QT下的TCP通信


    一、TCP通信过程

    (1)服务器端:

      服务器端有QTcpServer的监听套接字,运用listen()方法监听网卡的ip和端口。

      如果有新的连接传过来,并且连接成功,服务器会触发newConnection(),通过槽函数取出连接过来的通信套接字QTcpSocket

      如果有数据成功传送过来,对方的通信套接字QTcpSocket会触发readyRead(),通过槽函数可以对接收的数据进行处理

    (2)客户端

      首先根据ip和端口,通过通信套接字QTcpSocket的connectToHost()方法主动和服务器建立连接

      如果连接成功,通信套接字QTcpSocket会自动触发connected(),通过槽函数可以进行操作

      如果有数据成功传送过来,对方的通信套接字QTcpSocket会触发readyRead(),通过槽函数可以对接收的数据进行处理

        

    二、实例代码如下:

    QT_HelloWorld11.pro

     1 #-------------------------------------------------
     2 #
     3 # Project created by QtCreator 2017-08-30T21:18:55
     4 #
     5 #-------------------------------------------------
     6 
     7 QT       += core gui network #添加network模块
     8 
     9 greaterThan(QT_MAJOR_VERSION, 4): QT += widgets
    10 
    11 TARGET = QT_HelloWorld11
    12 TEMPLATE = app
    13 
    14 
    15 SOURCES += main.cpp
    16         serverwidget.cpp 
    17     clientwidget.cpp
    18 
    19 HEADERS  += serverwidget.h 
    20     clientwidget.h
    21 
    22 FORMS    += serverwidget.ui 
    23     clientwidget.ui
    24 
    25 CINFIG += C++11

    main.cpp

     1 #include "serverwidget.h"
     2 #include <QApplication>
     3 //包含头文件
     4 #include "clientwidget.h"
     5 
     6 
     7 int main(int argc, char *argv[])
     8 {
     9     QApplication a(argc, argv);
    10     //显示服务器窗口
    11     ServerWidget w;
    12     w.show();
    13 
    14     //显示客户端窗口
    15     ClientWidget w2;
    16     w2.show();
    17 
    18     return a.exec();
    19 }

    serverwidget.h

     1 #ifndef SERVERWIDGET_H
     2 #define SERVERWIDGET_H
     3 
     4 #include <QWidget>
     5 #include <QTcpServer>//监听套接字
     6 #include <QTcpSocket>//通信套接字(建立好连接的套接字)
     7 
     8 namespace Ui {
     9 class ServerWidget;
    10 }
    11 
    12 class ServerWidget : public QWidget
    13 {
    14     Q_OBJECT
    15 
    16 public:
    17     explicit ServerWidget(QWidget *parent = 0);
    18     ~ServerWidget();
    19 
    20 private slots:
    21     void on_button_send_clicked();
    22 
    23     void on_button_close_clicked();
    24 
    25 private:
    26     Ui::ServerWidget *ui;
    27 
    28     QTcpServer * tcpServer;//监听套接字
    29     QTcpSocket * tcpSocket;//通信套接字
    30 
    31 };
    32 
    33 #endif // SERVERWIDGET_H

    clientwidget.h

     1 #ifndef CLIENTWIDGET_H
     2 #define CLIENTWIDGET_H
     3 
     4 #include <QWidget>
     5 #include <QTcpSocket>//通信套接字
     6 
     7 namespace Ui {
     8 class ClientWidget;
     9 }
    10 
    11 class ClientWidget : public QWidget
    12 {
    13     Q_OBJECT
    14 
    15 public:
    16     explicit ClientWidget(QWidget *parent = 0);
    17     ~ClientWidget();
    18 
    19 private slots:
    20     void on_pushButton_connect_clicked();
    21 
    22     void on_pushButton_send_clicked();
    23 
    24     void on_pushButton_close_clicked();
    25 
    26 private:
    27     Ui::ClientWidget *ui;
    28 
    29     QTcpSocket *tcpSocket;
    30 };
    31 
    32 #endif // CLIENTWIDGET_H

    serverwidget.cpp

     1 #include "serverwidget.h"
     2 #include "ui_serverwidget.h"
     3 #include <QTcpServer>//监听套接字
     4 #include <QTcpSocket>//通信套接字(建立好连接的套接字)
     5 
     6 ServerWidget::ServerWidget(QWidget *parent) :
     7     QWidget(parent),
     8     ui(new Ui::ServerWidget)
     9 {
    10     ui->setupUi(this);
    11 
    12     setWindowTitle(QString::fromLocal8Bit("服务器:8888"));
    13 
    14     tcpServer = NULL;//先赋值为空,在后面做空值判断,防止空指针的错误
    15     tcpSocket = NULL;
    16 
    17     //实例化 监听套接字
    18     tcpServer = new QTcpServer(this);//指定父对象,让其自动回收空间
    19     //监听
    20     tcpServer->listen(QHostAddress::Any,8888);//默认绑定当前网卡上的所有ip
    21 
    22     //如果有新的连接传过来,并连接成功,服务器触发newConnection()方法
    23     connect(tcpServer,&QTcpServer::newConnection,
    24             [=]()
    25             {
    26                 //取出建立好连接的套接字
    27                 tcpSocket = tcpServer->nextPendingConnection();//取出当前最近的一次连接的套接字
    28                 //获取对方(客户端)的IP和端口
    29                 QString ip = tcpSocket->peerAddress().toString();
    30                 qint16 port = tcpSocket->peerPort();
    31                 QString temp = QString("[%1:%2]:成功连接").arg(ip).arg(port);//组包
    32                 //在服务器端显示
    33                 ui->textEdit_read->setText(temp);
    34 
    35                 //此处不能直接放在构造函数中,因为如果直接放在那,还没分配tcpSocket空间,会程序异常
    36                 //如果有数据传送成功,对方的通信套接字会触发readyRead(),需要在对应的槽函数做接收处理
    37                 connect(tcpSocket,&QTcpSocket::readyRead,
    38                         [=]()
    39                         {
    40                             //从通信套接字中取出内容
    41                             QByteArray array = tcpSocket->readAll();
    42                             //然后将内容追加到显示文本中
    43                             ui->textEdit_read->append(array);
    44                         }
    45                         );
    46 
    47                 //如果对方主动断开连接,通信套接字会自动触发disconnected()
    48                 connect(tcpSocket,&QTcpSocket::disconnected,
    49                         [=]()
    50                         {
    51                             ui->textEdit_read->append(QString::fromLocal8Bit("对方主动断开连接"));
    52                         }
    53                         );
    54             }
    55             );
    56 
    57 }
    58 
    59 ServerWidget::~ServerWidget()
    60 {
    61     delete ui;
    62 }
    63 
    64 void ServerWidget::on_button_send_clicked()
    65 {
    66     if(tcpSocket == NULL)
    67     {
    68         return;
    69     }
    70 
    71     //获取编辑区内容
    72     QString str = ui->textEdit_write->toPlainText();
    73     //给对方发送数据,使用套接字是tcpSocket
    74     tcpSocket->write(str.toUtf8().data());
    75 }
    76 
    77 void ServerWidget::on_button_close_clicked()
    78 {
    79     if(tcpSocket == NULL)
    80     {
    81         return;
    82     }
    83     //主动和客户端断开连接
    84     tcpSocket->disconnectFromHost();
    85     tcpSocket->close();
    86 
    87     tcpSocket = NULL;
    88 }

    clientwidget.cpp

     1 #include "clientwidget.h"
     2 #include "ui_clientwidget.h"
     3 //需要包含头文件
     4 #include <QHostAddress>
     5 
     6 ClientWidget::ClientWidget(QWidget *parent) :
     7     QWidget(parent),
     8     ui(new Ui::ClientWidget)
     9 {
    10     ui->setupUi(this);
    11     setWindowTitle(QString::fromLocal8Bit("客户端"));
    12 
    13     tcpSocket = NULL;
    14 
    15     //分配空间,指定父对象
    16     tcpSocket = new QTcpSocket(this);
    17 
    18     //当tcpSocket套接字建立连接成功
    19     //如果成功和对方建立连接,通信套接字会自动触发connected()
    20     connect(tcpSocket,&QTcpSocket::connected,
    21             [=]()
    22             {
    23                 ui->textEdit_read->setText(QString::fromLocal8Bit("成功和服务器建立连接"));
    24             }
    25             );
    26 
    27     //如果有数据传送成功,对方的通信套接字会触发readyRead(),需要在对应的槽函数做接收处理
    28     connect(tcpSocket,&QTcpSocket::readyRead,
    29             [=]()
    30             {
    31                 //从通信套接字中取出内容
    32                 QByteArray array = tcpSocket->readAll();
    33                 //然后将内容追加到显示文本中
    34                 ui->textEdit_read->append(array);
    35             }
    36             );
    37 
    38     //如果对方主动断开连接,通信套接字会自动触发disconnected()
    39     connect(tcpSocket,&QTcpSocket::disconnected,
    40             [=]()
    41             {
    42                 ui->textEdit_read->append(QString::fromLocal8Bit("对方主动断开连接"));
    43             }
    44             );
    45 
    46 }
    47 
    48 ClientWidget::~ClientWidget()
    49 {
    50     delete ui;
    51 }
    52 
    53 void ClientWidget::on_pushButton_connect_clicked()
    54 {
    55     //获取服务器ip和端口
    56     QString ip = ui->lineEdit_IP->text();
    57     qint16 port = ui->lineEdit_port->text().toInt();
    58 
    59     //主动和服务器建立连接
    60     tcpSocket->connectToHost(QHostAddress(ip),port);
    61 }
    62 
    63 void ClientWidget::on_pushButton_send_clicked()
    64 {
    65     //获取编辑框内容
    66     QString str = ui->textEdit_Write->toPlainText();
    67     //发送数据
    68     tcpSocket->write( str.toUtf8().data() );
    69 }
    70 
    71 void ClientWidget::on_pushButton_close_clicked()
    72 {
    73     //主动和服务器断开连接
    74     tcpSocket->disconnectFromHost();
    75     tcpSocket->close();
    76 }

    serverwidget.ui

    clientwidget.ui

    三、TCP传递文件

      实现以下功能:

        客户端连接到服务器

        服务器端选择文件,然后发送

        客户端接收文件,并提示接收成功

      大概流程图如下:

        

    代码如下:

    QT_HelloWorld14.pro

     1 #-------------------------------------------------
     2 #
     3 # Project created by QtCreator 2017-08-31T19:08:18
     4 #
     5 #-------------------------------------------------
     6 
     7 QT       += core gui network
     8 
     9 greaterThan(QT_MAJOR_VERSION, 4): QT += widgets
    10 
    11 TARGET = QT_HelloWorld14
    12 TEMPLATE = app
    13 
    14 
    15 SOURCES += main.cpp
    16         serverwidget.cpp 
    17     clientwidget.cpp
    18 
    19 HEADERS  += serverwidget.h 
    20     clientwidget.h
    21 
    22 FORMS    += serverwidget.ui 
    23     clientwidget.ui
    24 
    25 CONFIG += C++11

    main.cpp

     1 #include "serverwidget.h"
     2 #include <QApplication>
     3 #include "clientwidget.h"
     4 
     5 int main(int argc, char *argv[])
     6 {
     7     QApplication a(argc, argv);
     8     ServerWidget w;
     9     w.show();
    10     //显示客户端窗口
    11     ClientWidget w2;
    12     w2.show();
    13 
    14     return a.exec();
    15 }

    clientwidget.h

     1 #ifndef CLIENTWIDGET_H
     2 #define CLIENTWIDGET_H
     3 
     4 #include <QWidget>
     5 #include <QTcpSocket>
     6 #include <QFile>
     7 
     8 namespace Ui {
     9 class ClientWidget;
    10 }
    11 
    12 class ClientWidget : public QWidget
    13 {
    14     Q_OBJECT
    15 
    16 public:
    17     explicit ClientWidget(QWidget *parent = 0);
    18     ~ClientWidget();
    19 
    20 private slots:
    21     void on_pushButton_connect_clicked();
    22 
    23 private:
    24     Ui::ClientWidget *ui;
    25     QTcpSocket *tcpSocket;//通信套接字
    26 
    27     QFile file;//文件对象
    28     QString fileName;//文件名字
    29     qint64 fileSize;//文件大小
    30 
    31     qint64 receiveSize;//已经接收文件的大小
    32     bool isStart ;//标志位,判断是不是开始的部分(文件名和文件大小)
    33 };
    34 
    35 #endif // CLIENTWIDGET_H

    serverwidget.h

     1 #ifndef SERVERWIDGET_H
     2 #define SERVERWIDGET_H
     3 
     4 #include <QWidget>
     5 #include <QTcpServer>//监听套接字
     6 #include <QTcpSocket>//通信套接字
     7 #include <QFile>
     8 #include <QTimer>
     9 
    10 
    11 namespace Ui {
    12 class ServerWidget;
    13 }
    14 
    15 class ServerWidget : public QWidget
    16 {
    17     Q_OBJECT
    18 
    19 public:
    20     explicit ServerWidget(QWidget *parent = 0);
    21     ~ServerWidget();
    22 
    23 private slots:
    24     void on_pushButton_file_clicked();
    25 
    26     void on_pushButton_send_clicked();
    27 
    28     void sendDate();//发送文件数据
    29 
    30 private:
    31     Ui::ServerWidget *ui;
    32     QTcpServer *tcpServer;//监听套接字
    33     QTcpSocket *tcpSocket;//通信套接字
    34 
    35     QFile file;//文件对象
    36     QString fileName;//文件名字
    37     qint64 fileSize;//文件大小
    38 
    39     qint64 sendSize;//已经发送文件的大小
    40     QTimer timer; //定时器,用来间隔头文件和文件正文
    41 };
    42 
    43 #endif // SERVERWIDGET_H

    clientwidget.cpp

      1 #include "clientwidget.h"
      2 #include "ui_clientwidget.h"
      3 #include <QTcpSocket>
      4 #include <QByteArray>
      5 #include <QDebug>
      6 #include <QFile>
      7 #include <QMessageBox>
      8 #include <QHostAddress>
      9 #include <QIODevice>//需要包含此头文件,防止出现"device not open"的错误
     10 
     11 ClientWidget::ClientWidget(QWidget *parent) :
     12     QWidget(parent),
     13     ui(new Ui::ClientWidget)
     14 {
     15     ui->setupUi(this);
     16 
     17     setWindowTitle(QString::fromLocal8Bit("客户端"));
     18     tcpSocket = new QTcpSocket(this);
     19     isStart = true;//刚开始时,为true
     20     ui->progressBar->setValue(0);//初始化,进度条为0
     21 
     22     //数据传送过来成功,自动触发QTcpSocket::readyRead信号
     23     connect(tcpSocket,&QTcpSocket::readyRead,
     24             [=]()
     25             {
     26                 //取出接收的内容
     27                 QByteArray buf = tcpSocket->readAll();
     28                 //接收头文件
     29                 if(isStart == true)
     30                 {
     31                     //接收头文件
     32                     isStart = false;
     33                     //解析头部信息 (文件名##文件大小)
     34                     //拆包
     35                     //初始化工作
     36                     fileName = QString(buf).section("##",0,0);//以"##"分割,获取从第1部分开始,到第1部分结束的字符串
     37                     fileSize = QString(buf).section("##",1,1).toInt();
     38                     receiveSize = 0;
     39                     qDebug() << QString::fromLocal8Bit("客户端接收的头文件:%1 ").arg(fileName) ;
     40 
     41                     //打开文件,并向里面写内容
     42                     file.setFileName(fileName);//关联文件名字
     43                     bool isOK = file.open(QIODevice::WriteOnly);
     44                     if(isOK == false)//如果打开文件失败,中断函数
     45                     {
     46                         qDebug() << "WriteOnly error 37" ;
     47                         tcpSocket->disconnectFromHost();//断开连接
     48                         tcpSocket->close();//关闭套接字
     49                         return;
     50                     }
     51 
     52                     //弹出对话框,显示接收文件的信息
     53                     //QString str =  QString::fromLocal8Bit("接收的文件:[%1  %2kb]").arg(fileName).arg(fileSize);
     54                     //QMessageBox::information(this,QString::fromLocal8Bit("文件信息"),str);
     55 
     56                     //设置进度条
     57                     ui->progressBar->setMinimum(0);//最小值
     58                     ui->progressBar->setMaximum(fileSize);//最大值,因为进度条最大值是int类型,防止范围不够用,最好除以一个数,使最大的范围变小一些
     59                     ui->progressBar->setValue(0);//当前值
     60 
     61                 }
     62                 //接收文件正文
     63                 else
     64                 {
     65                     //接收文件正文
     66                     qint64 len = file.write(buf);//向新文件中写buf数据,并返回写入的数据长度
     67                     if(len > 0)
     68                     {
     69                         //计算累计接收的数据大小
     70                         receiveSize += len;
     71                         //每接收一部分数据,就向服务器端发送一个信息
     72                         //QString str = QString::number(receiveSize);
     73                         //tcpSocket->write( str.toUtf8().data() );
     74                     }
     75                     //更新进度条
     76                     //ui->progressBar->setValue(receiveSize);//当前值
     77                     //接收数据完成
     78                     if(receiveSize == fileSize)
     79                     {
     80                         //先给服务器发送(接收文件完成的信息)
     81                         //tcpSocket->write("file done");
     82                         //弹出对话框,提示文件接收完成
     83                         QMessageBox::information(this,"done",QString::fromLocal8Bit("文件接收完成 50"));
     84                         file.close();//关闭文件
     85                         tcpSocket->disconnectFromHost();//断开连接
     86                         tcpSocket->close();
     87                     }
     88                 }
     89             }
     90             );
     91 }
     92 
     93 ClientWidget::~ClientWidget()
     94 {
     95     delete ui;
     96 }
     97 
     98 void ClientWidget::on_pushButton_connect_clicked()
     99 {
    100     //获取服务器的ip和端口
    101     QString ip = ui->lineEdit_ip->text();
    102     qint16 port = ui->lineEdit_port->text().toInt();
    103 
    104     //主动和服务器建立连接
    105     tcpSocket->connectToHost(QHostAddress(ip),port);
    106 }

    serverwidget.cpp

      1 #include "serverwidget.h"
      2 #include "ui_serverwidget.h"
      3 #include <QHostAddress>
      4 #include <QFileDialog>
      5 #include <QFileInfo>
      6 #include <QDebug>
      7 #include <QTimer>
      8 #include <QIODevice>
      9 
     10 ServerWidget::ServerWidget(QWidget *parent) :
     11     QWidget(parent),
     12     ui(new Ui::ServerWidget)
     13 {
     14     ui->setupUi(this);
     15     setWindowTitle(QString::fromLocal8Bit("服务器端口为:9999"));
     16 
     17     //两个按钮都不能按
     18     ui->pushButton_file->setEnabled(false);
     19     ui->pushButton_send->setEnabled(false);
     20 
     21     //监听套接字
     22     tcpServer = new QTcpServer(this);
     23     //监听
     24     tcpServer->listen(QHostAddress::Any,9999);
     25     //如果客户端成功和服务器连接
     26     //tcpServer会自动触发newConnection()
     27     connect(tcpServer,&QTcpServer::newConnection,
     28             [=]()
     29             {
     30                 //取出建立好连接的套接字
     31                 tcpSocket = tcpServer->nextPendingConnection();
     32                 //获取对方的ip和端口
     33                 QString ip = tcpSocket->peerAddress().toString();
     34                 quint16 port = tcpSocket->peerPort();
     35                 //QString str = QString("[%1:%2] 成功连接").arg(ip).arg(port);
     36                 QString str = QString::fromLocal8Bit("[%1:%2] 成功连接").arg(ip).arg(port);
     37                 //显示到编辑区
     38                 ui->textEdit->setText(str);
     39 
     40                 //成功连接后,才能按选择文件按钮
     41                 ui->pushButton_file->setEnabled(true);
     42 
     43                 //此处不能直接放在构造函数中,因为如果直接放在那,还没分配tcpSocket空间,会程序异常
     44                 //如果有数据传送成功,对方的通信套接字会触发readyRead(),需要在对应的槽函数做接收处理
     45                 /*connect(tcpSocket,&QTcpSocket::readyRead,
     46                         [=]()
     47                         {
     48                             //从通信套接字中取出内容
     49                             QByteArray array = tcpSocket->readAll();
     50                             //如果接收到"文件接收完毕"的信号
     51                             if(QString(array) == "file done")
     52                             {
     53                                 ui->textEdit->append(QString::fromLocal8Bit("文件发送完毕"));
     54                                 file.close();//关闭文件
     55                                 tcpSocket->disconnectFromHost();//断开与客户端的连接
     56                                 tcpSocket->close();
     57                             }
     58                             else
     59                             {
     60                                 qDebug() << array;
     61                             }
     62                         }
     63                         );
     64                         */
     65             }
     66             );
     67 
     68 
     69     //此处定时器只是起到延迟传送的作用,启动之后,立刻关掉,执行发送文件方法
     70     connect(&timer,&QTimer::timeout,
     71             [=]()
     72             {
     73                 //关闭定时器
     74                 timer.stop();
     75                 //发送文件
     76                 sendDate();
     77             }
     78             );
     79 
     80 }
     81 
     82 ServerWidget::~ServerWidget()
     83 {
     84     delete ui;
     85 }
     86 //选择文件按钮
     87 void ServerWidget::on_pushButton_file_clicked()
     88 {
     89     //打开文件对话框,选择文件
     90     QString filePath = QFileDialog::getOpenFileName(this,"open","../");
     91     //如果选择的文件有效
     92     if(filePath.isEmpty() == false)
     93     {
     94         fileName.clear();
     95         fileSize = 0;
     96 
     97         //获取文件信息
     98         QFileInfo info(filePath);
     99         fileName = info.fileName();
    100         fileSize = info.size();//获取文件名字和大小
    101         sendSize = 0;//发送文件的大小
    102 
    103         //只读方式打开
    104         //指定文件的名字
    105         file.setFileName(filePath);//指定文件,然后才能打开
    106         bool isOK = file.open(QIODevice::ReadOnly);
    107         if(isOK == false)
    108         {
    109             qDebug() << QString::fromLocal8Bit("只读方式打开文件失败 77");
    110         }
    111         //在编辑区提示打开文件的路径
    112         ui->textEdit->append(filePath);
    113         //把选择文件按钮禁用
    114         ui->pushButton_file->setEnabled(false);
    115         //让发送文件按钮可用
    116         ui->pushButton_send->setEnabled(true);
    117     }
    118     else
    119     {
    120         qDebug() << QString::fromLocal8Bit("选择文件出错 59");
    121     }
    122 }
    123 //发送文件按钮
    124 void ServerWidget::on_pushButton_send_clicked()
    125 {
    126     //先发送文件头信息  文件名##文件大小
    127     QString head = QString("%1##%2").arg(fileName).arg(fileSize);
    128     //发送头部信息
    129     qint64 len = tcpSocket->write( head.toUtf8() );
    130 
    131     qDebug() << head.toUtf8() << len ;
    132     if(len > 0)//头部信息发送成功
    133     {
    134         //发送真正的文件
    135         //防止TCP黏包文件
    136         //需要通过定时器延时20ms(保证头文件先发送过去,然后再发送文件正文)
    137         timer.start(20);//启动定时器
    138     }
    139     else
    140     {
    141         qDebug() << QString::fromLocal8Bit("头部信息发送失败 110");
    142         file.close();
    143         //改变按钮可用状态
    144         ui->pushButton_file->setEnabled(true);
    145         ui->pushButton_send->setEnabled(false);
    146     }
    147     //再发送真正的文件信息
    148 }
    149 //发送文件数据
    150 void ServerWidget::sendDate()
    151 {
    152     qint64 len = 0;
    153     do
    154     {
    155         //每次发送数据的大小(4kb)
    156         char buf[4*1024] = {0};
    157         len = 0;
    158         //往文件中读数据
    159         len = file.read(buf,sizeof(buf));
    160         //发送数据,读多少发多少
    161         len = tcpSocket->write(buf,len);
    162         //发送的数据累计
    163         sendSize += len;
    164     }while(len>0);
    165 
    166     //是否发送文件完毕
    167     if(sendSize == fileSize)
    168     {
    169         ui->textEdit->append(QString::fromLocal8Bit("文件发送完毕 128"));
    170         file.close();
    171         //把客户端断开
    172         tcpSocket->disconnectFromHost();
    173         tcpSocket->close();
    174     }
    175 }

    clientwidget.ui

    serverwidget.ui

  • 相关阅读:
    python实现快排算法,传统快排算法,数据结构
    pyaudio音频录制python
    python性能测试,请求QPS测试
    tensorflow如何切换CPU和GPU
    warmup预热学习率
    pytorch两种模型保存方式
    一个简单docker服务镜像的制作,手把手教你制作一个flask的docker容器服务镜像。
    threading的定时器模块,python,每间隔一段时间执行一次任务
    SVM简单分类的使用 sklearn机器学习
    Swoole从入门到入土(18)——WebSocket服务器[心跳ping]
  • 原文地址:https://www.cnblogs.com/blog-ccs/p/7457870.html
Copyright © 2020-2023  润新知