大部分资料整理自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
相似。QHostInfo
和QNetworkInterface
获取ip地址的区别在于,QNetworkInterface
会返回更多的地址,比如本机的192.168.1.5
,QHostInfo
不会返回这个地址。
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通信:
TCP
(Transmission Control Protocol
)是一种被大多数Internet
网络协议(如HTTP
和FTP
)用于数据传输的低级网络协议,它是可靠的、面向流、面向连接的传输协议,特别适用于连续数据的传输。
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
类实现标准的网络通信协议,如POP3
、SMTP
和NNTP
,也可以设计自定义协议。
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
客户端使用QTcpSocket
与TCP
服务器建立连接并通信。
客户端的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
:(本机ip
:192.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
,还有Null
、LocalHost
、LocalHostIPv6
、Broadcast
和Any
。)
接着,打开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
连接创建一个线程。