十一、TCP服务端、客户端程序编写及测试

TCP 协议(Transmission Control Protocol)全称是传输控制协议是一种面向连接的、可靠的、基于字节流的传输层通信协议

简单来说就是用来网络通信的。

TCP 通信必须先建立 TCP 连接,通信端分为客户端和服务端。

服务端通过监听某个端口来监听是否有客户端连接到来,如果有连接到来,则建立新的 socket 连接;客户端通过 ip 和port 连接服务端,当成功建立连接之后,就可进行数据的收发了。

新建一个WIdget项目,我们先添加network模块:

我们先进行槽函数的连接(先创建槽函数):

我们创建Socket对象,先把类添加进来:

按F4:(在 Qt 中, Qt 把 socket 当成输入输出流来对待的,数据的收发是通过 read()和 write()来进行的,需要与我们常见的 send()与 recv()进行区分。

然后要接收消息或者是发送消息的话要有一些信号的。

OK,接收消息的函数已经写好了。

我们看一下状态,因为TCP服务端有时候跟客户端要断开连接,或者不在线的时候要做出一些处理的,这比较重要。既然有消息发出的信号,就肯定有连接状态的信号。

接下来就是是否要监听客户端的到来?监听哪个端口?

我们先来设计一个界面:

Ctrl+b先构建一下。

然后我们在新的连接里面打印一下客户端的消息:

首先打印下客户端连接的状态,就是状态改变的参数先完善一下,先链接才会获取到IP地址等。

下面是详细代码(服务器端程序完成):

复制代码
//widget.h
#ifndef WIDGET_H
#define WIDGET_H

#include <QWidget>
#include <QTcpServer>
#include <QTcpSocket>

QT_BEGIN_NAMESPACE
namespace Ui { class Widget; }
QT_END_NAMESPACE

class Widget : public QWidget
{
    Q_OBJECT

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

private:
    Ui::Widget *ui;
    QTcpServer *tcpServer;

private slots:
    void mNewConnection();
    void receiveMessages();
    void mStateChanged(QAbstractSocket::SocketState);
    void on_pushButton_3_clicked();
    void on_pushButton_clicked();
};
#endif // WIDGET_H

//widget.cpp
#include "widget.h"
#include "ui_widget.h"

Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
{
    ui->setupUi(this);
    tcpServer = new QTcpServer(this);
    //新的连接
    connect(tcpServer, SIGNAL(newConnection()), this, SLOT(mNewConnection()));//连接槽函数
}

Widget::~Widget()
{
    delete ui;
}

void Widget::mNewConnection()
{
    //与客户端连接(他不是主动去连接的,而是客户端主动连接服务端)
    QTcpSocket *tmpTcpSocket = tcpServer->nextPendingConnection();
    
    //客户端的ip地址
    QString ipaddr = tmpTcpSocket->peerAddress().toString();
    quint16 port = tmpTcpSocket->peerPort();
    //打印客户端连接的IP信息
    ui->textBrowser->append("客户端的ip地址:" + ipaddr);
    //打印客户端连接的端口信息
    ui->textBrowser->append("客户端的端口:" + QString::number(port));
    
    connect(tmpTcpSocket, SIGNAL(readyRead()), this, SLOT(receiveMessages()));//连接槽函数
    connect(tmpTcpSocket, SIGNAL(stateChanged(QAbstractSocket::SocketState)),
            this, SLOT(mStateChanged(QAbstractSocket::SocketState)));//连接槽函数
}

void Widget::receiveMessages()
{
    QTcpSocket *tmpTcpSocket = (QTcpSocket *)sender();
    //接收消息
    ui->textBrowser->append(tmpTcpSocket->readAll());
 
}

void Widget::mStateChanged(QAbstractSocket::SocketState state)
{
    QTcpSocket *tmpTcpSocket = (QTcpSocket *)sender();
    switch(state) {
    //已经断开
    case QAbstractSocket::UnconnectedState:
        ui->textBrowser->append("客户端断开连接");
        delete tmpTcpSocket;
        break;
    //已经连接
    case QAbstractSocket::ConnectedState:
        ui->textBrowser->append("客户端已连接");
        break;
    default:
        break;
    }
    delete tmpTcpSocket;
}

//发送消息按钮clicked
void Widget::on_pushButton_3_clicked()
{
    QList <QTcpSocket *> socketList = tcpServer->findChildren<QTcpSocket *>();
    foreach (QTcpSocket *tmpTcpSocket, socketList) {
        //服务端向每个客户服务端发送消息
        tmpTcpSocket->write(ui->lineEdit->text().toUtf8());
    }
}
//开始监听按钮clicked
void Widget::on_pushButton_clicked()
{
    //绑定ip地址和端口
    tcpServer->listen(QHostAddress("192.168.18.130"), 9999);
    
}

接下来我们将用Qt编写Tcp客户端应用程序:

在TCP服务端的应用程序中我们使用了两个类,即QTcpServer和QTcpSocket,在客户端里面只需要使用QTcpSocket类。

TCP客户端的程序编写·流程如下:

新建一个项目:

信号槽添加进来:

我们设计它的界面:

ctrl+b构建一下。

主要代码展示:

复制代码
//widget.h
#ifndef WIDGET_H
#define WIDGET_H

#include <QWidget>
#include <QTcpSocket>
#include <QHostAddress>

QT_BEGIN_NAMESPACE
namespace Ui { class Widget; }
QT_END_NAMESPACE

class Widget : public QWidget
{
    Q_OBJECT

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

private:
    Ui::Widget *ui;
    QTcpSocket *tcpSocket;

private slots:
    void receiveMessage();
    void mStateChanged(QAbstractSocket::SocketState);
    void on_pushButton_3_clicked();
    void on_pushButton_clicked();
    void on_pushButton_2_clicked();
};
#endif // WIDGET_H

//widget.cpp
#include "widget.h"
#include "ui_widget.h"

Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
{
    ui->setupUi(this);
    tcpSocket = new QTcpSocket(this);
    connect(tcpSocket, SIGNAL(readyRead()), this, SLOT(receiveMessage()));
    connect(tcpSocket, SIGNAL(stateChanged(QAbstractSocket::SocketState)), this, SLOT(mStateChanged(QAbstractSocket::SocketState)));
}

Widget::~Widget()
{
    delete ui;
}

void Widget::receiveMessage()
{
    //接收消息
    ui->textBrowser->append("服务器端:" + tcpSocket->readAll());
}

void Widget::mStateChanged(QAbstractSocket::SocketState state)
{
    switch(state) {
    case QAbstractSocket::UnconnectedState:
        ui->textBrowser->append("与服务端断开连接");
        break;
    case QAbstractSocket::ConnectedState:
        ui->textBrowser->append("已连接服务端");
        break;
    default:
        break;

    }
}

//发送消息槽函数clicked
void Widget::on_pushButton_3_clicked()
{
    //发送消息
    if (tcpSocket->state() == QAbstractSocket::ConnectedState)
        tcpSocket->write(ui->lineEdit->text().toUtf8());
    else
        ui->textBrowser->append("请先于服务端连接");
}
//连接服务器槽函数clicked
void Widget::on_pushButton_clicked()
{
    //连接服务端,指定服务端的IP地址或端口
    tcpSocket->connectToHost(QHostAddress("192.168.18.130"), 10000);
}
//断开连接槽函数clicked
void Widget::on_pushButton_2_clicked()
{
    //断开连接
    tcpSocket->disconnectFromHost();
}

接下来我们将进行TCP客户端与TCP服务器端的测试:

首先我们设置下服务器项目和客户端项目的标题,如下:

我们将他们运行,服务器端点击监听,客户端点击连接服务器,发现连接失败。

我们找下原因:先检查下程序,我们发现ip ok,端口不对,我们将客户端的端口也改为和服务器端一样9999,再次执行上一段的动作:

我们点击停止监听,会断开,这里执行无效。我们转到停止监听的槽函数,发现没写:

在按钮组里他们才会互斥

灰色的话就说明不可选的。

客户端也是:

其实还有一个问题我们没有考虑,只有连上之后才可以用才对。所以互斥还要改下:

我们用程序来写:

我们再运行下服务端:

服务端出现服务异常,这是为什么?

一般程序异常的话肯定是指针的问题。

此时我们进行Debug,发现我们删除了指针,我们这么修改:

此时我们再次执行:发现运行正常。

接下来我们验证下退出的时候socket是不是被删除了。

这与该段代码的设置有莫大的关系!

这样可以避免浪费内存。

下面我们补充下客户端和服务端打印信息的内容:

我们再次修改下:

将服务端的默认发送消息也编辑下:

然后我们验证下:

接下来我们将修改下显示内容,修改的位置见下面详细的代码:

cpp 复制代码
//服务器端widget.cpp
#include "widget.h"
#include "ui_widget.h"

Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
{
    ui->setupUi(this);
    tcpServer = new QTcpServer(this);
    this->setWindowTitle("服务端");
    //新的连接
    connect(tcpServer, SIGNAL(newConnection()), this, SLOT(mNewConnection()));//连接槽函数
}

Widget::~Widget()
{
    delete ui;
}

void Widget::mNewConnection()
{
    //与客户端连接(他不是主动去连接的,而是客户端主动连接服务端)
    QTcpSocket *tmpTcpSocket = tcpServer->nextPendingConnection();

    //客户端的ip地址
    QString ipaddr = tmpTcpSocket->peerAddress().toString();
    quint16 port = tmpTcpSocket->peerPort();
    //打印客户端连接的IP信息
    ui->textBrowser->append("服务端:客户端的ip地址:" + ipaddr);
    //打印客户端连接的端口信息
    ui->textBrowser->append("服务端:客户端的端口:" + QString::number(port));

    connect(tmpTcpSocket, SIGNAL(readyRead()), this, SLOT(receiveMessages()));//连接槽函数
    connect(tmpTcpSocket, SIGNAL(stateChanged(QAbstractSocket::SocketState)),
            this, SLOT(mStateChanged(QAbstractSocket::SocketState)));//连接槽函数
}

void Widget::receiveMessages()
{
    QTcpSocket *tmpTcpSocket = (QTcpSocket *)sender();
    //接收消息
    ui->textBrowser->append("客户端:" + tmpTcpSocket->readAll());

}

void Widget::mStateChanged(QAbstractSocket::SocketState state)
{
    QTcpSocket *tmpTcpSocket = (QTcpSocket *)sender();
    switch(state) {
    //已经断开
    case QAbstractSocket::UnconnectedState:
        ui->textBrowser->append("服务端:客户端断开连接");
        tmpTcpSocket->deleteLater();
        //delete tmpTcpSocket;
        break;
    //已经连接
    case QAbstractSocket::ConnectedState:
        ui->textBrowser->append("服务端:客户端已连接");
        break;
    default:
        break;
    }
    //delete tmpTcpSocket;
}

//发送消息按钮clicked
void Widget::on_pushButton_3_clicked()
{
    QList <QTcpSocket *> socketList = tcpServer->findChildren<QTcpSocket *>();
    qDebug() << "QTcpSocket 数量:" << socketList.count() << endl;
    if(socketList.count() == 0) {
        ui->textBrowser->append("当前没有客户端连接,请先与客户端连接");
        return;
    }
    foreach (QTcpSocket *tmpTcpSocket, socketList) {
        //服务端向每个客户服务端发送消息
        tmpTcpSocket->write(ui->lineEdit->text().toUtf8());
    }
    ui->textBrowser->append("服务端:" + ui->lineEdit->text());
}
//开始监听按钮clicked
void Widget::on_pushButton_clicked()
{
    //绑定ip地址和端口
    tcpServer->listen(QHostAddress("192.168.18.130"), 9999);
    ui->textBrowser->append("服务端:监听的IP地址和端口:192.168.18.130,9999");
}

void Widget::on_pushButton_2_clicked()
{
    //停止监听
    tcpServer->close();
}
cpp 复制代码
//客户端widget.cpp
#include "widget.h"
#include "ui_widget.h"

Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
{
    ui->setupUi(this);
    this->setWindowTitle("客户端");
    ui->pushButton_2->setEnabled(false);

    tcpSocket = new QTcpSocket(this);    
    connect(tcpSocket, SIGNAL(readyRead()), this, SLOT(receiveMessage()));
    connect(tcpSocket, SIGNAL(stateChanged(QAbstractSocket::SocketState)), this, SLOT(mStateChanged(QAbstractSocket::SocketState)));
}

Widget::~Widget()
{
    delete ui;
}

void Widget::receiveMessage()
{
    //接收消息
    ui->textBrowser->append("服务器端:" + tcpSocket->readAll());
}

void Widget::mStateChanged(QAbstractSocket::SocketState state)
{
    switch(state) {
    case QAbstractSocket::UnconnectedState:
        ui->textBrowser->append("客户端:与服务端断开连接");
        ui->pushButton->setEnabled(true);
        ui->pushButton_2->setEnabled(false);
        break;
    case QAbstractSocket::ConnectedState:
        ui->textBrowser->append("客户端:已连接服务端");
        ui->pushButton->setEnabled(false);
        ui->pushButton_2->setEnabled(true);
        break;
    default:
        break;

    }
}

//发送消息槽函数clicked
void Widget::on_pushButton_3_clicked()
{
    //发送消息
    if (tcpSocket->state() == QAbstractSocket::ConnectedState) {
        tcpSocket->write(ui->lineEdit->text().toUtf8());
        ui->textBrowser->append("客户端" + ui->lineEdit->text());
    }
    else
        ui->textBrowser->append("请先于服务端连接");
}
//连接服务器槽函数clicked
void Widget::on_pushButton_clicked()
{
    ui->textBrowser->append("客户端:监听的IP地址和端口:192.168.18.130,9999");
    //连接服务端,指定服务端的IP地址或端口
    tcpSocket->connectToHost(QHostAddress("192.168.18.130"), 9999);
}
//断开连接槽函数clicked
void Widget::on_pushButton_2_clicked()
{
    //断开连接
    tcpSocket->disconnectFromHost();
}

执行效果如下:

客户端的端口是随机的,假设多个客户端连接主机的话,它的端口肯定是不一样的。

我们还需要修改一些逻辑:

首先:

首先我们停止监听初始化的时候是不可用的。

我们再次执行测试PASS!!!

我们再跑一个客户端出来:

这里实现了服务器和客户端 一对多的通信!


相关推荐
四代水门1 小时前
三、计算机网络
服务器·计算机网络·asp.net
xywww1682 小时前
真实后台页实测:Opus 5 看图写前端的可用边界在哪
linux·服务器·前端·数据库·人工智能·gpt
nnerddboy2 小时前
Rust教程05:结构体,枚举与模式匹配
开发语言·网络·rust
秋田君3 小时前
QT_绘图原理双缓冲机制
服务器·数据库·qt
想要入门的程序猿3 小时前
Qt插件QPluginLoader
开发语言·qt
GlobalInfo3 小时前
AI与另类数据融合,市场研究正在从“经验驱动”走向“数据智能”
大数据·网络·人工智能·ai
天吾cc3 小时前
RTT-MQTT
网络·单片机·嵌入式硬件·算法
千视kiloview3 小时前
“轻”不等于降级:IP时代广播制作系统的轻量化与Hybrid架构实践
网络·tcp/ip·架构
GlueNa2SiO34 小时前
10-Docker生产环境部署与K8s入门
笔记·学习·docker·容器·kubernetes
不爱土豆唯爱马铃薯4 小时前
升级 AiPy Pro 2.0 后,我把安全中心这几项挨个打开了数据安全、沙盒权限、工作空间隔离——这些 2.0 新功能对科研协作场景的实际影响
网络·安全·php