Qt5开发学习之网络与通信(十二)

在应用程序开发中网络编程非常重要,目前互联网通行的TCP/IP协议,自上而下分为四层:应用层、传输层、网络层和网络接口层。实际编写网络应用程序时只使用到传输层和应用层,所用到的协议主要为UDP、TCP、HTTP和FTP。
虽然目前主流的操作系统都提供了统一的套接字抽象编程接口,用于编写不同层次的网络程序,但是这种方式比较繁琐,甚至有时需要应用底层操作系统相关数据结构,Qt提供了一个网络模块QtNetwork完美解决了问题。

获取本机网络信息

在网络应用中,经常需要获得本机的主机名、IP地址和硬件地址等信息。运用QHostInfo、QNetworkInterface、QNetworkAddressEntry可获得本机信息。
实例使用网络模块获取主机名和IP地址:

#include <QWidget>
#include <QLabel>
#include <QPushButton>
#include <QLineEdit>
#include <QGridLayout>
#include <QMessageBox>

// 使用网络模块需要在pro文件中添加:QT += network
#include <QHostInfo>
#include <QNetworkInterface>

class NetworkInfo : public QWidget
{
    Q_OBJECT

public:
    NetworkInfo(QWidget *parent = 0);
    ~NetworkInfo();

    void getHostInformation();

private:
    QLabel *hostLabel;
    QLineEdit *hostLineEdit;
    QLabel *ipLabel;
    QLineEdit *ipLineEdit;

    QPushButton *detailBtn;
    QGridLayout *mainLayout;

public slots:
    void slotDetail();
};

#include "NetworkInfo.h"

NetworkInfo::NetworkInfo(QWidget *parent)
    : QWidget(parent)
{
    this->resize(500, 500);

    hostLabel = new QLabel(tr("HostName"));
    hostLineEdit = new QLineEdit;
    ipLabel = new QLabel(tr("ipAdress"));
    ipLineEdit = new QLineEdit;

    detailBtn = new QPushButton(tr("Detail"));
    mainLayout = new QGridLayout(this);
    mainLayout->addWidget(hostLabel, 0, 0);
    mainLayout->addWidget(hostLineEdit, 0, 1);
    mainLayout->addWidget(ipLabel, 1, 0);
    mainLayout->addWidget(ipLineEdit, 1, 1);
    mainLayout->addWidget(detailBtn, 2, 0, 1, 2);

    getHostInformation();
    connect(detailBtn, &QPushButton::clicked, this, &NetworkInfo::slotDetail);
}

NetworkInfo::~NetworkInfo()
{

}

void NetworkInfo::getHostInformation()
{
    // 获得主机名
    QString localHostName = QHostInfo::localHostName();
    hostLineEdit->setText(localHostName);

    // 根据主机名获得相关主机信息
    QHostInfo hostInfo = QHostInfo::fromName(localHostName);
    // 获得主机IP地址列表
    QList<QHostAddress> listAddress = hostInfo.addresses();
    if (!listAddress.isEmpty())
    {
        ipLineEdit->setText(listAddress.first().toString());
    }
}

void NetworkInfo::slotDetail()
{
    QString detail = "";
    // 主机IP地址和网络接口列表
    QList<QNetworkInterface> list = QNetworkInterface::allInterfaces();
    for (int i = 0; i < list.count(); ++i)
    {
        QNetworkInterface interface = list.at(i);
        // 获得网络接口名称
        detail = detail + tr("设备:") + interface.name() + "\n";
        // 获得硬件名称
        detail = detail + tr("硬件地址:") + interface.hardwareAddress() + "\n";
        QList<QNetworkAddressEntry> entryList = interface.addressEntries();
        for (int j = 0; j < entryList.count(); ++j)
        {
            QNetworkAddressEntry entry = entryList.at(j);
            // 没个接口包含多个IP地址
            detail = detail + "\t" + tr("IP地址:") + entry.ip().toString() + "\n";
            detail = detail + "\t" + tr("子网掩码:") + entry.netmask().toString() + "\n";
            detail = detail + "\t" + tr("广播地址:") + entry.broadcast().toString() + "\n";
        }
    }
    QMessageBox::information(this, tr("Detail"), detail);
}

获取到的如图:
这里写图片描述


基于UDP的网络广播程序

用户数据报协议(User Data Protocol,UDP)是一种简单轻量级、不可靠、面向数据报、无连接的传输层协议,可以应用在可靠性不是十分高的场合,如短消息和广播信息等。

适合应用的场景有:
1、网络数据大多为短消息;
2、拥有大量客户端;
3、对数据安全性无特殊要求;
4、网络负担非常重,但对相应速度要求高。

UDP协议的工作原理:UDP客户端向UDP服务器发送一定长度的请求报文,报文大小的限制与各系统的协议实现相关,但不能超过其下层IP协议规定的64K;UDP服务器同样以报文形式做出响应。如果服务器未收到此请求,客户端不会重发,因此报文的传输是不可靠的。例如-QQ就是用UDP协议发送消息的, 因此有时会出现收不到消息的情况。

UDP的客户端与服务器是不建立连接,直接调用发送和接收函数进行数据通信的,Qt通过QUdpSocket类实现UDP协议的编程。

首先模拟UDP服务器编程:

#include <QDialog>
#include <QLabel>
#include <QPushButton>
#include <QLineEdit>
#include <QVBoxLayout>
#include <QUdpSocket>
#include <QTimer>

class UDPServer : public QDialog
{
    Q_OBJECT

public:
    UDPServer(QWidget *parent = 0, Qt::WindowFlags f = 0);
    ~UDPServer();

private:
    QLabel *timerLabel;
    QLineEdit *textLineEdit;
    QPushButton *startBtn;
    QVBoxLayout *mainLayout;

    int port;
    bool isStarted;
    QUdpSocket *udpSocket;
    QTimer *timer;

public slots:
    void StartBtnClicked();
    void timeout();
};
#include "UDPServer.h"
#include <QHostAddress>

UDPServer::UDPServer(QWidget *parent, Qt::WindowFlags f)
    : QDialog(parent, f)
{
    setWindowTitle(tr("UDPServer"));

    timerLabel = new QLabel(tr("计时器:"));
    textLineEdit = new QLineEdit(this);
    startBtn = new QPushButton(tr("Start"));
    mainLayout = new QVBoxLayout(this);
    mainLayout->addWidget(timerLabel);
    mainLayout->addWidget(textLineEdit);
    mainLayout->addWidget(startBtn);

    // 设置UDP的端口号参数,服务器
    port = 9000;
    isStarted = false;
    udpSocket = new QUdpSocket(this);
    timer = new QTimer(this);

    connect(startBtn, &QPushButton::clicked, this, &UDPServer::StartBtnClicked);
    connect(timer, &QTimer::timeout, this, &UDPServer::timeout);
}

UDPServer::~UDPServer()
{

}

void UDPServer::StartBtnClicked()
{
    if (!isStarted)
    {
        startBtn->setText(tr("Stop"));
        timer->start(1000);
        isStarted = true;
    }
    else
    {
        startBtn->setText(tr("Start"));
        isStarted = false;
        timer->stop();
    }
}

// 完成向端口发送广播信息的功能
void UDPServer::timeout()
{
    QString msg = textLineEdit->text();

    int length = 0;
    if (msg == "")
    {
        return;
    }

    //  QHostAddress::Broadcast指定向地址广播发送
    if ((length = udpSocket->writeDatagram(msg.toUtf8(), msg.length(), QHostAddress::LocalHost, port)) != msg.length())
    {
        return;
    }
}

接下来是接收服务器发送数据的客户端:

#include <QDialog>
#include <QVBoxLayout>
#include <QTextEdit>
#include <QPushButton>
#include <QUdpSocket>

class UDPClient : public QDialog
{
    Q_OBJECT

public:
    UDPClient(QWidget *parent = 0);
    ~UDPClient();

public slots:
    void CloseBtnClicked();
    void DataReceived();
private:
    QTextEdit *receiveTextEdit;
    QPushButton *closeBtn;
    QVBoxLayout *mainLayout;

    int port;
    QUdpSocket *udpSocket;
};
#include "UDPClient.h"
#include <QMessageBox>
#include <QHostAddress>

UDPClient::UDPClient(QWidget *parent)
    : QDialog(parent)
{
    setWindowTitle(tr("UDPClient"));

    receiveTextEdit = new QTextEdit(this);
    closeBtn = new QPushButton(tr("Close"), this);
    mainLayout = new QVBoxLayout(this);
    mainLayout->addWidget(receiveTextEdit);
    mainLayout->addWidget(closeBtn);

    port = 9000;
    udpSocket = new QUdpSocket(this);

    // 绑定到指定端口上
    bool result = udpSocket->bind(port,QAbstractSocket::DontShareAddress);
    if (!result)
    {
        QMessageBox::information(this, tr("error"), tr("udp socket error"));
        return;
    }

    connect(closeBtn, &QPushButton::clicked, this, &UDPClient::CloseBtnClicked);
    //connect(udpSocket, &QUdpSocket::readyRead, this, &UDPClient::DataReceived);
    connect(udpSocket, SIGNAL(readyRead()), this, SLOT(DataReceived()));
}

UDPClient::~UDPClient()
{

}

void UDPClient::CloseBtnClicked()
{
    close();
}

// 响应readyRead信号,一但UDPsocket有数据可读时即可通过 readDatagram()将数据显示出来
void UDPClient::DataReceived()
{
    // 判断UdpSocket中是否有数据可读
    while (udpSocket->hasPendingDatagrams())
    {
        QByteArray datagram;
        datagram.resize(udpSocket->pendingDatagramSize());

        udpSocket->readDatagram(datagram.data(), datagram.size());
        QString msg = datagram.data();
        receiveTextEdit->insertPlainText(msg);
    }
}

完成之后,在服务端的输入框内输入数据点击start。客户端就会自动将接收到的数据显示出来:
这里写图片描述


基于TCP的网络聊天室程序

传输控制协议(Transmission Control Protocol ,TCP)是一种可靠地、面向连接的、面向数据流的传输协议,许多高层应用协议(HTTP、FTP)都是以它为基础的,TCP协议非常适合数据的连续传输。

Qt中使用QTcpSocket和QTcpServer类实现TCP编程。

首先创建TCP服务端:
第一步,编写服务器的处理客户端的逻辑

#include <QObject>
#include <QTcpSocket>

class TcpClient : public QTcpSocket
{
    Q_OBJECT
public:
    TcpClient(QObject *parent = 0);
signals:
    void updateClients(QString, int);
    void disconnected(int);
protected slots:
    void dataReceived();
    void slotDisConnect();
};
#include "TcpClient.h"

TcpClient::TcpClient(QObject *parent)
{
    // readyRead()当有数据来到时触发
    connect(this, &TcpClient::readyRead, this, &TcpClient::dataReceived);
    // disconnected()在断开连接时触发
    connect(this, SIGNAL(disconnected()), this, SLOT(slotDisConnect()));
}

// 当有数据到来时,触发信号
void TcpClient::dataReceived()
{
    while (bytesAvailable() > 0)
    {
        int length = bytesAvailable();
        char buf[1024];
        read(buf, length);

        QString msg = buf;
        // 通知服务器向聊天室内所有成员发送广播信息
        emit updateClients(msg, length);
    }
}

void TcpClient::slotDisConnect()
{
    emit disconnected(this->socketDescriptor());
}

第二步,新建一个Server类,处理服务端逻辑

#include <QObject>
#include <QTcpServer>

#include "TcpClient.h"

class Server : public QTcpServer
{
    Q_OBJECT
public:
    explicit Server(QObject *parent = 0, int port = 0);
    // QList用来保存每一个与客户端连接的TcpClientSocket
    QList<TcpClient *> tcpClientList;
signals:
    void updateServer(QString, int);
public slots:
    void updateClients(QString, int);
    void slotDisconnected(int);
protected:
    void incomingConnection(int socketDescriptor);
};
#include "Server.h"

Server::Server(QObject *parent, int port) : QTcpServer(parent)
{
    // QHostAddress::Any对指定端口的任意地址进行监听,Ipv4的任意地址为0.0.0.0
    listen(QHostAddress::Any, port);
    // QHostAddress::Null 表示一个空地址
    // QHostAddress::LocalHost 表示Ipv4的本机地址为127.0.0.1
    // QHostAddress::Broadcast 表示广播地址为255.255.255.255
}

// 将任意客户端发来的信息进行广播
void Server::updateClients(QString msg, int length)
{
    // 通知服务器更新相应的显示状态
    emit updateServer(msg, length);

    // 实现信息的广播
    for (int i = 0; i < tcpClientList.count(); ++i)
    {
        QTcpSocket *item = tcpClientList.at(i);
        if (item->write(msg.toLatin1(), length) != length)
        {
            continue;
        }
    }
}

// 从tcpClientList列表中将断开连接的对象删除
void Server::slotDisconnected(int discriptor)
{
    for (int i = 0; i < tcpClientList.count(); ++i)
    {
        QTcpSocket *item = tcpClientList.at(i);
        if (item->socketDescriptor() == discriptor)
        {
            tcpClientList.removeAt(i);
            return;
        }
    }
    return;
}

// 当出现一个新的连接时,触发函数,socketDescriptor指定了连接的Socket描述符
void Server::incomingConnection(int socketDescriptor)
{
    TcpClient *tmp_client = new TcpClient(this);
    connect(tmp_client, SIGNAL(updateClients(QString,int)), this, SLOT(updateClients(QString,int)));
    connect(tmp_client, SIGNAL(disconnected(int)), this, SLOT(slotDisconnected(int)));

    tmp_client->setSocketDescriptor(socketDescriptor);
    tcpClientList.append(tmp_client);
}

最后,制作服务端界面

#include <QDialog>
#include <QListWidget>
#include <QLabel>
#include <QLineEdit>
#include <QPushButton>
#include <QGridLayout>

#include "Server.h"

class TcpServer : public QDialog
{
    Q_OBJECT

public:
    TcpServer(QWidget *parent = 0);
    ~TcpServer();

private:
    QListWidget *contentListWidget;
    QLabel *portLabel;
    QLineEdit *portLineEdit;
    QPushButton *createBtn;
    QGridLayout *mainLayout;

    int port;
    Server *server;

public slots:
    void slotCreateServer();
    void updateServer(QString, int);
};
#include "TcpServer.h"

TcpServer::TcpServer(QWidget *parent)
    : QDialog(parent)
{
    setWindowTitle(tr("TcpServer"));

    contentListWidget = new QListWidget;
    portLabel = new QLabel(tr("端口:"));
    portLineEdit = new QLineEdit;
    createBtn = new QPushButton(tr("创建聊天室"));
    mainLayout = new QGridLayout(this);
    mainLayout->addWidget(contentListWidget, 0, 0, 1, 2);
    mainLayout->addWidget(portLabel, 1, 0);
    mainLayout->addWidget(portLineEdit, 1, 1);
    mainLayout->addWidget(createBtn, 2, 0, 1, 2);

    port = 8010;
    portLineEdit->setText(QString::number(port));
    connect(createBtn, &QPushButton::clicked, this, &TcpServer::slotCreateServer);
}

TcpServer::~TcpServer()
{

}

// 创建一个TCP服务器
void TcpServer::slotCreateServer()
{
    server = new Server(this, port);
    connect(server, SIGNAL(updateServer(QString,int)), this, SLOT(updateServer(QString,int)));
    createBtn->setEnabled(false);
}

// 更新服务器上的显示信息
void TcpServer::updateServer(QString msg, int length)
{
    contentListWidget->addItem(msg.left(length));
}

接下来创建TCP聊天室的客户端:

#include <QDialog>
#include <QListWidget>
#include <QLabel>
#include <QLineEdit>
#include <QPushButton>
#include <QGridLayout>

#include <QHostAddress>
#include <QTcpSocket>

class TcpClient : public QDialog
{
    Q_OBJECT

public:
    TcpClient(QWidget *parent = 0);
    ~TcpClient();

private:
    QListWidget *contentListWidget;
    QLineEdit *sendLineEdit;
    QPushButton *sendBtn;
    QLabel *userNameLabel;
    QLineEdit *userNameLineEdit;
    QLabel *serverIPLabel;
    QLineEdit *serverIPLineEdit;
    QLabel *portLabel;
    QLineEdit *portLineEdit;
    QPushButton *enterBtn;
    QGridLayout *mainLayout;

    bool status;
    int port;
    QHostAddress *serverIP;
    QString userName;
    QTcpSocket *tcpSocket;

public slots:
    void slotEnter();
    void slotConnect();
    void slotDisconnect();
    void slotDataRecevied();
    void slotSend();
};
#include "TcpClient.h"

#include <QMessageBox>
#include <QHostInfo>

TcpClient::TcpClient(QWidget *parent)
    : QDialog(parent)
{
    setWindowTitle(tr("TCP Client"));

    contentListWidget = new QListWidget;
    sendLineEdit = new QLineEdit;
    sendBtn = new QPushButton(tr("发送"));

    userNameLabel = new QLabel(tr("用户名:"));
    userNameLineEdit = new QLineEdit;

    serverIPLabel = new QLabel(tr("IP:"));
    serverIPLineEdit = new QLineEdit;

    portLabel = new QLabel(tr("端口:"));
    portLineEdit = new QLineEdit;

    enterBtn = new QPushButton(tr("进入聊天室"));
    mainLayout = new QGridLayout(this);
    mainLayout->addWidget(contentListWidget, 0, 0, 1, 2);
    mainLayout->addWidget(sendLineEdit, 1, 0);
    mainLayout->addWidget(sendBtn, 1, 1);
    mainLayout->addWidget(userNameLabel, 2, 0);
    mainLayout->addWidget(userNameLineEdit, 2, 1);
    mainLayout->addWidget(serverIPLabel, 3, 0);
    mainLayout->addWidget(serverIPLineEdit, 3, 1);
    mainLayout->addWidget(portLabel, 4, 0);
    mainLayout->addWidget(portLineEdit, 4, 1);
    mainLayout->addWidget(enterBtn, 5, 0, 1, 2);

    status = false;
    port = 8010;
    portLineEdit->setText(QString::number(port));
    serverIP = new QHostAddress();

    connect(sendBtn, &QPushButton::clicked, this, &TcpClient::slotSend);
    connect(enterBtn, &QPushButton::clicked, this, &TcpClient::slotEnter);

    sendBtn->setEnabled(false);
}

TcpClient::~TcpClient()
{

}

void TcpClient::slotEnter()
{
    if ( !status )
    {
        // 获取输入的IP地址
        QString ip = serverIPLineEdit->text();
        // 判断给定的IP地址是否能够被正确解析
        if (!serverIP->setAddress(ip))
        {
            QMessageBox::information(this, tr("error"), tr("Server ip adress error"));
            return;
        }
        if (userNameLineEdit->text() == "")
        {
            QMessageBox::information(this, tr("error"), tr("User name error"));
            return;
        }
        userName = userNameLineEdit->text();

        // 创建了一个TCP对象,并建立了连接
        tcpSocket = new QTcpSocket(this);
        connect(tcpSocket, &QTcpSocket::connected, this, &TcpClient::slotConnect);
        connect(tcpSocket, &QTcpSocket::disconnected, this, &TcpClient::slotDisconnect);
        connect(tcpSocket, &QTcpSocket::readyRead, this, &TcpClient::slotDataRecevied);
        // 与TCP服务器端连接,连接成功后发出connected()信号
        tcpSocket->connectToHost(*serverIP, port);

        status = true;
    }
    else
    {
        // 客户端主动断开连接并向服务器发送提示消息
        int length = 0;
        QString msg = userName + tr(":Leave That Room");

        if ( (length = tcpSocket->write(msg.toLatin1()), msg.length()) != msg.length())
        {
             return;
        }

        // 与TCP服务器断开连接,断开后发送disconnect信号
        tcpSocket->disconnectFromHost();
        status = false;
    }
}

void TcpClient::slotConnect()
{
    sendBtn->setEnabled(true);
    enterBtn->setText(tr("离开"));

    int length = 0;
    QString msg = userName + tr(":Enter This Room");
    if ((length = tcpSocket->write(msg.toLatin1(), msg.length())) != msg.length())
    {
        return;
    }
}

void TcpClient::slotDisconnect()
{
    sendBtn->setEnabled(false);
    enterBtn->setText(tr("进入聊天室"));
}

void TcpClient::slotDataRecevied()
{
    while (tcpSocket->bytesAvailable() > 0)
    {
        QByteArray datagram;
        datagram.resize(tcpSocket->bytesAvailable());

        tcpSocket->read(datagram.data(), datagram.size());

        QString msg = datagram.data();
        contentListWidget->addItem(msg.left(datagram.size()));
    }
}

void TcpClient::slotSend()
{
    if (sendLineEdit->text() == "")
    {
        return;
    }

    QString msg = userName + ":" + sendLineEdit->text();
    tcpSocket->write(msg.toLatin1(), msg.length());
    sendLineEdit->clear();
}

Qt网络应用初步开发

QUdpSocket、QTcpSocke、QTcpServer都是网络传输层上的类,他们封装实现的是低层次的网络进程通信的功能。而Qt网络应用开发是在此基础上进一步实现应用型的协议功能。应用协议(HTTP/FTP/SMTP)运行在TCP/UDP之上。

网络请求由QNetworkRequest类来表示,作为与请求有关的统一容器,在创建请求对象时指定的URL决定了请求使用的协议。QNetworkAccessManager类用于协调网络操作,每当一个请求创建后,该类用来调度他,并发送信号来报告进度;QNetworkReply用于网络请求的应答,他会在请求呗完成调度时由QNetworkAccessManager创建。

  • 1
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值