Qt实现客户端与服务器消息发送与文件传输

客户端与服务器之间的数据传送在很多案例场景里都会有应用。这里Jungle用Qt来简单设计实现一个场景,即:
①两端:服务器QtServer和客户端QtClient
②功能:服务端连接客户端,两者能够互相发送消息,传送文件,并且显示文件传送进度。
环境:VS2008+Qt4.8.6+Qt设计师

1.基本概念

客户端与服务器的基本概念不说了,关于TCP通信的三次握手等等,在经典教材谢希仁的《计算机网络》里都有详细介绍。这里说下两者是如何建立起通信连接的。
①IP地址:首先服务器和每一个客户端都有一个地址,即IP地址。(底层的MAC地址,不关心,因为TCP通信以及IP,是七层架构里面的网络层、传输层了,底层透明)。对于服务器来说,客户端的数量及地址是未知的,除非建立了连接。但是对于客户端来说,必须知道服务器的地址,因为两者之间的连接是由客户端主动发起的。
②端口号:软件层面的端口号,指的是“应用层的各种协议进程与运输实体进行层间交互的一种地址”。简而言之,每一个TCP连接都是一个进程,操作系统需要为每个进程分配一个协议端口(即每一个客户端与服务端的连接,不是两台主机的连接,而是两个端口的连接)。但一台主机通常会有很多服务,很多进程,单靠一个IP地址不能标识某个具体的进程或者连接。所以用端口号来标识访问的目标服务器以及服务器的目标服务类型。端口号也有分类,但这不是本文的重点,详见教材。
③TCP连接:总的来说,TCP的连接管理分为单个阶段:建立连接->数据传送->连接释放。在②里说到,每个TCP连接的是具体IP地址的主机的两个端口,即TCP连接的两个端点由IP地址和端口号组成,这即是套接字的概念:
套接字socket=IP:端口号
因此,我们要通过建立套接字来建立服务端与客户端的通信连接。

2.Qt相关类

QTcpSocket:提供套接字
QTcpServer:提供基于TCP的服务端,看官方文档的解释如下:
This class makes it possible to accept incoming TCP connections. You can specify the port or have QTcpServer pick one automatically. You can listen on a specific address or on all the machine’s addresses.
这个解释里面提到两点:
①指定端口:即开通哪一个端口用于建立TCP连接;
②监听:监听①中指定的端口是否有连接的请求。

3.UI设计

客户端:
这里写图片描述
服务端:
这里写图片描述

4.客户端实现

类设计如下:

class QtClient : public QWidget
{
	Q_OBJECT
public:
	QtClient(QWidget *parent = 0, Qt::WFlags flags = 0);
	~QtClient();

	void initTCP();
	void newConnect();

	private slots:
		连接服务器
		void connectServer();
		与服务器断开连接
		void disconnectServer();
		接收服务器发送的数据
		void receiveData();	
		向服务器发送数据
		void sendData();

		浏览文件
		void selectFile();
		发送文件
		void sendFile();
		更新文件发送进度
		void updateFileProgress(qint64);
		更新文件接收进度
		void updateFileProgress();

private:
	Ui::QtClientClass ui;
	QTcpSocket *tcpSocket;
	QTcpSocket *fileSocket;

	///文件传送
	QFile *localFile;
	///文件大小
	qint64 totalBytes;      //文件总字节数
	qint64 bytesWritten;    //已发送的字节数
	qint64 bytestoWrite;    //尚未发送的字节数
	qint64 filenameSize;    //文件名字的字节数
	qint64 bytesReceived;   //接收的字节数
	///每次发送数据大小
	qint64 perDataSize;
	QString filename;
	///数据缓冲区
	QByteArray inBlock;
	QByteArray outBlock;

	系统时间
	QDateTime current_date_time;
	QString str_date_time;
};

类实现如下:

#include "qtclient.h"

QtClient::QtClient(QWidget *parent, Qt::WFlags flags)
	: QWidget(parent, flags)
{
	ui.setupUi(this);
	this->initTCP();

	/文件传送相关变量初始化
	///每次发送数据大小为64kb
	perDataSize = 64*1024;
	totalBytes = 0;
	bytestoWrite = 0;
	bytesWritten = 0;
	bytesReceived = 0;
	filenameSize = 0;

	connect(this->ui.pushButton_openFile,SIGNAL(clicked()),this,SLOT(selectFile()));
	connect(this->ui.pushButton_sendFile,SIGNAL(clicked()),this,SLOT(sendFile()));
}

QtClient::~QtClient()
{
}

void QtClient::initTCP()
{
	this->tcpSocket = new QTcpSocket(this);
	connect(ui.pushButton_connect,SIGNAL(clicked()),this,SLOT(connectServer()));
	connect(ui.pushButton_disconnect,SIGNAL(clicked()),this,SLOT(disconnectServer()));
	connect(ui.pushButton_send,SIGNAL(clicked()),this,SLOT(sendData()));
}

void QtClient::connectServer()
{
	tcpSocket->abort();
	tcpSocket->connectToHost("127.0.0.1",6666);
	connect(tcpSocket,SIGNAL(readyRead()),this,SLOT(receiveData()));
}

这里说明一下两个方法:
①abort():官方文档给出了说明:
Aborts the current connection and resets the socket. Unlike disconnectFromHost(), this function immediately closes the socket, discarding any pending data in the write buffer.
即终止之前的连接,重置套接字。
②connectToHost():给定IP地址和端口号,连接服务器。这里我们给127.0.0.1,即本机地址,端口号随便给了个,一般来说介于49152~65535之间的都行。

void QtClient::disconnectServer()
{
	//这里不做实现了,大家自己定义吧O(∩_∩)O哈哈~
}

void QtClient::receiveData()
{
	/获取当前时间
	current_date_time = QDateTime::currentDateTime();
	str_date_time = current_date_time.toString("yyyy-MM-dd hh:mm:ss")+"\n";
	接收数据
	QString str = tcpSocket->readAll();
	显示
	str = "Server "+str_date_time+str;
	this->ui.textEdit->append(str);
}

void QtClient::sendData()
{
	发送数据
	QString str = ui.lineEdit->text();
	this->tcpSocket->write(ui.lineEdit->text().toLatin1());
	显示
	current_date_time = QDateTime::currentDateTime();
	str_date_time = current_date_time.toString("yyyy-MM-dd hh:mm:ss");
	str = "You "+str_date_time+"\n"+str;
	ui.textEdit->append(str);
}

这里说明QTCPSocket的两个方法:
①readAll():如果把一个socket比作一个通讯管道,那么这个方法的作用是读取该管道里的所有数据(格式为QByteArray);
②write():同上面的比喻,这个方法的作用是向管道里塞数据。

void QtClient::selectFile()
{
	this->fileSocket = new QTcpSocket(this);
	fileSocket->abort();
	fileSocket->connectToHost("127.0.0.1",8888);
	文件传送进度更新
	connect(fileSocket,SIGNAL(bytesWritten(qint64)),this,SLOT(updateFileProgress(qint64)));
	connect(fileSocket,SIGNAL(readyRead()),this,SLOT(updateFileProgress()));

	this->ui.progressBar->setValue(0);
	this->filename = QFileDialog::getOpenFileName(this,"Open a file","/","files (*)");
	ui.lineEdit_filename->setText(filename);
}

从上面那段代码可以看出,Jungle设计了两个socket,一个用于发送字符数据,另一个套接字用于传送文件。两个socket分别使用两个不同的端口。在服务端里也是这样,待会儿不再解释了。

void QtClient::sendFile()
{
	this->localFile = new QFile(filename);
	if(!localFile->open(QFile::ReadOnly))
	{
		ui.textEdit->append(tr("Client:open file error!"));
		return;
	}
	///获取文件大小
	this->totalBytes = localFile->size();
	QDataStream sendout(&outBlock,QIODevice::WriteOnly);
	sendout.setVersion(QDataStream::Qt_4_8);
	QString currentFileName = filename.right(filename.size()-filename.lastIndexOf('/')-1);
	
	qDebug()<<sizeof(currentFileName);
	保留总代大小信息空间、文件名大小信息空间、文件名
	sendout<<qint64(0)<<qint64(0)<<currentFileName;
	totalBytes += outBlock.size();
	sendout.device()->seek(0);
	sendout<<totalBytes<<qint64((outBlock.size()-sizeof(qint64)*2));

	bytestoWrite = totalBytes-fileSocket->write(outBlock);
	outBlock.resize(0);
}

这里同样说明两点:
①setVision():设定数据序列的版本,官方文档里说明这个不是必须的,但是推荐我们要去进行这一步的工作。我这里是Qt4.8.6,所以设定为Qt4.8.见下图(截自Qt官方文档)
这里写图片描述
②qint64:这个类型在Jungle之前的博客里也提到过,是指qt的无符号的整型,64位。

void QtClient::updateFileProgress(qint64 numBytes)
{
	已经发送的数据大小
	bytesWritten += (int)numBytes;

	如果已经发送了数据
	if(bytestoWrite > 0)
	{
		outBlock = localFile->read(qMin(bytestoWrite,perDataSize));
		///发送完一次数据后还剩余数据的大小
		bytestoWrite -= ((int)fileSocket->write(outBlock));
		///清空发送缓冲区
		outBlock.resize(0);
	}
	else
		localFile->close();

	更新进度条
	this->ui.progressBar->setMaximum(totalBytes);
	this->ui.progressBar->setValue(bytesWritten);

	如果发送完毕
	if(bytesWritten == totalBytes)
	{
		localFile->close();
		//fileSocket->close();
	}
}

void QtClient::updateFileProgress()
{
	QDataStream inFile(this->fileSocket);
	inFile.setVersion(QDataStream::Qt_4_8);

	///如果接收到的数据小于16个字节,保存到来的文件头结构
	if(bytesReceived <= sizeof(qint64)*2)
	{
		if((fileSocket->bytesAvailable()>=(sizeof(qint64))*2) && (filenameSize==0))
		{
			inFile>>totalBytes>>filenameSize;
			bytesReceived += sizeof(qint64)*2;
		}
		if((fileSocket->bytesAvailable()>=filenameSize) && (filenameSize != 0))
		{
			inFile>>filename;
			bytesReceived += filenameSize;
			filename = "ServerData/"+filename;
			localFile = new QFile(filename);
			if(!localFile->open(QFile::WriteOnly))
			{
				qDebug()<<"Server::open file error!";
				return;
			}
		}
		else
			return;
	}
	/如果接收的数据小于总数据,则写入文件
	if(bytesReceived < totalBytes)
	{
		bytesReceived += fileSocket->bytesAvailable();
		inBlock = fileSocket->readAll();
		localFile->write(inBlock);
		inBlock.resize(0);
	}

	数据接收完成时
	if(bytesReceived == totalBytes)
	{
		this->ui.textEdit->append("Receive file successfully!");
		bytesReceived = 0;
		totalBytes = 0;
		filenameSize = 0;
		localFile->close();
		//fileSocket->close();
	}
}

5.服务端实现

类的设计:

class QtServer : public QWidget
{
	Q_OBJECT

public:
	QtServer(QWidget *parent = 0, Qt::WFlags flags = 0);
	~QtServer();

	QTcpServer *server;
	QTcpSocket *socket;
	QTcpServer *fileserver;
	QTcpSocket *filesocket;

private slots:  
	void sendMessage(); 
	void acceptConnection();
	接收客户端发送的数据
	void receiveData();

	void acceptFileConnection();
	void updateFileProgress();
	void displayError(QAbstractSocket::SocketError socketError);

	///选择发送的文件
	void selectFile();
	void sendFile();
	更新文件传送进度
	void updateFileProgress(qint64);

private:
	Ui::QtServerClass ui;

	传送文件相关变量
	qint64 totalBytes;
	qint64 bytesReceived;
	qint64 bytestoWrite;
	qint64 bytesWritten;
	qint64 filenameSize;
	QString filename;
	///每次发送数据大小
	qint64 perDataSize;
	QFile *localFile;
	本地缓冲区
	QByteArray inBlock;
	QByteArray outBlock;

	系统时间
	QDateTime current_date_time;
	QString str_date_time;
};

实现:

#include "qtserver.h"
#include <QDataStream>
#include <QMessageBox>
#include <QString>
#include <QByteArray>

QtServer::QtServer(QWidget *parent, Qt::WFlags flags)
	: QWidget(parent, flags)
{
	ui.setupUi(this);

	this->socket = new QTcpSocket(this);
	this->server = new QTcpServer(this);
	///开启监听
	this->server->listen(QHostAddress::Any,6666);
	connect(this->server,SIGNAL(newConnection()),this,SLOT(acceptConnection()));
	connect(ui.pushButton_send,SIGNAL(clicked()),this,SLOT(sendMessage()));

	///文件传送套接字
	this->filesocket = new QTcpSocket(this);
	this->fileserver = new QTcpServer(this);
	this->fileserver->listen(QHostAddress::Any,8888);
	connect(this->fileserver,SIGNAL(newConnection()),this,SLOT(acceptFileConnection()));

	 文件传送相关变量初始化
	bytesReceived = 0;
	totalBytes = 0;
	filenameSize = 0;
	connect(ui.pushButton_selectFile,SIGNAL(clicked()),this,SLOT(selectFile()));
	connect(ui.pushButton_sendFile,SIGNAL(clicked()),this,SLOT(sendFile()));
}

QtServer::~QtServer()
{

}

void QtServer::acceptConnection()
{
	返回一个socket连接
	this->socket = this->server->nextPendingConnection();
	connect(socket,SIGNAL(readyRead()),this,SLOT(receiveData()));
}

void QtServer::acceptFileConnection()
{
	bytesWritten = 0;
	///每次发送数据大小为64kb
	perDataSize = 64*1024;
	this->filesocket = this->fileserver->nextPendingConnection();
	///接受文件
	connect(filesocket,SIGNAL(readyRead()),this,SLOT(updateFileProgress()));	
	connect(filesocket,SIGNAL(error(QAbstractSocket::SocketError)),this,SLOT(updateFileProgress(qint64)));
	connect(filesocket,SIGNAL(bytesWritten(qint64)),this,SLOT(displayError(QAbstractSocket::SocketError socketError)));
}

void QtServer::sendMessage()
{
	this->socket->write(ui.lineEdit->text().toLatin1());
	显示
	current_date_time = QDateTime::currentDateTime();
	str_date_time = current_date_time.toString("yyyy-MM-dd hh:mm:ss");
	QString str = "You "+str_date_time+"\n"+ui.lineEdit->text();
	ui.browser->append(str);
}

void QtServer::receiveData()
{
	/获取当前时间
	current_date_time = QDateTime::currentDateTime();
	str_date_time = current_date_time.toString("yyyy-MM-dd hh:mm:ss")+"\n";

	接收数据
	QString str = this->socket->readAll();

	显示
	str = "Client "+str_date_time+str;
	this->ui.browser->append(str);
}

void QtServer::updateFileProgress()
{
	QDataStream inFile(this->filesocket);
	inFile.setVersion(QDataStream::Qt_4_8);
	
	///如果接收到的数据小于16个字节,保存到来的文件头结构
	if(bytesReceived <= sizeof(qint64)*2)
	{
		if((filesocket->bytesAvailable()>=(sizeof(qint64))*2) && (filenameSize==0))
		{
			inFile>>totalBytes>>filenameSize;
			bytesReceived += sizeof(qint64)*2;
		}
		if((filesocket->bytesAvailable()>=filenameSize) && (filenameSize != 0))
		{
			inFile>>filename;
			bytesReceived += filenameSize;
			接收的文件放在指定目录下
			filename = "ClientData/"+filename;
			localFile = new QFile(filename);
			if(!localFile->open(QFile::WriteOnly))
			{
				qDebug()<<"Server::open file error!";
				return;
			}
		}
		else
			return;
	}
	/如果接收的数据小于总数据,则写入文件
	if(bytesReceived < totalBytes)
	{
		bytesReceived += filesocket->bytesAvailable();
		inBlock = filesocket->readAll();
		localFile->write(inBlock);
		inBlock.resize(0);
	}
	更新进度条显示
	this->ui.progressBar_fileProgress->setMaximum(totalBytes);
	this->ui.progressBar_fileProgress->setValue(bytesReceived);
	数据接收完成时
	if(bytesReceived == totalBytes)
	{
		this->ui.browser->append("Receive file successfully!");
		bytesReceived = 0;
		totalBytes = 0;
		filenameSize = 0;
		localFile->close();
		//filesocket->close();
	}
}

void QtServer::displayError(QAbstractSocket::SocketError socketError)
{
	qDebug()<<socket->errorString();
	socket->close();
}

void QtServer::selectFile()
{
	filesocket->open(QIODevice::WriteOnly);
	文件传送进度更新
	connect(filesocket,SIGNAL(bytesWritten(qint64)),this,SLOT(updateFileProgress(qint64)));

	this->filename = QFileDialog::getOpenFileName(this,"Open a file","/","files (*)");
	ui.lineEdit_fileName->setText(filename);
}

void QtServer::sendFile()
{
	this->localFile = new QFile(filename);
	if(!localFile->open(QFile::ReadOnly))
	{
		return;
	}
	///获取文件大小
	this->totalBytes = localFile->size();
	QDataStream sendout(&outBlock,QIODevice::WriteOnly);
	sendout.setVersion(QDataStream::Qt_4_8);
	QString currentFileName = filename.right(filename.size()-filename.lastIndexOf('/')-1);

	保留总代大小信息空间、文件名大小信息空间、文件名
	sendout<<qint64(0)<<qint64(0)<<currentFileName;
	totalBytes += outBlock.size();
	sendout.device()->seek(0);
	sendout<<totalBytes<<qint64((outBlock.size()-sizeof(qint64)*2));

	bytestoWrite = totalBytes-filesocket->write(outBlock);
	outBlock.resize(0);
}

void QtServer::updateFileProgress(qint64 numBytes)
{
	已经发送的数据大小
	bytesWritten += (int)numBytes;

	如果已经发送了数据
	if(bytestoWrite > 0)
	{
		outBlock = localFile->read(qMin(bytestoWrite,perDataSize));
		///发送完一次数据后还剩余数据的大小
		bytestoWrite -= ((int)filesocket->write(outBlock));
		///清空发送缓冲区
		outBlock.resize(0);
	}
	else
		localFile->close();

	如果发送完毕
	if(bytesWritten == totalBytes)
	{
		localFile->close();
		//filesocket->close();
	}
}

6.测试

这里写图片描述
这里写图片描述
这里发送了几条消息,并从客户端将《Windows网络编程技术.pdf》传到服务端,在服务端的ClientData文件夹里,该文件存在,证明程序可行!

7.源码

程序包:https://download.csdn.net/download/sinat_21107433/10823712

Github更新到了vs2013+Qt5,源代码上传到Git了,地址:https://github.com/FengJungle/QtSocket.git

欢迎关注知乎专栏Jungle是一个用Qt的工业Robot
欢迎关注Jungle的微信公众号:Jungle笔记
在这里插入图片描述

  • 56
    点赞
  • 412
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 123
    评论
### 回答1: Qt 是一个常用的跨平台应用框架,支持多种操作系统,可以用来开发客户端服务器应用程序。要实现客户端服务器消息发送文件传输,需要使用 Qt 的网络模块和文件IO模块。 首先,要实现客户端服务器之间的消息发送,可以使用 Qt 的套接字接口来进行网络通信。在客户端程序中,可以使用 QTcpSocket 类来创建一个 TCP 套接字,与服务器建立连接,并通过这个套接字发送消息。而在服务器程序中,可以使用 QTcpServer 类来创建一个监听套接字,接受客户端的连接,并通过这个套接字接收和处理客户端发送消息。 对于文件传输,可以借助于 Qt文件IO模块来实现。在客户端程序中,可以使用 QFile 类来打开要传输文件,并使用 QTcpSocket 的 write() 函数将文件内容发送服务器。而在服务器程序中,可以使用 QTcpSocket 的 read() 函数接收客户端发送文件内容,并使用 QFile 类的 write() 函数将内容保存到服务器文件系统中。 为了确保消息发送文件传输的可靠性,可以使用 Qt 的信号和槽机制来实现客户端可以通过监视套接字的 connected() 和 disconnected() 信号来判断与服务器的连接状态,并通过 error() 信号来处理连接错误。而服务器可以通过监听套接字的 newConnection() 信号来接受新的客户端连接,并通过 readyRead() 信号来接收客户端发送消息文件内容。 此外,为了提高消息发送文件传输的效率,可以使用 Qt 的多线程机制来实现并发处理。可以将套接字通信和文件IO操作放在独立的线程中进行,以避免阻塞主线程的运行。 总之,通过使用 Qt 的网络模块和文件IO模块,我们可以很方便地实现客户端服务器之间的消息发送文件传输。这种实现方式简单易懂,同时也具备可靠性和高效性。 ### 回答2: Qt 是一个跨平台的 C++ 应用程序开发框架,提供了丰富的类库和工具来简化开发过程。要实现客户端服务器之间的消息发送文件传输,我们可以使用 Qt 中的网络模块和文件操作模块。 对于消息发送,可以使用 Qt 的套接字类来实现客户端可以创建一个套接字并连接到指定的服务器。通过套接字的 write 方法可以将消息发送服务器,而服务器可以通过套接字的 read 方法来接收客户端发送消息。这样就实现客户端服务器之间的消息通信。 对于文件传输,也可以使用套接字类来实现客户端可以将要发送文件读取到内存中,并使用套接字的 write 方法将文件数据发送服务器服务器可以使用套接字的 read 方法接收客户端发送文件数据,并将接收到的文件数据保存到指定位置。这样就实现客户端服务器传输文件的功能。 在实现消息发送文件传输的过程中,需要注意以下几点: 1. 需要使用正确的 IP 地址或域名和端口号来建立客户端服务器之间的连接。 2. 如果发送消息文件较大,可以考虑对数据进行分块传输,以便提高传输效率。 3. 在接收文件时,可以使用文件打开和写入操作的错误处理来确保传输的完整性。 总之,使用 Qt 的网络模块和文件操作模块,我们可以方便地实现客户端服务器之间的消息发送文件传输功能。通过合理的设计和编码,可以实现高效可靠的通信。 ### 回答3: Qt是一个流行的跨平台应用程序开发框架,它提供了各种库和工具来简化软件开发。要实现客户端服务器之间的消息发送文件传输,我们可以使用Qt中的网络编程和文件操作模块。 在客户端服务器之间建立网络通信,我们可以使用Qt中的Qt Network模块。该模块提供了一种简单且高效的方式来创建TCP/IP网络连接并进行数据传输。我们可以使用QTcpSocket类在客户端服务器之间发送消息。通过在客户端上创建一个QTcpSocket对象并使用其connectToHost()方法连接服务器,我们可以发送和接收消息。使用QTcpSocket的write()方法可以发送消息,而readyRead()信号可以用于接收并处理服务器发送消息。 对于文件传输,我们可以使用Qt文件操作模块进行处理。我们可以使用QFile类打开和读写文件,以及QFileInfo类获取有关文件的信息。为了在客户端服务器之间传输文件,我们可以将文件分割为较小的数据块,并将每个数据块发送服务器。在服务器端,我们可以接收这些数据块并将它们重新组合成原始的文件。我们还可以使用QAbstractSocket类的readyRead()信号来处理文件传输过程中的读取操作。 此外,Qt还提供了不同类型的网络协议(如HTTP、FTP等)的支持。这使得我们在客户端服务器之间进行文件传输的方式更加灵活和多样化。例如,我们可以使用QHttp类来实现基于HTTP协议的文件传输,或者使用QFtp类来实现基于FTP协议的文件传输。 综上所述,使用Qt的网络编程和文件操作模块,我们可以轻松地实现客户端服务器之间的消息发送文件传输。这使得我们能够构建出功能强大且高效的网络应用程序。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 123
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

冯Jungle

您的支持是对我最大的鼓励

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值