文章目录
1 基础概念
1.1 网络通信概念
- MAC地址(硬件地址)
- 网络IP地址(比如192.168.0.xxx)
- 网络端口(实现多路通信,用来给不同应用程序来区分使用,范围(0~65535),1024之前的端口号已经有其他用途)
1.2 交换机和路由器
- 交换机:端到端转发,基于MAC地址实现不同设备数据转发,速度快
- 路由器:根据实际数据路线转发,基于网络IP地址实现不同网络之间的数据转发
- 路由器在网络层,咯尤其可以处理TCP/IP协议,交换机不可以
- 交换机在中继层,路由器根据IP地址寻址,交换机根据MAC地址寻址
- 路由器提供防火墙服务,具有虚拟拨号上网功能,交换机不具备
1.3 TCP和UDP
1.TCP(传输控制协议):
面向链接的协议,主要用于大量数据的场合。
一个TCP链接必须要经过三次握手才能建立链接。
三次握手:
第一次握手:建立连接时,客户端发送syn包(seq=j)到服务器,并进入SYN_SENT状态,等待服务器确认;
SYN:同步序列编号(Synchronize Sequence Numbers)。
第二次握手:服务器收到syn包,必须确认客户的SYN(ack=j+1),
同时自己也发送一个SYN包(seq=k),即SYN+ACK包,此时服务器进入SYN_RECV状态。
第三次握手:客户端收到服务器的SYN+ACK包,向服务器发送确认包ACK(ack=k+1),
此包发送完毕,客户端和服务器进入ESTABLISHED(TCP连接成功)状态,完成三次握手。
我们假设A和B是通信的双方。我理解的握手实际上就是通信,发一次信息就是进行一次握手。
第一次握手: A给B打电话说,你可以听到我说话吗?
第二次握手: B收到了A的信息,然后对A说: 我可以听得到你说话啊,你能听得到我说话吗?
第三次握手: A收到了B的信息,然后说可以的,我要给你发信息啦!
在三次握手之后,A和B都能确定这么一件事: 我说的话,你能听到; 你说的话,我也能听到。 这样,就可以开始正常通信了。
为什么不是两次握手或者四次握手?
如果两次,那么B无法确定B的信息A是否能收到,所以如果B先说话,可能后面的A都收不到,会出现问题。
如果四次,那么就造成了浪费,因为在三次结束之后,就已经可以保证A可以给B发信息,A可以收到B的信息; B可以给A发信息,B可以收到A的信息。
2.UDP(用户数据协议)
无连接协议,主要用于不要求分组顺序,少量数据的传输场合,数据传输效率高,但是容易掉包。
2 网络编程——TCP
QTcpServer——服务端
QTcpSocket——客户端
- 异步(非阻塞)访问
通过信号和槽来实现。比如:当我们调用链接函数去链接服务器时,并不是立即链接成功,当链接成功时,会接收一个信号。 - 同步(阻塞)访问
通过waitForxxxx()函数来实现阻塞,如果使用此方法来链接,采用多线程,否则,会导致ANR现象,UI线程会被阻塞。
注:
获取到的ip为::ffff:192.168.0.xxx
可以使用ip = ip.remove(0,7);保留192.168.0.xxx
客户端实现过程:
[1]客户向服务器发送链接请求
[2]发送数据
[3]建立与服务端读取数据的信号与槽,并且读取服务器数据
服务端实现过程:
[1]监听客户端
[2]建立新客户端信号相关的槽链接
[3]在新客户端信号相关的槽函数里,判断客户端是否向我发来消息,
当客户端发送消息时,该客户端会自动发送一个信号readyRead
[4]在读取消息槽函数中,要对客户端进行判断,到底是谁再给我发送消息。
MyTcpSocket
void MainWindow::on_linkBtn_clicked()
{
//[1]建立链接
// quint16 port = ui->ipLineEdit->text().toInt();
// socket.connectToHost(ui->ipLineEdit->text(),port);
socket.connectToHost(ui->ipLineEdit->text(),1991);
ui->ipTextBrowser->append(ui->ipLineEdit->text());
//[3]建立与服务端读取数据的链接
connect(&socket,&QTcpSocket::readyRead,
this,&MainWindow::readData);
}
void MainWindow::on_cleanBtn_clicked()
{
ui->sendMsgTextEdit->clear();
}
void MainWindow::on_sendBtn_clicked()
{
//[2]发送消息
QByteArray msg = ui->sendMsgTextEdit->toPlainText().toUtf8();
socket.write(msg);
ui->msgTextBrowser->append("客户端(我):");
ui->msgTextBrowser->append(msg);
}
void MainWindow::readData()
{
QString msg = socket.readAll();
ui->msgTextBrowser->append("服务端:");
ui->msgTextBrowser->append(msg);
}
MyTcpServer
void MainWindow::on_startBtn_clicked()
{
//[1]监听
// quint16 port = ui->portLineEdit->text().toInt();
// server.listen(QHostAddress::Any,port);
server.listen(QHostAddress::Any,1991);
//[2]建立新客户端信号相关的槽函数
connect(&server,&QTcpServer::newConnection,
this,&MainWindow::newClient);
}
void MainWindow::on_cleanBtn_clicked()
{
ui->sendMsgTextEdit->clear();
}
void MainWindow::on_sendBtn_clicked()
{
QByteArray msg = ui->sendMsgTextEdit->toPlainText().toUtf8();
socket->write(msg);
ui->msgTextBrowser->append("服务端(我):");
ui->msgTextBrowser->append(msg);
}
void MainWindow::newClient()
{
//[3]判断是谁在给我发送数据
socket = server.nextPendingConnection();
connect(socket,&QTcpSocket::readyRead,
this,&MainWindow::readData);
}
void MainWindow::readData()
{
//[4]
QTcpSocket *msocket = dynamic_cast<QTcpSocket*>(sender());
QString msg = msocket->readAll();
ui->msgTextBrowser->append("客户端:");
ui->msgTextBrowser->append(msg);
QString ip = msocket->peerAddress().toString();//::ffff:192.168.0.xxx
ip = ip.remove(0,7);//192.168.0.xxx
ui->ipTextBrowser->append(ip);
}
3 HTTP协议
[1]QNetworkAccessManager 管理类
Allows the application to send network requests and receive replies
[2]QNetworkReply 回应类
Contains the data and headers for a request sent with QNetworkAccessManager
[3]QNetworkRequest 请求类
Holds a request to be sent with QNetworkAccessManager
[4][signal] void QNetworkAccessManager::finished(QNetworkReply *reply)
当发送请求完成后会发送finshed信号
[5]QNetworkReply *QNetworkAccessManager::get(const QNetworkRequest &request)
Posts a request to obtain the contents of the target request
and returns a new QNetworkReply object opened for reading which emits the readyRead() signal whenever new data arrives.
实例:获取网络上的图片,并打印出来。
//建立finshed相关的槽函数
connect(&manger,&QNetworkAccessManager::finished,
this,&MainWindow::readData);
void MainWindow::on_sendBtn_clicked()
{
//发起网络请求
QUrl url(ui->lineEdit->text());
QNetworkRequest request(url);
manger.get(request);
}
void MainWindow::readData(QNetworkReply *reply)
{
//在槽函数中读取数据
QByteArray arr = reply->readAll();
QPixmap map;
map.loadFromData(arr);
map = map.scaled(ui->label->size());
ui->label->setPixmap(map);
}
4 UDP通信
- 每次写一个数据包,会发送bytesWritten(),有数据到达时,会发送readyRead()信号,不发送当前的信号,当前的数据包就会丢失,下一个数据包会发送自己的readyRead()。
- writeDatagram() 和readDatagram() / receiveDatagram()传输数据。
- UDP可以进行一对一的通信,并且它不需要建立链接,还可以进行组播
[1]socket端发送数据包,发送错误返回-1
qint64 QUdpSocket::writeDatagram(const char *data, qint64 size,
const QHostAddress &address, quint16 port)
qint64 QUdpSocket::writeDatagram(const QNetworkDatagram &datagram)
qint64 QUdpSocket::writeDatagram(const QByteArray &datagram,
const QHostAddress &host, quint16 port)
[2] 接收端绑定发送端
bool QAbstractSocket::bind(const QHostAddress &address,
quint16 port, QAbstractSocket::BindMode mode)
bool QAbstractSocket::bind(quint16 port, QAbstractSocket::BindMode mode)
[3]server端接收数据包
qint64 QUdpSocket::readDatagram(char *data, qint64 maxSize,
QHostAddress *address = nullptr, quint16 *port = nullptr)
UDPSocket
void MainWindow::on_sendBtn_clicked()
{
QString data = ui->textEdit->toPlainText();
qint64 ret =msocket.writeDatagram(data.toUtf8(),QHostAddress(ui->ipLineEdit->text()),ui->portLineEdit->text().toUShort());
if(ret != -1)
{
QMessageBox::information(this,"信息提示","发送成功");
}
}
UDPServer
//[2]建立readyRead信号的槽函数
connect(&msocket,&QUdpSocket::readyRead,
this,&MainWindow::readData);
void MainWindow::on_bandBtn_clicked()
{
//[1]绑定发送端
msocket.bind(QHostAddress::AnyIPv4,ui->portLineEdit->text().toUShort());
}
void MainWindow::readData()
{
char buffer[1024];
QHostAddress address;
quint16 port;
QByteArray datagarm;
while (msocket.hasPendingDatagrams())
{
//[3]读取发送端的数据
datagarm.resize(msocket.pendingDatagramSize());
msocket.readDatagram(datagarm.data(),datagarm.size(),&address,&port);
}
ui->textBrowser->append(address.toString() + ":" + datagarm);
// msocket.readDatagram(buffer,1024,&address,&port);
// ui->textBrowser->append(address.toString() + ":" + buffer);
}
5 JSON文件
5.1 获取JSON文件
[1]基础
链接https://www.sojson.com/来检验json文件是否正确
{}-----------json对象
[]-----------json数组
[2]解析JSON文件步骤
1.将服务器返回的数据转换成一个QJsonDOcument
2.根据文件的数据格式,判断json数据最开始是json数组还是json对象
QJsonObject object = doc.object();
QJsonArray arr = doc.array();
3.根据文件的数据格式,再根据key值来获取值,并且判断值的类型,用to系列函数来进行转换
QJsonObject object = fileDoc.object();
QJsonArray resultArr = object.value("result").toArray();
QJsonObject resultObj = resultArr.at(0).toObject();
QString runtime = resultObj.value("runtime").toString();
4.将获取的值给ui组件,如果是显示图片,则利用HHTP协议中的管理类、请求类以及回复类。
//http管理者finshed相关的槽函数
connect(&manger,&QNetworkAccessManager::finished,
this,&MainWindow::readData);
void MainWindow::on_getFilmInfoBtn_clicked()
{
QFile file(":/film.json");
bool isOk = file.open(QIODevice::ReadOnly);
if(isOk)
{
QByteArray fileArr = file.readAll();
//将QByteArray转化成QJsonDocument
QJsonDocument fileDoc = QJsonDocument::fromJson(fileArr);
QJsonObject object = fileDoc.object();
QJsonArray resultArr = object.value("result").toArray();
QJsonObject resultObj = resultArr.at(0).toObject();
QString runtime = resultObj.value("runtime").toString();
QString language = resultObj.value("language").toString();
QString film_locations = resultObj.value("film_locations").toString();
QString year = resultObj.value("year").toString();
QString poster = resultObj.value("poster").toString();
ui->languageLabel->setText(language);
ui->yearLabel->setText(year);
ui->filmLocationsLabel->setText(film_locations);
ui->runtimeLabel->setText(runtime);
qDebug() << poster;
QUrl url(poster);
QNetworkRequest request(url);
manger.get(request);
}
else
{
QMessageBox::information(this,"文件打开","打开文件失败");
}
}
void MainWindow::readData(QNetworkReply *reply)
{
QByteArray arr = reply->readAll();
QPixmap map;
map.loadFromData(arr);
map = map.scaled(ui->postLabel->size());
ui->postLabel->setPixmap(map);
}
5.2 创建JSON文件
解析json文件从外向内逐步解析,创建json文件从内向外逐步创建。
创建完成之后应该在JSON官网进行检验JSON文件是否正确。
[1]QJsonArray和QJsonObject插入数据
void QJsonArray::insert(int i, const QJsonValue &value)
QJsonArray::iterator QJsonArray::insert(QJsonArray::iterator before, const QJsonValue &value)
QJsonObject::iterator QJsonObject::insert(const QString &key, const QJsonValue &value)
[2]QJsonDocument插入数组或者对象
void QJsonDocument::setArray(const QJsonArray &array)
void QJsonDocument::setObject(const QJsonObject &object)
[3]将QJsonDocument转化成QByteArray
QByteArray QJsonDocument::toJson() const
QByteArray QJsonDocument::toJson(QJsonDocument::JsonFormat format) const
将QJsonDocument转换成UTF-8编码的数据
两种格式如下:
1.QJsonDocument::Indented 0
2.QJsonDocument::Compact 1
第一种的形式:
{
"Array": [
true,
999,
"string"
],
"Key": "Value",
"null": null
}
第二种的形式:
{"Array":[true,999,"string"],"Key":"Value","null":null}
创建一个员工staff类的json文件。
思路:
第一步:创建Staff类。
第二步:丛内向外逐步创建。
第三步:将QJsonDocument文件转换为QByteArray。
第四步:将内容写入文件中。
Staff *s1 = new Staff;
s1->setName("张三");
s1->setAge("28");
s1->setSex("男");
s1->setSalry("10000");
.......................
QJsonObject s1Obj;
s1Obj.insert("name",s1->getName());
s1Obj.insert("sex",s1->getSex());
s1Obj.insert("age",s1->getAge());
s1Obj.insert("salry",s1->getSalry());
.......................
QJsonArray staffArr;
staffArr.insert(0,s1Obj);
staffArr.insert(1,s2Obj);
staffArr.insert(2,s3Obj);
QJsonObject staffObj;
staffObj.insert("staff",staffArr);
QJsonDocument doc;
doc.setObject(staffObj);
QByteArray data = doc.toJson();
QFile file("./staff.json");
file.open(QIODevice::WriteOnly);
file.write(data);
file.close();