C++ Qt5学习笔记 2020-12-16(网络编程1:QHostInfo类,QNetworkInterface类,TCP服务端和客户端通信)

44 篇文章 8 订阅

大部分资料整理自Qt5.9 C++开发指南,代码和演示部分都是我修改和试验而来。

1、QHostInfo类:

(1)基本使用:
.pro中添加代码:

QT += network

mainwindow.h中引入QHostInfo

#include <QHostInfo>

mainwindow.cpp中:

QString hostName = QHostInfo::localHostName();   //本机主机名

QHostInfo hostInfo = QHostInfo::fromName(hostName);    //本机IP地址
QList<QHostAddress> addList = hostInfo.addresses();  //IP地址表 
addList.count();  //总共IP地址数据条数
//访问IP地址表第三条地址
QHostAddress aHost = addList.at(2);

获取IP地址协议类型:

//protocol()表示当前IP地址的协议类型,IPv4、IPv6以及其他类型
//获取aHost的地址类型,下面这段代码输出为:QAbstractSocket::IPv6Protocol,表示是IPv6地址
qDebug()<<aHost.protocol()

返回的是我上面取的第三个端口的IP地址的协议类型,protocol()的返回值还有:

QAbstractSocket::IPv6Protocol   //IPv6
QAbstractSocket::IPv4Protocol   //IPv4
QAbstractSocket::AnyIPProtocol  //IPv4或IPv6
QAbstractSocket::UnknownNetworkLayerProtocol  //其他类型

(2)QHostInfo的主要函数:

public:   //需要实例化一个对象来使用
    QList<QHostAddress>  addresses()   //返回主机hostName()的IP地址表
    HostInfoError error()      //如果主机查找失败,返回失败类型
    QString errorString()      //如果主机查找失败,返回错误描述字符串
    QString hostName()         //返回通过IP查找的主机名
    int lookupId()             //返回本次查找的ID

static:   //不一定需要实例化,可直接调用
    void abortHostLookup(int id)    //中断主机查找
    QHostInfo fromName(QString &name)  //返回指定的主机名的IP地址
    QString localDomainName()     //返回本机DNS域名
    QString localHostName()        //返回本机主机名
    int lookupHost(QString &name,QObject *receiver,char *member)  
    //以异步方式根据主机名查找主机的IP地址,并返回一个表示本次查找的ID,可用于abortHostLookup()  

(3)lookupHost的使用:
代码来自:https://blog.csdn.net/qq78442761/article/details/89310690

//mainwindow.h中:
void lookUp(const QHostInfo &host);

//mainwindow.cpp中:
#include "widget.h"
#include "ui_widget.h"
#include <QHostInfo>

Widget::Widget(QWidget *parent) :
    QWidget(parent),
    ui(new Ui::Widget)
{
    ui->setupUi(this);
    QHostInfo::lookupHost("www.baidu.com", this, SLOT(lookUp(QHostInfo)));
}
Widget::~Widget()
{
    delete ui;
}
void Widget::lookUp(const QHostInfo &host)
{
    if(host.error() != QHostInfo::NoError){
        qDebug() << "Lookup failed: " << host.errorString();
        return;
    }
    const auto addresses = host.addresses();
    for(const QHostAddress &address : addresses){
        qDebug() << "address:" << address.toString() << "  hostname:" << host.hostName();
    }
}

2、QNetworkInterface类:

(1)基本使用:
.pro中添加代码:

QT += network

mainwindow.h中引入QNetworkInterface

#include <QNetworkInterface>

mainwindow.cpp中添加代码:

(1-1) 通过allInterfaces网络接口获取ip:

//实例化接口对象并放入list容器中
QList<QNetworkInterface> aaa = QNetworkInterface::allInterfaces();    //获取所有网络接口
qDebug()<<u8"设备个数:"<<aaa.count();     //总共设备个数
qDebug()<<u8"设备名称:"<<aaa.at(3).humanReadableName();     //第4个设备的设备名称
qDebug()<<u8"硬件地址:"<<aaa.at(3).hardwareAddress();     //第4个设备的硬件地址

QList<QNetworkAddressEntry> entrylist = aaa.at(3).addressEntries();     //这里我理解为获取第4个设备的IP地址、子网掩码、广播地址的组
//QNetworkAddressEntry类由网络接口支持,存储了一个IP地址,子网掩码和广播地址
qDebug()<<u8"ip地址:"<<entrylist.at(1).ip().toString();  //第4个设备的第二个端口的ip地址
qDebug()<<u8"子网掩码:"<<entrylist.at(1).netmask().toString();   //第4个设备的第二个端口的子网掩码
qDebug()<<u8"广播地址:"<<entrylist.at(1).broadcast();  //第4个设备的第二个端口的广播地址

(1-2)通过allAddresses获取ip:
如果不需要子网掩码和广播地址,只获取ip地址时,可以用allAddresses(),用法与QHostInfo中的addresses相似。QHostInfoQNetworkInterface获取ip地址的区别在于,QNetworkInterface会返回更多的地址,比如本机的192.168.1.5QHostInfo不会返回这个地址。

QList<QHostAddress> bbb = QNetworkInterface::allAddresses();    //ip地址
QHostAddress bb1 = bbb.at(1);      //端口2的ip地址
qDebug()<<u8"ip地址为:"<<bb1<<u8"ip的协议类型为:"<<bb1.protocol();

(2)QNetworkInterface主要函数:

public:
    QList<QNetworkAddressEntry>  addressEntries()  //返回该网络接口(包括子网掩码和广播地址)的IP地址列表
    QString hardwareAddress()   //返回该接口的低级硬件地址,以太网里就是MAC地址
    QString humanReadableName()    //返回可以读懂的接口名称,如果名称不确定,得到的就是name()函数的返回值
    bool isValid()   //如果接口信息有效就返回true
    QString name()   //返回网络接口名称
static:
    QList<QHostAddress>   allAddresses()   //返回主机上所有IP地址的列表
    QList<QNetworkInterface>   allInterfaces()    //返回主机上所有接口的网络列表    

3、TCP通信:

TCPTransmission Control Protocol)是一种被大多数Internet网络协议(如HTTPFTP)用于数据传输的低级网络协议,它是可靠的、面向流、面向连接的传输协议,特别适用于连续数据的传输。

TCP通信必须先建立TCP连接,通信端分为客户端和服务器端。Qt提供QTcpSocket类和QTcpServer类用于建立TCP通信应用程序。服务器端程序必须使用QTcpServer用于端口监听,建立服务器;QTcpSocket用于建立连接后使用套接字(Socket)进行通信。

在这里插入图片描述

QTcpServer类的主要接口函数:

public:
    void close()    //关闭服务器,停止网络监听
    bool listen()    //在给定IP地址和端口上开始监听,若成功就返回true
    bool isListening()    //返回true表示服务器处于监听状态
    QTcpSocket *nextPendingConnection()    //返回下一个等待接入的连接
    QHostAddress serverAddress()   //如果服务器处于监听状态,返回服务器地址
    quint16 serverPort()    //如果服务器处于监听状态,返回服务器监听端口
    bool waitForNewConnection()    //以阻塞方式等待新的连接

signals:
   void acceptError(QAbstractSocket::SocketError socketError)   //当接收到一个新的连接发生错误时发射此信号,参数socketError描述了错误信息
   void newConnection()     //当有新的连接时发射此信号

protect:
    void incomingConnection(qintptr socketDescriptor)   //当有一个新的连接可用时,QTcpServer内部调用此函数,创建一个QTcpSocket对象,添加到内部可用新连接列表,然后发射newConnection()信号。用户若从QTcpServer继承定义类,可以重定义此函数,但必须调用addPendingConnection()由incomingConnection()调用,将创建的QTcpSocket添加到内部可用新连接列表。

服务器端程序首先需要用QTcpServer::listen()开始服务器端监听,可以指定监听的IP地址和端口,一般一个服务程序之间听某个端口的网络连接。
当有新的客户端接入时,QTcpServer内部的incomingConnection()函数会创建一个与客户端连接的QTcpSocket对象,然后发射信号newConnection()。在newConnection()信号的槽函数中,可以用nextPendingConnection()接受客户端的连接,然后使用QTcpSocket与客户端通信。
所以在客户端与服务器建立TCP连接后,具体的数据通信是通过QTcpSocket完成的。QTcpSocket类提供了TCP协议的接口,可以用QTcpSocket类实现标准的网络通信协议,如POP3SMTPNNTP,也可以设计自定义协议。
QTcpSocket是从QIODevice间接继承的类,所以具有流读写的功能。

在这里插入图片描述
QTcpSocket类除了构造函数和析构函数,其他函数都是从QAbstractSocket继承或重定义的。

QAbstractSocket用于TCP通信的主要接口函数:

public:
    void connectToHost(QHostAddress &address,quint16 port,) //以异步方式连接到指定IP地址和端口的TCP服务器,连接成功后会发射connected()信号
    void disconnectFromHost()  //断开Socket,关闭成功后发射disconnected()信号
    bool waitForConnected()   //等待直到建立socket连接
    bool waitForDisconnected()  //等待直到断开socket连接
    QHostAddress localAddress()  //返回本socket的地址
    quint16 localPort()    //返回本socket的端口
    QHostAddress peerAddress()    //在已连接状态下,返回对方socket的地址
    QString peerName()        //返回connectToHost()连接到的对方的主机名
    quint16 peerPort()       //在已连接状态下,返回对方socket的端口
    qint64 readBufferSize()  //返回内部读取缓冲区的大小,该大小决定了read()和readAll()函数能读出的数据的大小
    void setReadBufferSize(qint64 size)  //设置内部读取缓冲区大小
    qint64 bytesAvailable()       //返回需要读取的缓冲区的数据的字节数
    bool canReadLine()           //如果有行数据要从socket缓冲区读取,就返回true
    SocketState state()         //返回socket当前的状态

signals:
    void connected()    //connectToHost()成功连接到服务器后发射此信号
    void disconnected()  //当socket断开连接后发射此信号  
    void error(QAbstractSocket::SocketError socketError)   //当socket发生错误时发射此信号
    void hostFound()           //调用connectToHost()找到主机后发射此信号
    void ststeChanged(QAbstractSocket::SocketState socketState)    //当Socket的状态变化时发射此信号,参数socketState表示了socket当前的状态
    void readyRead()   //当缓冲区有新数据需要读取时发射此信号,在此信号的槽函数里读取缓冲区的数据

TCP客户端使用QTcpSocketTCP服务器建立连接并通信。

客户端的QTcpSocket实例首先通过connectToHost()尝试连接到服务器,需要指定服务器的IP地址和端口。connectToHost()是异步方式连接服务器,不会阻塞程序运行,连接后发射connected()信号。
如果需要使用阻塞方式连接服务器,则使用waitForConnected()函数阻塞程序运行,直到连接成功或失败。例如:

socket->connectToHost("192.168.1.100",1340);
    if(socket->waitForConnected(1000))
        qDebug("Connected!")

与服务器端建立socket连接后,就可以向缓冲区写数据或从接收缓冲区读取数据,实现数据的通信。当缓冲区优信数据进入时,会发射readyRead()信号,一般在此信号的槽函数里读取缓冲区数据。
QTcpSocket是从QIODeive间接继承的,所以可以使用数据读写功能。一个QTcpSocket实例既可以接收数据也可以发送数据,且接收与发射是异步工作的,有各自的缓冲区。

TCPServer程序具有如下的功能:
根据指定IP地址(本机地址)和端口打开网络监听,有客户端连接时创建socket连接;
采用基于行的数据通信协议,可以接收客户端发来的消息,也可以向客户端发送消息;
在状态栏显示服务器监听状态和socket的状态。

TCPClient程序具有如下功能:
通过IP地址和端口号连接到服务器;
采用基于行的数据通信协议,与服务器端收到消息;
处理QTcpSocket的StateChange()信号,在状态栏显示socket的状态。

接下来看尝试着使用TCP:(本机ip192.168.1.5,由于暂时只有一台电脑,所以客户端和服务端程序都运行在一台主机上,以下操作链接都会是建立在连接本地的端口上)

(1)简单的tcp连接:

首先,在Qt Creator中创建一个服务器端的项目 tcpServer_test

//在tcpServer_test.pro中引入network
QT += network

//在mainwindow.h中引入TCP
#include <QTcpServer>
public:
    QTcpServer *tcpServer;
public slots:
    void onNewConnection();    //连接成功槽函数
    
//在mainwindow.cpp中(ui中放置了一个label)
#include "mainwindow.h"
#include "ui_mainwindow.h"
MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
    , ui(new Ui::MainWindow)
{
    ui->setupUi(this);
    this->setWindowFlag(Qt::WindowStaysOnTopHint);
    this->setWindowTitle(u8"服务器端程序");
    tcpServer = new QTcpServer(this);
    connect(tcpServer,SIGNAL(newConnection()),this,SLOT(onNewConnection()));  //TCP建立新连接时触发
    tcpServer->listen(QHostAddress::Any,8888);   //监听本地任意ip地址的8888端口是否被TCP连接//必须
    ui->label->setText(u8"正在监听");
}
void MainWindow::onNewConnection()   //TCP新连接时触发
{
  ui->label->setText(u8"连接成功");
}
MainWindow::~MainWindow()
{
    delete ui;
}

(注:QHostAddress除了Any,还有NullLocalHostLocalHostIPv6BroadcastAny。)

接着,打开Qt Creator,再创建一个客户端项目 tcpSocket_test:(这里两个项目创建在同一台主机上)

//在tcpSocket_test.pro中引入network
QT += network

//mainwindow.h中引入QTcpSocket
#include <QTcpSocket>
QTcpSocket *tcpSocket;   //客户端套接字

//mainwindow.cpp中
#include "mainwindow.h"
#include "ui_mainwindow.h"

MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
    , ui(new Ui::MainWindow)
{
    this->setWindowFlag(Qt::WindowStaysOnTopHint);
    ui->setupUi(this);
    this->setWindowTitle(u8"客户端程序");
    tcpSocket = new QTcpSocket(this);   //初始化TCP socket对象
    tcpSocket->connectToHost("192.168.1.5", 8888);   //TCP连接的IP和端口,这里192.168.1.5表示本地//必须
}
MainWindow::~MainWindow()
{
    delete ui;
}

运行结果:

先执行服务器程序:
在这里插入图片描述
然后执行客户端程序:
在这里插入图片描述
服务器端检测到新的TCP连接,执行槽函数,简单的连接就完成了。

注:
报错:cannot retrieve debugging output.
由于同时打开了两个Qt Creator所引起的错误,不影响。

(2)传递tcp连接状态到服务端:

修改服务端程序

首先,在ui中放置3个label和1个pushButton

mainwindow.h中新增函数和声明套接字对象:

class MainWindow : public QMainWindow
{
    Q_OBJECT

public:
    MainWindow(QWidget *parent = nullptr);
    ~MainWindow();
    QTcpServer *tcpServer;   //tcpServer用于服务端套接字
    QTcpSocket *tcpSocket;   //tcpSocker用于接收客户端的客户端套接字

private:
    Ui::MainWindow *ui;

public slots:
    void onNewConnection();  //新连接
    void onSocketStateChange(QAbstractSocket::SocketState socketState);   //连接状态改变
    void onClientConnected();    //已连接
    void onClientDisconnected();   //未连接
    void close_tcpsocket();    //关闭连接
    //void onSocketReadyRead();   //读取socket传入的数据
};
#endif // MAINWINDOW_H

mainwindow.cpp中修改并添加槽函数:

#include "mainwindow.h"
#include "ui_mainwindow.h"

MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
    , ui(new Ui::MainWindow)
{
    ui->setupUi(this);
    this->setWindowTitle(u8"服务器端程序");
    this->setWindowFlag(Qt::WindowStaysOnTopHint);
    tcpServer = new QTcpServer(this);
    connect(tcpServer,SIGNAL(newConnection()),this,SLOT(onNewConnection()));
    tcpServer->listen(QHostAddress::Any,8888);
    ui->label_3->setText(u8"正在监听");
}

//新连接
void MainWindow::onNewConnection()
{
    ui->label_3->setText(u8"已经连接");
    tcpSocket = tcpServer->nextPendingConnection();   //获取与接入设备连接通信的QTcpSocket对象实例tcpSocket
    connect(tcpSocket,SIGNAL(),this,SLOT(onClientConnected()));
    onClientConnected();
    connect(tcpSocket, SIGNAL(disconnected()),this,SLOT(onClientConnected()));
    connect(tcpSocket,SIGNAL(stateChanged(QAbstractSocket::SocketState)),this,SLOT(onSocketStateChange(QAbstractSocket::SocketState)));
    onSocketStateChange(tcpSocket->state());
    connect(ui->pushButton,SIGNAL(clicked()),this,SLOT(close_tcpsocket()));
    //connect(tcpSocket,SIGNAL(readyRead()),this,SLOT(onSocketReadyRead()));
}

void MainWindow::close_tcpsocket()
{
    tcpServer->close();    //停止监听
    ui->label_3->setText(u8"结束监听");
}

//TCP连接ip和端口
void MainWindow::onClientConnected()    //连接时触发的槽函数
{
    ui->label->setText(u8"ip地址:" + tcpSocket->peerAddress().toString()   //ip地址
                       + "\n" + u8"端口:" + tcpSocket->peerPort());     //端口
}

//断开连接
void MainWindow::onClientDisconnected()
{
    ui->label->setText(u8"断开连接");
    tcpSocket->deleteLater();   //不会立即释放,延迟删除
}

//TCP连接状态切换
void MainWindow::onSocketStateChange(QAbstractSocket::SocketState socketState)
{
    switch(socketState)   //连接状态
    {
    case QAbstractSocket::UnconnectedState:  //未连接
        ui->label_2->setText(u8"Socket 状态:UnconnectedState");    break;
    case QAbstractSocket::HostLookupState:
        ui->label_2->setText(u8"Socket 状态:HostLookupState");    break;
    case QAbstractSocket::ConnectedState:
        ui->label_2->setText(u8"Socket 状态:ConnectedState");    break;
    case QAbstractSocket::BoundState:
        ui->label_2->setText(u8"Socket 状态:BoundState");    break;
    case QAbstractSocket::ClosingState:
        ui->label_2->setText(u8"Socket 状态:ClosingState");    break;
    case QAbstractSocket::ListeningState:
        ui->label_2->setText(u8"Socket 状态:ListeningState");
    case QAbstractSocket::ConnectingState:
        ui->label_2->setText(u8"Socket 状态:ConnectingState");    break;
    }
}

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

运行结果:

运行服务端程序:
在这里插入图片描述
运行客户端程序:
在这里插入图片描述

(3)TCPClient的数据通信:

TCP服务器端和客户端之间通过QTcpSocket通信时,需要规定两者之间的通信协议,即传输的数据内容如何解析。QTcpSocket间接继承于QIODevice,所以支持流读写功能。
Socket之间通信协议一般有两种,基于行的或基于数据块的。

基于行的数据通信协议一般用于纯文本数据的通信,每一行数据以一个换行符结束。canReadLine()函数判断是否有新的一行数据需要读取,再用readLine()函数读取一行数据,例如:

while(tcpClient->canReadLine())
        ui->plainTextEdit->appendPlainText("[in] " +tcpClient->readLine());

基于块的数据通信协议用于一般的二进制数据的传输,需要自定义具体的格式。

(3-1)客户端向服务器端发送数据:

服务器端程序:
(注:这里我又添加了一个label_4用来显示客户端传过来的数据)

//mainwindow.h中的信息接收槽函数注释去掉
void onSocketReadyRead();   //读取socket传入的数据

//mainwindow.cpp中的槽函数连接注释去掉
connect(tcpSocket,SIGNAL(readyRead()),this,SLOT(onSocketReadyRead()));  //读取缓冲区数据
//写槽函数功能
void MainWindow::onSocketReadyRead()
{
    while(tcpSocket->canReadLine())
        ui->label_4->setText(u8"接收到数据:"+tcpSocket->readLine());
}

readyRead()信号会在缓冲区接收到数据的时候触发。

客户机端程序:
由于客户端程序直接运行就触发了,而且只加了几句话,就直接把代码贴上来了。

MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
    , ui(new Ui::MainWindow)
{
    ui->setupUi(this);
    this->setWindowTitle(u8"客户端程序");
    this->setWindowFlag(Qt::WindowStaysOnTopHint);

    tcpSocket = new QTcpSocket(this);
    tcpSocket->connectToHost("192.168.1.5", 8888);

    QString msg = u8"你好!";
    QByteArray str = msg.toUtf8();
    str.append('\n');
    tcpSocket->write(str);
}

运行结果:
运行服务端程序:
在这里插入图片描述
运行客户端程序:
在这里插入图片描述

(3-2)服务器端向客户端发送数据:

使用方法和客户端发送数据给服务器端一样,把代码位置交换,服务器端代码放到客户端,客户端代码放到服务端来就行了。

在客户端程序:(ui中放置了一个用来显示数据的label
还是一样直接放代码:

//mainwindow.h中声明槽函数
public slots:
    void onSocketReadyRead();
    
//mainwindow.cpp中
#include "mainwindow.h"
#include "ui_mainwindow.h"

MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
    , ui(new Ui::MainWindow)
{
    ui->setupUi(this);
    this->setWindowTitle(u8"客户端程序");
    this->setWindowFlag(Qt::WindowStaysOnTopHint);

    tcpSocket = new QTcpSocket(this);
    tcpSocket->connectToHost("192.168.1.5", 8888);

    QString msg = u8"你好!";
    QByteArray str = msg.toUtf8();
    str.append('\n');
    tcpSocket->write(str);
    
    //接收服务器数据的信号槽
    connect(tcpSocket,SIGNAL(readyRead()),this,SLOT(onSocketReadyRead()));
}

//槽函数
void MainWindow::onSocketReadyRead()
{
    //读取缓冲区文本
    while(tcpSocket->canReadLine())
        ui->label->setText(u8"接收到数据:"+tcpSocket->readLine());
}

在服务端程序:
由于我TCP连接写在客户端的UI初始化中,并没有使用按钮来构建连接,所以必须先运行服务器端,所以,我把数据传递写在了按钮里,那就重写之前的关闭监听的按钮。

void MainWindow::close_tcpsocket()
{
    //tcpServer->close();    //停止监听
    
    //发送数据给客户端
    QString msg = u8"你好客户端!";
    QByteArray str = msg.toUtf8();
    str.append('\n');
    tcpSocket->write(str);

    ui->label_3->setText(u8"结束监听");
}

运行结果:
运行服务器端和客户端:

在这里插入图片描述

注: TCPServer只允许一个TCPClient客户端接入,而一般的TCP服务器程序允许多个客户端接入,为了使每个socket连接独立通信互不影响,一般采用多线程,即为一个socket连接创建一个线程。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值