FTP 异步操作上的 ERROR_IO_PENDING [Wininet C++]

Posted

技术标签:

【中文标题】FTP 异步操作上的 ERROR_IO_PENDING [Wininet C++]【英文标题】:ERROR_IO_PENDING on FTP Asynchronous Operation [Wininet C++] 【发布时间】:2015-02-24 20:55:30 【问题描述】:

我最近一直在开发一个需要与服务器建立 FTP 连接并从中下载/上传文件的应用程序。出于性能原因,我想一次下载多个文件。出于这个原因,我尝试使用InternetOpen 函数和INTERNET_FLAG_ASYNC 标志以及InternetSetStatusCallback 函数在Wininet API 上实现异步操作。这是我的代码示例,我想在其中递归地列出远程服务器主目录中的所有文件:

      /*Global variables*/
            HANDLE MayContinue=0;
            DWORD LatestResult=1;
        /*Prototypes*/
        void CALLBACK CallbackFunction(HINTERNET,DWORD_PTR,DWORD,LPVOID,DWORD);

    //Iteration function called by main()

        void FTPIterate()
            
                WIN32_FIND_DATAA data;
                HINTERNET Internet;
                INTERNET_STATUS_CALLBACK call;
                HINTERNET h_file;

                 MayContinue = ::CreateEvent (NULL, FALSE, FALSE, NULL);
                iconnect=InternetOpen(NULL,INTERNET_OPEN_TYPE_PROXY,proxy_url,NULL,INTERNET_FLAG_ASYNC);
                call=InternetSetStatusCallback(iconnect,(INTERNET_STATUS_CALLBACK)CallbackFunction);

                while(f[FLAG_FTP_ITERATE])
                
                    MayContinue = ::CreateEvent(NULL,FALSE,FALSE,NULL);
                    InternetConnect(iconnect,ftp_url,INTERNET_DEFAULT_FTP_PORT,ftp_user,ftp_pass,INTERNET_SERVICE_FTP,NULL,LatestResult);
                       WaitForSingleObject (MayContinue, INFINITE);
                       server=(HINTERNET)LatestResult;
                           printf("Server handle: %i\n",(int)server);
                           printf("Server Error: %i\n",GetLastError());
                           SetLastError(0);
                        MayContinue = ::CreateEvent(NULL,FALSE,FALSE,NULL);
                        FtpFindFirstFile(server,ftp_base,&data,INTERNET_FLAG_NO_CACHE_WRITE,LatestResult);
                        WaitForSingleObject(MayContinue,INFINITE);
                        h_file=(HINTERNET)LatestResult;
                            //do stuff
                            printf("FindFirstFile handle: %i\n",(int)h_File);
                        while((MayContinue = ::CreateEvent(NULL,FALSE,FALSE,NULL)) && InternetFindNextFileA(h_file,&data))
                        
                            WaitForSingleObject(MayContinue,INFINITE);
                            //do stuff
                        
                              printf("FindNextFile Error: %i\n",GetLastError()); //loop is never executed
                    InternetCloseHandle(server);
                   

            

        void CALLBACK CallbackFunction(HINTERNET hInternet,DWORD_PTR dwContext,DWORD dwInternetStatus,LPVOID lpvStatusInformation,DWORD dwStatusInformationLength)
        
        if (dwInternetStatus == INTERNET_STATUS_REQUEST_COMPLETE)
          
            LatestResult = ((LPINTERNET_ASYNC_RESULT)lpvStatusInformation)->dwResult;
            SetEvent (MayContinue);
          
        

我的代码基于来自 Stack Overflow 的 this 帖子。当我运行它时,首先在调用InternetConnect 后出现错误,即ERROR_IO_PENDING。根据 WinAPI 参考,这意味着仍有一些操作正在执行。对WaitForSingleObject 的调用不应该防止这种情况发生吗? (实际上,InternetConnect 返回的HINTERNET 句柄似乎是有效的)。 当我调用FtpFindFirstFile 函数时,它会正确检索第一个文件,但是当我在InternetFindNextFile 函数中使用它返回的HINTERNET 句柄(这似乎是有效的)时,它会失败并出现错误INVALID_HANDLE_VALUE

编辑:我在使用 Remy 的代码时遇到了这些错误:

Connect Handle 00CC0004
Waiting for server handle
    Unable to find first file. OS Error: 6 //aren't those calls to FindFirstFile weird if InternetConnect hasn't returned yet?
Waiting for server handle
    Unable to find first file. OS Error: 6
Waiting for server handle
    Unable to find first file. OS Error: 6
Waiting for server handle
    Unable to find first file. OS Error: 6
Waiting for server handle
    Unable to find first file. OS Error: 6
Waiting for server handle
    Unable to find first file. OS Error: 6
Waiting for server handle.
Unable to connect to Server. Inet Error: 12015 Waiting for server handle

谁能帮我找出错误? 提前谢谢你。

【问题讨论】:

"我首先在调用InternetConnect 后得到一个错误,即ERROR_IO_PENDING" - 你知道这个如何吗?您似乎没有检查 InternetConnect 的结果以获取成功的 HANDLE 返回值,也没有任何代码在返回句柄为 NULL 的情况下检查 GetLastError() 的子路径。 @WhozCraig 正如我在帖子中所说,为了清楚起见,我省略了错误检查。实际上,在我的源代码中,我检查了 InternetConnect 返回的 HANDLE(在调用 WaitForSingleObject 之后),将其转换为 ìnt`(不为空),然后调用 GetLastError(),它返回 @987654343 @。稍后在FtpFindFirstFileInternetFindNextFile 之后执行类似的检查。我正在编辑 sn-p 以显示它。 ERROR_IO_PENDING 实际上并不是一个错误。它只是意味着 I/O 操作已成功启动,但尚未完成。由于你请求异步操作,ERROR_IO_PENDING是正常的。 【参考方案1】:

ERROR_IO_PENDING 错误来自InternetOpen() 本身。由于WaitForSingleObject() 成功,它不会覆盖GetLastError()(它只在错误时这样做,就像大多数API 所做的那样),所以错误是从InternetOpen() 的结果中转移过来的。这不是使用GetLastError() 的正确方法。假设所有 API 都覆盖了 GetLastError()(如果文档记录为使用 GetLastError()),并确保仅在 API 失败时调用它立即(除非记录为在成功条件下使用)。

您的代码所做的是不是异步的!您正在发出异步 API 调用,但您正在等待它们的结果,这违背了目的。您的代码是同步运行的,就像您要省略 INTERNAL_FLAG_ASYNC 标志和 WaitForSingleObject() 调用一样(更不用说您通过不必要地调用 CreateEvent() 来泄漏事件资源),例如:

void LogError(const char *Op)

    DWORD err = GetLastError();

    if (err == ERROR_INTERNET_EXTENDED_ERROR)
    
        LPSTR szBuffer;
        DWORD dwLength = 0;

        InternetGetLastResponseInfoA(&err, NULL, &dwLength);
        if (GetLastError() != INSUFFICIENT_BUFFER)
        
            printf("%s. Unknown Inet Error. OS Error: %u", Op, GetLastError());
            return;
        

        szBuffer = new char[dwLength+1];
        InternetGetLastResponseInfoA(&err, szBuffer, &dwLength);
        szBuffer[dwLength] = 0;

        printf("%s. Inet Error: %u %s", Op, err, szBuffer);
        delete[] szBuffer;
    
    else
    
        LPSTR lpBuffer = NULL;
        DWORD dwLen = FormatMessageA(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_ARGUMENT_ARRAY | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, NULL, err, 0, (LPSTR)&lpBuffer, 0, NULL);

        if (lpBuffer)
        
            printf("%s. OS Error: %u %s", Op, err, lpBuffer);
            LocalFree(lpBuffer);
        
        else
            printf("%s. OS Error: %u", Op, err);
    

    printf("\n");


void FTPIterate()

    WIN32_FIND_DATAA data;
    HINTERNET hConnect;
    HINTERNET hServer;
    HINTERNET hFile;

    hConnect = InternetOpen(NULL, INTERNET_OPEN_TYPE_PROXY, proxy_url, NULL, 0);
    if (hConnect == NULL)
    
        LogError("Unable to Open Internet");
        return;
    

    printf("Connect handle: %p\n", hConnect);

    while (f[FLAG_FTP_ITERATE])
    
        printf("Connecting to Server\n");

        hServer = InternetConnect(hConnect, ftp_url, INTERNET_DEFAULT_FTP_PORT, ftp_user, ftp_pass, INTERNET_SERVICE_FTP, NULL, 0);
        if (hServer == NULL)
        
            LogError("Unable to connect to Server");
            continue;
        

    printf("Connected to Server. Server handle: %p\n", hServer);
        printf("Finding first file\n");

        hFile = FtpFindFirstFileA(hServer, ftp_base, &data, INTERNET_FLAG_NO_CACHE_WRITE, 0);
        if (hFile == NULL)
        
            if (GetLastError() == ERROR_NO_MORE_FILES)
                printf("No files were found\n");
            else
                LogError("Unable to find first file");
        
        else
        
            printf("Find handle: %p\n", hFile);

            do
            
                //do stuff

                printf("Finding next file\n");

                if (!InternetFindNextFileA(hFile, &data))
                
                    if (GetLastError() == ERROR_NO_MORE_FILES)
                        printf("No more files were found\n");
                    else
                        LogError("Unable to find next file")

                    break;
                
            
            while (true);

            InternetCloseHandle(hFile);
        

        InternetCloseHandle(hServer);
       

    InternetCloseHandle(hConnect);

要使此代码异步运行,请摆脱所有等待并实现一个状态机,您的回调会根据需要推进,例如:

enum FTPState ftpConnect, ftpWaitingForConnect, ftpFindFirstFile, ftpWaitingForFirstFind, ftpFindNextFile, ftpWaitingForNextFind, ftpProcessFile, ftpDisconnect;

struct REQ_CONTEXT

    FTPState State;
    WIN32_FIND_DATAA data;
    HINTERNET hConnect;
    HINTERNET hServer;
    HINTERNET hFile;
    HANDLE hDoneEvent;
;

void LogError(const char *Op, DWORD err)

    if (err == ERROR_INTERNET_EXTENDED_ERROR)
    
        LPSTR szBuffer;
        DWORD dwLength = 0;

        InternetGetLastResponseInfoA(&err, NULL, &dwLength);
        if (GetLastError() != ERROR_INSUFFICIENT_BUFFER)
        
            printf("%s. Unknown Inet Error. OS Error: %u", Op, GetLastError());
            return;
        

        szBuffer = new char[dwLength+1];
        InternetGetLastResponseInfoA(&err, szBuffer, &dwLength);
        szBuffer[dwLength] = 0;

        printf("%s. Inet Error: %u %s", Op, err, szBuffer);
        delete[] szBuffer;
    
    else
    
        LPSTR lpBuffer = NULL;
        DWORD dwLen = FormatMessageA(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_ARGUMENT_ARRAY | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, NULL, err, 0, (LPSTR)&lpBuffer, 0, NULL);

        if (lpBuffer)
        
            printf("%s. OS Error: %u %s", Op, err, lpBuffer);
            LocalFree(lpBuffer);
        
        else
            printf("%s. OS Error: %u", Op, err);
    

    printf("\n");


void LogError(const char *Op)

    LogError(Op, GetLastError());


void DoNextStep(REQ_CONTEXT *ctx)

    do
    
        if ((ctx->State == ftpConnect) && (!f[FLAG_FTP_ITERATE]))
        
            printf("Done!\n");
            SetEvent(ctx->hDoneEvent);
            return;
        

        switch (ctx->State)
        
            case ftpConnect:
            
                printf("Connecting to Server\n");

                HINTERNET hServer = InternetConnect(ctx->hConnect, ftp_url, INTERNET_DEFAULT_FTP_PORT, ftp_user, ftp_pass, INTERNET_SERVICE_FTP, NULL, (DWORD_PTR)ctx);
                if (hServer != NULL)
                
                    if (ctx->hServer == NULL)
                    
                        ctx->hServer = hServer;
                        printf("Server handle: %p\n", ctx->hServer);
                    

                    printf("Connected to Server\n");
                    ctx->State = ftpFindFirstFile;
                
                else if (GetLastError() == ERROR_IO_PENDING)
                
                    if (ctx->hServer == NULL)
                        printf("Waiting for Server handle\n");

                    printf("Waiting for Server connect to complete\n");
                    ctx->State = ftpWaitingForConnect;
                
                else
                    LogError("Unable to connect to Server");

                break;
            

            case ftpWaitingForConnect:
                return;

            case ftpFindFirstFile:
            
                printf("Finding first file\n");

                HINTERNET hFile = FtpFindFirstFileA(ctx->hServer, ftp_base, &ctx->data, INTERNET_FLAG_NO_CACHE_WRITE, (DWORD_PTR)ctx);
                if (hFile != NULL)
                
                    if (ctx->hFile == NULL)
                    
                        ctx->hFile = hFile;
                        printf("Find handle: %p\n", ctx->hFile);
                    

                    ctx->State = ftpProcessFile;
                
                else if (GetLastError() == ERROR_IO_PENDING)
                
                    if (ctx->hFile == NULL)
                        printf("Waiting for Find handle\n");

                    printf("Waiting for Find to complete\n");
                    ctx->State = ftpWaitingForFirstFind;
                
                else
                
                    if (GetLastError() == ERROR_NO_MORE_FILES)
                        printf("No files were found\n");
                    else
                        LogError("Unable to find first file");

                    ctx->State = ftpDisconnect;
                

                break;
            

            case ftpWaitingForFirstFind:
            case ftpWaitingForNextFind:
                return;

            case ftpProcessFile:
            
                //do stuff

                printf("Finding next file\n");
                if (!InternetFindNextFileA(ctx->hFile, &ctx->data))
                
                    if (GetLastError() == ERROR_IO_PENDING)
                    
                        printf("Waiting for next file to complete\n");

                        ctx->State = ftpWaitingForNextFind;
                    
                    else
                    
                        if (GetLastError() == ERROR_NO_MORE_FILES)
                            printf("No more files were found\n");
                        else
                            LogError("Unable to find next file");

                        ctx->State = ftpDisconnect;
                    
                

                break;
            

            case ftpDisconnect:
            
                printf("Disconnecting\n");

                if (ctx->hFile != NULL)
                
                    InternetCloseHandle(ctx->hFile);
                    ctx->hFile = NULL;
                

                if (ctx->hServer != NULL)
                
                    InternetCloseHandle(ctx->hServer);
                    ctx->hServer = NULL;
                

                ctx->State = ftpConnect;
                break;
            
        
    
    while (true);


void CALLBACK CallbackFunction(HINTERNET hInternet, DWORD_PTR dwContext, DWORD dwInternetStatus, LPVOID lpvStatusInformation, DWORD dwStatusInformationLength)

    REQ_CONTEXT *ctx = (REQ_CONTEXT*) dwContext;

    switch (dwInternetStatus)
    
        case INTERNET_STATUS_HANDLE_CREATED:
        
            LPINTERNET_ASYNC_RESULT Result = (LPINTERNET_ASYNC_RESULT) lpvStatusInformation;

            switch (ctx->State)
            
                case ftpConnect:
                case ftpWaitingForConnect:
                    ctx->hServer = (HINTERNET) Result->dwResult;
                    printf("Server handle: %p\n", ctx->hServer);
                    break;

                case ftpFindFirstFile:
                case ftpWaitingForFirstFind:
                    ctx->hFile = (HINTERNET) Result->dwResult;
                    printf("Find handle: %p\n", ctx->hFile);
                    break;
            

            break;
        

        case INTERNET_STATUS_REQUEST_COMPLETE:
        
            LPINTERNET_ASYNC_RESULT Result = (LPINTERNET_ASYNC_RESULT) lpvStatusInformation;

            switch (ctx->State)
            
                case ftpWaitingForConnect:
                
                    if (!Result->dwResult)
                    
                        LogError("Unable to connect to Server", Result->dwError);
                        ctx->State = ftpDisconnect;
                    
                    else
                    
                        printf("Connected to Server\n");
                        ctx->State = ftpFindFirstFile;
                    

                    break;
                

                case ftpWaitingForFirstFind:
                case ftpWaitingForNextFind:
                
                    if (!Result->dwResult)
                    
                        if (Result->dwError == ERROR_NO_MORE_FILES)
                            printf("No %sfiles were found\n", (ctx->State == ftpWaitingForNextFind) ? "more " : "");
                        else if (ctx->State == ftpWaitingForFirstFind)
                            LogError("Unable to find first file", Result->dwError);
                        else
                            LogError("Unable to find next file", Result->dwError);

                        ctx->State = ftpDisconnect;
                    
                    else
                        ctx->State = ftpProcessFile;

                    break;
                
            

            DoNextStep(ctx);
            break;
        
    


void FTPIterate()

    REQ_CONTEXT ctx = 0;
    ctx.State = ftpConnect;

    ctx.hDoneEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
    if (ctx.hDoneEvent == NULL)
    
        LogError("Unable to Create Done Event");
        return;
    

    ctx.hConnect = InternetOpen(NULL, INTERNET_OPEN_TYPE_PROXY, proxy_url, NULL, INTERNET_FLAG_ASYNC);
    if (ctx.hConnect == NULL)
    
        LogError("Unable to Open Internet");
        CloseHandle(ctx.hDoneEvent);
        return;
    

    printf("Connect handle: %p\n", ctx.hConnect);
    InternetSetStatusCallback(ctx.hConnect, &CallbackFunction);

    DoNextStep(&ctx);
    WaitForSingleObject(ctx.hDoneEvent, INFINITE);

    InternetCloseHandle(ctx.hConnect);
    CloseHandle(ctx.hDoneEvent);

【讨论】:

感谢您的回复。至于错误代码,SetLastError不是将代码重置为SUCCES吗?关于异步操作,我的目标不是在索引时执行它们,而是在稍后下载时执行它们,尽管这部分代码没有显示。感谢您的帮助! 在循环的第一次迭代中,直到您记录了从 InternetConnect() 结转的错误之后,您才调用 SetLastError() 对不起。我以为你在谈论InternetConnect()而不是InternetOpen。事实上,我没有正确处理该函数的错误。再次感谢。 当我编译你的代码时,InternetConnect 函数返回一个NULLhandle。错误代码是ERROR_IO_PENDING,对于一定数量的DoNextRequest()调用(它进入switch()的第一个case,设置错误然后返回)直到它变为ERROR_INTERNET_OUT_OF_HANDLES。相同的功能在同步模式下表现良好。错误来自哪里? 如何处理身份验证以及如何异步下载文件是与本讨论的问题无关的单独问题。此讨论的问题已按要求回答。 *** 指南在这个问题上很明确。不同的问题应该分开。无论如何,登录失败与异步无关,您可能只是提供了错误的凭据。 FtpGetFile() 的异步工作方式与其他函数相同 - 调用它,如果您收到 I/O 挂起错误,则等待回调告诉您下载何时完成。

以上是关于FTP 异步操作上的 ERROR_IO_PENDING [Wininet C++]的主要内容,如果未能解决你的问题,请参考以下文章

删除FTP服务器上的文件

java实现读取ftp服务器上的csv文件

thinkphp5获取ftp上的文件列表

redis消息上的异步操作

FTP操作使用

Centos7搭建FTP服务器