找回密码
 立即注册
Qt开源社区 门户 查看内容

QT软件开发-第12章 网络编程 12.1~12.2

2019-5-22 20:39| 发布者: admin| 查看: 2550| 评论: 2

摘要: 点击上方蓝字关注我们网络编程在应用程序开发中非常重要,一般的应用程序都会涉及到网络通信。网络编程说的就是互联网通用的TCP/IP协议,TCP/IP的层次分为4层:(1)应用层:属于高层的网络传输协议。例如:HTTP、FT ...


点击上方蓝字关注我们





网络编程在应用程序开发中非常重要,一般的应用程序都会涉及到网络通信。网络编程说的就是互联网通用的TCP/IP协议,TCP/IP的层次分为4层:

(1)应用层:属于高层的网络传输协议。例如:HTTP、FTP、DNS协议等。

(2)传输层:底层的网络传输协议。例如:TCP、UDP、RTP、SCTP协议等。

(3)网络互连层对于TCP/IP来说这是因特网协议。

(4)网络接口层:例如以太网、Wi-Fi、MPLS底层驱动等。

对于应用编程来说,我们只需要用到其中的应用层和传输层(统称为上层)。目前主流的操作系统都有自己的一套上层网络编程接口,比如:windows、linux系统等。但这些编程接口使用起来相对比较繁琐,而且需要依赖底层操作系统的一些数据结构。QT为了解决跨平台的问题,本身提供了一套完整的上层网络编程接口,这套接口包含在QT的network模块内。如果要使用QT网络编程相关的一些类,就需要先包含network模块。包含方式:在当前pro工程文件中加入以下代码。

QT += network

关于QtNetwork模块的详细介绍可以查看QT自带的帮助文档。查找方式截图如下:



图12-0-1 Qt Network模块帮助文档

下表列出了QT5提供的所有网络编程相关的C++类:

QAbstractNetworkCache

实现缓存的接口

QAbstractSocket

所有类型套接字的基础功能

QAuthenticator

认证对象

QDnsDomainNameRecord

存储关于域名信息记录

QDnsHostAddressRecord

存储主机地址信息记录

QDnsLookup

表示DNS查找相关操作

QDnsMailExchangeRecord

DNS MX记录信息

QDnsServiceRecord

DNS SRV记录信息

QDnsTextRecord

DNS TXT存储信息记录

QHostAddress

存放主机的IP地址信息

QHostInfo

关于查询主机名的静态函数

QHttpMultiPart

通过HTTP发送的类似于MIME的多部件消息

QHttpPart

包含了HTTP多部件MIME消息的主体部分

QLocalServer

基于本地套接字的服务器

QLocalSocket

本地套接字

QNetworkAccessManager

允许应用程序发送网络请求和接收应答

QNetworkAddressEntry

保存一个IP地址,连同掩码和广播地址

QNetworkCacheMetaData

缓存信息

QNetworkConfiguration

一个或者多个接入点设置的抽象

QNetworkConfigurationManager

管理系统提供的网络设置

QNetworkCookieJar

实现了一个简单的jar QNetworkCookie对象

QNetworkDiskCache

基本磁盘高速缓存

QNetworkInterface

列出主机的网络地址和网络接口

QNetworkProxy

网络层代理

QNetworkProxyFactory

细粒度代理选择

QNetworkProxyQuery

用于查询代理设置套接字

QNetworkReply

包含了QNetworkAccessManager发送请求时的数据和头

QNetworkRequest

包含了QNetworkAccessManager发送的请求

QNetworkSession

支配系统的接入点,并且在当多个客户接入了同一个接入点的情况下,允许使用会话管理

QSslCertificateExtension

访问X509证书扩展的API

QSslCipher

代表一个SSL加密密码

QSslConfiguration

配置和SSL连接的状态

QSslEllipticCurve

代表了一种密码算法

QSslError

SSL error

QSslKey

私钥和公钥接口

QSslPreSharedKeyAuthenticator

共享密钥认证密码

QSslSocket

客户端和服务器的SSL加密套接字

QTcpServer

基于tcp的服务器

QTcpSocket

TCP套接字

QUdpSocket

udp套接字

表12-0-1 Qt Network模块包含的所有C++类

12.1 获取本机网络信息


获取本机网络信息相关的类如下:QHostInfo、QHostAddress、QNetworkInterface、QNetworkAddressEntry。

12.1.1 获取本机IP地址


下面代码通过QHostAddress和QNetworkInterface类列出本机全部的IP地址。

#include <QApplication>

#include <QHostInfo>

#include <QNetworkInterface>

#include <QLabel>

int main(int argc, char *argv[])

{

    QApplication a(argc, argv);

    QString addr_info;

    addr_info="列出本机全部IP地址:\n";

    QList<QHostAddress> list = QNetworkInterface::allAddresses();

    for(int i=0;i<list.count();i++)

    {

        QHostAddress addr=list.at(i);

        if(addr.protocol() == QAbstractSocket::IPv4Protocol)

        {

          addr_info+=addr.toString();

          addr_info+="\n";

        }

    }

    QLabel label;

    label.setText(addr_info);

    label.show();

    return a.exec();

}



 图12-1-1 获取IP地址

以上代码中主要的函数是allAddresses()函数,该函数是QNetworkInterface类提供的一个静态函数,用于获取本机的所有的IP地址信息,使用QList<QHostAddress>列表方式返回,列表中的每一个成员就是一个QHostAddress类。函数原型如下:

[static] QList<QHostAddress> QNetworkInterface::allAddresses()

QHostAddress类提供一种独立于平台和协议的方式来保存IPv4和IPv6地址。QHostAddress通常与QTcpSocket、QTcpServer、QUdpSocket一起使用,来连接到主机或建立一个服务器。可以通过setAddress()来设置一个主机地址,使用toIPv4Address()、toIPv6Address()或toString()来检索主机地址。通过protocol()来检查协议类型等。下面将介绍QHostAddress常用的一些接口函数用法:

    1. QHostAddress常用的地址枚举

QHostAddress::LocalHost

本地主机IPV4地址。相当于("127.0.0.1")

QHostAddress::LocalHostIPv6

本地主机IPV6地址。相当于("::1")

QHostAddress::Broadcast

广播地址。相当于("255.255.255.255")

QHostAddress::AnyIPv4

IPv4的所有地址.相当于("0.0.0.0")

QHostAddress::AnyIPv6

IPv6的所有地址。相当于("::")

QHostAddress::Any

本地所有地址。包括IPV4和IPV6

    2. 构造QHostAddress

QHostAddress::QHostAddress(quint32 ip4Addr)

QHostAddress::QHostAddress(quint8 *ip6Addr)

QHostAddress::QHostAddress(const QString &address)

    QHostAddress的构造函数中可以传入ipv4和ipv6类型IP地址。还可以通过字符串方式传入。

示例:

QHostAddress ipaddr("192.168.1.123");

    3. 设置IP地址

void QHostAddress::setAddress(quint32 ip4Addr)

void QHostAddress::setAddress(quint8 *ip6Addr)

bool QHostAddress::setAddress(const QString &address)

除了构造函数可以传入IP地址外,也可以通过setAddress函数进行设置。如果需要清除IP地址,可以通过clear()函数实现,clear()将IP地址设置为0.0.0.0。

    4. 获取IP地址

Q_IPV6ADDR QHostAddress::toIPv6Address() const

quint32 QHostAddress::toIPv4Address() const

QString QHostAddress::toString() const   //使用字符串形式返回

以上3个函数用于将QHostAddress对象包含的IP地址使用特定的格式返回。

示例:

QHostAddress addr("192.168.1.1");

addr.toString(); //返回:"192.168.1.1"

addr.toIPv4Address(); //返回:3232235777

Q_IPV6ADDR ipv6_addr=addr.toIPv6Address();

for(int i = 0; i < 16; ++i)

{

    //ipv6_addr[i];

}

    5. 获取主机网络层的通信协议

QAbstractSocket::NetworkLayerProtocol QHostAddress::protocol() const

返回值QAbstractSocket是一个枚举类型,其中:

QAbstractSocket::IPv4Protocol 表示IPv4协议

QAbstractSocket::IPv6Protocol 表示IPv6协议

12.1.2 获取本地网络连接详细信息


下面代码通过QNetworkInterface与QNetworkAddressEntry类获取本机的网络连接详细信息。比如:MAC地址、子网掩码等等。

#include <QApplication>

#include <QHostInfo>

#include <QtNetwork>

#include <QHostInfo>

#include <QDebug>

#include <QLabel>

#include <QNetworkInterface>

#include <QHostAddress>

#include <QMessageBox>

int main(int argc, char *argv[])

{

    QApplication a(argc, argv);

    QString addr_info;

    QList<QNetworkInterface> list = QNetworkInterface::allInterfaces();

    for(int i=0;i<list.count();i++)

    {

        QNetworkInterface inter=list.at(i);

        addr_info+="设备名称:"+inter.name()+"\n";

        addr_info+="MAC地址:"+inter.hardwareAddress()+"\n";

        addr_info+="连接名称:"+inter.humanReadableName()+"\n";

   QList<QNetworkAddressEntry> entry=inter.addressEntries();

        for(int n=0;n<entry.count();n++)

        {

               QNetworkAddressEntry netaddr=entry.at(n);

               addr_info+="IP地址:"+netaddr.ip().toString()+"\n";

               addr_info+="子网掩码:"+netaddr.netmask().toString()+"\n";

        }

    }

    QMessageBox::information(NULL,"本机网络连接详细信息",addr_info,QMessageBox::Ok);

    return a.exec();

}



 图12-1-2 获取本机网络连接详细信息

12.2 搭建TCP服务器与客户端


TCP提供了一种面向连接、可靠的字节流服务。面向连接比较好理解,就是连接双方在通信前需要预先建立一条连接,这犹如实际生活中的打电话。助于可靠性,TCP协议中涉及了诸多规则来保障通信链路的可靠性。因此,对可靠性要求高的数据通信场合,一般使用TCP协议进行传输数据。

QT提供了QTcpSocket和QTcpServer类实现TCP协议编程。其中QTcpSocket类是QAbstractSocket的子类,提供一个TCP套接字,用于建立TCP连接和传输的数据流。QTcpServer类提供了一个基于tcp的服务器搭建方法。这个类可以接受传入的TCP连接,可以选择监听特定的地址或者本机的所有地址。每次有新的客户端连接时会发出newConnection()信号。

下面给出了TCP协议通信的模型图:



 图12-2-1 TCP协议通信模型图

12.2.1 QTcpSocket类介绍


QTcpSocket提供了TCP通信的套接字,该类是QAbstractSocket的子类,下图介绍了QTcpSocket的继承关系。



 图12-2-2 QTcpSocket继承关系图

QTcpSocket类所有的成员函数都继承于QAbstractSocket类和QIODevice类。下面介绍TCP网络编程中会用到的函数接口与相关的信号。

  • QTcpSocket类继承于QAbstractSocket类的相关函数介绍

    1. 绑定端口

bool QAbstractSocket::bind(const QHostAddress &address, quint16 port = 0, BindMode mode = DefaultForPlatform)

bool QAbstractSocket::bind(quint16 port = 0, BindMode mode = DefaultForPlatform)

Bind函数一般用在服务器端,用于将本地地址与一套接口捆绑。

    2. 连接到服务器主机

(1) void QAbstractSocket::connectToHost(const QString &hostName, quint16 port, OpenMode openMode = ReadWrite, NetworkLayerProtocol protocol = AnyIPProtocol)

(2) void QAbstractSocket::connectToHost(const QHostAddress &address, quint16 port, OpenMode openMode = ReadWrite)

connectToHost函数一般在客户端使用,用于连接服务器。其中const QHostAddress &address参数表示填写服务器的IP地址信息,quint16 port参数填写服务器的端口号。

    3. 关闭套接字

void QAbstractSocket::disconnectFromHost()

void QAbstractSocket::abort()

关闭套接字给出了以上两种方法:使用disconnectFromHost函数关闭套接字时,不会立即关闭套接字,会将缓冲区的数据读写完毕后,再发出disconnected()关闭信号。而abort()函数会立即关闭套接字,丢弃缓冲区里所有数据。

    4. 获取本地主机的地址信息

QHostAddress QAbstractSocket::localAddress() const   //获取本地主机的IP地址

quint16 QAbstractSocket::localPort() const  //获取本地主机的端口号

    5. 获取远程主机的地址信息

QHostAddress QAbstractSocket::peerAddress() const  //获取远程主机IP地址

quint16 QAbstractSocket::peerPort() const    //获取远程主机端口号

QString QAbstractSocket::peerName() const  //获取远程主机名称

示例:

void Widget::new_TCP_Connection()

{

    //获取下一个等待连接的QTcpSocket对象

    server_socket=tcp_server->nextPendingConnection();

    qDebug()<<server_socket->localPort();

    qDebug()<<server_socket->localAddress().toString();

    qDebug()<<server_socket->peerPort();

    qDebug()<<server_socket->peerName();

    qDebug()<<server_socket->peerAddress();

}

    6. 设置缓冲区大小

qint64 QAbstractSocket::readBufferSize() const

void QAbstractSocket::setReadBufferSize(qint64 size)

默认情况下,缓冲区的大小为0,表示缓冲区大小不受限制。

    7. 获取套接字的状态

SocketState QAbstractSocket::state() const

状态枚举值:

QAbstractSocket::ConnectedState 表示套接字已经建立连接。

QAbstractSocket::UnconnectedState 表示套接字已经断开连接

    8. 获取套接字描述符

qintptr QAbstractSocket::socketDescriptor() const

如果返回值为负值,表示该套接字不可用。可以使用此函数判断该套接字对象是否已经断开连接。

    9. 等待套接字断开

bool QAbstractSocket::waitForDisconnected(int msecs = 30000)

示例:

socket->disconnectFromHost();

  if (socket->state() == QAbstractSocket::UnconnectedState ||

  socket->waitForDisconnected(1000))

  qDebug("Disconnected!");



  • QTcpSocket类继承于QIODevice类的相关函数介绍

    1. 读出缓冲区中接收到的网络数据

QByteArray QIODevice::read(qint64 maxSize)

qint64 QIODevice::read(char *data, qint64 maxSize)

QByteArray QIODevice::readAll()

readAll函数比较常用,直接将缓冲区的数据全部读出,使用QByteArray类返回。

    2. 发送数据

qint64 QIODevice::write(const char *data, qint64 maxSize)

qint64 QIODevice::write(const char *data)

qint64 QIODevice::write(const QByteArray &byteArray)

write函数用于将数据写入缓冲区,最终发送给服务器或者客户端。

    3. 检测是否有数据可读

bool QAbstractSocket::atEnd() const

如果缓冲区有数据可读将返回true。

  • QTcpSocket类相关信号介绍

    1. 成功建立连接信号

[signal] void QAbstractSocket::connected()

当客户端与服务器成功建立连接时会发出connected()信号,该信号在客户端使用。用于响应是否成功连接上服务器。

    2. 断开连接信号

[signal] void QAbstractSocket::disconnected()

当套接字断开连接时,会发出disconnected信号,可在disconnected信号关联的槽函数里做释放操作。如果有必要,可以将该信号直接与void QObject::deleteLater()槽函数关联,直接删除对象。

    3. 产生错误信号

void error(QAbstractSocket::SocketError socketError)

套接字在通信过程中发生了错误,会发出error信号。通过该信号形参可以判断错误的类型。

    4. 数据可读信号

[signal] void QIODevice::readyRead()

当缓冲区有新的数据可读时,会发出readyRead信号。可在readyRead信号关联的槽函数里读取数据。

12.2.2 QtcpServer介绍


QTcpServer类提供了一个基于tcp服务器的创建方式,QTcpServer可以接受TCP客户端的连接。下面将介绍QTcpServer类常用的函数接口。

    1. 设置服务器监听的IP和端口

bool QTcpServer::listen(const QHostAddress &address = QHostAddress::Any, quint16 port = 0)

如果端口是0,表示自动选择一个端口进行监听。如果地址是QHostAddress::Any,表示服务器将监听本地所有IP地址。成功返回true,否则返回false。

    2. 判断当前服务器监听状态

bool QTcpServer::isListening() const

如果当前服务器正在监听返回true,否则返回false。

    3. 获取当前服务器地址信息

QHostAddress QTcpServer::serverAddress() const

当客户端连接上服务器时,才可使用serverAddress函数获取当前服务器的地址信息。如果服务器监听的地址是全部地址,返回的IP地址是0。

    4. 获取等待连接的客户端QTcpSocket套接字对象

QTcpSocket *QTcpServer::nextPendingConnection()

该函数一般在newConnection()信号的槽函数中调用。

    5. 设置服务器允许连接的最大数量

void QTcpServer::setMaxPendingConnections(int numConnections)  //设置最大连接的数量

int QTcpServer::maxPendingConnections() const  //返回当前剩余的可连接数量

默认情况下最大连接数量为30。

    6. 处理新连接

[virtual protected] void QTcpServer::incomingConnection(qintptr socketDescriptor)

incomingConnection函数是一个虚函数,可在子类中重写该函数。只要有客户端连接上服务器,就会调用incomingConnection函数,可在incomingConnection函数内部处理客户端的连接。

    7. 获取新连接的客户端信息

QTcpSocket *QTcpServer::nextPendingConnection()

该函数在实例化QTcpServer类,接收到newConnection()信号时调用,用于获取新连接的客户端。

示例:

//服务器模式:响应新连接的客户端

void QWidget_SharedImage::NewTcpConnection()

{

    /*获取新客户端的套接字信息*/

    QTcpSocket *ServerSocket=LocalTcpServer->nextPendingConnection();

    /*关联可读信号*/

    connect(ServerSocket,SIGNAL(readyRead()),this,SLOT(ReadTcpClientData()));

    /*关联断开信号*/

    connect(ServerSocket,SIGNAL(disconnected()),this,SLOT(TcpClientDisconnected()));

    TcpFarClientList.append(ServerSocket);//将连接上的客户端添加到列表,用于记录已经连接的客户端

.................其他功能代码省略..........................

}

    8. 使用本机套接字初始化socket连接

bool QAbstractSocket::setSocketDescriptor(qintptr socketDescriptor, SocketState socketState = ConnectedState, OpenMode openMode = ReadWrite)

是子类化(继承)方式实现TCP服务器时,新连接上来的客户端会通过传入一个套接字描述符socketDescriptor用于初始化QAbstractSocket,可将此套接字传入到其他线程进行处理。如果socketDescriptor为有效的套接字描述符,则返回true; 否则返回false。套接字以openMode指定的模式打开,并进入由socketState指定的套接字状态。

注意:使用相同的本地套接字描述符不可能初始化两个抽象套接字。

    9. 等待数据发送完毕

bool QAbstractSocket::waitForBytesWritten(int msecs = 30000)

从QIODevice :: waitForBytesWritten()重新实现。

此功能会阻塞,直到socket上至少写入一个字节并发出bytesWritten()信号。 该函数在msecs毫秒后超时; 默认超时时间为30000毫秒。

如果发送bytesWritten()信号,该函数返回true; 否则返回false(如果发生错误或操作超时)。

注意:此功能可能在Windows上随机失败。如果您的软件将在Windows上运行,请考虑使用事件循环和bytesWritten()信号。

    10. 连接到服务器

void QAbstractSocket::connectToHost(const QHostAddress &address, quint16 port, OpenMode openMode = ReadWrite)

此函数在TCP客户端调用,用于连接到指定的TCP服务器。

    11. 等待连接

bool QAbstractSocket::waitForConnected(int msecs = 30000)

此函数在TCP客户端调用,一直等到套接字连接。 如果连接已建立,则此函数返回true; 否则返回false。在返回false的情况下,可以调用error()来确定错误的原因。

如果msecs是-1,则该函数不会超时。

以下示例等待一秒钟以建立连接:

示例:

socket->connectToHost("imap", 143);

  if (socket->waitForConnected(1000))

      qDebug("Connected!");

    12. 等待有数据可读

bool QAbstractSocket::waitForReadyRead(int msecs = 30000)

从QIODevice :: waitForReadyRead()重新实现。

此功能会阻止,直到有新的数据可用于读取并且readyRead()信号已发出。 该函数在msecs毫秒后超时; 默认超时时间为30000毫秒。

如果readyRead()信号发出并且有新的数据可供读取,该函数返回true; 否则返回false(如果发生错误或操作超时)。

注意:此功能可能在Windows上随机失败。如果您的软件将在Windows上运行,请考虑使用事件循环和readyRead()信号。

  • QTcpSocket用到的信号

    1. 新连接请求信号

[signal] void QTcpServer::newConnection()

当客户端连接上服务器时,会触发newConnection信号。可在newConnection关联的槽函数中创建套接字。

    2. 连接错误信号

[signal] void QTcpServer::acceptError(QAbstractSocket::SocketError socketError)

acceptError信号是在接受一个新的客户端连接,产生错误时发出。socketError参数描述了错误发生的类型。

12.2.3 TCP服务器与客户端创建步骤


  •  创建TCP服务器有两种方式:

第一种方式步骤:

(1) 子类化QTcpServer类(重新继承QTcpServer)

(2) 调用listen函数设置监听端口和IP地址

(3) 重新实现服务器端的incomingConnection虚函数,该函数用来处理连接上的TCP请求,创建socket类实现通信。

(4) 关联disconnected信号,处理断开的客户端套接字

第二种方式步骤:

(1) 实例化QTcpServer类

(2) 调用listen函数设置监听端口和IP地址

(3) 关联newConnection信号。用于处理新连接上来的客户端请求,创建socket类实现通信

(4) 关联disconnected信号,处理断开的客户端套接字

  • 创建TCP客户端步骤

(1) 实例化QTcpSocket类

(2) 绑定connected和disconnected信号。用于响应连接和断开服务器的操作

(3) 调用connectToHost函数连接服务器

下面将编写TCP服务器与客户端的创建模型示例,先学习简单服务器与客户端的创建方法。

  • 创建服务器模型(配套程序编号CH12-1)

(1) “widget.h”文件代码示例

#ifndef WIDGET_H

#define WIDGET_H

#include <QWidget>

#include <QTcpServer>

#include <QUdpSocket>

#include <QtNetwork>

#include <QHostInfo>

#include <QTcpSocket>

#include <QHostAddress>

class Widget : public QWidget

{

    Q_OBJECT

public:

    Widget(QWidget *parent = 0);

    ~Widget();

    QTcpServer *TcpServer;

    QTcpSocket *ServerSocket;

public slots:

        void NewTcpConnection();

        void ReadTcpData();

        void TcpClientDisconnected();

};

#endif // WIDGET_H

(2) “widget.c”文件代码示例

#include "widget.h"

Widget::Widget(QWidget *parent): QWidget(parent)

{

     /*1. 实例化服务器*/

     TcpServer= new QTcpServer;

     /*2. 设置监听的端口*/

     TcpServer->listen(QHostAddress::Any,8888);

     /*3. 关联连接信号,检测是否有新的客户端连接*/

     connect(TcpServer,SIGNAL(newConnection()),this,SLOT(NewTcpConnection()));

}

Widget::~Widget()

{

}

void Widget::NewTcpConnection()

{

   /*创建本地服务器套接字*/

   ServerSocket=TcpServer->nextPendingConnection();

   /*关联可读信号*/

   connect(ServerSocket,SIGNAL(readyRead()),this,SLOT(ReadTcpData()));

   /*关联断开信号*/

   connect(ServerSocket,SIGNAL(disconnected()),this,SLOT(TcpClientDisconnected()));

}

void Widget::ReadTcpData()

{

    //读取数据

}

void Widget::TcpClientDisconnected()

{

     //处理断开连接的情况

}

  • 创建客户端模型(配套程序编号CH12-2)

(1) “widget.h”文件代码

#ifndef WIDGET_H

#define WIDGET_H

#include <QWidget>

#include <QTcpServer>

#include <QUdpSocket>

#include <QtNetwork>

#include <QHostInfo>

#include <QTcpSocket>

#include <QHostAddress>

class Widget : public QWidget

{

    Q_OBJECT

public:

    Widget(QWidget *parent = 0);

    ~Widget();

    QHostAddress *ServerAddr;

    QTcpSocket *TcpClientSocket;

public slots:

    void slotConnected();

    void slotDisconnected();

    void dataReceived();

};

#endif // WIDGET_H

(2) “widget.c”文件代码

#include "widget.h"

Widget::Widget(QWidget *parent): QWidget(parent)

{

    /*1. 创建TCP套接字*/

     TcpClientSocket = new QTcpSocket(this);

    /*2. 设置服务器IP地址*/

     ServerAddr =new QHostAddress("192.168.49.1");

    /*3. 连接信号槽*/

    connect(TcpClientSocket,SIGNAL(connected()),this,SLOT(slotConnected()));

    connect(TcpClientSocket,SIGNAL(disconnected()),this,SLOT(slotDisconnected()));

    connect(TcpClientSocket,SIGNAL(readyRead()),this,SLOT(dataReceived()));

    /*4. 尝试连接服务器主机*/

    TcpClientSocket->connectToHost(*ServerAddr,8080);

}

Widget::~Widget()

{

}

void Widget::slotConnected()

{

  //连接上服务器

}

void Widget::slotDisconnected()

{

    //断开服务器

}

void Widget::dataReceived()

{

    //有数据可读

}

12.2.4 TCP网络调试助手实例


为了更好的讲解TCP服务器与客户端的使用方法,下面将编写一个TCP调试助手实例。该调试助手实现了TCP服务器与客户端的创建,服务器可以同时与多个客户端进行通讯,并且可以检测当前在线的客户端。 (配套程序编号CH12-3)

TCP调试助手实例运行效果图如下:



图12-2-4-1 TCP网络调试助手运行界面

(1) “widget.ui”设计师界面



图12-2-4-2 UI设计界面

(2) “widget.h”头文件代码示例

widget.h文件中主要用于声明需要用到的各种类指针和槽函数的声明。

#ifndef WIDGET_H

#define WIDGET_H

#include <QWidget>

#include <QTcpServer>

#include <QHostInfo>

#include <QUdpSocket>

#include <QtNetwork>

#include <QHostInfo>

#include <QDebug>

#include <QTcpSocket>

#include <QHostAddress>

#include <QDebug>

#include <QMessageBox>

#include <QLineEdit>

#include <QHBoxLayout>

#include <QComboBox>

namespace Ui {

class Widget;

}

class Widget : public QWidget

{

    Q_OBJECT

public:

    explicit Widget(QWidget *parent = 0);

    ~Widget();

    int TxCount;

    int RxCount;

    //关于客户端模式相关的变量定义

    QTcpServer *TcpServer;

    QTcpSocket *ServerSocket;

    QTcpSocket *LocalTcpClientSocket;

    QLineEdit *ClientEditProt;

    QLineEdit *ClientEditIP;

    QLabel *ClientShowLocalProtLabel;

    QLabel *ClientShowLocalIptLabel;

    QHBoxLayout *ClientLayout;

    void comboBox_config();

    void NewClinet();

    //关于服务器相关的变量定义

    QTcpServer *LocalTcpServer;

    void NewServer();

    QList<QTcpSocket*> TcpFarClientList;

    QLabel *SaverShowLocalProtLabel;

    QComboBox *ClientComboBoxList;

    QHBoxLayout *ServerLayout;

private slots:

    void on_pushButton_connect_clicked();

    void LocalTcpClientConnectedSlot();

    void LocalTcpClientDisconnectedSlot();

    void LocalTcpClientReadDtatSlot();

    void NewTcpConnection();

    void TcpClientDisconnected();

    void ReadTcpClientData();

    void on_comboBox_protocol_activated(int index);

    void on_pushButton_SendData_clicked();

    void on_pushButton_clear_clicked();

private:

    Ui::Widget *ui;

};

#endif // WIDGET_H

(3) “widget.cpp”文件代码示例

widget.cpp文件中编写了全部的功能代码,实现了众多服务器与客户端槽函数。

#include "widget.h"

#include "ui_widget.h"

Widget::Widget(QWidget *parent) :QWidget(parent),

    ui(new Ui::Widget)

{

    ui->setupUi(this);

    comboBox_config();

}

//基本配置

void Widget::comboBox_config()

{

    /*第一部分:网络设置*/

    //1.1 配置协议

    ui->comboBox_protocol->addItem("TCP Server");

    ui->comboBox_protocol->addItem("TCP Client");

    //1.2. 获取并配置本地IP地址

    QList<QHostAddress> list = QNetworkInterface::allAddresses();

    for(int i=0;i<list.count();i++)

    {

        QHostAddress addr=list.at(i);

        if(addr.protocol() == QAbstractSocket::IPv4Protocol)

        {

          ui->comboBox_ipaddress->addItem(addr.toString());

        }

    }

   //1.3 设置可编辑性

    ui->comboBox_ipaddress->setEditable(true);

   //1.4. 设置默认端口号

    ui->lineEdit_prot->setText("8080");

    /*第二部分:发送设置*/

    //2.1设置循环发送时间

    ui->lineEdi_LoopTime->setText("1000");

    //2.2 设置默认发送的内容

    ui->lineEdit_InputTxDtat->setText("欢迎学习QT5编程!");

    //接收数量置0

    TxCount=0;

    RxCount=0;

    /*第三部分:选择客户端时用于显示本地端口和IP*/

    ClientShowLocalProtLabel =new QLabel("本地端口:");

    ClientShowLocalIptLabel =new QLabel("本地IP地址:");

    ClientEditProt= new QLineEdit;

    ClientEditIP= new QLineEdit;

    ClientLayout = new QHBoxLayout;

    ClientLayout->addWidget(ClientShowLocalIptLabel);

    ClientLayout->addWidget(ClientEditIP);

    ClientLayout->addWidget(ClientShowLocalProtLabel);

    ClientLayout->addWidget(ClientEditProt);

    /*第四部分:选服务器时用于显示已经连接的客户端*/

    SaverShowLocalProtLabel =new QLabel("已连接的客户端列表:");

    ClientComboBoxList=new QComboBox;

    ClientComboBoxList->setMinimumWidth(180);

    ServerLayout = new QHBoxLayout;

    ServerLayout->addWidget(SaverShowLocalProtLabel);

    ServerLayout->addWidget(ClientComboBoxList);

    ServerLayout->addStretch();

}

Widget::~Widget()

{

    delete ui;

}

//创建服务器或者连接到服务器

void Widget::on_pushButton_connect_clicked()

{

    if(ui->pushButton_connect->text()=="连接")

    {

        switch(ui->comboBox_protocol->currentIndex())

        {

            case 0: //服务器模式

                NewServer();

                break;

            case 1: //客户端模式

                NewClinet();

                break;

        }

    }else

    {

        switch(ui->comboBox_protocol->currentIndex())

        {

            case 0: //服务器模式

                ui->pushButton_connect->setText("连接");

                for(int i=0;i<TcpFarClientList.count();i++)

                {

                    TcpFarClientList.at(i)->close();

TcpFarClientList.removeAt(i);

                }

                LocalTcpServer->close();//关闭服务器

                //取消客户端列表显示

                ui->verticalLayout_9->removeItem(ServerLayout);

                ClientComboBoxList->close();

                SaverShowLocalProtLabel->close();

                ui->comboBox_ipaddress->setEnabled(true);

                ui->comboBox_protocol->setEnabled(true);

                ui->lineEdit_prot->setEnabled(true);

                break;

            case 1: //客户端模式

                LocalTcpClientSocket->close();

                break;

        }

    }

}

//服务器模式:创建服务器

void Widget::NewServer()

{

    /*1. 实例化服务器*/

    LocalTcpServer= new QTcpServer;

    /*2. 设置监听的端口和IP地址*/

    quint16 port=QString(ui->lineEdit_prot->text()).toInt();

    if(ui->comboBox_ipaddress->currentText()=="QHostAddress::Any")

    {

        LocalTcpServer->listen(QHostAddress::Any,port);

    }else

    {

       QHostAddress addr(ui->comboBox_ipaddress->currentText());

       LocalTcpServer->listen(addr,port);

    }

    /*3. 关联连接信号,检测是否有新的客户端连接*/

    connect(LocalTcpServer,SIGNAL(newConnection()),this,SLOT(NewTcpConnection()));

    ui->pushButton_connect->setText("断开连接");

    //添加布局,显示已经连接的客户端列表

    ui->verticalLayout_9->insertLayout(1,ServerLayout);

    ClientComboBoxList->show();

    SaverShowLocalProtLabel->show();

    //创建服务器之后设置控件可用

    ui->comboBox_ipaddress->setEnabled(false);

    ui->comboBox_protocol->setEnabled(false);

    ui->lineEdit_prot->setEnabled(false);

}

//客户端模式:创建客户端

void Widget::NewClinet()

{

    /*1. 创建本地客户端TCP套接字*/

    LocalTcpClientSocket = new QTcpSocket;

    /*2. 设置服务器IP地址*/

    QString Ipaddr=ui->comboBox_ipaddress->currentText();

    QHostAddress FarServerAddr(Ipaddr);

    /*3. 连接客户端的信号槽*/

    connect(LocalTcpClientSocket,SIGNAL(connected()),this,SLOT(LocalTcpClientConnectedSlot()));

    connect(LocalTcpClientSocket,SIGNAL(disconnected()),this,SLOT(LocalTcpClientDisconnectedSlot()));

    connect(LocalTcpClientSocket,SIGNAL(readyRead()),this,SLOT(LocalTcpClientReadDtatSlot()));

    /*4. 尝试连接服务器主机*/

    int prot=ui->lineEdit_prot->text().toInt();

    LocalTcpClientSocket->connectToHost(FarServerAddr,prot);

}

//客户端模式:响应连接上服务器之后的操作

void Widget::LocalTcpClientConnectedSlot()

{

    //显示本地端口和IP

    ClientEditProt->setText(QString::number(LocalTcpClientSocket->localPort()));

    ClientEditIP->setText(LocalTcpClientSocket->localAddress().toString());

    ui->verticalLayout_9->insertLayout(1,ClientLayout);

    ClientEditProt->show();

    ClientEditIP->show();

    ClientShowLocalProtLabel->show();

    ClientShowLocalIptLabel->show();

    //当连接上服务器之后设置控件不可用

    ui->comboBox_ipaddress->setEnabled(false);

    ui->comboBox_protocol->setEnabled(false);

    ui->lineEdit_prot->setEnabled(false);

    ui->pushButton_connect->setText("断开连接");

}

//客户端模式:断开服务器

void Widget::LocalTcpClientDisconnectedSlot()

{

   ui->verticalLayout_9->removeWidget(ClientEditProt);

   ui->verticalLayout_9->removeWidget(ClientEditIP);

   ui->verticalLayout_9->removeWidget(ClientShowLocalProtLabel);

   ui->verticalLayout_9->removeWidget(ClientShowLocalIptLabel);

   ui->verticalLayout_9->removeItem(ClientLayout);

   ClientEditProt->close();

   ClientEditIP->close();

   ClientShowLocalProtLabel->close();

   ClientShowLocalIptLabel->close();

   ui->pushButton_connect->setText("连接");

   //当断开上服务器之后设置控件可用

   ui->comboBox_ipaddress->setEnabled(true);

   ui->comboBox_protocol->setEnabled(true);

   ui->lineEdit_prot->setEnabled(true);

}

//客户端模式:读取服务器发过来的数据

void Widget::LocalTcpClientReadDtatSlot()

{

   QByteArray array=LocalTcpClientSocket->readAll();

   //记录接收的字节数并显示

   TxCount+=QString(array).toLocal8Bit().length();

   ui->lcdNumber_RxNumber->display(TxCount);

   QString text;

   //判断是否需要显示时间

   if(ui->checkBox_ShowTime->isChecked())

   {

       QDateTime time = QDateTime::currentDateTime();   //获取系统现在的时间

       text+= time.toString("yyyy-MM-dd hh:mm:ss ddd"); //设置显示格式

       text+=" :";

   }

   text+=QString("").fromLocal8Bit(array);

   ui->plainTextEdit_ShowData->appendPlainText(text);

}

void Widget::on_comboBox_protocol_activated(int index)

{

    switch(index)

    {

        case 0: //服务器模式

            ui->label_ip->setText("(2)本地IP地址");

            ui->label_prot->setText("(3)本地端口号");

            ui->comboBox_ipaddress->clear();

            ui->comboBox_ipaddress->addItem("QHostAddress::Any");

            break;

        case 1: //客户端模式

            ui->label_ip->setText("(2)服务器IP地址");

            ui->label_prot->setText("(3)服务器端口号");

            ui->comboBox_ipaddress->clear();

            break;

    }

    QList<QHostAddress> list = QNetworkInterface::allAddresses();

    for(int i=0;i<list.count();i++)

    {

        QHostAddress addr=list.at(i);

        if(addr.protocol() == QAbstractSocket::IPv4Protocol)

        {

          ui->comboBox_ipaddress->addItem(addr.toString());

        }

    }

}

//发送数据

void Widget::on_pushButton_SendData_clicked()

{

    QString text=ui->lineEdit_InputTxDtat->text();

    if(text.isEmpty())

    {

        QMessageBox::warning(this,"发送错误","发送的数据不能为空!",QMessageBox::Ok);

        return;

    }

    QByteArray array=text.toLocal8Bit();

    int count=0;

    switch(ui->comboBox_protocol->currentIndex())

    {

        case 0: //服务器模式

if(TcpFarClientList.count()<=0)

            {

                QMessageBox::warning(this,"发送错误","没有连接的客户端!",QMessageBox::Ok);

                return;

            }

            for(int i=0;i<TcpFarClientList.count();i++)

            {

                //取出地址列表中的一个客户端地址

                QTcpSocket *item = TcpFarClientList.at(i);

                count=item->write(array);

            }

            TxCount+=count;

            break;

        case 1: //客户端模式

            if(LocalTcpClientSocket->state()==QAbstractSocket::ConnectedState)

            {

                TxCount+=LocalTcpClientSocket->write(array);

            }

            else

            {

                LocalTcpClientSocket->close();

                QMessageBox::warning(this,"发送错误","未连接服务器!",QMessageBox::Ok);

                return;

            }

            break;

    }

    ui->lcdNumber_TxNumber->display(TxCount);

}

//清除计数

void Widget::on_pushButton_clear_clicked()

{

    TxCount=0;

    RxCount=0;

    ui->lcdNumber_RxNumber->display(0);

    ui->lcdNumber_TxNumber->display(0);

}

//服务器模式:响应新连接的客户端

void Widget::NewTcpConnection()

{

    /*创建本地服务器套接字*/

    QTcpSocket *ServerSocket=LocalTcpServer->nextPendingConnection();

    /*关联可读信号*/

    connect(ServerSocket,SIGNAL(readyRead()),this,SLOT(ReadTcpClientData()));

    /*关联断开信号*/

    connect(ServerSocket,SIGNAL(disconnected()),this,SLOT(TcpClientDisconnected()));

    TcpFarClientList.append(ServerSocket);//添加到列表

    //显示已经连接的客户端

    ClientComboBoxList->clear();

    for(int i=0;i<TcpFarClientList.count();i++)

    {

        QString info=TcpFarClientList.at(i)->peerAddress().toString();

        info+=":";

        info+=QString::number(TcpFarClientList.at(i)->peerPort());

        ClientComboBoxList->addItem(info);

    }

}

//服务器模式:响应断开的客户端

void Widget::TcpClientDisconnected()

{

    for(int i=0;i<TcpFarClientList.count();i++)

    {

        //取出地址列表中的一个客户端地址

        QTcpSocket *item = TcpFarClientList.at(i);

        //判断该客户端是否已经断开

        if(item->socketDescriptor()==-1)

        {

      &n

1人点赞鲜花

握手

雷人

路过

鸡蛋

刚表态过的朋友 (1 人)


公告
可以关注我们的微信公众号yafeilinux_friends获取最新动态,或者加入QQ会员群进行交流:190741849、186601429(已满) 我知道了