Qt Socket网络编程
Posted cpp_learner
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Qt Socket网络编程相关的知识,希望对你有一定的参考价值。
网络编程在现在互联网时代是非常重要的,所以这篇博客将带你了解QT语言是如何使用Socket进行TCP和UPD通讯的!
图文超详细,一看就会!不会评论区找我!
目录
一、概述
先来简单了解一下TPC与UDP通讯的知识点!
-
TCP
tcp传输控制协议是一种面向连接的、可靠的、基于字节流的传输层通信协议;tcp通讯一定要经过三次握手才可以连接成功进行通讯;且,tcp通讯只能一对一进行连接;现在大多数通讯都是使用tcp协议,例如用于发送文件等。QTcpSocket继承自QAbstractSocket,与QUdpSocket传输的数据报不同的是,QTcpSocket传输的是连续的数据流,尤其适合连续的数据传输,TCP一般分为客户端和服务端,即C/S (Client/Server模型)。
QTcpSocket代表了两个独立的数据流,一个用来读取数据,一个用来写入数据,分别采用QTcpSocket::read()及QTcpSocket::write()操作,读取数据前先调用QTcpSocket::bytesAvailable来确定已有足够的数据可用。
QTcpServer处理客户端的连接,可通过QTcpServer::listen()监听客户端发来的连接请求,每当有客户端连接时会发射newConnection()信号,QTcpSocket可用于读取客户端发来的数据报,亦可发送数据报。
-
UDP
upd协议集支持一个无连接的传输协议,该协议称为用户数据报协议。udp为应用程序提供了一种无需建立连接就可以发送封装的 IP 数据包的方法。也就是说,udp通讯无需经过三次握手就可以实现通信;但它是一种无脑式通讯,他关心接收方是否接收到了数据;所以udp通讯效率是非常高的,他一般被用于发送文字、视频通话等。UDP是一个轻量级、不可靠、面向数据报的、无连接的协议,多用于可靠性要求不严格,不是非常重要的传输。
QUdpSocket类继承自QAbstractSocket,用来发送和接收UDP数据报,”Socket”即套接字,套接字即IP地址+端口号。其中IP地址指定了网络中的一台主机,二端口号则指定了该主机上的一个网络程序,使用套接字即可实现网络上的两个应用程序之间的通信。QUdpSocket支持IPv4广播,要广播数据报,则只需发送到一个特殊的地QHostAddress::Broadcast(即255.255.255.255),数据报一般建议发送字节数小于512字节。端口号选择1024-65535(1024以下的常用作保留端口号,如FTP常用端口号21,Telnet常用端口号23,DNS域名服务器常用端口53等)。
新建项目时,QT编译工具需要在.pro文件中添加代码:QT += network
VS编译工具需要勾选network项:
注意:
QTcpSocket被称为通信套接字;
QTcpServer被称为监听套接字。
二、TCP通信
1. 发送文字
首先你得先了解QtTCP通信的流程,下面将根据下图进行讲解:
- 服务端调用listen函数进行监听,是否有客户端与其进行连接;
- 客户端需要进行主动与客户端连接,调用connectToHost进行连接;
- 服务端:如果与客户端连接成功,服务器会触发newConnection信号;
- 客户端:如果与服务器连接成功,客户端会触发connected信号;
- 当断开连接,客户端的通信套接字会触发disconnected信号,服务器的通信套接字也会触发disconnected信号;
- 服务器:触发newConnection信号后,必须在槽函数中实例化QTcpSocket对象;
- 服务端与客户端都是通过自己的通信套接字使用wirte函数进行发送信息;
- 服务端与客户端接收到对方发过来的消息时,都会触发readyRead信号,然后就可以在对应槽函数做接受处理;
- 服务端与客户端的断开都是使用通信套接字调用disconnectFromHost函数进行断开处理。
好了,了解清楚这些内容后,写代码就好理解了!
最总运行效果:
1). 服务端
简单写下操作过程,详细请看下方全部代码!
服务端用到的信号:
newConnection、readyRead、disconnected
-
实例化监听套接字 与 进行监听
// 监听套接字,指定父对象,让其自动回收空间 tcpServer = new QTcpServer(this); // 监听 tcpServer->listen(QHostAddress::AnyIPv4, 8888);
参数一:指定连接的地址类型;如:QHostAddress::AnyIPv4 或者 QHostAddress::AnyIPv6 或者 QHostAddress::Any等
参数二:端口号。此指定的端口号作为客户端连接的依据之一。 -
连接成功后,实例化通信套接字
// 取出建立好链接的套接字 tcpSocket = tcpServer->nextPendingConnection(); // 实例化服务端的通信套接字 // 获取对方的IP和端口 QString ip = tcpSocket->peerAddress().toString(); qint16 port = tcpSocket->peerPort();
-
给客户端发送数据
// 给对方发送数据 QString str = "你好呀!"; tcpSocket->write(str.toUtf8().data());
因为有中文,所以需要utf8进行修饰,然后转换为const char *类型进行发送!也可以说是QByteArray 类型。
-
接收数据
// 从通信套接字中读取内容 QByteArray arrays = tcpSocket->readAll(); // 读取所有内容
最简单的就是使用readAll方法一下子读取客户端发来的全部内容。
-
与客户端断开连接
// 主动和客户端断开连接 tcpSocket->disconnectFromHost(); tcpSocket->close();
-
服务器取消监听
tcpServer ->close();
-
信号与槽的连接
// 当有客户端连接过来后会触发newConnection信号 connect(tcpServer, &QTcpServer::newConnection, this, &_QTcpServer::ConnectSucceed); // 当有消息发送过来,对方的通信套接字会触发readyRead信号 connect(tcpSocket, &QTcpSocket::readyRead, this, &_QTcpServer::ReceiveInformation); // 当服务器主动断开连接会触发disconnected信号 connect(tcpSocket, &QTcpSocket::disconnected, this, &_QTcpServer::DisConnect);
ui界面:
全部代码实现(VS代码):
_QTcpServer.h文件
#pragma once
#include <QtWidgets/QWidget>
#include "ui_QTcpServer.h"
#include <QTcpSocket> // 通信套接字
#include <QTcpServer> // 监听套接字
#pragma execution_character_set("utf-8") // qt支持显示中文
class _QTcpServer : public QWidget {
Q_OBJECT
public:
_QTcpServer(QWidget *parent = Q_NULLPTR);
private slots:
void ConnectSucceed();
void ReceiveInformation();
void DisConnect();
void on_btnSend_clicked();
void on_btnClose_clicked();
private:
Ui::QTcpServerClass ui;
QTcpServer *tcpServer; // 监听套接字
QTcpSocket *tcpSocket; // 通信套接字
};
_QTcpServer.cpp文件
#include "_QTcpServer.h"
_QTcpServer::_QTcpServer(QWidget *parent)
: QWidget(parent) {
ui.setupUi(this);
tcpServer = Q_NULLPTR;
tcpSocket = Q_NULLPTR;
// 监听套接字,指定父对象,让其自动回收空间
tcpServer = new QTcpServer(this);
tcpServer->listen(QHostAddress::AnyIPv4, 8888);
setWindowTitle("服务器 端口号:8888");
// 当有客户端连接过来后会触发newConnection信号
connect(tcpServer, &QTcpServer::newConnection, this, &_QTcpServer::ConnectSucceed);
}
void _QTcpServer::ConnectSucceed() {
// 取出建立好链接的套接字
tcpSocket = tcpServer->nextPendingConnection();
// 获取对方的IP和端口
QString ip = tcpSocket->peerAddress().toString();
qint16 port = tcpSocket->peerPort();
QString temp = QString("[%1:%2]:成功连接...").arg(ip).arg(port);
ui.txtEditRead->setText(temp);
// 当有消息发送过来,对方的通信套接字会触发readyRead信号
connect(tcpSocket, &QTcpSocket::readyRead, this, &_QTcpServer::ReceiveInformation);
// 当服务器主动断开连接会触发disconnected信号
connect(tcpSocket, &QTcpSocket::disconnected, this, &_QTcpServer::DisConnect);
}
void _QTcpServer::ReceiveInformation() {
// 从通信套接字中读取内容
QByteArray arrays = tcpSocket->readAll();
// 追加设置内容
ui.txtEditRead->append(arrays);
}
void _QTcpServer::DisConnect() {
//ui.txtEditRead->append("客户端主动断开连接...");
}
void _QTcpServer::on_btnSend_clicked() {
if (tcpSocket == Q_NULLPTR) {
// 追加设置内容
ui.txtEditRead->append("未与客户端连接...");
return;
}
// 获取编辑区内容
QString str = ui.txtEditWirte->toPlainText();
ui.txtEditWirte->clear();
// 给对方发送数据
tcpSocket->write(str.toUtf8().data());
}
void _QTcpServer::on_btnClose_clicked() {
if (tcpSocket == Q_NULLPTR) {
// 追加设置内容
ui.txtEditRead->append("未与客户端连接...");
return;
}
// 主动和客户端断开连接
tcpSocket->disconnectFromHost();
tcpSocket->close();
tcpSocket = Q_NULLPTR;
ui.txtEditRead->append("与客户端断开连接...");
}
2). 客户端
简单写下操作过程,详细请看下方全部代码!
客户端用到的信号:
connected、readyRead、disconnected
-
实例化通信套接字
// 分配控件指定父对象 tcpSocket = new QTcpSocket(this);
-
与服务器进行连接
// 获取服务器IP和端口 qint16 port = 8888; QString ip = "127.0.0.1"; // 主动和服务器建立连接 tcpSocket->connectToHost(QHostAddress(ip), port);
记得,参数一,需要使用QHostAddress进行修饰!
-
发送数据
// 发送信息 QString str = "你也好!"; tcpSocket->write(str.toUtf8().data());
-
读取服务器发送过来的数据
// 读取服务器发送过来的消息 QByteArray arrays = tcpSocket->readAll();
-
主动与服务器断开连接
// 主动与服务器断开连接 tcpSocket->disconnectFromHost(); tcpSocket->close();
-
信号与槽的连接
// 成功与服务器连接会触发connected信号 connect(tcpSocket, &QTcpSocket::connected, this, &_QTcpSocket::SuccessfulConnection); // 当有消息发送过来,通信套接字会触发readyRead信号 connect(tcpSocket, &QTcpSocket::readyRead, this, &_QTcpSocket::ReceiveInformation); // 当服务器主动断开连接会触发disconnected信号 connect(tcpSocket, &QTcpSocket::disconnected, this, &_QTcpSocket::DisConnect);
ui界面:
全部代码实现(VS代码):
_QTcpSocket.h文件
#pragma once
#include <QWidget>
#include "ui__QTcpSocket.h"
#include <QTcpSocket> // 通信套接字
#pragma execution_character_set("utf-8") // qt支持显示中文
class _QTcpSocket : public QWidget {
Q_OBJECT
public:
_QTcpSocket(QWidget *parent = Q_NULLPTR);
~_QTcpSocket();
private slots:
void on_btnConnect_clicked();
void on_btnSend_clicked();
void on_btnClose_clicked();
void SuccessfulConnection();
void ReceiveInformation();
void DisConnect();
private:
Ui::_QTcpSocket ui;
QTcpSocket *tcpSocket;
};
_QTcpSocket.cpp文件
#include "_QTcpSocket.h"
#include <QHostAddress>
_QTcpSocket::_QTcpSocket(QWidget *parent)
: QWidget(parent) {
ui.setupUi(this);
tcpSocket = Q_NULLPTR;
setWindowTitle("客户端");
// 分配控件指定父对象
tcpSocket = new QTcpSocket(this);
// 成功与服务器连接会触发connected信号
connect(tcpSocket, &QTcpSocket::connected, this, &_QTcpSocket::SuccessfulConnection);
// 当有消息发送过来,通信套接字会触发readyRead信号
connect(tcpSocket, &QTcpSocket::readyRead, this, &_QTcpSocket::ReceiveInformation);
// 当服务器主动断开连接会触发disconnected信号
connect(tcpSocket, &QTcpSocket::disconnected, this, &_QTcpSocket::DisConnect);
}
_QTcpSocket::~_QTcpSocket() {}
void _QTcpSocket::on_btnSend_clicked() {
// 获取输入框中的内容
QString str = ui.txtWirte->toPlainText();
ui.txtWirte->clear();
// 发送信息
tcpSocket->write(str.toUtf8().data());
}
void _QTcpSocket::on_btnClose_clicked() {
// 主动与服务器断开连接
tcpSocket->disconnectFromHost();
tcpSocket->close();
ui.txtRead->append("与服务器断开连接...");
}
void _QTcpSocket::SuccessfulConnection() {
ui.txtRead->append("成功与服务器连接...");
}
void _QTcpSocket::ReceiveInformation() {
// 读取服务器发送过来的消息
QByteArray arrays = tcpSocket->readAll();
// 追加显示
ui.txtRead->append(arrays);
}
void _QTcpSocket::DisConnect() {
if (ui.txtRead == Q_NULLPTR) {
return;
}
//ui.txtRead->append("服务器与你断开连接...");
}
void _QTcpSocket::on_btnConnect_clicked() {
// 获取服务器IP和端口
qint16 port = ui.lineEditPort->text().toInt();
QString ip = ui.lineEditIP->text();
// 主动和服务器建立连接
tcpSocket->connectToHost(QHostAddress(ip), port);
}
2. 发送文件
发送文件,通信流程与发送文字差不多,差别就是一些执行步骤需要注意的。
由于TCP固有的毛病,粘包,所以这里列举了两种方式解决粘包的问题!
1). 定时器方式解决粘包问题
看发送文件流程图
-
发送方选择一个文件,获取文件的大小和文件名;
QString filePath = QFileDialog::getOpenFileName(this, "open", "../"); // 获取文件信息 QFileInfo info(filePath); fileName = info.fileName(); fileSize = info.size();
-
发送方将文件大小和文件名组成一个包发送给接收方;
// 先发送文件头信息 文件名##文件大小 QString head = QString("%1##%2").arg(fileName).arg(fileSize); // 发送头部信息 qint64 len = tcpSocket->write(head.toUtf8().data());
-
接收方接收文件信息包,解析获得文件大小和文件名字信息;在本地创建一个名字一样的文件;
// 取出接收的内容 QByteArray buf = tcpSocket->readAll(); // 解析头部信息 fileName = QString(buf).section("##", 0, 0); fileSize = QString(buf).section("##", 1, 1).toInt(); // 创建一个文件 file.setFileName(fileName); bool isOk = file.open(QIODevice::WriteOnly);
-
发送方使用定时器延时20毫秒;
// 防止TCP粘包文件 // 需要通过定时器延时20毫秒 timer->start(20); /* 解决粘包,方式一,使用定时器 */
-
发送发读取文件数据,发送给接收方,读多少发多少;
// 每次发送数据的大小 char buf[4 * 1024] = { 0 }; len = 0; // 往文件中读数据 len = file.read(buf, sizeof(buf)); // 发送数据,读多少,发多少 len = tcpSocket->write(buf, len);
-
接收方接收文件数据,对方发多少,接收多少;
// 取出接收的内容 QByteArray buf = tcpSocket->readAll();
-
接收方将接收到的文件数据写入文件中;
// 接收的文件数据写入文件 qint64 len = file.write(buf);
-
发送方判断是否发送文件完毕;
// 是否发送文件完毕 if (sendSize == fileSize) { QMessageBox::information(this, "提示", QString("发送文件完毕! 文件大小:%1").arg(sendSize)); file.close(); // 把客户端断开 tcpSocket->disconnectFromHost(); tcpSocket->close(); }
-
接收方判断是否接收文件完毕。
// 当两个值相等,说明文件接收完毕 if (recvSize == fileSize) { file.close(); QMessageBox::information(this, "提示", "文件接收完毕!"); isStart = true; tcpSocket->disconnectFromHost(); tcpSocket->close(); }
好了,了解清楚这些内容后,写代码就好理解了!
最总运行效果:
全部代码实现(VS代码):
服务端:QTcpSendFile.h
#pragma once
#include <QtWidgets/QWidget>
#include "ui_QTcpSendFile.h"
#include qt中如何实现多线程?