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

时间:2015-02-24 20:55:31

标签: c++ winapi asynchronous ftp wininet

我最近一直致力于一个需要与服务器建立FTP连接并从中下载/上传文件的应用程序。出于性能原因,我想一次下载多个文件。出于这个原因,我尝试使用Wininet函数以及InternetOpen标志以及INTERNET_FLAG_ASYNC函数在InternetSetStatusCallback 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不应该阻止这种情况发生吗? (实际上,HINTERNET返回的InternetConnect句柄似乎是有效的。 当我调用FtpFindFirstFile函数时,它正确地检索了第一个文件,但是当我使用它在InternetFindNextFile函数中返回的HINTERNET句柄(它似乎也是有效的)时,它失败并出现错误{ {1}}。

编辑:在使用Remy的代码时,我会解决这些错误:

INVALID_HANDLE_VALUE

有人可以帮我找到错误吗? 提前谢谢。

1 个答案:

答案 0 :(得分:2)

ERROR_IO_PENDING错误来自InternetOpen()本身。由于WaitForSingleObject()成功,因此不会覆盖GetLastError()(它只会出错,就像大多数API那样),因此错误将从InternetOpen()的结果中继承。这是使用GetLastError()的正确方法。假设所有API都覆盖GetLastError()(如果记录为完全使用GetLastError()),并确保仅在API失败时才将其称为 (除非记录为在使用期间使用)成功条件)。

您的代码正在执行的是 NOT 异步!您正在发出异步API调用,但您正在等待其结果,这会破坏目的。您的代码是同步执行的,就像您省略INTERNAL_FLAG_ASYNC标志和WaitForSingleObject()调用一样(更不用说您通过不必要地调用CreateEvent()来泄漏事件资源),例如:< / p>

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);
}