TCP通信之QTcpServer和QTcpSocket,服务器和客户端通讯

简 述: 了解TCP通信之QTcpServerQTcpSocket,服务器和客户端通讯,书写一个简单地例子;然后写了一个小的 Qt例子,用来实现和验证它的空间的一些属性和功能的用法。


相关博文:

有着一些关联的博文其他参考:


系统环境:

编程环境: MacOS 10.14.6 (18G103) 编程软件: Qt 5.9.8Qt Creator 4.8.2


Tcp通信概述:

我想能够看到网络这一部分的人,基本都是有着基本的网络认知;这里为了这一个系列的完整性,还是决定将本系列的基本知识仍旧写出来。万一之前的你是没有怎么接触过这类篇文章的鸭鸭呢?就算是完全不知,但是一些基础的还是能够让你能够有所了解,再具体的就进行自行goole和专业📚进行扩展深入扩展学习,博文尽量都提一笔,其余更多待你自行发掘。

TCP特点:

可靠,面向流传播,面向协议的一种传输协议;特别适合用于连续数据传输。实际🌰:QQ的在线发送文件,就是为了确保文件发送的完整性,使用的TCP协议。


QTcpServer属性:

TCP通信,必须先进行TCP连接,通信端口分为服务器端口和客户端。

QTcpServer是从QObject继承的类,主要是用于服务器建立网络监听,创立Socket连接的;其中主要的一些常用接口,提供·如下:

类型函数功能
公共函数void close()关闭服务器,停止网络监听
bool listen()在给定IP地址和端口上开始监听,若是成功,就返回true
bool isListening()返回true表示服务器处于监听状态
QTcpSocket * nextPendingConnection()返回下一个等待接入的连接
QHostAddres serverAddress()如果服务器处于监听状态,返回服务器地址
quint16 serverPort()如果服务器处于监听状态,返回服务器端口
bool waitForNewConnection()以阻塞方式等待新的连接
信号void acceptError( QAbstractSocket::SocketError socketError )当接受一个新的连接时大声了错误,就发射此信号;参数socketError描述错误信息
void new Connection()当有新的信号连接时候,发射此信号
保护函数void incomingConnection(qintptr socketDescriptor)当有一个新的连接可用时,QTcpServer内部调用此函数,创建一个QTcpSocket对象,添加到内部可用新连接列表,然后发射newConnetion()信号,用户若是从QTcpServer继承定义类,可以重定义此函数,但是必须调用QtcpSocket继承定义类,可以重新定义此函数,但是必须调用addPendingConnetion()
void addPendingConnection(QTcp Socket *socket)由incomingtion()调用,将创建的QTcpSocket添加到内部新可用连接列表

服务器的程序首先是需要调用QTcpServer进行监听的【这个时候,是指定监听的IP和port的】,然后当QScoket连接的时候,会发射信号📶,newConnection(),在newConnection()的对应好的槽函数中,可以用nextPendingConnetion()接收客户端的连接,然后使用QSocket与客户端进行通信。

QTcp/QUdp继承关系图:


QAbstractSocket接口讲解:

其中主要的QAbstractSocket的主要的接口函数如下:

类型函数功能
公共函数void connectToHost(QHostAddress headdress, quint16 port,)以异步的方式连接到指定的IP地址和端口的TCP服务器,连接成功会发射connected()信号
void disconnectFromHost()断开sokcet,关闭成功后发射disconnection()信号
bool waitForConnected()等待直到建立socket连接
bool waitForDisconnected()等待直到断开socket连接
QHostAddress localAddress()返回本socket的地址
quint16 localPort()返回本socket的端口
QHostAddress peerAddress()在已经连接状态下,返回对方socket的地址
QString peerName()返回connetToHost()连接到对方的主机的名
quint16 peerPort()在已连接的状态下,返回对方的socket的port
qint64 readBuffer Size()返回内部读取缓冲区的大小的数据的字节数;该大小决定了read()和readAll()函数能够读取出来的数据的大小
void setReadBuAerSize(qint64 size)数据内部读取缓冲区的数据的字节数
qint64 bytesAvailable()返回需要读取的缓冲区的字节数字
bool canReadLine()如果有行数据要从socket缓冲区读取,就返回true
SocketState state()返回当前socket状态
信号📶void connected()connectionToHost()成功连接到服务器后发射此信号
void disconnected()当socket断开连接时,发射此信号
void error(QAbstractSocket::SocketError socketError)当socket发生错误时,发射此信号
void hostFound()调用connectToHost()找到主机后发射此信号
void stateChanged(QAbstractSocket::SocketState socketState)当socket的状态变化时候发射此信号,参数socketState表示了socket当前的状态
void ready Read()当缓冲区的数据需要读取时候,发射此信号,在此信号的槽函数里面读取缓冲区的数据

运行效果:

这里先放一张运行效果图:


源码分析:

这里将TCP通信的服务器和客户端拆分为两个小的项目来写,其结构如下:

其中核心部分的源码,重点和一些难点以及需要注意的一些地方,贴出来如下:

其中服务器端:

/*
 * Copyright (C)  2019 ~ 2019 touwoyimuli.  All rights reserved.
 *
 * Author:  touwoyimuli <touwoyimuli@gmai.com>
 *
 * github:  https://github.com/touwoyimuli
 * blogs:   https://touwoyimuli.github.io/
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <https://touwoyimuli.github.io/>.
 */
#ifndef EXTCPSERVER_H
#define EXTCPSERVER_H

#include <QMainWindow>
#include <QLabel>
#include <QTcpServer>
#include <QTcpSocket>
#include <QHostInfo>

namespace Ui {
class ExTcpServer;
}

class ExTcpServer : public QMainWindow
{
    Q_OBJECT

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

private:
    QString getLocalIp();      //获取本机 IP

protected:
    void closeEvent(QCloseEvent* event);

private slots:
//UI的槽函数
    void on_actStart_triggered();      //开始监听
    void on_actStop_triggered();       //停止监听
    void on_actClear_triggered();      //清除文本框内容
    void on_actQuit_triggered();       //退出程序
    void on_btnSend_clicked();         //发送消息

//自定义的槽函数
    void onSocketReadyRead();          //读取 socket 传入时候的数据
    void onClientConnected();          //client socket conneted
    void onClientDisonnected();        //client socket disconneted
    void onNewConnection();            //QTcpServer 的 newConnect() 信号
    void onSocketStateChange(QAbstractSocket::SocketState socketState);

private:
    Ui::ExTcpServer *ui;

    QLabel* m_labListen;
    QLabel* m_labSocket;
    QTcpServer* m_tcpServer;
    QTcpSocket* m_tcpSocket;


};

#endif // EXTCPSERVER_H

#include "ExTcpServer.h"
#include "ui_ExTcpServer.h"

ExTcpServer::ExTcpServer(QWidget *parent) :
    QMainWindow(parent),
    ui(new Ui::ExTcpServer)
{
    ui->setupUi(this);

    m_labListen = new QLabel("监听状态:");
    m_labSocket = new QLabel("socket状态:");
    m_labListen->setMidLineWidth(200);
    m_labSocket->setMinimumWidth(200);
    ui->statusBar->addWidget(m_labListen);
    ui->statusBar->addWidget(m_labSocket);

    QString localeIp = getLocalIp();
    setWindowTitle(windowTitle() + "---IP地址:" + localeIp);
    ui->comboBox->addItem(localeIp);

    m_tcpServer = new QTcpServer(this);
    connect(m_tcpServer, SIGNAL(newConnection()), this, SLOT(onNewConnection()));
}

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

QString ExTcpServer::getLocalIp()
{
    QString hostName = QHostInfo::localHostName();
    QHostInfo hostInfo = QHostInfo::fromName(hostName);
    ui->plainTextEdit->appendPlainText("本机名称:" + hostName);
    QString locaIp;

    QList<QHostAddress> list = hostInfo.addresses();

    if (list.empty())
        return "null QString";

    foreach (QHostAddress addr, list) {
        if (addr.protocol() == QAbstractSocket::IPv4Protocol) {
            locaIp = addr.toString();
            break;
        }
    }

    return locaIp;
}

void ExTcpServer::closeEvent(QCloseEvent *event)   //关闭窗口时候停止监听
{
    if (m_tcpServer->isListening())
        m_tcpServer->close();

    event->accept();
}

void ExTcpServer::on_actStart_triggered()
{
    QString Ip = ui->comboBox->currentText();
    quint16 port = ui->spinBox->value();

    QHostAddress addr(Ip);
    m_tcpServer->listen(addr, port);  //监听指定的 IP 和指定的 port
    ui->plainTextEdit->appendPlainText("服务器地址为:" + m_tcpServer->serverAddress().toString() + "   服务器端口:" + QString::number(m_tcpServer->serverPort()));
    ui->plainTextEdit->appendPlainText("开始监听...");

    ui->actStart->setEnabled(false);
    ui->actStop->setEnabled(true);
    m_labListen->setText("监听状态:正在监听...");
}

void ExTcpServer::on_actStop_triggered()
{
    if (!m_tcpServer->isListening())
        return;

    m_tcpServer->close();  //停止监听

    ui->actStart->setEnabled(true);
    ui->actStop->setEnabled(false);
    m_labListen->setText("监听状态:监听已经停止");
}

void ExTcpServer::on_actClear_triggered()
{
    ui->plainTextEdit->clear();
}

void ExTcpServer::on_actQuit_triggered()
{
    close();
}

void ExTcpServer::on_btnSend_clicked()
{
    QString msg = ui->lineEdit->text();
    ui->plainTextEdit->appendPlainText("[服务器:]" + msg);
    ui->lineEdit->clear();
    ui->plainTextEdit->hasFocus();

    QByteArray str = msg.toUtf8();
    str.append('\n');
    m_tcpSocket->write(str);
}

void ExTcpServer::onSocketReadyRead()     //读取缓冲区行文本
{
    while (m_tcpSocket->canReadLine()) {
        ui->plainTextEdit->appendPlainText("[客户端:]" + m_tcpSocket->readLine());
    }
}

void ExTcpServer::onClientConnected()    //客户端连接时
{
    ui->plainTextEdit->appendPlainText("客户端套接字连接\n对等(peer)地址:" + m_tcpSocket->peerAddress().toString()
                                       + "    对等(peer)端口:" +  QString::number(m_tcpSocket->peerPort()));

}

void ExTcpServer::onClientDisonnected()  //客户端断开连接时
{
    ui->plainTextEdit->appendPlainText("客户端套接字断开");
    m_tcpSocket->deleteLater();
}

void ExTcpServer::onNewConnection()
{
    m_tcpSocket = m_tcpServer->nextPendingConnection();   //创建 socket

    connect(m_tcpSocket, SIGNAL(connected()), this, SLOT(onClientConnected()));
    connect(m_tcpSocket, SIGNAL(disconnected()), this, SLOT(onClientDisonnected()));
    connect(m_tcpSocket, SIGNAL(stateChanged(QAbstractSocket::SocketState)), this, SLOT(onSocketStateChange(QAbstractSocket::SocketState)));
    onSocketStateChange(m_tcpSocket->state());
    connect(m_tcpSocket, SIGNAL(readyRead()), this, SLOT(onSocketReadyRead()));
}

void ExTcpServer::onSocketStateChange(QAbstractSocket::SocketState socketState)
{
    switch (socketState) {
    case QAbstractSocket::UnconnectedState:
        m_labSocket->setText("socket状态:UnconnectedState");
        break;
    case QAbstractSocket::HostLookupState:
        m_labSocket->setText("socket状态:HostLookupState");
        break;
    case QAbstractSocket::ConnectingState:
        m_labSocket->setText("socket状态:ConnectingState");
        break;
    case QAbstractSocket::ConnectedState:
        m_labSocket->setText("socket状态:ConnectedState");
        break;
    case QAbstractSocket::BoundState:
        m_labSocket->setText("socket状态:BoundState");
        break;
    case QAbstractSocket::ClosingState:
        m_labSocket->setText("socket状态:ClosingState");
        break;
    case QAbstractSocket::ListeningState:
        m_labSocket->setText("socket状态:ListeningState");
        break;
    default:
        m_labSocket->setText("socket状态:其他未知状态...");
        break;
    }
}

其中客户端:

#ifndef EXTCPCLIENT_H
#define EXTCPCLIENT_H

#include <QMainWindow>
#include <QLabel>
#include <QTcpSocket>
#include <QHostInfo>

namespace Ui {
class ExTcpClient;
}

class ExTcpClient : public QMainWindow
{
    Q_OBJECT

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

private:
    QString getLocalIp();                  //获取本本机 IP

protected:
    void closeEvent(QCloseEvent *event);

private slots:
    //UI 定义的槽函数
    void on_actConnect_triggered();     //请求连接到服务器
    void on_actDisconnect_triggered();  //断开与服务器的连接
    void on_actClear_triggered();       //清除内容
    void on_actQuit_triggered();        //退出程序
    void on_btnSend_clicked();          //发送文本消息

    //自定义的槽函数
    void onConnected();
    void onDisconnected();
    void onSocketReadyRead();           //从socket读取传入的数据
    void onSocketStateChange(QAbstractSocket::SocketState socketState);

private:
    Ui::ExTcpClient *ui;

    QLabel* m_labSocket;
    QTcpSocket* m_tcpSocket;
};

#endif // EXTCPCLIENT_H

#include "ExTcpClient.h"
#include "ui_ExTcpClient.h"

ExTcpClient::ExTcpClient(QWidget *parent) :
    QMainWindow(parent),
    ui(new Ui::ExTcpClient)
{
    ui->setupUi(this);

    m_labSocket = new QLabel("socket状态:");
    m_labSocket->setMidLineWidth(150);
    ui->statusBar->addWidget(m_labSocket);

    m_tcpSocket = new QTcpSocket(this);

    QString localIp = getLocalIp();
    this->setWindowTitle(windowTitle() + "----本机IP:" + localIp);
    ui->comboBox->addItem(localIp);

    connect(m_tcpSocket, SIGNAL(connected()), this, SLOT(onConnected()));
    connect(m_tcpSocket, SIGNAL(disconnected()), this, SLOT(onDisconnected()));
    connect(m_tcpSocket, SIGNAL(stateChanged(QAbstractSocket::SocketState)), this, SLOT(onSocketStateChange(QAbstractSocket::SocketState)));
    connect(m_tcpSocket, SIGNAL(readyRead()), this, SLOT(onSocketReadyRead()));
}

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

QString ExTcpClient::getLocalIp()
{
    QString hostName = QHostInfo::localHostName();
    QHostInfo hostInfo = QHostInfo::fromName(hostName);
    ui->plainTextEdit->appendPlainText("本机名称:" + hostName);
    QString localIp;

    foreach (QHostAddress addr, hostInfo.addresses()) {
        if (QAbstractSocket::IPv4Protocol == addr.protocol()) {
            localIp = addr.toString();
            break;
        }
    }
    return localIp;
}

void ExTcpClient::closeEvent(QCloseEvent *event)
{
    if (m_tcpSocket->state() == QAbstractSocket::ConnectedState)
        m_tcpSocket->disconnectFromHost();

    event->accept();
}

void ExTcpClient::onConnected()
{
    ui->plainTextEdit->appendPlainText("已经连接到服务器\n客户端套接字连接\n对等(peer)地址:" + m_tcpSocket->peerAddress().toString()
                                       + "    对等(peer)端口:" +  QString::number(m_tcpSocket->peerPort()));
    ui->actConnect->setEnabled(false);
    ui->actDisconnect->setEnabled(true);
}

void ExTcpClient::onDisconnected()
{
    ui->plainTextEdit->appendPlainText("已经断开与服务器的连接\n");
    ui->actConnect->setEnabled(true);
    ui->actDisconnect->setEnabled(false);
}

void ExTcpClient::onSocketReadyRead()
{
    while (m_tcpSocket->canReadLine()) {
        ui->plainTextEdit->appendPlainText("[服务器:]" + m_tcpSocket->readLine());
    }
}

void ExTcpClient::onSocketStateChange(QAbstractSocket::SocketState socketState)
{
    switch (socketState) {
    case QAbstractSocket::UnconnectedState:
        m_labSocket->setText("socket状态:UnconnectedState");
        break;
    case QAbstractSocket::HostLookupState:
        m_labSocket->setText("socket状态:HostLookupState");
        break;
    case QAbstractSocket::ConnectingState:
        m_labSocket->setText("socket状态:ConnectingState");
        break;
    case QAbstractSocket::ConnectedState:
        m_labSocket->setText("socket状态:ConnectedState");
        break;
    case QAbstractSocket::BoundState:
        m_labSocket->setText("socket状态:BoundState");
        break;
    case QAbstractSocket::ClosingState:
        m_labSocket->setText("socket状态:ClosingState");
        break;
    case QAbstractSocket::ListeningState:
        m_labSocket->setText("socket状态:ListeningState");
        break;
    default:
        m_labSocket->setText("socket状态:其他未知状态...");
        break;
    }
}

void ExTcpClient::on_actConnect_triggered()
{
    QString addr = ui->comboBox->currentText();
    quint16 port = ui->spinBox->value();
    m_tcpSocket->connectToHost(addr, port);
}

void ExTcpClient::on_actDisconnect_triggered()
{
    if(m_tcpSocket->state() == QAbstractSocket::ConnectedState)
        m_tcpSocket->disconnectFromHost();
}

void ExTcpClient::on_actClear_triggered()
{
    ui->plainTextEdit->clear();
}

void ExTcpClient::on_actQuit_triggered()
{
    close();
}

void ExTcpClient::on_btnSend_clicked()
{
    QString msg = ui->lineEdit->text();
    ui->plainTextEdit->appendPlainText("[客户端:]" + msg);
    ui->lineEdit->clear();
    ui->lineEdit->setFocus();

    QByteArray str = msg.toUtf8();
    str.append('\n');
    m_tcpSocket->write(str);
}


源码下载:

https://github.com/xmuli/QtExamples【QtTcpEx】

  • 9
    点赞
  • 40
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论
Qt是一个跨平台的应用程序开发框架,它可以用来开发各种类型的应用程序,包括TCP服务器客户端。 在Qt中,可以使用Qt网络模块来实现TCP服务器客户端的功能。要同时运行TCP服务器客户端,可以在同一个应用程序中创建两个Qt网络对象,分别用于实现服务器客户端的功能。 首先,我们需要创建一个QTcpServer对象来实现TCP服务器的功能。通过调用QTcpServer的listen()函数来监听指定的IP地址和端口号,等待客户端的连接请求。当有新的客户端连接时,QTcpServer会自动发出newConnection()信号,我们可以通过连接这个信号的槽函数来处理新客户端的连接。 然后,我们需要创建一个QTcpSocket对象来实现TCP客户端的功能。通过调用QTcpSocket的connectToHost()函数来连接到服务器的IP地址和端口号。连接成功后,我们可以发送和接收数据。 在应用程序中,可以创建一个主窗口,同时创建一个QTcpServer对象和一个QTcpSocket对象。在主窗口中,可以增加一些用户界面元素,例如按钮和文本框,用于操作服务器客户端。当点击按钮时,可以通过QTcpServerQTcpSocket对象来实现相应的功能,例如启动服务器、连接到服务器、发送数据等。 需要注意的是,在同一个应用程序中同时运行TCP服务器客户端可能需要一些线程管理的技术,以保证服务器客户端之间的通信不会阻塞主线程的运行。可以使用Qt的多线程技术来实现这一点,例如将服务器客户端的功能代码放在不同的线程中运行。 总而言之,通过使用Qt的网络模块,可以很方便地实现TCP服务器客户端的功能,并在同一个应用程序中同时运行它们。
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

偕臧x

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值