ftp 如何多线程 上传 java 多线程ftp客户端

admin2024-06-04  11

基本FTP客户端

QT C++实现的FTP下载客户端

环境说明

FTP服务器:CentOS7.8 + vsFTPD 3.0.2

客户端:win10+QT 5.15.2

实现的不是一个功能全的FTP客户端,而是程序中有从FTP服务器下载文件的需求,主要实现了下载的功能,包括断点续传,没有实现多线程下载。多线程下载的实现与断点续传有点关系,看懂了断点续传,实现多线程下载就简单了。

FTP协议是建立在TCP基础上的,在实现时用的就是Soclass="superseo">cket编程,客户端和服务端之间发送消息,消息的格式见上篇博文的最后几张图。

建立Socket连接

WSADATA dat;
int ret;

//初始化,很重要
if (::WSAStartup(MAKEWORD(2,2),&dat) != 0)  //Windows Sockets Asynchronous启动
{
    cout<<"Init Failed: "<<GetLastError()<<endl;
    emit emitInfo(network , "Init Failed!\n");
    return -1;
}

//创建Socket
controlSocket=socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);
if(controlSocket==INVALID_SOCKET)
{
    cout<<"Creating Control Socket Failed: "<<GetLastError()<<endl;
    emit emitInfo(network , "Creating Control Socket Failed.\n");
    return -1;
}

//构建服务器访问参数结构体
serverAddr.sin_family=AF_INET;
serverAddr.sin_addr.S_un.S_addr=inet_addr(ip_addr.c_str()); //地址
serverAddr.sin_port=htons(PORT);            //端口
memset(serverAddr.sin_zero,0,sizeof(serverAddr.sin_zero));

//连接
ret = ::connect(controlSocket,(struct sockaddr*)&serverAddr,sizeof(serverAddr));
if(ret==SOCKET_ERROR)
{
    cout<<"Control Socket Connecting Failed: "<<GetLastError()<<endl;
    emit emitInfo(network , "Control Socket Connecting Failed\n");
    return -1;
}

用户名密码登录:

//用户名
executeCmd("USER " + username);
if(recvControl(331) != 0)
{
    emit emitInfo(userpass, "");
}

//密码
executeCmd("PASS " + password);
if(recvControl(230) != 0)
{
    emit emitInfo(userpass, "用户名或密码错误!");
    return -1;
}

更改目录

executeCmd("CWD "+tardir);
if(recvControl(250) != 0)
{
    emit emitInfo(directory, "FTP目录不存在!");
    return -1;
}

切换Binary模式

memset(buf, 0, BUFLEN);
executeCmd("TYPE I");
if(recvControl(200) != 0)
{
    emit emitInfo(filename, "切换BINARY模式失败!");
    return -1;
}

列出当前目录下所有文件

int FtpClient::listPwd()
{
    intoPasv();
    executeCmd("LIST -al");
    recvControl(150);
    memset(databuf, 0, DATABUFLEN);
    string fulllist;
    int ret = recv(dataSocket, databuf, DATABUFLEN-1, 0);
    while(ret>0)
    {
        databuf[ret] = 'int dataPort, ret;
//切换到被动模式
executeCmd("PASV");
recvControl(227);

//返回的信息格式为---h1,h2,h3,h4,p1,p2
//其中h1,h2,h3,h4为服务器的地址,p1*256+p2为数据端口
dataPort = getPortNum();
//客户端数据传输socket
dataSocket = socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);
serverAddr.sin_port = htons(dataPort);    //更改连接参数中的port值
ret = ::connect(dataSocket,(struct sockaddr*)&serverAddr,sizeof(serverAddr));
if(ret == SOCKET_ERROR)
{
    cout<<"Data Socket connecting Failed: "<<GetLastError()<<endl;
    return -1;
}

cout<<"Data Socket connecting is success."<<endl;
return 0;';
        fulllist += databuf;
        ret = recv(dataSocket, databuf, DATABUFLEN-1, 0);
    }

    removeSpace(fulllist);

    int lastp, lastq, p, q;
    vector<string> eachrow;
    string rawrow;
    string item;
    filelist.clear();
    p = fulllist.find("\r\n");
    lastp = 0;

    while(p>=0)
    {
        eachrow.clear();
        rawrow = fulllist.substr(lastp, p-lastp);

        q = rawrow.find(' ');
        lastq = 0;
        for(int i=0; i<8; i++)
        {
            item = rawrow.substr(lastq, q-lastq);
            eachrow.push_back(item);
            lastq = q + 1;
            q = rawrow.find(' ', lastq);
        }

        item = rawrow.substr(lastq);
        eachrow.push_back(item);
        filelist.push_back(eachrow);

        lastp = p + 2;
        p = fulllist.find("\r\n", lastp);
    }

    closesocket(dataSocket);
    recvControl(226);
    return 0;
}

切换成PASV模式

int cur = 0;
ofstream ofile;
string localFile = localDir + "/" + localName;
QFileInfo fileinfo(QString::fromStdString(localFile));
int ss = fileinfo.size();

ofile.open(localFile, ios_base::binary);

if(intoPasv() == -1)
{
    ofile.close();
    emit emitInfo(network, "进入pasv模式失败!");
    return -1;
}

executeCmd("RETR "+remoteName);
if(recvControl(150) != 0)
{
    ofile.close();
    emit emitInfo(filename, "FTP文件不存在!");
    return -1;
}

memset(databuf, 0, DATABUFLEN);
int ret = recv(dataSocket, databuf, DATABUFLEN, 0);

while(ret > 0)
{
    cur += ret;
    //cout << cur << " : " << size;

    emit emitProcess(cur, size);

    ofile.write(databuf, ret);
    ofile.flush();
    ret = recv(dataSocket, databuf, DATABUFLEN, 0);

    if(ret == -1)
    {
        cout << "sending file, socker error!" << endl;
        emit emitInfo(network, "传输文件时失败,网络断开!");
        break;
    }
}

ofile.close();

下载文件:

断点续传

int cur = 0; ofstream ofile; string localFile = localDir + "/" + localName; QFileInfo fileinfo(QString::fromStdString(localFile)); int ss = fileinfo.size(); if(resume) { if(ss > 0) { if(ss >= size) { // 本地文件比ftp上大或相等,默认覆盖 cout << "s >= size-----" << endl; //ofile.seekp(0, std::ios::beg); ofile.open(localFile, ios_base::binary); } else { if(setTypeI() == -1) { //cout << "设置BINARY模式失败,不能断点续传,从头开始!" << endl; //ofile.seekp(0, std::ios::beg); // 设置BINARY模式失败,不能断点续传,从头开始 //ofile.open(localFile, ios_base::binary); emit emitInfo(network, "设置BINARY模式失败!"); return -1; } else { if(restFile(ss) == -1) { //ofile.open(localFile, ios_base::binary); //ofile.seekp(0, std::ios::beg); // 设置断点续传失败,从头开始 emit emitInfo(network, "设置续传模式失败!"); return -1; } else { cout << "begin resume break-point!" << endl; ofile.open(localFile, ios_base::binary|ios_base::app); cur += ss; } } } } else { ofile.open(localFile, ios_base::binary); } } else { ofile.open(localFile, ios_base::binary); } if(intoPasv() == -1) { ofile.close(); emit emitInfo(network, "进入pasv模式失败!"); return -1; } executeCmd("RETR "+remoteName); if(recvControl(150) != 0) { ofile.close(); emit emitInfo(filename, "FTP文件不存在!"); return -1; } memset(databuf, 0, DATABUFLEN); int ret = recv(dataSocket, databuf, DATABUFLEN, 0); while(ret > 0) { cur += ret; //cout << cur << " : " << size; emit emitProcess(cur, size); ofile.write(databuf, ret); ofile.flush(); ret = recv(dataSocket, databuf, DATABUFLEN, 0); if(ret == -1) { cout << "sending file, socker error!" << endl; emit emitInfo(network, "传输文件时失败,网络断开!"); break; } } ofile.close();

实现断点续传,主要用到REST命令,从特定的偏移量开始传输文件。
先获取本地文件大小,与服务器文件比较,如果小于服务器文件大小,则开始断点续传,本地文件用append模式打开,从文件末尾写入。
设置偏移量REST 本地文件大小,之前还要切换成Binary模式,一般FTP服务器默认的是Ascii模式,Ascii模式是不能进行断点续传的。
核心代码:

类封装

class FtpClient : public QObject class ClientThread : public QThread class ClientManager : public QObject

主要包含了三个类:

connect(&m_Client, SIGNAL(emitProcess(int,int)), this, SLOT(on_emitDownloadSize(int,int)));
 connect(&m_Client, SIGNAL(emitError(int,QString)), this, SLOT(on_emitError(int,QString)));

ClientManager类是对外的接口类,FtpClient类是ftp客户端,进行与服务器进行交互的类,ClientThread是线程执行类,把一系列的ftpclient类的调用封装在一个线程函数中。
FtpClient类向ClientManager类通过signal报告信息,是否登录成功,文件下载进度等。ClientManager类接收到后根据需要把重要的信息向上再次抛出signal。

上层程序调用是首先响应signal

m_ClientManager.setDownloadInfo(ui->textEdit1->toPlainText(), ui->textEdit1_2->toPlainText(), ui->textEdit1_3->toPlainText(), ui->textEdit1_4->toPlainText(), ui->textEdit1_5->toPlainText(), ui->textEdit1_6->toPlainText(), ui->textEdit1_7->toPlainText());
 m_ ClientManager.startDownload();

然后就是调用两个函数实现文件下载

多线程下载

把程序运行过程中与FTP服务器交互的主要信息打印出来如下:

ftp 如何多线程 上传 java 多线程ftp客户端,ftp 如何多线程 上传 java 多线程ftp客户端_c++,第1张

 

我的程序中没有实现多线程下载。如果要做也是要用REST命令。根据线程数,计算每个线程下载的偏移量。每个线程各自通过pasv模式向服务端连接数据端口,然后设置平移量,下载特定大小的字节后就停止下载,最后再本地把几个文件段拼接起来。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明原文出处。如若内容造成侵权/违法违规/事实不符,请联系SD编程学习网:675289112@qq.com进行投诉反馈,一经查实,立即删除!