C++QT开发——TCP&UDP网络编程

网络编程

编写具有网络功能的程序就要用到Qt Network模块。该模块提供了一系列的接口用于TCP/IP编程。什么HTTP发送/接收请求啊、cookies相关的啊、DNS啊等都有对应的C++类可操作。使用network模块,需要在pro文件中添加“QT += network”。

Qt5中所有网络相关的C++类的继承关系如下图:

 本文福利,费领取Qt开发学习资料包、技术视频,内容包括(C++语言基础,Qt编程入门,QT信号与槽机制,QT界面开发-图像绘制,QT网络,QT数据库编程,QT项目实战,QT嵌入式开发,Quick模块等等)↓↓↓↓↓↓见下面↓↓文章底部点击费领取↓↓

1. QHostInfo

QHostInfo类为主机名查找提供了静态函数 。

QHostInfo查找与主机名关联的IP地址或与IP地址关联的主机名。 这个类提供了两个方便的静态函数:一个异步工作并在找到主机时发出信号,另一个阻塞并返回QHostInfo对象。

异步查找主机的IP地址,调用lookupHost(),它接受主机名或IP地址、接收方对象和槽签名作为参数并返回ID。 您可以通过使用查找ID调用abortHostLookup()来中止查找。

//通过域名查找ip
QHostInfo::lookupHost("www.baidu.com",this,[](const QHostInfo& info)
{
    qDebug()<<info.hostName()<<info.addresses();
});
//查找ip是否存在
QHostInfo::lookupHost("183.232.231.172",this,[](const QHostInfo& info)
{
    qDebug()<<info.hostName()<<info.addresses();
});

当结果准备好时,将调用该槽。 结果存储在QHostInfo对象中。 调用addresses()获取主机的IP地址列表,调用hostName()获取所查找的主机名。

如果查找失败,error()将返回所发生的错误类型。 errorString()给出了可读的查找错误描述。

如果你想要一个阻塞查找,使用QHostInfo::fromName()函数:

QHostInfo info = QHostInfo::fromName("smtp.qq.com");
qDebug()<<info.hostName()<<info.addresses();

QHostInfo通过IDNA和Punycode标准支持国际化域名(IDNs)。

要检索本地主机的名称,请使用静态QHostInfo::localHostName()函数。

qDebug()<< QHostInfo::localHostName();

2. QHostAddress

QHostAddress类提供一个IP地址。

这个类以独立于平台和协议的方式保存IPv4或IPv6地址。

QHostAddress通常与QTcpSocket、QTcpServer和QUdpSocket一起使用,以连接到主机或建立服务器。

主机地址用setAddress()设置,用toIPv4Address()、toIPv6Address()或toString()检索。 可以使用protocol()检查类型。

注意:请注意QHostAddress不做DNS查找。 QHostInfo是需要的。

这个类还支持常见的预定义地址:Null、LocalHost、LocalHostIPv6、Broadcast和Any。

枚举

描述

QHostAddress::Null

空地址对象。 相当于QHostAddress()。 参见QHostAddress: isNull()。

QHostAddress::LocalHost

IPv4本地主机地址。 相当于QHostAddress(127.0.0.1)。

QHostAddress::localhsotIPv6

IPv6本地主机地址。 相当于QHostAddress(“::1”)。

QHostAddress::Broadcast

IPv4广播地址。 相当于QHostAddress(“255.255.255.255”)

QHostAddress::AnyIPv4

IPv4任何地址。 相当于QHostAddress(“0.0.0.0”)。 与此地址绑定的套接字只在IPv4接口上监听。

QHostAddress::AnyIPv6

IPv6任何地址。 相当于QHostAddress(“::”)。 与此地址绑定的套接字只在IPv6接口上监听。

QHostAddress::Any

双栈任意地址。 与此地址绑定的套接字将侦听IPv4和IPv6接口。

3. QNetworkInterface

QNetworkInterface类提供了主机的IP地址和网络接口的列表。

QNetworkInterface表示一个连接到正在运行程序的主机的网络接口。 每个网络接口可以包含0个或多个IP地址,每个IP地址可选地与一个网络掩码和/或一个广播地址相关联。 这类三元组的列表可以通过addressEntries()获得。 或者,当网络掩码或广播地址或其他信息不需要时,使用方便的allAddresses()函数只获取活动接口的IP地址。

QNetworkInterface还使用hardwareAddress()报告接口的硬件地址。

并非所有操作系统都支持报告所有特性。 在所有平台中,只有IPv4地址保证被这个类列出。 其中IPv6地址列表仅支持在Windows、Linux、macOS和bsd等操作系统上使用。

  • 这个方便的函数返回主机上找到的所有IP地址。
QList<QHostAddress> addrlist = QNetworkInterface::allAddresses();
for(QHostAddress addr : addrlist)                                  
{                                                                  
    qDebug()<<addr.protocol()<<addr.toString();                    
}
  • 返回主机上找到的所有网络接口的列表。 如果失败,它将返回一个没有元素的列表。
QList<QNetworkInterface> networkList =  QNetworkInterface::allInterfaces();
for(auto inter : networkList)
{
    if(!inter.isValid())
        continue;
    //输出此网络接口的名称、接口的类型、MAC地址和 在Windows上返回这个网络接口的人类可读的名称(如以太网、本地连接等)
    qDebug()<<inter.name()<<inter.type()<<inter.hardwareAddress()<<inter.humanReadableName();
    //输出网络接口对应的ip地址
    for(auto entrys : inter.addressEntries())
    {
        qDebug()<<entrys.ip();
    }
}

4. QNetworkAddressEntry

QNetworkAddressEntry类存储一个由网络接口支持的IP地址,以及它相关的网络掩码和广播地址。

//返回IPv4地址和子掩码相关联的广播地址
QHostAddress broadcast() const
//返回网络接口中的IPv4或IPv6地址
QHostAddress ip() const
//返回与IP地址相关联的子网掩码
QHostAddress netmask() const

5. QAbstractSocket

QAbstractSocket类提供了所有套接字类型通用的基本功能 。

QAbstractSocket是QTcpSocket和QUdpSocket的基类,包含这两个类的所有通用功能。 如果你需要一个套接字,你有两个选择:

  • 实例化QTcpSocket或QUdpSocket。
  • 创建本机套接字描述符,实例化QAbstractSocket,并调用setSocketDescriptor()来包装本机套接字。

TCP(传输控制协议)是一种可靠的、面向流的、面向连接的传输协议。 UDP(用户数据报协议)是一个不可靠的、面向数据报的、无连接的协议。 在实践中,这意味着TCP更适合于数据的连续传输,而更轻量级的UDP可以在可靠性不重要的情况下使用。

QAbstractSocket的API统一了这两种协议之间的大部分差异。 例如,尽管UDP是无连接的,但connectToHost()为UDP套接字建立了一个虚拟连接,使您能够以几乎相同的方式使用QAbstractSocket,而不管底层协议是什么。 在内部,QAbstractSocket记住传递给connectToHost()的地址和端口,read()和write()等函数使用这些值。

在任何时候,QAbstractSocket都有一个状态(由state()返回)。 初始状态为UnconnectedState。 调用connectToHost()后,套接字首先进入HostLookupState(socket正在查找主机名)。 如果找到主机,QAbstractSocket进入ConnectingState并发出hostFound()信号。 当连接建立后,它进入ConnectedState并发出connected()。 如果在任何阶段发生错误,则会触发error()。 每当状态改变时,就会触发stateChanged()。 为了方便起见,如果套接字已经准备好读写,isValid()将返回true,但是请注意,在读写发生之前,套接字的状态必须是ConnectedState。

通过调用Read()或write()读取或写入数据,或使用方便的函数readLine()和readAll()。 QAbstractSocket还从QIODevice继承了getChar()、putChar()和ungetChar(),它们处理单个字节。 当数据被写入套接字时将发出bytesWritten()信号。 注意,Qt不限制写缓冲区的大小。 你可以通过听这个信号来监控它的大小。

readyRead()信号在每次到达一个新的数据块时被触发。 然后bytesAvailable()返回可用于读取的字节数。 通常,您将readyRead()信号连接到一个插槽并读取那里的所有可用数据。 如果您没有一次读取所有数据,其余的数据将在稍后仍然可用,并且任何新的传入数据将被附加到QAbstractSocket的内部读缓冲区。 要限制读缓冲区的大小,可以调用setReadBufferSize()。

要关闭套接字,调用disconnectFromHost()。 QAbstractSocket进入QAbstractSocket:: ClosingState。 在将所有挂起的数据写入套接字之后,QAbstractSocket实际上关闭了套接字,输入QAbstractSocket::UnconnectedState,并发出disconnected()。 如果您想立即中止连接,丢弃所有挂起的数据,可以调用abort()。 如果远程主机关闭连接,QAbstractSocket将发出错误(QAbstractSocket::RemoteHostClosedError),在此期间,套接字状态仍然是ConnectedState,然后将发出disconnected()信号。

通过调用 peerPort() 和 peerAddress() 获取连接的对等方的端口和地址。 peerName() 返回传递给 connectToHost() 的对等方的主机名。 localPort() 和 localAddress() 返回本地套接字的端口和地址。

QAbstractSocket提供了一组函数,用于挂起调用线程,直到发出某些信号。 这些函数可以用来实现阻塞套接字:

  • waitForConnected()将阻塞,直到建立连接。
  • waitForReadyRead()会阻塞,直到有新的数据可以读取。
  • waitForBytesWritten()将阻塞,直到一个数据负载被写入套接字。
  • waitForDisconnected()将阻塞,直到连接关闭。

下面是一段示例:

int numRead = 0, numReadTotal = 0;
char buffer[50];
​
forever 
{
     numRead  = socket.read(buffer, 50);
     // do whatever with array
     numReadTotal += numRead;
     if (numRead == 0 && !socket.waitForReadyRead())
         break;
}

如果waitForReadyRead()返回false,则表示连接已经关闭或发生了错误。

使用阻塞套接字编程与使用非阻塞套接字编程完全不同。 阻塞套接字不需要事件循环,通常会导致更简单的代码。 然而,在GUI应用程序中,阻塞套接字应该只在非GUI线程中使用,以避免冻结用户界面。 请参阅fortuneclient和blockingfortuneclient示例来了解这两种方法的概述。

注意:我们不建议将阻塞函数与信号一起使用。 应该使用两种可能性中的一种。

QAbstractSocket可以与QTextStream和QDataStream的流操作符(操作符<<()和操作符>>())一起使用。 但是有一个问题需要注意:在尝试使用操作符>>()读取数据之前,必须确保有足够的数据可用。

如果想要了解更多信息,可以查阅QNetworkAccessManager和QTcpServer。

QAbstractSocket类继承自 : QIODevice.

本文福利,费领取Qt开发学习资料包、技术视频,内容包括(C++语言基础,Qt编程入门,QT信号与槽机制,QT界面开发-图像绘制,QT网络,QT数据库编程,QT项目实战,QT嵌入式开发,Quick模块等等)↓↓↓↓↓↓见下面↓↓文章底部点击费领取↓↓

信号

void connected()//这个信号是在调用connectToHost()并成功建立连接之后发出的。
void disconnected()//当套接字断开连接时,会发出此信号。
void error(QAbstractSocket::SocketError socketError)//这个信号是在发生错误后发出的。socketError参数描述发生的错误类型。
void hostFound()//这个信号是在调用connectToHost()并且主机查找成功之后发出的。
void proxyAuthenticationRequired(const QNetworkProxy &proxy, QAuthenticator *authenticator)//当使用需要身份验证的代理时,可以发出此信号。然后可以用所需的详细信息填充身份验证者对象,以允许身份验证并继续连接。
void stateChanged(QAbstractSocket::SocketState socketState)//每当QAbstractSocket的状态发生变化时,就会发出这个信号。socketState参数是新的状态。

•从QIODevice继承4个信号

void aboutToClose()//这个信号是在设备即将关闭时发出的。如果你有需要在设备关闭之前执行的操作,连接这个信号(例如,如果你有数据在一个单独的缓冲区,需要写入设备)。
void bytesWritten(qint64 bytes)//每当向设备写入有效数据时,就会发出这个信号。bytes参数设置为写入此有效负载的字节数。
void readChannelFinished()//当该设备中的输入(读取)流关闭时,该信号被发出。一旦检测到关闭,就会触发该事件,这意味着可能仍然有数据可用read()进行读取。
void readyRead()//每当从设备读取新数据时,该信号就会发出一次。只有当有新的数据可用时,它才会再次发出,例如当网络数据的新负载到达您的网络套接字时,或者当一个新的数据块被附加到您的设备时。

•从QObject继承了2个信号

void destroyed(QObject *obj = Q_NULLPTR)//该信号在对象obj被销毁之前立即发出,并且不能被阻塞。所有对象的子对象在信号发出后立即被销毁。
void objectNameChanged(const QString &objectName)//该信号在对象的名称被更改之后发出。新的对象名称作为objectName传递。注意:这是私人信号。它可以用于信号连接,但不能由用户发出。注意:属性objectName的通知符信号。参见QObject::的objectName。

公有函数

函数

描述

void abort()

终止当前连接并重置套接字。与disconnectFromHost()不同,这个函数会立即关闭套接字,丢弃写缓冲区中任何挂起的数据。

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

使用BindMode模式绑定到端口上的地址。

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

这是一个重载函数。绑定到QHostAddress:任何端口端口,使用BindMode模式。

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

尝试连接到给定端口上的hostName。protocol参数可用于指定使用哪种网络协议(ea. IPv4或IPv6)。

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

这是一个重载函数。尝试连接到端口上的地址。

virtual void disconnectFromHost()

试图关闭套接字。如果有挂起的数据等待写入,QAbstractSocket将进入ClosingState并等待,直到所有数据都已写入。最后,它将进入UnconnectedState并发出disconnected()信号。

SocketError error() const

返回上次发生的错误类型。

bool flush()

该函数尽可能多地从内部写缓冲区写入底层网络套接字,而不会阻塞。如果写入了任何数据,该函数返回true;否则返回false。

bool isValid() const

如果套接字有效并可以使用,则返回true;否则返回false。

QHostAddress localAddress() const

如果可用,返回本地套接字的主机地址;否则返回QHostAddress::零。

quint16 localPort() const

如果可用,返回本地套接字的主机端口号(以本机字节顺序);否则返回啊。

PauseModes pauseMode() const

返回此套接字的暂停模式。

QHostAddress peerAddress() const

如果套接字处于ConnectedState状态,返回连接的对等体的地址;否则返回QHostAddress::零。

QString peerName() const

返回connectToHost()指定的对等体名称,如果connectToHost()没有被调用,则返回一个空QString。

quint16 peerPort() const

如果套接字在ConnectedState中,返回连接的对等体的端口;否则返回0。

QNetworkProxy proxy() const

返回此套接字的网络代理。默认情况下使用QNetworkProxy::DefaultProxy,这意味着这个套接字将查询应用程序的默认代理设置。

qint64 readBufferSize() const

返回内部读缓冲区的大小。这限制了在调用read()或readAll()之前客户端可以接收的数据量。读取缓冲区大小为O(默认值)意味着缓冲区没有大小限制,确保没有数据丢失。

virtual void resume()

在套接字上继续数据传输。此方法仅应在套接字被设置为暂停通知并接收到通知之后使用。目前唯一支持的通知是QSslSocket::sslErrors()。如果套接字没有暂停,调用此方法会导致未定义的行为。

void setPauseMode(PauseModes pauseMode)

控制在收到通知时是否暂停。pauseMode参数指定套接字应该暂停的条件。目前唯一支持的通知是QSslSocket::sslErrors()。如果设置为PauseOnSslErrors,套接字上的数据传输将暂停,需要通过调用resume()再次显式启用。默认情况下,该选项设置为PauseNever。在连接到服务器之前必须调用此选项,否则将导致未定义的行为。

void setProxy(const QNetworkProxy &networkProxy)

将此套接字的显式网络代理设置为networkProxy。要禁用这个套接字的代理,使用QNetworkProxy::NoProxy代理类型:

virtual void setReadBufferSize(qint64 size)

将QAbstractSocket的内部读取缓冲区的大小设置为大小字节。

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

使用本机套接字描述符socketDescriptor初始化QAbstractSocket。如果socketDescriptor被接受为有效的套接字描述符,则返回true;否则返回false。socket以openMode指定的模式打开,并进入socketState指定的socket状态。读取和写入缓冲区将被清除,丢弃任何挂起的数据。

virtual void setSocketOption(QAbstractSocket::SocketOption option, const QVariant &value)

将给定选项设置为由value描述的值。注意:在Windows运行时上,在连接套接字之前必须设置QAbstractSocket::KeepAliveOption。

virtual qintptr socketDescriptor() const

如果可用,则返回QAbstractSocket对象的本机套接字描述符;否则返回1。

virtual QVariant socketOption(QAbstractSocket::SocketOption option)

返回选项的值。

SocketType socketType() const

返回套接字类型(TCP, UDP或其他)。

SocketState state() const

返回套接字的状态。

virtual bool waitForConnected(int msecs = 30000)

等待直到套接字被连接,最长可达msecs毫秒。如果连接已经建立,这个函数返回true;否则返回false。在返回false的情况下,可以调用error()来确定错误的原因。

virtual bool waitForDisconnected(int msecs = 30000)

等待直到套接字断开连接,最长可达msecs毫秒。如果连接已经断开,这个函数返回true;否则返回false。在返回false的情况下,可以调用error()来确定错误的原因。

6. QTcpServer(监听套接字)

QTcpServer类提供了一个基于tcp服务器的监听套接字。

这个类使接收传入的TCP连接成为可能。 您可以指定端口或让QTcpServer自动选择一个端口。 您可以监听一个特定的地址或所有机器的地址。

调用listen()让服务器监听传入的连接。 然后,每当客户机连接到服务器时,就会发出newConnection()信号。

调用nextPendingConnection()接受挂起的连接作为已连接的QTcpSocket。 该函数返回一个指向QAbstractSocket::ConnectedState中的QTcpSocket的指针,您可以使用该指针与客户端通信。

如果发生了错误,serverError()返回错误的类型,可以调用errorString()来获得人们可读的关于发生了什么事情的描述。

当监听连接时,服务器监听的地址和端口可用serverAddress()和serverPort()获取。

调用close()会使QTcpServer停止监听传入的连接。

虽然QTcpServer主要是为使用事件循环而设计的,但也可以不使用事件循环。 在这种情况下,必须使用waitForNewConnection(),它会阻塞直到连接可用或超时过期。

QTcpServer类继承自: QObject

信号

void acceptError(QAbstractSocket::SocketError socketError)//当接受新连接导致错误时,会发出此信号。
void newConnection()//每当有新的连接可用时,就会发出这个信号。

公有函数

函数

描述

void close()

关闭服务器。服务器将不再监听传入的连接。

QString errorString() const

返回最近发生的错误的人类可读的描述。

virtual bool hasPendingConnections() const

如果服务器有一个挂起的连接,返回true;否则返回

bool isListening() const

如果服务器当前正在监听传入的连接,则返回true;否则返回false。

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

服务器开始监听指定addres和port上的连接。

int maxPendingConnections() const

返回挂起的已接受连接的最大数目。默认值为30。

virtual QTcpSocket *nextPendingConnection()

返回下一个挂起的连接作为已连接的QTcpSocket对象。

void pauseAccepting()

暂停接受新连接。排队连接将保持在队列中。

QNetworkProxy proxy() const

返回此套接字的网络代理。默认使用QNetworkProxy::DefaultProxy。

void resumeAccepting()

恢复接受新连接。

QHostAddress serverAddress() const

如果服务器正在监听连接,返回服务器地址;否则返回QHostAddress::零。

QAbstractSocket::SocketError serverError() const

返回发生的最后一个错误的错误码。

quint16 serverPort() const

如果服务器正在监听连接,返回服务器的端口;否则返回0。

void setMaxPendingConnections(int numConnections)

将挂起的接受连接的最大数量设置为numConnections。在调用nextPendingConnection()之前,QTcpServer将接受不超过numConnections的传入连接。默认情况下,这个限制是30个挂起的连接。

void setProxy(const QNetworkProxy &networkProxy)

将此套接字的显式网络代理设置为networkProxy。

bool setSocketDescriptor(qintptr socketDescriptor)

设置此服务器在监听到socketDescriptor的传入连接时应该使用的套接字描述符。如果套接字设置成功,返回true;否则返回false。

qintptr socketDescriptor() const

设置此服务器在监听到socketDescriptor的传入连接时应该使用的套接字描述符。如果套接字设置成功,返回true;否则返回false。假定套接字处于监听状态。

bool waitForNewConnection(int msec = 0, bool *timedOut = Q_NULLPTR)

等待最多毫秒或直到传入连接可用。如果连接可用,返回true;否则返回false。如果操作超时且timeout不是O,则* timeout将被设为true。

•从QObject继承了31个公共函数,具体查看Qt帮助文档

7. QTcpSocket(通信套接字)

TCP(Transmission Control Protocol,传输控制协议)

TCP是一个用于数据传输的地城网络协议,多个网络协议包括(HTTP和FTP都是基于TCP协议),TCP是面向数据流和面向连接的可靠的传输协议。

QTcpSocket类继承自QAbstractSocket,与QUdpSocket传输的数据报不同的是,QTcpSocket传输的是连续的数据流,尤其适合连续的数据传输,TCP一般分为客户端和服务端,即C/S (Client/Server模型)。

QTcpSocket代表了两个独立的数据流,一个用来读取数据,一个用来写入数据,分别采用QTcpSocket::read()及QTcpSocket::write()操作,读取数据前先调用QTcpSocket::bytesAvailable来确定已有足够的数据可用。

QTcpServer处理客户端的连接,可通过QTcpServer::listen()监听客户端发来的连接请求,每当有客户端连接时会发射newConnection()信号,QTcpSocket可用于读取客户端发来的数

信号

公有函数

•从QAbstractSocket继承了37个公共函数,上面有写QAbstractSocket的公有函数

•从QIODevice继承了33个公共函数,具体不详细描述,常用的有以下函数:

qint64 QIODevice::read(char *data, qint64 maxSize)//从设备读取最多maxSize字节到数据,并返回读取的字节数。如果发生错误,例如试图读取以WriteOnly模式打开的设备时,此函数返回-1。
    
QByteArray QIODevice::read(qint64 maxSize)//这是一个重载函数。从设备读取最多maxSize字节,并将读取的数据作为QByteArray返回。这个函数没有方法报告错误;返回一个空的QByteArray可能意味着当前没有数据可供读取,或者发生了错误。
    
QByteArray QIODevice::readAll()//从设备读取所有剩余数据,并以字节数组的形式返回。这个函数没有方法报告错误;返回一个空的QByteArray可能意味着当前没有数据可供读取,或者发生了错误。
    
qint64 QIODevice::write(const char *data, qint64 maxSize)//将数据写入设备的数据最多为maxSize字节。返回实际写入的字节数,如果发生错误则返回-1。
    
qint64 QIODevice::write(const char *data)//这是一个重载函数。将以零结尾的8位字符字符串写入设备。返回实际写入的字节数,如果发生错误则返回-1。
    
qint64 QIODevice::write(const QByteArray &byteArray)//这是一个重载函数。将byteArray的内容写入设备。返回实际写入的字节数,如果发生错误则返回-1。

•从QObject继承了31个公共函数,具体查看Qt帮助文档

7.1 TCP聊天程序

Qt Tcp通信和windows的类似,分服务端和客户端,模型如下

 

运行效果:

服务器:

 

客户端:

 

文件结构:

 本文福利,费领取Qt开发学习资料包、技术视频,内容包括(C++语言基础,Qt编程入门,QT信号与槽机制,QT界面开发-图像绘制,QT网络,QT数据库编程,QT项目实战,QT嵌入式开发,Quick模块等等)↓↓↓↓↓↓见下面↓↓文章底部点击费领取↓↓

7.1.1 TCP服务器

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 slots:
    void on_btnSend_clicked();
​
    void on_benClose_clicked();
​
private:
    Ui::Widget *ui;
​
    QTcpServer* tcpServer;//监听套接字
    QTcpSocket* tcpSocket;//通信套接字
};
#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 = nullptr;
    tcpSocket = nullptr;
​
    setWindowTitle("Server");
    ui->textEditRead->setReadOnly(true);
​
    tcpServer = new QTcpServer(this);
​
    tcpServer->listen(QHostAddress::Any, 8888);
​
    connect(tcpServer,&QTcpServer::newConnection,[=](){
        //取出建立好连接的套接字
        tcpSocket = tcpServer->nextPendingConnection();
        //获取对方的IP和端口
        QString ip = tcpSocket->peerAddress().toString();
        qint16 port = tcpSocket->peerPort();
        QString temp = QString("[%1 : %2]: 成功连接").arg(ip).arg(port);
        ui->textEditRead->setText(temp);
​
        connect(tcpSocket,&QTcpSocket::readyRead,[=](){
            //从通信套接字中取出内容
            QByteArray array = tcpSocket->readAll();
            ui->textEditRead->append(array);
        });
​
    });
​
​
}
​
Widget::~Widget()
{
    delete ui;
}
​
​
void Widget::on_btnSend_clicked()
{
    if(tcpSocket == nullptr)
    {
        return;
    }
​
    //获取编辑区内容
    QString str = ui->textEditWrite->toPlainText();
​
    //给对方发送数据,使用套接字是tcpSocket
    tcpSocket->write(str.toUtf8().data());
​
    //清空编辑区内容
    ui->textEditWrite->clear();
}
​
void Widget::on_benClose_clicked()
{
    if(tcpSocket == nullptr)
    {
        return;
    }
​
    //主动和对方断开连接
    tcpSocket->disconnectFromHost();
    tcpSocket->close();
    tcpSocket = nullptr;
}
​

7.1.2 TCP客户端

widget.h代码

#ifndef WIDGET_H
#define WIDGET_H
​
#include <QWidget>
#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 slots:
    void on_btn_Connect_clicked();
​
    void on_btn_Send_clicked();
​
    void on_btn_Close_clicked();
​
private:
    Ui::Widget *ui;
​
    QTcpSocket* tcpSocket;
};
#endif // WIDGET_H
​

widget.cpp代码

#include "widget.h"
#include "ui_widget.h"
​
#include <QHostAddress>
​
Widget::Widget(QWidget *parent): QWidget(parent) , ui(new Ui::Widget)
{
    ui->setupUi(this);
​
    setWindowTitle("Client");
    ui->textEditRead->setReadOnly(true);
​
    tcpSocket = nullptr;
​
    tcpSocket = new QTcpSocket(this);
​
    connect(tcpSocket,&QTcpSocket::connected,[=](){
        ui->textEditRead->setText("成功和服务器建立好连接!");
    });
​
    connect(tcpSocket,&QTcpSocket::readyRead,[=](){
        //从通信套接字中取出内容
        QByteArray array = tcpSocket->readAll();
        ui->textEditRead->append(array);
    });
}
​
Widget::~Widget()
{
    delete ui;
}
​
​
void Widget::on_btn_Connect_clicked()
{
    //获取服务器Ip和端口
    QString ip = ui->lineEditIP->text();
    qint16 port = ui->lineEditPort->text().toInt();
​
    //主动和服务器建立连接
    tcpSocket->connectToHost(QHostAddress(ip),port);
}
​
void Widget::on_btn_Send_clicked()
{
    //获取编辑区内容
    QString str = ui->textEditWrite->toPlainText();
​
    //给对方发送数据,使用套接字是tcpSocket
    tcpSocket->write(str.toUtf8().data());
​
    //清空编辑区内容
    ui->textEditWrite->clear();
}
​
void Widget::on_btn_Close_clicked()
{
    //主动和对方断开连接
    tcpSocket->disconnectFromHost();
    tcpSocket->close();
}
​

8. QUdpSocket(通信套接字)

UDP(User Datagram Protocol,用户数据报协议)

UDP是一个轻量级、不可靠、面向数据报的、无连接的协议,多用于可靠性要求不严格,不是非常重要的传输。

QUdpSocket类继承自QAbstractSocket,用来发送和接收UDP数据报,”Socket”即套接字,套接字即IP地址+端口号。其中IP地址指定了网络中的一台主机,二端口号则指定了该主机上的一个网络程序,使用套接字即可实现网络上的两个应用程序之间的通信。

QUdpSocket支持IPv4广播,要广播数据报,则只需发送到一个特殊的地址QHostAddress::Broadcast(即255.255.255.255),数据报一般建议发送字节数小于512字节。端口号选择1024-65535(1024以下的常用作保留端口号,如FTP常用端口号21,Telnet常用端口号23,DNS域名服务器常用端口53等)。

信号

公有函数

函数

描述

bool hasPendingDatagrams() const

如果至少有一个数据报等待读取,则返回true;否则返回false。

bool joinMulticastGroup(const QHostAddress &groupAddress)

加入操作系统选择的默认接口上groupAddress指定的组播组。套接字必须处于BoundState状态,否则会发生错误。

bool joinMulticastGroup(const QHostAddress &groupAddress, const QNetworkInterface &iface)

这是一个重载函数。在接口上加入组播组地址groupAddress。

bool leaveMulticastGroup(const QHostAddress &groupAddress)

在操作系统选择的默认接口上离开groupAddress指定的组播组。套接字必须处于BoundState状态,否则会发生错误。

bool leaveMulticastGroup(const QHostAddress &groupAddress, const QNetworkInterface &iface)

这是一个重载函数。离开接口上指定groupAddress的组播组。

QNetworkInterface multicastInterface() const

返回多播数据报的出接口。这对应于IPv4套接字的IP_MULTICAST_IF套接字选项和IPv6套接字的IPV6_MULTICAST_IF套接字选项。如果之前没有设置接口,这个函数将返回一个无效的qnetworkinterface。套接字必须处于BoundState状态,否则返回无效的QNetworkInterface。

qint64 pendingDatagramSize() const

返回第一个挂起的UDP数据报的大小。如果没有可用的数据报,这个函数返回-1。

qint64 readDatagram(char *data, qint64 maxSize, QHostAddress *address = nullptr, quint16 *port = nullptr)

接收不大于maxSize字节的数据报,并将其存储在数据中。发送方的主机地址和端口存储在address和port中(除非指针是0)。成功返回数据报的大小;否则返回1。

QNetworkDatagram receiveDatagram(qint64 maxSize = -1)

接收一个不大于maxSize字节的数据报,并在QNetworkDatagram对象中返回它,以及发送者的主机地址和端口。如果可能,该函数还将尝试确定数据报的目的地址、端口和接收时的跳数。

void setMulticastInterface(const QNetworkInterface &iface)

将组播数据报的出接口设置为当前接口。这对应于IPv4套接字的IP_MULTICAST_IF套接字选项和IPv6套接字的IPV6_MULTICAST_IF套接字选项。套接字必须处于BoundState状态,否则此函数不执行任何操作。

qint64 writeDatagram(const char *data, qint64 size, const QHostAddress &address, quint16 port)

将大小为size的数据报发送到端口上的主机地址。返回成功发送的字节数;否则返回1。

qint64 writeDatagram(const QByteArray &datagram, const QHostAddress &host, quint16 port)

这是一个重载函数。将数据报发送到主机地址和端口。

•从QAbstractSocket继承了37个公共函数,上面有写QAbstractSocket的公有函数

•从QIODevice继承了33个公共函数,具体不详细描述,常用的有以下函数:

qint64 QIODevice::read(char *data, qint64 maxSize)//从设备读取最多maxSize字节到数据,并返回读取的字节数。如果发生错误,例如试图读取以WriteOnly模式打开的设备时,此函数返回-1。
    
QByteArray QIODevice::read(qint64 maxSize)//这是一个重载函数。从设备读取最多maxSize字节,并将读取的数据作为QByteArray返回。这个函数没有方法报告错误;返回一个空的QByteArray可能意味着当前没有数据可供读取,或者发生了错误。
    
QByteArray QIODevice::readAll()//从设备读取所有剩余数据,并以字节数组的形式返回。这个函数没有方法报告错误;返回一个空的QByteArray可能意味着当前没有数据可供读取,或者发生了错误。
    
qint64 QIODevice::write(const char *data, qint64 maxSize)//将数据写入设备的数据最多为maxSize字节。返回实际写入的字节数,如果发生错误则返回-1。
    
qint64 QIODevice::write(const char *data)//这是一个重载函数。将以零结尾的8位字符字符串写入设备。返回实际写入的字节数,如果发生错误则返回-1。
    
qint64 QIODevice::write(const QByteArray &byteArray)//这是一个重载函数。将byteArray的内容写入设备。返回实际写入的字节数,如果发生错误则返回-1。

•从QObject继承了31个公共函数,具体查看Qt帮助文档

8.1 UDP单播,多播,广播

QUdpSocket也支持UDP组播。 使用joinMulticastGroup()和leaveMulticastGroup()控制组成员,使用QAbstractSocket::MulticastTtlOption和QAbstractSocket::MulticastLoopbackOption设置TTL和loopback套接字选项。 使用setMulticastInterface()控制组播数据报的出接口,使用multicastInterface()查询出接口。

使用QUdpSocket,您还可以使用connectToHost()建立到UDP服务器的虚拟连接,然后使用read()和write()交换数据报,而不需要为每个数据报指定接收者。

Broadcast Sender、Broadcast Receiver、Multicast Sender和Multicast Receiver示例演示了如何在应用程序中使用QUdpSocket。

 

8.1.1 单播

单播(Unicast)是在一个单个的发送者和一个接受者之间通过网络进行的通信。

  • 发送端:
//创建通信套接字
QUdpSocket *udpSocket = new QUdpSocket(this);
static int i=0;
​
//将数据报发送到主机地址和端口。
udpSocket->writeDatagram(QString("DataGram %1").arg(i++).toUtf8(),QHostAddress("127.0.0.1"),8888);
  • 接受端:
QUdpSocket *udpSocket = new QUdpSocket(this);
​
//使用BindMode模式绑定到端口上的地址。
udpSocket->bind(QHostAddress("127.0.0.1"),8888);
​
//连接信号与槽
connect(m_udpSocket,&QUdpSocket::readyRead,this,&MainWindow::onReadyRead);
​
void MainWindow::onReadyRead()
{
    QUdpSocket* udpSocket = dynamic_cast<QUdpSocket*>(sender());
    if(udpSocket)
    {
        //接收一个不大于maxSize字节的数据报,并在QNetworkDatagram对象中返回它,以及发送者的主机地址和端口。
        qDebug()<<udpSocket->receiveDatagram().data();
    }
}

8.1.2 多播

多播(Multicast)是一点对多点的通信,IPv6没有采用IPv4中的组播术语,而是将广播看成是多播的一个特殊例子。

多播与单播步骤是一样的,只有IP地址有所区别。

多播的地址是特定的,D类地址用于多播。D类IP地址就是多播IP地址,即224.0.0.0至239.255.255.255之间的IP地址,并被划分为局部连接多播地址、预留多播地址和管理权限多播地址3类:

  • 1,局部多播地址:在224.0.0.0~224.0.0.255之间,这是为路由协议和其他用途保留的地址,路由器并不转发属于此范围的IP包
  • 2,预留多播地址:在224.0.1.0~238.255.255.255之间,可用于全球范围(如Internet)或网络协议。
  • 3,管理权限多播地址:在239.0.0.0~239.255.255.255之间,可供组织内部使用,类似于私有IP地址,不能用于Internet,可限制多播范围。

多播的程序设计使用setsockopt()函数和getsockopt()函数来实现,组播的选项是IP层的,其选项值和含义参见11.5所示。

选项

描述

IP_MULTICAST_TTL

设置多播组数据的TTL值

IP_ADD_MEMBERSHIP

在指定接口上加入组播组

IP_DROP_MEMBERSHIP

退出组播组

IP_MULTICAST_IF

获取默认接口或设置接口

IP_MULTICAST_LOOP

禁止组播数据回送

多播程序设计的框架

要进行多播的编程,需要遵从一定的编程框架。多播程序框架主要包含套接字初始化、设置多播超时时间、加入多播组、发送数据、接收数据以及从多播组中离开几个方面。其步骤如下:

(1)建立一个socket。

(2)然后设置多播的参数,例如超时时间TTL、本地回环许可LOOP等。

(3)加入多播组。

(4)发送和接收数据。

(5)从多播组离开。

  • 发送端:
QUdpSocket *udpSocket = new QUdpSocket(this);
//给某个指定的组发数据报
static int i = 0;
QByteArray datagram = "multi message " + QByteArray::number(i++);
​
//将数据报发送到主机地址和端口。
m_udpSocket->writeDatagram(datagram,QHostAddress("238.239.239.239"),45678);
  • 接受端:
QUdpSocket *udpSocket = new QUdpSocket(this);
​
//允许端口和ip重用,多个socket绑定同一ip和端口
udpSocket->bind(QHostAddress::AnyIPv4,45678,QUdpSocket::BindFlag::ShareAddress | QUdpSocket::ReuseAddressHint);
​
//多播是分组的,加入一个指定组
udpSocket->joinMulticastGroup(QHostAddress("238.239.239.239"));
​
connect(&udpSocket,&QUdpSocket::readyRead,this,&Receiver::onReadyRead);
connect(&udpSocket,QOverload<QUdpSocket::SocketError>::of(&QUdpSocket::error),this,[=](){qDebug()<<"haserror"<<udpSocket.errorString();});
​
​
void Receiver::onReadyRead()
{
    QByteArray datagram;
    while(udpSocket->hasPendingDatagrams())//hasPendingDatagrams()如果至少有一个数据报等待读取,则返回true;
    {
        datagram.resize(udpSocket.pendingDatagramSize());//pendingDatagramSize()返回第一个挂起的UDP数据报的大小。
        udpSocket->readDatagram(datagram.data(),datagram.size());//接收不大于maxSize字节的数据报,并将其存储在数据中。
        ui->label->setText(tr("Received datagram:%1").arg(datagram.constData()));
    }
}

8.1.3 广播

广播(broadcast)是一点到所有点的通信方式。

广播与组播是一样的,只是ip地址有所不同,而且不用加入指定的组。单播的数据只是收发数据的特定主机进行处理,组播在特定组之间进行处理,而广播的数据整个局域网都进行处理。

“广播”可以理解为一个人通过广播喇叭对在场的全体说话,这样做的好处是通话效率高,信息一下子就可以传递到全体。

“广播”在网络中的应用较多,如客户机通过DHCP自动获得IP地址的过程就是通过广播来实现的。但是同单播和多播相比,广播几乎占用了子网内网络的所有带宽。拿开会打一个比方吧,在会场上只能有一个人发言,想象一下如果所有的人同时都用麦克风发言,那会场上就会乱成一锅粥。

在IP网络中,广播地址用IP地址“255.255.255.255”来表示,这个IP地址代表同一子网内所有的IP地址。

  • 发送端:
QUdpSocket *udpSocket = new QUdpSocket(this);
​
//给某个指定的组发数据报
static int i = 0;
QByteArray datagram = "multi message " + QByteArray::number(i++);
​
//将数据报发送到主机地址和端口。
udpSocket->writeDatagram(datagram,QHostAddress(QHostAddress::Broadcast),45678);
  • 接受端:
QUdpSocket *udpSocket = new QUdpSocket(this);
​
//允许端口和ip重用,多个socket绑定同一ip和端口
udpSocket->bind(QHostAddress::AnyIPv4,45678,QUdpSocket::BindFlag::ShareAddress | QUdpSocket::ReuseAddressHint);
​
connect(&m_udpSocket,&QUdpSocket::readyRead,this,&Receiver::onReadyRead);
connect(&m_udpSocket,QOverload<QUdpSocket::SocketError>::of(&QUdpSocket::error),this,[=](){qDebug()<<"haserror"<<m_udpSocket.errorString();});
​
​
void Receiver::onReadyRead()
{
    QByteArray datagram;
    while(udpSocket->hasPendingDatagrams())//hasPendingDatagrams()如果至少有一个数据报等待读取,则返回true;
    {
        datagram.resize(m_udpSocket.pendingDatagramSize());//pendingDatagramSize()返回第一个挂起的UDP数据报的大小。
        
        udpSocket->readDatagram(datagram.data(),datagram.size());//接收不大于maxSize字节的数据报,并将其存储在数据中。
        ui->label->setText(tr("Received datagram:%1").arg(datagram.constData()));
    }
}

本文福利,费领取Qt开发学习资料包、技术视频,内容包括(C++语言基础,Qt编程入门,QT信号与槽机制,QT界面开发-图像绘制,QT网络,QT数据库编程,QT项目实战,QT嵌入式开发,Quick模块等等)↓↓↓↓↓↓见下面↓↓文章底部点击费领取↓↓

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值