在应用程序开发中网络编程非常重要,目前互联网通行的TCP/IP协议,自上而下分为四层:应用层、传输层、网络层和网络接口层。实际编写网络应用程序时只使用到传输层和应用层,所用到的协议主要为UDP、TCP、HTTP和FTP。
虽然目前主流的操作系统都提供了统一的套接字抽象编程接口,用于编写不同层次的网络程序,但是这种方式比较繁琐,甚至有时需要应用底层操作系统相关数据结构,Qt提供了一个网络模块QtNetwork完美解决了问题。
在网络应用中,经常需要获得本机的主机名、IP地址和硬件地址等信息。运用QHostInfo、QNetworkInterface、QNetworkAddressEntry可获得本机信息。
实例使用网络模块获取主机名和IP地址:
#include
#include
#include
#include
#include
#include
// 使用网络模块需要在pro文件中添加:QT += network
#include
#include
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 listAddress = hostInfo.addresses();
if (!listAddress.isEmpty())
{
ipLineEdit->setText(listAddress.first().toString());
}
}
void NetworkInfo::slotDetail()
{
QString detail = "";
// 主机IP地址和网络接口列表
QList 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 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);
}
用户数据报协议(User Data Protocol,UDP)是一种简单轻量级、不可靠、面向数据报、无连接的传输层协议,可以应用在可靠性不是十分高的场合,如短消息和广播信息等。
UDP协议的工作原理:UDP客户端向UDP服务器发送一定长度的请求报文,报文大小的限制与各系统的协议实现相关,但不能超过其下层IP协议规定的64K;UDP服务器同样以报文形式做出响应。如果服务器未收到此请求,客户端不会重发,因此报文的传输是不可靠的。例如-QQ就是用UDP协议发送消息的, 因此有时会出现收不到消息的情况。
UDP的客户端与服务器是不建立连接,直接调用发送和接收函数进行数据通信的,Qt通过QUdpSocket类实现UDP协议的编程。
首先模拟UDP服务器编程:
#include
#include
#include
#include
#include
#include
#include
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
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
#include
#include
#include
#include
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
#include
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。客户端就会自动将接收到的数据显示出来:
传输控制协议(Transmission Control Protocol ,TCP)是一种可靠地、面向连接的、面向数据流的传输协议,许多高层应用协议(HTTP、FTP)都是以它为基础的,TCP协议非常适合数据的连续传输。
Qt中使用QTcpSocket和QTcpServer类实现TCP编程。
首先创建TCP服务端:
第一步,编写服务器的处理客户端的逻辑
#include
#include
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
#include
#include "TcpClient.h"
class Server : public QTcpServer
{
Q_OBJECT
public:
explicit Server(QObject *parent = 0, int port = 0);
// QList用来保存每一个与客户端连接的TcpClientSocket
QList 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
#include
#include
#include
#include
#include
#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
#include
#include
#include
#include
#include
#include
#include
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
#include
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();
}
QUdpSocket、QTcpSocke、QTcpServer都是网络传输层上的类,他们封装实现的是低层次的网络进程通信的功能。而Qt网络应用开发是在此基础上进一步实现应用型的协议功能。应用协议(HTTP/FTP/SMTP)运行在TCP/UDP之上。
网络请求由QNetworkRequest类来表示,作为与请求有关的统一容器,在创建请求对象时指定的URL决定了请求使用的协议。QNetworkAccessManager类用于协调网络操作,每当一个请求创建后,该类用来调度他,并发送信号来报告进度;QNetworkReply用于网络请求的应答,他会在请求呗完成调度时由QNetworkAccessManager创建。