通过网络安装打开时正确关闭文件描述符

Correctly close file descriptor when opened through network mount

我目前正在尝试找出当文件描述符指向远程文件并且连接丢失时如何正确关闭它。

我有一个简单的示例程序,它在 sshfs 安装文件夹上打开文件描述符并开始写入文件。

我找不到如何处理连接丢失的情况。

void *write_thread(void* arg);

int main()
{
    pthread_t thread;
    int fd = -1;

    if(-1 == (fd = open("/mnt/testfile.txt", O_CREAT | O_RDWR | O_NONBLOCK, S_IRWXU)))
    {
        fprintf(stderr, "Error oppening file : %m\n");
        return EXIT_FAILURE;
    }
    else
    {
        if(0 > pthread_create(&thread, NULL, write_thread, &fd))
        {
            fprintf(stderr, "Error launching thread : %m\n");
            return EXIT_FAILURE;
        }
        fprintf(stdout, "Waiting 10 seconds before closing\n");
        sleep(10);
        if(0 > close(fd))
        {
            fprintf(stderr, "Error closing file descriptor: %m\n");
        }
    }
}

void *write_thread(void* arg)
{
    int fd = *(int*)arg;
    int ret;

    while(1)
    {
        fprintf(stdout, "Write to file\n", fd);
        if(0 > ( ret = write(fd, "Test\n", 5)))
        {
            fprintf(stderr, "Error writing to file : %m\n");
            if(errno == EBADF)
            {
                if(-1 == close(fd))
                {
                    fprintf(stderr, "Close failed : %m\n");
                }
                return NULL;
            }
        }
        else if(0 == ret)
        {
            fprintf(stderr, "Nothing happened\n");
        }
        else
        {
            fprintf(stderr, "%d bytes written\n", ret);
        }
        sleep(1);
    }
}

当连接丢失时(即我拔下我的板之间的以太网电缆),主线程中的 close 总是阻止我是否使用标志 O_NONBLOCK。

写入调用有时会立即因 EBADF 错误而失败,有时会持续很长时间才会失败。

我的问题是 write 调用在连接丢失时并不总是失败,所以我无法将事件触发到线程中,我也无法从主线程触发它,因为 close 永远阻塞。

所以我的问题是:如何在 C 中正确处理这种情况?

question is: how to correctly handle this case in C?

你根本做不到。文件句柄被设计成统一和简单的,无论它们指向哪里。当安装了一个设备,并且连接(物理或虚拟)崩溃时,即使在命令行级别,事情也会变得棘手。

远程文件系统存在一个根本性的问题,一方面你必须缓存一些东西以使性能保持在可用的水平,另一方面在多个客户端中缓存会导致冲突,而这些冲突不是被服务器看到。

例如,NFS 默认选择缓存,如果缓存脏了,它将挂起,直到连接恢复。

Documentation for sshfs 表示类似的行为。

从 grepping sshfs' source code 看来,它似乎根本不支持 O_NONBLOCK

None其中与C有关。

IMO 你最好的选择是切换到 nfs,然后挂载例如-o soft -o timeo=15 -o retrans=1.

这可能会导致数据 corruption/loss 在某些情况下,当网络断开时,主要是当有多个客户端或客户端崩溃时,但它确实支持 O_NONBLOCK 并且在任何情况下都会return EIO 如果在请求进行中连接丢失。

经过一番挖掘后,我发现 SSH 挂载可以配置为在没有任何反应的情况下断开连接并断开与服务器的连接。


在客户端设置 ServerAliveInterval X 以在服务器在 X 秒后无响应时断开连接。

在服务器端设置 ClientAliveCountMax X 以在客户端在 X 秒后无响应时断开连接。

ServerAliveCountMax YClientAliveCountMax Y 也可用于在断开连接之前重试 Y 次。


应用此配置后,当连接无响应时,Linux 会自动删除 sshfs 挂载。

使用此配置,write 调用首先失败 Input/output error,然后 Transport endpoint is not connected

这足以检测到连接丢失,从而在退出前清理混乱。