ERROR_IO_PENDING on FTP 异步操作 [Wininet C++]

ERROR_IO_PENDING on FTP Asynchronous Operation [Wininet C++]

我最近一直在开发一个需要与服务器建立 FTP 连接并从中获取 download/upload 文件的应用程序。出于性能原因,我想一次下载多个文件。出于这个原因,我尝试使用 InternetOpen 函数以及 INTERNET_FLAG_ASYNC 标志以及 InternetSetStatusCallbackWininet 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 post。当我 运行 它时,我首先在调用 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

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

ERROR_IO_PENDING 错误来自 InternetOpen() 本身。由于 WaitForSingleObject() 成功,它不会覆盖 GetLastError()(它只在出错时这样做,就像大多数 API 所做的那样),所以错误是从 [= 的结果中继承的13=]。这不是 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);
}