概述:
TCP提供一种面向连接的、可靠的字节流服务。面向连接意味着两个使用TCP的应用(通常是一个客户和一个服务器)在彼此交换数据包之前必须先建立一个TCP连接。这一过程与打电话很相似,先拨号振铃,等待对方摘机说“喂”,然后才说明是谁。在一个TCP连接中,仅有两方进行彼此通信。
TCP传输数据的时候是很少出现丢包情况的,因为它本身的特点,详细介绍看百度
一,传输文件的过程:
实现服务端选择文件给客户端发送:
过程如下:
上述过程是在建立Tcp链接之后的,要使用Tcp发送文件肯定需要先建立Tcp连接的,可以看看这个Tcp简单文本发送
二,服务端的实现
1,建立连接
tcpServer = new QTcpServer(this);
tcpServer->listen(QHostAddress::Any,8888);
setWindowTitle("服务器端口为8888");
/*无连接的时候按钮无效*/
ui->file->setEnabled(false);
ui->send->setEnabled(false);
/*如果客户端成功和服务端连接*/
connect(tcpServer,&QTcpServer::newConnection,[=](){
/*取出建立好链接的套接字*/
tcpSocket = tcpServer->nextPendingConnection();
/*获取对方IP和端口*/
QString ip = tcpSocket->peerAddress().toString();
quint16 port = tcpSocket->peerPort();
QString str = QString("[%1:%2]成功链接").arg(ip).arg(port);
ui->textEdit->setText(str);/*显示编辑区*/
/*恢复按钮*/
ui->file->setEnabled(true);
});
在连接到客户端的时候,打印出IP地址和端口号,并且显示在文本编辑区中。刚开始之前对按钮进行了一个不使能操作。也就是说,在没有连接的时候是无法点击按钮的。
2,选择文件的实现
我们需要在服务端选择文件并且发送给客户端。
QString path = QFileDialog::getOpenFileName(this,"选择文件","../");
if(path.isEmpty() == false)
{
fileName.clear();
fileSize = 0;
/*获取文件信息*/
QFileInfo info(path);
fileName = info.fileName();
fileSize = info.size();
/*发送文件的大小*/
sendSize = 0;
/*只读方式打开*/
file.setFileName(path);
if(file.open(QIODevice::ReadOnly) == true)
{
}else{
qDebug() << "打开失败";
}
/*追加文件路径信息*/
ui->textEdit->append(path);
ui->file->setEnabled(false);
ui->send->setEnabled(true);
}else{
qDebug() << "文件路径无效";
}
}
在发送文件之前先发送文件的信息,比如文件名,文件大小等。
关于文件信息的获取使用Qt中的QFileInfo
3,发送文件信息
void Widget::on_send_clicked()
{
/*先发送文件头信息*/
QString headMessage = QString("%1##%2").arg(fileName).arg(fileSize);
ReciveOk = false;
quint64 len = tcpSocket->write(headMessage.toUtf8().data());
/*发送头信息成功*/
if(len > 0 )
{
/*防止Tcp连包问题,使用定时器延时20ms*/
timer.start(20);
}else{
qDebug() << "头文件信息发送失败";
file.close();
ui->file->setEnabled(true);
ui->send->setEnabled(false);
}
对于文件信息的话是类似于数据的头信息,包括文件名,大小等,可以通过组包(组合成为一个字符串),在接收到这个字符串之后,再把信息分开即可。
4,发送数据
对于数据发送为了防止头信息和数据出现相互干扰的情况,需要分开发送,在发送头信息之后,延时(使用定时器)然后发送数据,确保客户端先收到头信息,再收到数据。
void Server::sendData()
{
qint64 len = 0;
sendsize = 0;
do
{
char buf[4*1024] = {0};
len = 0;
len = file.read(buf, sizeof(buf));
len = tcpSocket->write(buf, len);
sendsize += len;
}while(len > 0);
/*判断数据是否发送完毕*/
if(sendsize == fileSize)
{
ui->textEdit->append("文件发送完毕");
file.close();
/*断开客户端*/
tcpSocket->disconnectFromHost();
}
}
所以整个服务端代码如下:
#include "server.h"
#include "ui_server.h"
Server::Server(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Server)
{
ui->setupUi(this);
/*分配空间*/
tcpServer = new QTcpServer(this);
ui->selectFile->setEnabled(false);
ui->sendButton->setEnabled(false);
/*监听*/
tcpServer->listen(QHostAddress::Any, 8888);
/*如果客户端成功和服务器连接,套接字发出信号*/
connect(tcpServer, &QTcpServer::newConnection, [=](){
/*取出建立好连接的套接字*/
tcpSocket = tcpServer->nextPendingConnection();
/*获取对方的IP和端口*/
QString ip = tcpSocket->peerAddress().toString();
quint16 port = tcpSocket->peerPort();
/*组包*/
QString str = QString("Clinet [%1:%2] connected!").arg(ip).arg(port);
/*显示到编辑区 或者弹出对话框*/
ui->textEdit->setText(str);
ui->selectFile->setEnabled(true);
});
connect(&timer, &QTimer::timeout, [=](){
timer.stop();
sendData();
});
}
Server::~Server()
{
delete ui;
}
void Server::on_selectFile_clicked()
{
QString path = QFileDialog::getOpenFileName(this, "open", "../");
if(path.isEmpty() == false)
{
filename.clear();
fileSize = 0;
/*获取文件信息*/
QFileInfo info(path);
filename = info.fileName();
fileSize = info.size();
sendsize = 0;
file.setFileName(path);
bool openResult = file.open(QIODevice::ReadOnly);
if(openResult == false)
{
}
ui->textEdit->append(path);
ui->selectFile->setEnabled(false);
ui->sendButton->setEnabled(true);
}else
{
/*路径无效*/
}
}
void Server::on_sendButton_clicked()
{
/*先发送文件头信息*/
QString headMessage = QString("%1##%2").arg(filename).arg(fileSize);
qint64 sendLen = tcpSocket->write(headMessage.toUtf8());
if(sendLen > 0)/*头部信息发送成功*/
{
timer.start(20);
}else {
qDebug() << "头部信息发送失败";
file.close();
ui->selectFile->setEnabled(true);
ui->sendButton->setEnabled(false);
}
}
void Server::sendData()
{
qint64 len = 0;
sendsize = 0;
do
{
char buf[4*1024] = {0};
len = 0;
len = file.read(buf, sizeof(buf));
len = tcpSocket->write(buf, len);
sendsize += len;
}while(len > 0);
/*判断数据是否发送完毕*/
if(sendsize == fileSize)
{
ui->textEdit->append("文件发送完毕");
file.close();
/*断开客户端*/
// tcpSocket->disconnectFromHost();
}
}
三,客户端的实现
客户端主要就是解析服务端发送过来的头信息以及读文件即可
代码及注释如下:
#include "client.h"
#include "ui_client.h"
#include <QMessageBox>
#include <QHostAddress>
#include <QFileDialog>
#include <QProgressBar>
#include <QDebug>
client::client(QWidget *parent) :
QWidget(parent),
ui(new Ui::client)
{
ui->setupUi(this);
tcpSocket = new QTcpSocket(this);
isStart = 1;
connect(tcpSocket, &QTcpSocket::readyRead,[=](){
/*取出接收的内容*/
QByteArray buf = tcpSocket->readAll();
if(isStart == 1)
{
isStart = 0;
fileName = QString(buf).section("##", 0, 0);
fileSize = QString(buf).section("##", 1, 1).toInt();
RecvSize = 0;
/*弹出接收文件的信息*/
QString str = QString("接收的文件:[%1: %2KB]").arg(fileName).arg(fileSize);
QMessageBox::information(this, "接收文件信息", str);
/*打开文件*/
QString path = QFileDialog::getSaveFileName(this,"save","../","souce(*.cpp)");
if(path.isEmpty() == false)
{
file.setFileName(path);
//打开文件
if(file.open(QIODevice::WriteOnly) == true)
{
}
}
/*
file.setFileName(fileName);
bool isOk = file.open(QIODevice::WriteOnly);
if(isOk == false)
{
qDebug() << "文件打开失败";
}*/
}
if(isStart == 0)
{
/*文件信息*/
qint64 len = file.write(buf.data());
RecvSize += len;
qDebug() << "执行到写数据的的地方";
if(RecvSize == fileSize)
{
file.close();
QMessageBox::information(this, "完成", "文件接收完成");
tcpSocket->disconnectFromHost();
tcpSocket->close();
}
}
});
}
client::~client()
{
delete ui;
}
void client::on_connectButton_clicked()
{
/*获取IP和端口*/
QString ip = ui->lineEditIP->text();
quint64 port = ui->lineEditPort->text().toInt();
tcpSocket->connectToHost(QHostAddress(ip),port);
}
void client::recvFile()
{
QString path = QFileDialog::getSaveFileName(this,"save","../","souce(*.cpp)");
if(path.isEmpty() == false)
{
file.setFileName(path);
//打开文件
if(file.open(QIODevice::WriteOnly) == true)
{
}
}
}
对于客户端来说,主要就是获取发送的头信息,不能和数据相互连包,其余和之前的Tcp数据传输一样。