Qt Socket网络编程

Posted cpp_learner

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Qt Socket网络编程相关的知识,希望对你有一定的参考价值。

网络编程在现在互联网时代是非常重要的,所以这篇博客将带你了解QT语言是如何使用Socket进行TCP和UPD通讯的!

图文超详细,一看就会!不会评论区找我!



一、概述

先来简单了解一下TPC与UDP通讯的知识点!

  1. 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可用于读取客户端发来的数据报,亦可发送数据报。

  2. 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通信的流程,下面将根据下图进行讲解:
在这里插入图片描述

  1. 服务端调用listen函数进行监听,是否有客户端与其进行连接;
  2. 客户端需要进行主动与客户端连接,调用connectToHost进行连接;
  3. 服务端:如果与客户端连接成功,服务器会触发newConnection信号;
  4. 客户端:如果与服务器连接成功,客户端会触发connected信号;
  5. 当断开连接,客户端的通信套接字会触发disconnected信号,服务器的通信套接字也会触发disconnected信号;
  6. 服务器:触发newConnection信号后,必须在槽函数中实例化QTcpSocket对象;
  7. 服务端与客户端都是通过自己的通信套接字使用wirte函数进行发送信息;
  8. 服务端与客户端接收到对方发过来的消息时,都会触发readyRead信号,然后就可以在对应槽函数做接受处理;
  9. 服务端与客户端的断开都是使用通信套接字调用disconnectFromHost函数进行断开处理。

好了,了解清楚这些内容后,写代码就好理解了!

最总运行效果:
在这里插入图片描述


1). 服务端

简单写下操作过程,详细请看下方全部代码!

服务端用到的信号:
newConnection、readyRead、disconnected

  1. 实例化监听套接字 与 进行监听

    // 监听套接字,指定父对象,让其自动回收空间
    tcpServer = new QTcpServer(this);
    
    // 监听
    tcpServer->listen(QHostAddress::AnyIPv4, 8888);
    

    参数一:指定连接的地址类型;如:QHostAddress::AnyIPv4 或者 QHostAddress::AnyIPv6 或者 QHostAddress::Any等
    参数二:端口号。此指定的端口号作为客户端连接的依据之一。

  2. 连接成功后,实例化通信套接字

    // 取出建立好链接的套接字
    tcpSocket = tcpServer->nextPendingConnection();	// 实例化服务端的通信套接字
    
    // 获取对方的IP和端口
    QString ip = tcpSocket->peerAddress().toString();
    qint16 port = tcpSocket->peerPort();
    
  3. 给客户端发送数据

    // 给对方发送数据
    QString str = "你好呀!";
    tcpSocket->write(str.toUtf8().data());
    

    因为有中文,所以需要utf8进行修饰,然后转换为const char *类型进行发送!也可以说是QByteArray 类型。

  4. 接收数据

    // 从通信套接字中读取内容
    QByteArray arrays = tcpSocket->readAll();	// 读取所有内容
    

    最简单的就是使用readAll方法一下子读取客户端发来的全部内容。

  5. 与客户端断开连接

    // 主动和客户端断开连接
    tcpSocket->disconnectFromHost();
    tcpSocket->close();
    
  6. 服务器取消监听

    tcpServer ->close();
    
  7. 信号与槽的连接

    // 当有客户端连接过来后会触发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

  1. 实例化通信套接字

    // 分配控件指定父对象
    tcpSocket = new QTcpSocket(this);
    
  2. 与服务器进行连接

    // 获取服务器IP和端口
    qint16 port = 8888;
    QString ip = "127.0.0.1";
    
    // 主动和服务器建立连接
    tcpSocket->connectToHost(QHostAddress(ip), port);
    

    记得,参数一,需要使用QHostAddress进行修饰!

  3. 发送数据

    // 发送信息
    QString str = "你也好!";
    tcpSocket->write(str.toUtf8().data());
    
  4. 读取服务器发送过来的数据

    // 读取服务器发送过来的消息
    QByteArray arrays = tcpSocket->readAll();
    
  5. 主动与服务器断开连接

    // 主动与服务器断开连接
    tcpSocket->disconnectFromHost();
    tcpSocket->close();
    
  6. 信号与槽的连接

    // 成功与服务器连接会触发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). 定时器方式解决粘包问题

看发送文件流程图
在这里插入图片描述

  1. 发送方选择一个文件,获取文件的大小和文件名;

    QString filePath = QFileDialog::getOpenFileName(this, "open", "../");
    // 获取文件信息
    QFileInfo info(filePath);
    fileName = info.fileName();
    fileSize = info.size();
    
  2. 发送方将文件大小和文件名组成一个包发送给接收方;

    // 先发送文件头信息  文件名##文件大小
    QString head = QString("%1##%2").arg(fileName).arg(fileSize);
    // 发送头部信息
    qint64 len = tcpSocket->write(head.toUtf8().data());
    
  3. 接收方接收文件信息包,解析获得文件大小和文件名字信息;在本地创建一个名字一样的文件;

    // 取出接收的内容
    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);
    
  4. 发送方使用定时器延时20毫秒;

    // 防止TCP粘包文件
    // 需要通过定时器延时20毫秒
    timer->start(20);	/* 解决粘包,方式一,使用定时器 */
    
  5. 发送发读取文件数据,发送给接收方,读多少发多少;

    // 每次发送数据的大小
    char buf[4 * 1024] = { 0 };
    len = 0;
    
    // 往文件中读数据
    len = file.read(buf, sizeof(buf));
    
    // 发送数据,读多少,发多少
    len = tcpSocket->write(buf, len);		
    
  6. 接收方接收文件数据,对方发多少,接收多少;

    // 取出接收的内容
    QByteArray buf = tcpSocket->readAll();
    
  7. 接收方将接收到的文件数据写入文件中;

    // 接收的文件数据写入文件
    qint64 len = file.write(buf);
    
  8. 发送方判断是否发送文件完毕;

    // 是否发送文件完毕
    if (sendSize == fileSize) {
    	QMessageBox::information(this, "提示", QString("发送文件完毕! 文件大小:%1").arg(sendSize));
    	file.close();
    
    	// 把客户端断开
    	tcpSocket->disconnectFromHost();
    	tcpSocket->close();
    }
    
  9. 接收方判断是否接收文件完毕。

    // 当两个值相等,说明文件接收完毕
    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中如何实现多线程?

QT socket网络通信

Qt Socket网络编程

Qt编程遇到的问题,我在qt中直接使用C语言的程序片段,有问题 ,求解

QT 实用代码片段

QT tcp 编程 【在线等】