网上这方面的资源不是很多,大多数都是Http的断点续传。因小项目需求需要做一个断点传输的功能,思考后自己设计了。

服务器和客户端都基于QT5.9,在这里只给出服务器传输文件到客户端,客户端上传文件到服务器原理一样。

原理如下:

  • 将文件分块传输,比如每次发送4096字节。
  • 保证客户端正确接收字节且写入文件,然后向服务器反馈接收成功信息。
  • 服务器收到反馈信息继续进行发送。
  • 暂停下载后记录断点,欲再次下载则发送断点等数据到服务器。
  • 服务器收到断点后,根据断点将文件指针移动到断点位置继续发送。

在这里只给出部分代码,全部代码可查看github。

客户端:

头文件


#pragma once
#ifndef DOWNLOADFILE_H_
#define DOWNLOADFILE_H_
#include "stdafx.h
#include <QFile>
#include <QString>
#include "FileInfo.h"
//extern TcpClient * tcp;
extern QString ip;
extern int port;
extern QString globalUserName;//只负责下载就行了
class DownloadFile : public QObject
{
Q_OBJECTpublic:
explicit DownloadFile(QString,QString,int);
//这个构造函数是下载断点文件,第一个是总数据,第二个是断点位置
DownloadFile(int,QString,QString, qint64 ,qint64 ,int );
~DownloadFile();
bool insertRecord();
bool updateRecord();private:
int index; //序号
QTcpSocket *tcpSocket; //连接服务器的socket
QTime downloadTime;
QTimer *timer;
QFile *newFile;
QByteArray inBlock;
QByteArray outBlock; //发数据给服务器
QByteArray buffer; //用来缓存的
QString fileName;
QString saveFileName; //文件路径加文件名
QString filePath;
QString openFileName; //直接打开的文件名qint64 RtotalSize; //总共需要发送的文件大小(文件内容&文件名信息)
qint64 byteReceived; //已经接收的大小 ‘’qint64 receiveStatus = -1; //默认为继续发送qint64 sumBlock;
qint64 breakPoint;
qint64 cbreakPoint;
qint64 recordId;
int breakFileId;
QString breakFileName;
QString breakFilePath;qint64 fileId;
int receiveTime;    //接受的次数
double speed;QString leftTime; //剩余时间
bool isBreakFile = false;
void init();
bool keepOn;
void countLeftTime(float);private slots:void receiveFile(); //从服务器下载文件到本地。
void receiveBreakFile();
void updateSpeed();
void stopReceive(int);signals:
//void sendDisconnect(QString); //这是什么
void downloadOver(int);
void updateProgress(int, qint64, qint64);
void updateSpeedLabel(int, double,QString);void addToBreakFile(int, qint64 recordId,int fileId,
QString fileName,QString filePath,
qint64 breakPoint);};
#endif // !DOWNLOADFILE_H_

downlaodFile.cpp

#include "DownloadFile.h"
#include <QFileDialog>
#include <QtWin>
#include "MyMessageBox.h"
#include "Database.h"
DownloadFile::DownloadFile(QString mFileName, QString mFilePath,int num)
{
fileName = mFileName;
filePath = mFilePath; //总文件就是 filePath + fileName;
index = num;
keepOn = false;
init();qDebug() << "new downloadFile :" <<QThread::currentThreadId()
<< keepOn;
}
//fileId的名字可能和filename不一样
DownloadFile::DownloadFile(int mFileId,QString mFileName,QString mFilePath, qint64 mBreakPoint,qint64 mRecordId, int num)
{
breakFileName = mFileName;
breakFilePath = mFilePath; //总文件就是 filePath + fileName;
breakFileId = mFileId;
cbreakPoint = mBreakPoint;
recordId = mRecordId;
index = num;
keepOn = true;
init();
}DownloadFile::~DownloadFile()
{
//delete ui;
}//初始化界面
void DownloadFile::init()
{
QString bs;
QString data;
tcpSocket = new QTcpSocket(); //新建一个socket
tcpSocket->abort(); //取消已有的连接
tcpSocket->connectToHost(ip, port); //将这个socket连接到服务器
byteReceived = 0;
receiveTime = 0;
if (!keepOn)
{
bs = "downloadFile";
data = bs + "#" + fileName + "#" + globalUserName;
}
else
{bs = "downloadBreakFile";
//cbreakPoint断点处,因为等于sumblock的值
data = bs + "#" + QString::number(breakFileId) + "#" + QString::number(cbreakPoint) + "#" + globalUserName ;
}QByteArray datasend = data.toUtf8(); //发送UTF8过去
if (tcpSocket->write(datasend))
{
qDebug() << "send data to server: " << data;connect(tcpSocket, SIGNAL(readyRead()), this, SLOT(receiveFile()),Qt::DirectConnection); //绑定信号槽}
else
{
qDebug() << "send filed :" << data;
}
}void DownloadFile::receiveFile()
{
outBlock.clear();
QDataStream out(&outBlock, QIODevice::WriteOnly);
if (byteReceived == 0) //才刚开始接收数据,此数据为文件信息
{
timer = new QTimer(this);
connect(timer, SIGNAL(timeout()), this, SLOT(updateSpeed())); // ***就是你所说的响应函数
timer->start(1000); // 每隔1s更新速度标签QDataStream in(tcpSocket);
in >> RtotalSize >> byteReceived >> fileName>> fileId;
sumBlock = RtotalSize / (4 * 1024) + 1; //文件块数等于总大小除以每块的数量
qDebug() << "the file sumBlock :" << sumBlock;
//filePath = "files/";
saveFileName = filePath + fileName;
qDebug() << "the file of head: " << fileName
<< "totalSize: " << RtotalSize
<< " first byteReceived: " << byteReceived
<< "start saveFileName:" << saveFileName <<"fileId: "<<fileId;if(!keepOn)
{
qDebug() << "nimabi" << keepOn;
//设计一个文件读写不重复的功能
QString suffix = fileName.right(fileName.size() - fileName.lastIndexOf('.') - 1); //获取文件后缀; //这里和服务器的不一样
QString fullName = fileName.left(fileName.size() - (fileName.size() - fileName.lastIndexOf('.'))); //获取文件名int i = 0;  //查询文件名是否重复
while (1)
{
QFile *existFile = new QFile(filePath + fileName); //就是判断是否重复
//qDebug() << "exitsFile" << mFilePath + mFileName;
if (existFile->exists()) //如果文件存在
{
i++;
fileName = fullName + "(" + QString::number(i, 10) + ")." + suffix;//qDebug() << "the fileName :" << mFileName;
}
else
break;
}
newFile = new QFile(filePath + fileName); //新建一个文件,写入
newFile->open(QFile::WriteOnly);
//
//qint64 fileWrited = newFile->size();breakPoint = receiveTime;
}
//如果是写断点,则直接打开不要新建
else
{
qDebug() << "gogon!!!!!!!!!!!!!";
newFile = new QFile(breakFilePath + breakFileName);
//append模式,在文件的后面
newFile->open(QFile::WriteOnly| QFile::Append);
newFile->atEnd();
receiveTime = cbreakPoint - 1;
byteReceived += (cbreakPoint-1) * 4 * 1024;
breakPoint = cbreakPoint - 1;
}//开始计时!
downloadTime.start();
outBlock.clear();
//qDebug() << outBlock;
qDebug() << "the status:" << receiveStatus << "the sumBlock:" << sumBlock << "the breakPoint:" << breakPoint;
out << receiveStatus << sumBlock << breakPoint;
//qDebug() << outBlock;
tcpSocket->write(outBlock);
//tcpSocket->waitForBytesWritten();}
else //正式读取文件内容
{
while (tcpSocket->bytesAvailable() > 0)
{
receiveTime++;
breakPoint = receiveTime;
if (tcpSocket->bytesAvailable() < 4 * 1024)
inBlock = tcpSocket->read(tcpSocket->bytesAvailable());
else
inBlock = tcpSocket->read(4 * 1024);
byteReceived += inBlock.size();
float useTime = downloadTime.elapsed();
speed = (byteReceived) / useTime;
//计算时间,传入一个float
countLeftTime(RtotalSize / speed / 1000 - useTime / 1000);//要是信号大于0则代表停止传输,发送断点位置给服务器if (receiveStatus > 0)
{
qDebug() << "stop receive!";
outBlock.clear();
out << receiveStatus<<sumBlock<<breakPoint;
if (insertRecord())
{
qDebug() << "insert downloadRecord success!";
}
else
{
qDebug() << "insert failed!";
}
}
else
{
//如果写入完成向服务器发送一个标记,-1为继续发送
if (newFile->write(inBlock))
{
qDebug() << "write sucess!";
newFile->flush();
emit updateProgress(index, byteReceived, RtotalSize);
receiveStatus = -1;
outBlock.clear();qDebug() << "the status:" << receiveStatus << "the sumBlock:" << sumBlock << "the breakPoint:" << breakPoint;
out << receiveStatus << sumBlock << breakPoint;
qDebug() << "ThreadId: " << QThread::currentThreadId()
<< fileName << " the " << receiveTime << "recv block " << inBlock.size()
<< "current receive the byte is :" << byteReceived
<< " and the total " << RtotalSize;
}
else
{
qDebug() << "write fail!";
receiveStatus = -2;
outBlock.clear();
qDebug() << "the status:" << receiveStatus << "the sumBlock:" << sumBlock << "the breakPoint:" << breakPoint;
out << receiveStatus << sumBlock << breakPoint;
}
}
//qDebug() << "outBlock" << outBlock;
tcpSocket->write(outBlock); //写数据给服务器
tcpSocket->waitForBytesWritten();
}
}if (byteReceived == RtotalSize)
{
qDebug() << "receive is done, and the byteReceived is :" << byteReceived <<" the RtotalSize is :"<< RtotalSize;
/*qDebug() << "the file name:" << file.getFileName();
qDebug() << "the userid :" << user.getUserId();
qDebug() << "the userid from file:" << file.getUserId();*/
if (!keepOn)
{
if (insertRecord())
{
qDebug() << "insert downloadRecord success!";
}
else
{
qDebug() << "insert failed!";
}
}
else
{
updateRecord();
}inBlock.clear();
byteReceived = 0;
RtotalSize = 0;
receiveTime = 0;
newFile->close();
timer->stop();
disconnect(timer, SIGNAL(timeout()), this, SLOT(updateSpeed())); // ***就是你所说的响应函数disconnect(tcpSocket, SIGNAL(readyRead()), this, SLOT(receiveFile()));
emit downloadOver(index);
}
}//直接开始写入文件,不对要确认一下总文件大小
void DownloadFile::receiveBreakFile()
{
outBlock.clear();
QDataStream out(&outBlock, QIODevice::WriteOnly);
if (byteReceived == 0) //才刚开始接收数据,此数据为文件信息
{
QTimer *timer = new QTimer(this);
connect(timer, SIGNAL(timeout()), this, SLOT(updateSpeed())); // ***就是你所说的响应函数
timer->start(1000); // 每隔1s更新速度标签QDataStream in(tcpSocket);
in >> RtotalSize >> byteReceived >> fileName >> fileId;
sumBlock = RtotalSize / (4 * 1024) + 1; //文件块数等于总大小除以每块的数量
qDebug() << "the file sumBlock :" << sumBlock;
//filePath = "files/";
saveFileName = filePath + fileName;
qDebug() << "the file of head: " << fileName
<< "totalSize: " << RtotalSize
<< " first byteReceived: " << byteReceived
<< "start saveFileName:" << saveFileName << "fileId: " << fileId;
//设计一个文件读写不重复的功能
QString suffix = fileName.right(fileName.size() - fileName.lastIndexOf('.') - 1); //获取文件后缀;
//这里和服务器的不一样
QString fullName = fileName.left(fileName.size() - (fileName.size() - fileName.lastIndexOf('.'))); //获取文件名int i = 0;  //查询文件名是否重复
while (1)
{
QFile *existFile = new QFile(filePath + fileName); //就是判断是否重复
//qDebug() << "exitsFile" << mFilePath + mFileName;
if (existFile->exists()) //如果文件存在
{
i++;
fileName = fullName + "(" + QString::number(i, 10) + ")." + suffix;}
else
break;
}newFile = new QFile(filePath + fileName); //新建一个文件,写入
//qDebug() << "fileName:";
newFile->open(QFile::WriteOnly);
//开始计时!
downloadTime.start();
outBlock.clear();
breakPoint = receiveTime;
//qDebug() << outBlock;
qDebug() << "the status:" << receiveStatus << "the sumBlock:" << sumBlock << "the breakPoint:" << breakPoint;
out << receiveStatus << sumBlock << breakPoint;
//qDebug() << outBlock;
tcpSocket->write(outBlock);
tcpSocket->waitForBytesWritten();}
else //正式读取文件内容
{
while (tcpSocket->bytesAvailable() > 0)
{
receiveTime++;
breakPoint = receiveTime;
if (tcpSocket->bytesAvailable() < 4 * 1024)
inBlock = tcpSocket->read(tcpSocket->bytesAvailable());
else
inBlock = tcpSocket->read(4 * 1024);
byteReceived += inBlock.size();
float useTime = downloadTime.elapsed();
speed = (byteReceived) / useTime;
//计算时间,传入一个float
countLeftTime(RtotalSize / speed / 1000 - useTime / 1000);
//要是信号大于0则代表停止传输,发送断点位置给服务器
if (receiveStatus > 0)
{
qDebug() << "stop receive!";
outBlock.clear();
newFile->close();
out << receiveStatus << sumBlock << breakPoint;
if (insertRecord())
{
qDebug() << "insert downloadRecord success!";
}
else
{
qDebug() << "insert failed!";
}
}
else
{
//如果写入完成向服务器发送一个标记,-1为继续发送
if (newFile->write(inBlock))
{
newFile->flush();
emit updateProgress(index, byteReceived, RtotalSize);
receiveStatus = -1;
outBlock.clear();qDebug() << "the status:" << receiveStatus << "the sumBlock:" << sumBlock << "the breakPoint:" << breakPoint;
out << receiveStatus << sumBlock << breakPoint;
qDebug() << "ThreadId: " << QThread::currentThreadId()
<< fileName << " the " << receiveTime << "recv block " << inBlock.size()
<< "current receive the byte is :" << byteReceived
<< " and the total " << RtotalSize;
}
else
{
receiveStatus = -2;
outBlock.clear();
qDebug() << "the status:" << receiveStatus << "the sumBlock:" << sumBlock << "the breakPoint:" << breakPoint;
out << receiveStatus << sumBlock << breakPoint;
}
}
tcpSocket->write(outBlock); //写数据给服务器
}
}if (byteReceived == RtotalSize)
{
qDebug() << "receive is done, and the byteReceived is :" << byteReceived << " the RtotalSize is :" << RtotalSize;
if (insertRecord())
{
qDebug() << "insert downloadRecord success!";
}
else
{
qDebug() << "insert failed!";
}
emit downloadOver(index);
inBlock.clear();
byteReceived = 0;
RtotalSize = 0;
receiveTime = 0;
newFile->close();
disconnect(tcpSocket, SIGNAL(readyRead()), this, SLOT(receiveFile()));
}
}void DownloadFile::updateSpeed()
{
emit updateSpeedLabel(index, speed, leftTime);
}void DownloadFile::countLeftTime(float mleftTime)
{
int hour = 0, min = 0, second = mleftTime;
hour = second / 3600;
second %= 3600;
min = second / 60;
second %= 60;
leftTime = QString::number(hour).sprintf("%02d", hour) + ":"
+ QString::number(min).sprintf("%02d", min) + ":"
+ QString::number(second).sprintf("%02d", second);
}//接收到停止发送的信号
void DownloadFile::stopReceive(int)
{
receiveStatus = receiveTime;
}bool DownloadFile::insertRecord()
{
QString sql = "insert into DownloadRecord(r_fileName,r_filePath,r_fileSize,r_sumBlock,r_breakPoint,r_fileId)values('"
+ fileName + "','" + filePath + "','"
+ QString::number(RtotalSize) + "'," + QString::number(sumBlock) + ","
+ QString::number(breakPoint)+"," + QString::number(fileId) + ")";
qDebug() << sql;
QSqlQuery insert;
insert.exec(sql);
if (insert.isActive())
{
qDebug() << sql;
QString sql1 = "select * from DownloadRecord order by r_Id desc limit 0,1";
QSqlQuery query;
query.exec(sql1);
BreakFile breakFile;
if (query.next())
{
breakFile.recordId = query.value(0).toInt();
}breakFile.fileId = fileId;
breakFile.fileName = fileName;
breakFile.breakPoint = breakPoint;
breakFile.filePath = filePath;
emit addToBreakFile(index, breakFile.recordId, breakFile.fileId,
breakFile.fileName, breakFile.filePath, breakFile.breakPoint);return true;
}return false;
}

这里的代码都是部分代码,如需demo则要自己实现。

服务器代码:

fileId = list[1].toInt();
breakPoint =list[2].toInt();
globalUserName = list[3];
File file;
file.queryFileById(fileId);
QString breakFileName = file.getFileName();
if (openFile(breakFileName))
{
disconnect(this, &MyTcpSocket::readyRead, this, &MyTcpSocket::readData);
connect(this, SIGNAL(readyRead()), this, SLOT(goOnSend()), Qt::DirectConnection);
qDebug() << "send thread now" << QThread::currentThreadId();QDataStream out(&outBlock, QIODevice::WriteOnly);
byteToWrite = localFile->size(); //剩余数据的大小
qDebug() << "the file bytetowrite: " << byteToWrite;
StotalSize = localFile->size();
loadSize = 4 * 1024; //每次发送数据的大小
//前面两个是文件大小和发送文件头的大小,后面是文件名和用户名out << qint64(0) << qint64(0) << breakFileName << fileId;
StotalSize += outBlock.size(); //总大小为文件大小加上文件名等信息大小
byteToWrite += outBlock.size();
qDebug() << "the total bytetowrite: " << byteToWrite;
out.device()->seek(0); //回到字节流起点来写好前面连个qint64,分别为总大小和文件名等信息大小
out << StotalSize << qint64(outBlock.size());
//qDebug() << "the file head:" << outBlock;
breakPoint--;
localFile->seek(breakPoint * loadSize); //文件直接seek到断点处  //这里是关键
byteToWrite -= breakPoint * loadSize;   //
sendTimes = breakPoint;
this->write(outBlock); //将读到的文件信息发送到套接字
this->waitForBytesWritten();
}
bool MyTcpSocket::openFile(QString filename)
{
//qDebug() << globalUserName << " will send a file to server";
//byteToWrite = 0;
RtotalSize = 0;
//sendTimes = 0;
outBlock.clear();
//这个不能用
sendFileName = filename;
localFile = new QFile("files/" + filename);
if (localFile->open(QFile::ReadOnly))
{
qDebug() << "open file:" << filename << " success!";
//qDebug() << "thread now" << currentThreadId();
//sendFile();
return true;
}
else
{
qDebug() << "open file failed!";
return false;
}}void MyTcpSocket::goOnSend()
{
//qDebug() << "goOnSend";
QDataStream in(this);
in >> receiveStatus >> sumBlock >> breakPoint;
qDebug() << "the status:" << receiveStatus << "the sumBlock:" << sumBlock << "the breakPoint:" << breakPoint;
//读入接收成功标志(-1为继续发送,-2为暂停,大于等于0的为断点处
if (receiveStatus == -1)
{
//判断一下,大于才减少
if (byteToWrite >= loadSize)
{outBlock = localFile->read(loadSize);
byteToWrite -= loadSize; //剩余数据大小
}
else
{
outBlock = localFile->read(byteToWrite);
byteToWrite = 0;
}
this->write(outBlock); //将这个信息写入socket
this->waitForBytesWritten();
sendTimes++;
qDebug() << " threadId:" << QThread::currentThreadId() << "sockID:" << socketDescriptor1
<< " " << currentFileName << "the " << sendTimes << "the loadSize:" << loadSize
<< " left byteTowrite: " << byteToWrite;
}
//暂停发送?
else if (receiveStatus == -2)
{}
//向数据库插入断点,其实这个文件发送次数就是断点处
else if (receiveStatus > 0)
{
//插入记录
insertRecord(sumBlock, breakPoint);
}if (byteToWrite == 0) //发送完毕
{
//qDebug()<<QString::fromLocal8Bit("文件发送完毕!");
//disconnect(tcpSocket, SIGNAL(bytesWritten(qint64)), this, SLOT(goOnSend(qint64)));
sendTimes = 0;
//插入记录
insertRecord(sumBlock, breakPoint);
qDebug() << " threadId:" << QThread::currentThreadId()
<< "sockID:" << socketDescriptor1 << currentFileName << "the file has sended";
localFile->close(); //发送完文件要关闭,不然不能对其进行下一步操作
//emit receiveDone();
}}

QT实现TCP断点传输文件相关推荐

  1. java如何实现tcp传输图像_如何在java中实现TCP服务器和TCP客户端传输文件

    我实现了简单的TCP服务器和TCP客户端类,可以从客户端发送消息到服务器,消息将在服务器端转换为大写,但是如何实现从服务器到客户端的传输文件,并从客户端上传文件到服务器.以下代码是我所得到的. TCP ...

  2. Python网络编程——使用TCP方式传输文件

    TCP文件下载器 客户端 需求:输入要下载的文件名,从服务器端将文件拷贝到本地 步骤: 1.创建TCP套接字,绑定端口 2.连接服务端 3.输入要下载的文件名 4.将文件名编码,并发送到服务端 5.接 ...

  3. WinForm制作文件传输助手,Tcp局域网传输文件,传输速度受限于宽带和硬盘速度上限

    先看看传输使用界面的效果,可以选择发送和接收. 需要同一局域网的电脑先选择IP(IP能自动读取到)开启接收,注意这里需要自己更改一下文件名和存储格式,也可以不改传完再改,然后另一台电脑选择文件发送,之 ...

  4. 【传输文件】文件传输协议FTP、SFTP和SCP

    网络通信协议分层 应用层: HTTP(Hypertext Transfer Protocol 超文本传输协议,显示网页) DNS(Domain Name System) FTP(File Transf ...

  5. Qt下Tcp传输文件

    Qt下Tcp传输文件 文章目录 Qt下Tcp传输文件 1.服务端 2.客户端 1.服务端 //ServerWidgets.h #ifndef SERVERWIDGET_H #define SERVER ...

  6. QT中TCP文件传输

    QT中TCP文件传输 一.UI文件 1.serverwidget.ui 2.clientwidget.ui 二..h文件 1.serverwidget.h 2.clientwidget.h 三.cpp ...

  7. Qt网络编程小项目-基于Tcp协议的文件传输项目

    目录 项目描述 Qt下Tcp服务器端和客户端流程: 具体流程: 客户端: 服务器端: 源码: 服务器端: 服务器头文件: 服务器源文件: 服务器端ui 客户端: 客户端头文件: 客户端源文件: 客户端 ...

  8. Qt TCP协议 传输简单字符串实例

    TCP协议的程序使用的是客户端/服务器模式,在Qt中提供了QTcpSocket类来编写客户端程序,使用QTcpServer类编写服务器端程序. A 转载:http://mobile.51cto.com ...

  9. 基于TCP的大文件传输c语言项目

    文章目录 前言:功能实现 tcp文件传输的基本过程: 1.用户登录 1.1创建数据库 2.文件普通下载和上传的实现: 2.1 普通下载 2.2 普通上传 2.3 文件秒上传的实现 2.断点下载和断点上 ...

  10. TCP协议实现文件传输

    使用TCP协议实现传输文件     程序分为发送端和接收端.首先在传输文件数据之前,发送端会把将装有文件名称和文件长度等 信息的数据包发送至接收端.接收端收到文件名称和文件长度信息后会创建好空白文件. ...

最新文章

  1. 網絡問題flapping between port
  2. UI组件之TextView及其子类
  3. 多任务学习漫谈:以损失之名
  4. 中剪取一种颜色的板块_不知道UI设计中APP界面版式如何排版?来看这个!
  5. oracle的表空间的检查,oracle数据库检查所有表空间使用率的脚本
  6. E-Prime 2.0 用了一段时间出现警告信息无法编辑实验程序
  7. pandas安装了但是import不了
  8. 手机麦克风结构原理图_麦克风工作原理是什么
  9. matlab读取hdf显示,matlab读取hdf
  10. 9月1日起施行《中华人民共和国数据安全法》发布(附全文
  11. 明御运维审计与风险控制系统远程桌面(server2012、2016系统)报错error:NLA or TLS security negotiation failure, Please check...
  12. 架构中的应用-XTT 篇
  13. ROS launch文件标签解释
  14. 访存模式分析实验思路(毕设笔记9)
  15. 上载人生(数字天堂)
  16. STM32 CUBEIDE MacOS首次使用笔记
  17. 持续盈利背后,水滴“新增长”难寻?
  18. meego linux版本,记MeeGo的多系统启动
  19. 关于印发《湖南省首版次软件产品认定管理办法》的通知
  20. jenkins插件调用job_Jenkins迁移job插件Job Import Plugin流程详解

热门文章

  1. 中南大学计算机2020研究生分数线,中南大学2020年考研复试分数线已公布
  2. 文件管理大师android,文件管理大师
  3. Jmeter .jmx 改为.jtl
  4. 传感器 | 密度测量系列:1.密度测量的基础知识
  5. turtle递归作图绘制谢尔宾斯基地毯【详解】
  6. ParticleDesigner 粒子编辑器使用
  7. 获取post请求的数据
  8. 更换鼠标垫(鼠标)的心路历程
  9. C语言会员卡计费系统
  10. (经验分享)作为一名普通本科计算机专业学生,我大学四年到底走了多少弯路