C++ / a working model

153 / 163   ·   C11   ·   约 8 分钟

网络文件系统(NFS)

先记住这句话

本章介绍早期成功的分布式文件系统 NFS,重点讲解客户端-服务器模型如何实现数据共享与透明访问,以及 NFSv2 如何通过完全无状态的协议实现服务器崩溃后的瞬间恢复。

本篇内容
  1. 为何需要远程文件服务
  2. 透明的客户端文件系统
  3. 无状态设计换取快速恢复
  4. 开放规范带来的生态
  5. 运行示例
  6. 动手练习

官方章节 PDF

为何需要远程文件服务

多台工作站各自拥有本地磁盘时,用户很难在不同机器间看到同一份文件。把数据放在少数服务器上,所有客户端通过网络访问,自然获得统一视图。备份、权限和物理安全也可以集中处理,而不必在每台机器上重复。

透明的客户端文件系统

应用程序仍然调用普通的 open、read、write 等系统调用。客户端文件系统负责把这些调用翻译成网络请求,服务器完成磁盘或缓存操作后把结果送回。对程序员来说远程文件几乎和本地文件没有区别,只是延迟可能稍高。

无状态设计换取快速恢复

服务器故意不记住任何客户端信息:没有打开文件表,没有当前偏移,也没有缓存哪些块。每条协议消息都携带完成该操作所需的全部参数,例如文件句柄、偏移和长度。服务器重启后立刻可以处理新请求,客户端最多重发刚才那一条消息即可。

开放规范带来的生态

Sun 公开了精确的消息格式而不是做成封闭产品,任何厂商都可以实现兼容服务器。竞争促使性能和可靠性不断提升,也让 NFS 成为事实上的标准。

常见误区

  • 把本地文件描述符直接用在网络请求里,导致服务器崩溃后无法识别
  • 把短暂的网络分区当成服务器永久宕机而放弃重试
  • 在客户端长时间缓存脏数据却从未写回,造成多客户端视图不一致

运行一个例子

最低标准 C11 · 完整程序 · 下载 .c

#include <stdio.h>
#include <string.h>
#include <stdint.h>

typedef uint32_t nfs_fh_t;

static const char file_data[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
static const size_t file_len = 26;

int nfs_read(nfs_fh_t fh, uint64_t offset, uint32_t count, char *buf, uint32_t *nread) {
    printf("NFS-REQ READ fh=%u off=%llu cnt=%u\n", (unsigned)fh, (unsigned long long)offset, count);
    if (fh != 7) {
        printf("NFS-ERR stale handle\n");
        return -1;
    }
    if (offset >= file_len) {
        *nread = 0;
        printf("NFS-REP 0 bytes (EOF)\n");
        return 0;
    }
    size_t remain = file_len - (size_t)offset;
    uint32_t copy = count < remain ? count : (uint32_t)remain;
    memcpy(buf, file_data + (size_t)offset, copy);
    *nread = copy;
    printf("NFS-REP %u bytes\n", copy);
    return 0;
}

int main(void) {
    char buf[32];
    uint32_t got;
    nfs_fh_t fh = 7;
    printf("Client starts using NFS file handle 7\n");
    if (nfs_read(fh, 0, 5, buf, &got) == 0) {
        buf[got] = '\0';
        printf("DATA: %s\n", buf);
    }
    if (nfs_read(fh, 10, 5, buf, &got) == 0) {
        buf[got] = '\0';
        printf("DATA: %s\n", buf);
    }
    printf(">>> simulated server crash and restart <<<\n");
    if (nfs_read(fh, 20, 10, buf, &got) == 0) {
        buf[got] = '\0';
        printf("DATA: %s\n", buf);
    }
    nfs_read(fh, 30, 4, buf, &got);
    printf("EOF reached as expected\n");
    return 0;
}

在本地编译

gcc -std=c11 -Wall -Wextra -Wpedantic -Werror ostep-49-nfs.c -o example && ./example

预期结果

Client starts using NFS file handle 7
NFS-REQ READ fh=7 off=0 cnt=5
NFS-REP 5 bytes
DATA: ABCDE
NFS-REQ READ fh=7 off=10 cnt=5
NFS-REP 5 bytes
DATA: KLMNO
>>> simulated server crash and restart <<<
NFS-REQ READ fh=7 off=20 cnt=10
NFS-REP 6 bytes
DATA: UVWXYZ
NFS-REQ READ fh=7 off=30 cnt=4
NFS-REP 0 bytes (EOF)
EOF reached as expected

CHECK YOUR UNDERSTANDING

合上答案,试着解释。

如果 NFS 协议包含一个会返回整数描述符的 open 调用,服务器崩溃后客户端发出的下一次 read 会遇到什么困难?无状态设计如何避免该困难?

查看参考答案

服务器内存中的描述符到文件的映射会丢失,read 无法知道该读哪个文件。无状态协议让每次 read 都自带文件句柄和偏移,服务器无需任何先前状态即可完成操作。

继续查证

标准草案与官方章节会更新;版本标记只说明示例最低要求。

回到目录