none
[请问]wfsopen 加上 ccs=UTF-8后 大规模并发访问出现EBADF错误 RRS feed

  • 问题

  • #include <windows.h>
    #include <stdio.h>
    #include <locale.h>
    #include <sys/stat.h>
    #include <direct.h>
    #include <stdarg.h>
    #include <time.h>
    #include <share.h>
    #include <assert.h>
    #include <errno.h>
    
    int LogOut(    const char* pszFormat,            
                       ...)
    {
        FILE* fp;
        FILE* fp1;
        int iRetTmp = 0;
        wchar_t    szOutputStrW[1024+1] = {0};      
        wchar_t    szOutputStrOutW[1024+1] = {0};   
        char    szOutputStr[1024+1] = {0};        
    
        va_list vlArgs;
    
        wsprintfW(
            szOutputStrW,L"%s\n",L"Hello %s %d");
    
        while(1==1)
        {
            fp = _wfsopen(L"c:\\1/LOG.txt",L"at, ccs=UTF-8",_SH_DENYRW);
            if (fp == NULL && (errno == EACCES))
            {
                continue;
            }
            else if (fp == NULL) {
                char szLog[256];
                sprintf(szLog,"err is : %d %d\n",errno,GetLastError());
                fp1=fopen("c:\\1\\test.log","a");
                fputs(szLog,fp1);
                fclose(fp1);
                return 0;
           }
        break;
        }
        va_start(vlArgs,pszFormat);
        wvsprintfW(
        szOutputStrOutW,szOutputStrW,vlArgs        
        );
    
        iRetTmp = fputws(szOutputStrOutW,fp);
        va_end(vlArgs);
        fclose(fp);
        return 0;
    
    }
    int main(void)
    {
       LogOut("mpmp",L"momo BEGIN",123);
       LogOut("mpmp",L"momo END",123);
    }

    如上一段测试代码在大并发同时执行时(在批处理中 写180条 [start XXXXX.exe] )。

    fopen 会出现大量的EBADF错误。

    EBADF的意义:

    Bad file number. There are two possible causes: 1) The specified file
    descriptor is not a valid value or does not refer to an open file. 2) An attempt
    was made to write to a file or device opened for read-only access.

    从这个解释来看感觉不应该在fopen时出现这个错误。

    如果不加上ccs=UTF-8. 则没有出现EBADF错误。

    请问,大概是什么原因会导致这个错误出现。

    我如果想避免它,应该怎么做?比如判断如果是EBADF错误,就忽略它 再open一次。

    谢谢。


    Langrisser

    2019年7月3日 9:52

答案

  • 这个有什么可奇怪的?返回的错误代码不一定总是同一个值,只有GetLastError才是唯一准确的错误报告。比如你用的这个wfopen,这根本不是Windows API,这个是C库里面的东西,就和C++库里面的std::ofstream一样,最终内部都要转换成Windows API。Windows API返回的错误码不是一对一转换成C库或C++库里面的错误码,所以必须以GetLastError为准,GetLastError是Windows API。
    • 已标记为答案 Langrisser 2019年7月10日 2:28
    2019年7月9日 10:10

全部回复

  • 你好,

    感谢您在MSDN论坛发帖提问。

    >>如果不加上ccs=UTF-8. 则没有出现EBADF错误。请问,大概是什么原因会导致这个错误出现。

    _wfopen_s支持 Unicode 文件流。 我们打开新的或现有的 Unicode 文件,则需要将指定所需编码的ccs标志传递给_wfopen_s。在 Unicode 模式下打开用于写入的文件将自动在其中写入 BOM。如果模式下是 "ccs =encoding", _wfopen_s首先尝试打开具有读访问权限和写访问权限的文件。 如果成功,此函数将读取 BOM 以确定文件的编码;如果失败,此函数将使用文件的默认编码。 在任一情况下, _wfopen_s以只写访问权重新打开文件。

    _wfopen_s是宽字符版本fopen_s ; _wfopen_s的参数是宽字符串。wchar_t和宽字符函数主要用于Windows中以UTF16编码处理Unicode。

    我建议你可以参考以下链接:https://docs.microsoft.com/en-us/cpp/c-runtime-library/reference/fopen-s-wfopen-s?view=vs-2019

    fopen_s和_wfopen_s打开的文件不可共享。根据你的代码,要求文件可共享,则使用_fsopen,_wfsopen和相应的共享模式常量。但是就我看来_fsopen,_wfsopen不支持Unicode文件流。

    我建议你可以参考以下链接:https://docs.microsoft.com/en-us/cpp/c-runtime-library/reference/fsopen-wfsopen?view=vs-2019

    Best Regards,

    Jeanine Zhang

    • 已标记为答案 Langrisser 2019年7月4日 6:31
    • 取消答案标记 Langrisser 2019年7月4日 7:33
    2019年7月4日 3:25
    版主
  • 您好,

    非常感谢您的回答。

    但是我将不支持UNICODE流的_wfsopen替换为支持UNICODE流的_wfopen_s后,依旧出现上述错误。

    详细请参考下面的代码。

    请问我该怎么做?

    谢谢

    #include <windows.h>
    #include <stdio.h>
    #include <locale.h>
    #include <sys/stat.h>
    #include <direct.h>
    #include <stdarg.h>
    #include <time.h>
    #include <share.h>
    #include <assert.h>
    #include <errno.h>
    
    int LogOut(    const char* pszFormat,            
                       ...)
    {
        FILE* fp;
        FILE* fp1;
        int iRetTmp = 0;
        wchar_t    szOutputStrW[1024+1] = {0};      
        wchar_t    szOutputStrOutW[1024+1] = {0};   
        char    szOutputStr[1024+1] = {0};        
            errno_t err;
    
        va_list vlArgs;
    
        wsprintfW(
            szOutputStrW,L"%s\n",L"Hello %s %d");
    
        while(1==1)
        {
            err = _wfopen_s(&fp,L"c:\\1/LOG.txt",L"at, ccs=UTF-8");
            if (fp == NULL && (errno == EACCES))
            {
                continue;
            }
            else if (fp == NULL) {
                char szLog[256];
                sprintf(szLog,"err is : %d %d\n",err,GetLastError());
                fp1=fopen("c:\\1\\test.log","a");
                fputs(szLog,fp1);
                fclose(fp1);
                return 0;
           }
        break;
        }
        va_start(vlArgs,pszFormat);
        wvsprintfW(
        szOutputStrOutW,szOutputStrW,vlArgs        
        );
    
        iRetTmp = fputws(szOutputStrOutW,fp);
        va_end(vlArgs);
        fclose(fp);
        return 0;
    
    }
    int main(void)
    {
       LogOut("mpmp",L"momo BEGIN",123);
       LogOut("mpmp",L"momo END",123);
    }

    2019年7月4日 6:28
  • 你好,

    我建议你检查一下文件是否可以实际打开:

    err = _wfopen_s(&fp, L"c:\\1/LOG.txt", L"at, ccs=UTF-8");
    	if (err == 0)
    	{
    		printf("The file 'c:\\1/LOG.txt' was opened\n");
    	}
    	else
    	{
    		printf("The file 'c:\\1/LOG.txt' was not opened\n");
    	}

    错误代码 EBADF :参数fp指定的文件未被打开,或打开状态为只读

    Best Regards,

    Jeanine Zhang

    2019年7月5日 5:28
    版主
  • 已经将打开文件部分修改为如下:

    err = _wfopen_s(&fp,L"c:\\1/LOG.txt",L"at, ccs=UTF-8");
            if (err == 0)
    	    {
                char szLog[256];
                sprintf(szLog,"The file 'c:\\1/LOG.txt' was opened\n");
                fp1=fopen("c:\\1\\test.log","a");
                fputs(szLog,fp1);
                fclose(fp1);
    	    }
    	    else if ((err == EACCES))
            {
                continue;
            }
            else {
                char szLog[256];
                sprintf(szLog,"err is : %d %d\n",err,GetLastError());
                fp1=fopen("c:\\1\\test.log","a");
                fputs(szLog,fp1);
                fclose(fp1);
                return 0;
           }

     在并发180个执行时。

          test.log中的内容:

    The file 'c:\1/LOG.txt' was opened
    The file 'c:\1/LOG.txt' was opened
    The file 'c:\1/LOG.txt' was opened
    err is : 9 32
    The file 'c:\1/LOG.txt' was opened
    The file 'c:\1/LOG.txt' was opened
    The file 'c:\1/LOG.txt' was opened
    err is : 9 32
    The file 'c:\1/LOG.txt' was opened
    The file 'c:\1/LOG.txt' was opened
    The file 'c:\1/LOG.txt' was opened
    The file 'c:\1/LOG.txt' was opened

    大部分文件可以打开,个别会出现EBADF错误


    Langrisser

    2019年7月5日 8:34
  • 你好,

    >>大部分文件可以打开,个别会出现EBADF错误

    GetLastError函数返回32:该进程无法访问该文件,因为该文件正由另一个进程使用。所以产生错误代码 EBADF :参数fp指定的文件未被打开。

    这是我的代码,但是不能复现你的错误:

    #include <windows.h>
    #include <stdio.h>
    #include <locale.h>
    #include <sys/stat.h>
    #include <direct.h>
    #include <stdarg.h>
    #include <time.h>
    #include <share.h>
    #include <assert.h>
    #include <errno.h>
    
    int main(void)
    {
    
        FILE *fp, *fp1;
        errno_t err;
        int i = 0;
        for ( i= 0; i <= 300; i++)
        {
            err = _wfopen_s(&fp, L".......", L"at,ccs=UTF-8");
            if (err == 0)
            {
                char szLog[256];
                sprintf(szLog, "The file 'text' was opened\n");
                fp1 = fopen(".....", "a");
                fputs(szLog, fp1);
                fclose(fp1);
            }
            else if(err == EACCES)
            {
                continue;
            }
    
            else {
                char szLog[256];
                sprintf(szLog, "err is : %d %d\n", err, GetLastError());
                fp1 = fopen("........", "a");
                fputs(szLog, fp1);
                fclose(fp1);
    
            }
            fclose(fp);
        }
        return 0;
    }

    您能否尝试运行这段代码,查看是否会出现EBADF错误。如果有问题,请随时与我们联系。

    Best Regards,

    Jeanine Zhang

        
    2019年7月8日 9:51
    版主
  • 您好,我运行了一下也出 EBADF错误了。

    The file 'text' was opened
    The file 'text' was opened
    err is : 9 32
    The file 'text' was opened
    The file 'text' was opened

    有一点您可能没有注意到。我的需求是并发,而不是循环300次。

    我执行的方法是调用一个批处理。批处理的写法如下:

    start c:\\1\XXXX.exe
    start c:\\1\XXXX.exe
    start c:\\1\XXXX.exe
    start c:\\1\XXXX.exe
    start c:\\1\XXXX.exe
    start c:\\1\XXXX.exe
    start c:\\1\XXXX.exe
    start c:\\1\XXXX.exe
    start c:\\1\XXXX.exe
    start c:\\1\XXXX.exe
    start c:\\1\XXXX.exe
    start c:\\1\XXXX.exe
    start c:\\1\XXXX.exe
    ...一共180个。
    

    只有这样才是并发,否则都是循序。

    谢谢


    Langrisser

    2019年7月9日 1:29
  • 最开始你用的是wfsopen,后面全部都是wfopen(没有了s),因此后面的都不支持并发。
    2019年7月9日 7:29
  • 最开始你用的是wfsopen,后面全部都是wfopen(没有了s),因此后面的都不支持并发。

    您好,

    关于_wfopen_s 的介绍中有一句:

    Files that are opened by fopen_s and _wfopen_s are not sharable.

    出处:

    https://docs.microsoft.com/en-us/cpp/c-runtime-library/reference/fopen-s-wfopen-s?view=vs-2019

    他只是not sharable. 并不是不支持并发。

    在并发时他会报“EACCES”错误而不应该是“EBADF”。

    而报这两个错误时的getlasterror都是32(文件已经被打开)。

    前者说明有另一个fopen占用了文件,而后者,是操作这个fd时,这个fd是不对的(我文件还没有打开呢,哪里来的不对的fd?)。

    所以我搞不懂了。

    谢谢。


    Langrisser


    2019年7月9日 8:29
  • 不管你是用wfopen、fopen、wfsopen......,还是其它什么(比如std::ofstream),所有这些最终都要调用CreateFile(https://docs.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-createfilew)。这个CreateFile才是真正起作用的那个Windows API,如果要并发,就必须支持同时读取,也就是FILE_SHARE_READ,在fsopen中被称作sharable。不支持sharable,那怎么并发?如果没有FILE_SHARE_READ(也就是sharable),那么当一个文件被打开时,它就会被锁定,只有当占用这个文件的句柄被释放后才能再次读取,只有被标记为FILE_SHARE_READ之后才能并发同时读取。
    2019年7月9日 8:41
  • 不管你是用wfopen、fopen、wfsopen......,还是其它什么(比如std::ofstream),所有这些最终都要调用CreateFile(https://docs.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-createfilew)。这个CreateFile才是真正起作用的那个Windows API,如果要并发,就必须支持同时读取,也就是FILE_SHARE_READ,在fsopen中被称作sharable。不支持sharable,那怎么并发?如果没有FILE_SHARE_READ(也就是sharable),那么当一个文件被打开时,它就会被锁定,只有当占用这个文件的句柄被释放后才能再次读取,只有被标记为FILE_SHARE_READ之后才能并发同时读取。

    您好,我并不需要它们能同时读写。我只需要知道打开文件时出了错误。就可以了。

    然后我需要根据出的错误来进行后面的工作。

    我可以根据说明文档知道文件被占用了不能打开应该返回:errno=EACCES getlasterror()=ERROR_SHARING_VIOLAT

    但是实际情况是还会偶尔出现:errno=EBADF getlasterror()=ERROR_SHARING_VIOLAT。

    那么出现errno=EBADF 这个从文档中找不到合理的解释阿。

    所以我就很困惑了。

    谢谢。


    Langrisser

    2019年7月9日 9:46
  • 这个有什么可奇怪的?返回的错误代码不一定总是同一个值,只有GetLastError才是唯一准确的错误报告。比如你用的这个wfopen,这根本不是Windows API,这个是C库里面的东西,就和C++库里面的std::ofstream一样,最终内部都要转换成Windows API。Windows API返回的错误码不是一对一转换成C库或C++库里面的错误码,所以必须以GetLastError为准,GetLastError是Windows API。
    • 已标记为答案 Langrisser 2019年7月10日 2:28
    2019年7月9日 10:10
  • 谢谢。

    我理解了。


    Langrisser

    2019年7月10日 2:28