您的位置:首页 > 理论基础 > 计算机网络

Qt5开发学习之网络与通信(十二)

2017-09-07 16:13 585 查看
在应用程序开发中网络编程非常重要,目前互联网通行的TCP/IP协议,自上而下分为四层:应用层、传输层、网络层和网络接口层。实际编写网络应用程序时只使用到传输层和应用层,所用到的协议主要为UDP、TCP、HTTP和FTP。
虽然目前主流的操作系统都提供了统一的套接字抽象编程接口,用于编写不同层次的网络程序,但是这种方式比较繁琐,甚至有时需要应用底层操作系统相关数据结构,Qt提供了一个网络模块QtNetwork完美解决了问题。


获取本机网络信息

在网络应用中,经常需要获得本机的主机名、IP地址和硬件地址等信息。运用QHostInfo、QNetworkInterface、QNetworkAddressEntry可获得本机信息。

实例使用网络模块获取主机名和IP地址:

#include <QWidget>
#include <QLabel>
#include <QPushButton>
#include <QLineEdit>
#include <QGridLayout>
#include <QMessageBox>

// 使用网络模块需要在pro文件中添加:QT += network
#include <QHostInfo>
#include <QNetworkInterface>

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<QHostAddress> listAddress = hostInfo.addresses();
if (!listAddress.isEmpty())
{
ipLineEdit->setText(listAddress.first().toString());
}
}

void NetworkInfo::slotDetail()
{
QString detail = "";
// 主机IP地址和网络接口列表
QList<QNetworkInterface> 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<QNetworkAddressEntry> 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);
}


获取到的如图:



基于UDP的网络广播程序

用户数据报协议(User Data Protocol,UDP)是一种简单轻量级、不可靠、面向数据报、无连接的传输层协议,可以应用在可靠性不是十分高的场合,如短消息和广播信息等。

适合应用的场景有:
1、网络数据大多为短消息;

2、拥有大量客户端;

3、对数据安全性无特殊要求;

4、网络负担非常重,但对相应速度要求高。

UDP协议的工作
4000
原理:UDP客户端向UDP服务器发送一定长度的请求报文,报文大小的限制与各系统的协议实现相关,但不能超过其下层IP协议规定的64K;UDP服务器同样以报文形式做出响应。如果服务器未收到此请求,客户端不会重发,因此报文的传输是不可靠的。例如-QQ就是用UDP协议发送消息的, 因此有时会出现收不到消息的情况。

UDP的客户端与服务器是不建立连接,直接调用发送和接收函数进行数据通信的,Qt通过QUdpSocket类实现UDP协议的编程。

首先模拟UDP服务器编程:

#include <QDialog>
#include <QLabel>
#include <QPushButton>
#include <QLineEdit>
#include <QVBoxLayout>
#include <QUdpSocket>
#include <QTimer>

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 <QHostAddress>

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 <QDialog>
#include <QVBoxLayout>
#include <QTextEdit>
#include <QPushButton>
#include <QUdpSocket>

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 <QMessageBox>
#include <QHostAddress>

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。客户端就会自动将接收到的数据显示出来:



基于TCP的网络聊天室程序

传输控制协议(Transmission Control Protocol ,TCP)是一种可靠地、面向连接的、面向数据流的传输协议,许多高层应用协议(HTTP、FTP)都是以它为基础的,TCP协议非常适合数据的连续传输。

Qt中使用QTcpSocket和QTcpServer类实现TCP编程。

首先创建TCP服务端:

第一步,编写服务器的处理客户端的逻辑

#include <QObject>
#include <QTcpSocket>

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 <QObject>
#include <QTcpServer>

#include "TcpClient.h"

class Server : public QTcpServer
{
Q_OBJECT
public:
explicit Server(QObject *parent = 0, int port = 0);
// QList用来保存每一个与客户端连接的TcpClientSocket
QList<TcpClient *> 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 <QDialog>
#include <QListWidget>
#include <QLabel>
#include <QLineEdit>
#incl
d254
ude <QPushButton>
#include <QGridLayout>

#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 <QDialog>
#include <QListWidget>
#include <QLabel>
#include <QLineEdit>
#include <QPushButton>
#include <QGridLayout>

#include <QHostAddress>
#include <QTcpSocket>

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 <QMessageBox>
#include <QHostInfo>

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


Qt网络应用初步开发

QUdpSocket、QTcpSocke、QTcpServer都是网络传输层上的类,他们封装实现的是低层次的网络进程通信的功能。而Qt网络应用开发是在此基础上进一步实现应用型的协议功能。应用协议(HTTP/FTP/SMTP)运行在TCP/UDP之上。

网络请求由QNetworkRequest类来表示,作为与请求有关的统一容器,在创建请求对象时指定的URL决定了请求使用的协议。QNetworkAccessManager类用于协调网络操作,每当一个请求创建后,该类用来调度他,并发送信号来报告进度;QNetworkReply用于网络请求的应答,他会在请求呗完成调度时由QNetworkAccessManager创建。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息