编程语言
首页 > 编程语言> > Qt - TCP&UDP网络编程

Qt - TCP&UDP网络编程

作者:互联网

网络编程

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

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

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();

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接口。

QNetworkInterface

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

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

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

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

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();
    }
}

QNetworkAddressEntry

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

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

 

QAbstractSocket

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

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

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

我们举个例子:

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的流操作符(操作符<<()和操作符>>())一起使用。 但是有一个问题需要注意:在尝试使用操作符>>()读取数据之前,必须确保有足够的数据可用。

 

QTcpSocket

QTcpSocket类提供一个TCP套接字。

QTcpSocket *tcpSocket = new QTcpSocket(this);
tcpSocket->connectToHost("127.0.0.1",8888);
​
//此信号在调用connectToHost()并成功建立连接后发出。  
void connected()
//这个信号在套接字被断开时发出。      
void disconnected()
//此信号在发生错误后发出。当发出此信号时,套接字可能还没有准备好重新连接。 在这种情况下,尝试重新连接应该从事件循环中完成。 例如,使用带有0作为超时的QTimer::singleShot()。   
void error(QAbstractSocket::SocketError socketError)
//在调用connectToHost()并成功查找主机之后,将发出此信号。    
void hostFound()
//每当有新的数据可以从设备的当前读取通道读取时,就会发出此信号。    
void readyRead()

 

QUdpSocket

UDP(用户数据报协议)是一种轻量级、不可靠、面向数据报、无连接的协议。 当可靠性不重要时,可以使用它。 QUdpSocket是QAbstractSocket的一个子类,允许您发送和接收UDP数据报。

使用该类最常见的方法是使用bind()绑定到一个地址和端口,然后调用writeDatagram()和readDatagram() / receiveDatagram()来传输数据。 如果你想使用标准的QIODevice函数read(), readLine(), write()等,你必须首先通过调用connectToHost()将套接字直接连接到对等体。

套接字在每次将数据报写入网络时发出bytesWritten()信号。 如果您只是想发送数据报,则不需要调用bind()。

readyRead()信号在数据报到达时被触发。 在这种情况下,hasPendingDatagrams()返回true。 调用pendingDatagramSize()获取第一个挂起的数据报的大小,并调用readDatagram()或receiveDatagram()读取它。

注意:当接收到readyRead()信号时,应该读取传入的数据报,否则该信号将不会为下一个数据报发出。

Example:

void Server::initSocket()
{
   udpSocket = new QUdpSocket(this);
   udpSocket->bind(QHostAddress::LocalHost, 7755);

   connect(udpSocket, &QUdpSocket::readyRead,
              this, &Server::readPendingDatagrams);
}

void Server::readPendingDatagrams()
{
   while (udpSocket->hasPendingDatagrams()) {
          QNetworkDatagram datagram = udpSocket->receiveDatagram();
          processTheDatagram(datagram);
   }
}

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。

UDP单播,多播,广播

单播

单播(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);
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)
    {
        qDebug()<<udpSocket->receiveDatagram().data();
    }
}
多播(组播)

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

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

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

多播的程序设计使用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);
connect(&m_udpSocket,&QUdpSocket::readyRead,this,&Receiver::onReadyRead);
connect(&m_udpSocket,QOverload<QUdpSocket::SocketError>::of(&QUdpSocket::error),this,[=](){qDebug()<<"haserror"<<m_udpSocket.errorString();});
//允许端口和ip重用,多个socket绑定同一ip和端口
m_udpSocket.bind(QHostAddress::AnyIPv4,45678,QUdpSocket::BindFlag::ShareAddress | QUdpSocket::ReuseAddressHint);
//加入指定组
m_udpSocket.joinMulticastGroup(QHostAddress("238.239.239.239"));

void Receiver::onReadyRead()
{
    QByteArray datagram;
    while(m_udpSocket.hasPendingDatagrams())
    {
        datagram.resize(m_udpSocket.pendingDatagramSize());
        m_udpSocket.readDatagram(datagram.data(),datagram.size());
        ui->label->setText(tr("Received datagram:%1").arg(datagram.constData()));
    }
}

QUdpSocket-Qt使用Udp通讯实现服务端和客户端

单播,多播(组播),广播,详细讲解呀

UDP 单播、广播、多播

UDP中转服务器

QT利用UDP实现简单群聊系统

广播

广播(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++);
m_udpSocket->writeDatagram(datagram,QHostAddress(QHostAddress::Broadcast),45678);
QUdpSocket *udpSocket = new QUdpSocket(this);
connect(&m_udpSocket,&QUdpSocket::readyRead,this,&Receiver::onReadyRead);
connect(&m_udpSocket,QOverload<QUdpSocket::SocketError>::of(&QUdpSocket::error),this,[=](){qDebug()<<"haserror"<<m_udpSocket.errorString();});
//允许端口和ip重用,多个socket绑定同一ip和端口
m_udpSocket.bind(QHostAddress::AnyIPv4,45678,QUdpSocket::BindFlag::ShareAddress | QUdpSocket::ReuseAddressHint);

void Receiver::onReadyRead()
{
    QByteArray datagram;
    while(m_udpSocket.hasPendingDatagrams())
    {
        datagram.resize(m_udpSocket.pendingDatagramSize());
        m_udpSocket.readDatagram(datagram.data(),datagram.size());
        ui->label->setText(tr("Received datagram:%1").arg(datagram.constData()));
    }
}

 

QTcpServer

QTcpServer类提供了一个基于tcp的服务器。

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

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

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

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

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

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

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

Server::Server(QObject *parent) : QObject(parent)
{
    m_tcpServer = new QTcpServer(this);
    m_tcpServer->listen(QHostAddress::Any,6666);
    connect(m_tcpServer,&QTcpServer::acceptError,this,&Server::onAcceptError);
    connect(m_tcpServer,&QTcpServer::newConnection,this,&Server::onNewConnection);
}
void Server::onAcceptError(QAbstractSocket::SocketError socketErr)
{
    qDebug()<<"hasError"<<socketErr;
}

void Server::onNewConnection()
{
    qDebug()<<"newConnection";
    //获取下一个待处理的连接
    QTcpSocket* tcpSocket = m_tcpServer->nextPendingConnection();
    m_tcps.push_back(tcpSocket);
    tcpSocket->write("hello");
    qDebug()<<m_tcps.size();
    connect(tcpSocket,&QTcpSocket::readyRead,this,&Server::onReadyRead);
}

void Server::onReadyRead()
{
    QTcpSocket *tcpsokcet = dynamic_cast<QTcpSocket*>(sender());
    if(tcpsokcet)
    {
         qDebug() <<"server"<<QHostAddress(tcpsokcet->peerAddress().toIPv4Address()).toString()<<tcpsokcet->peerName()<<tcpsokcet->peerPort();
    }
}

常用函数

//服务器开始监听指定addres和port上的连接,有新链接发出newConnection()信号
bool listen(const QHostAddress &address = QHostAddress::Any, quint16 port = 0)
//将下一个挂起的连接作为已连接的QTcpSocket对象返回。  在 newConnection()的槽函数中使用
virtual QTcpSocket *nextPendingConnection()


//当接受新连接导致错误时将发出此信号。 socketError参数描述了发生的错误类型。  
[signals] void acceptError(QAbstractSocket::SocketError socketError)
//每当有新连接可用时,就会发出此信号。      
[signals] void newConnection()    
 

标签:多播,UDP,Qt,QAbstractSocket,TCP,地址,QHostAddress,QUdpSocket,接字
来源: https://www.cnblogs.com/zhuchunlin/p/16485946.html