阿里云主机折上折
  • 微信号
Current Site:Index > File descriptor

File descriptor

Author:Chuan Chen 阅读数:26752人阅读 分类: Node.js

Basic Concepts of File Descriptors

A file descriptor (FD) is an operating system-level concept. It is a non-negative integer used to identify an open file or I/O resource. In Unix-like systems, the "everything is a file" philosophy makes file descriptors a core mechanism for accessing various I/O devices.

const fs = require('fs');

// Get a file descriptor
fs.open('example.txt', 'r', (err, fd) => {
  if (err) throw err;
  console.log(`File descriptor is: ${fd}`);
  
  // Remember to close the file descriptor
  fs.close(fd, (err) => {
    if (err) throw err;
  });
});

File Descriptor Handling in Node.js

Node.js provides file system operation interfaces through the fs module, where many APIs involve the use of file descriptors. Unlike directly using file paths, operating on files via file descriptors avoids repeated path resolution and permission checks.

const fs = require('fs');
const fsPromises = fs.promises;

// Using Promise-style APIs
async function readWithFd() {
  let fd;
  try {
    fd = await fsPromises.open('data.txt', 'r');
    const buffer = Buffer.alloc(1024);
    const { bytesRead } = await fd.read(buffer, 0, buffer.length, 0);
    console.log(buffer.toString('utf8', 0, bytesRead));
  } finally {
    if (fd) await fd.close();
  }
}

File Descriptors for Standard Streams

In Unix systems, there are three predefined file descriptors:

  • 0: Standard input (stdin)
  • 1: Standard output (stdout)
  • 2: Standard error (stderr)

In Node.js, these standard streams can be accessed via the process object:

process.stdin.on('data', (data) => {
  process.stdout.write(`You entered: ${data}`);
  process.stderr.write(`Log: Received input ${new Date().toISOString()}\n`);
});

Advanced Usage of File Descriptors

File Locking

File locking can be implemented via file descriptors to prevent multiple processes from modifying a file simultaneously:

const fs = require('fs');

fs.open('lockfile', 'wx', (err, fd) => {
  if (err) {
    if (err.code === 'EEXIST') {
      console.error('File is already locked');
      return;
    }
    throw err;
  }
  
  // Perform operations requiring the lock
  console.log('Acquired file lock');
  
  // Release the lock after operations
  fs.close(fd, (err) => {
    if (err) throw err;
    console.log('Released file lock');
  });
});

File Truncation

Files can be truncated via file descriptors:

const fs = require('fs');

fs.open('largefile.txt', 'r+', (err, fd) => {
  if (err) throw err;
  
  // Truncate the file to 100 bytes
  fs.ftruncate(fd, 100, (err) => {
    if (err) throw err;
    console.log('File truncated successfully');
    fs.close(fd, () => {});
  });
});

Relationship Between File Descriptors and Streams

Node.js' Stream abstraction also uses file descriptors under the hood. For example, when creating a readable stream:

const fs = require('fs');

// Underlying file descriptor will be opened
const readStream = fs.createReadStream('largefile.txt');

readStream.on('open', (fd) => {
  console.log(`File descriptor ${fd} opened`);
});

readStream.on('close', () => {
  console.log('File descriptor closed');
});

File Descriptor Leakage Issues

Failing to properly close file descriptors can lead to resource leaks, which is particularly problematic in long-running Node.js services:

const fs = require('fs');

// Wrong approach - causes descriptor leaks
function leakDescriptors() {
  for (let i = 0; i < 1000; i++) {
    fs.open('temp.txt', 'w', (err, fd) => {
      if (err) throw err;
      // Forgot to close fd
    });
  }
}

// Correct approach
function properDescriptorHandling() {
  let count = 0;
  
  function next() {
    if (count >= 1000) return;
    
    fs.open('temp.txt', 'w', (err, fd) => {
      if (err) throw err;
      
      // Process the file...
      
      fs.close(fd, (err) => {
        if (err) throw err;
        count++;
        next();
      });
    });
  }
  
  next();
}

Monitoring File Descriptor Usage

During development, you can monitor file descriptor usage with the following methods:

const fs = require('fs');
const os = require('os');

// Check process file descriptor limits
console.log(`File descriptor limit: ${os.constants.UV_FS_O_FILEMAP}`);

// On Linux systems, you can check /proc/<pid>/fd directory
if (process.platform === 'linux') {
  const fdDir = `/proc/${process.pid}/fd`;
  fs.readdir(fdDir, (err, files) => {
    if (err) {
      console.error('Failed to read fd directory:', err);
      return;
    }
    console.log(`Current number of open file descriptors: ${files.length}`);
  });
}

File Descriptors and Performance Optimization

Proper management of file descriptors can significantly improve I/O performance:

const fs = require('fs');

// Inefficient approach - open and close file for each operation
function inefficientWrite(data) {
  fs.open('data.log', 'a', (err, fd) => {
    if (err) throw err;
    
    fs.write(fd, data + '\n', (err) => {
      if (err) throw err;
      
      fs.close(fd, (err) => {
        if (err) throw err;
      });
    });
  });
}

// Efficient approach - keep file descriptor open
let logFd;
fs.open('data.log', 'a', (err, fd) => {
  if (err) throw err;
  logFd = fd;
});

process.on('exit', () => {
  if (logFd !== undefined) {
    fs.closeSync(logFd);
  }
});

function efficientWrite(data) {
  if (logFd === undefined) {
    // Handle case where file isn't open
    return inefficientWrite(data);
  }
  
  fs.write(logFd, data + '\n', (err) => {
    if (err) throw err;
  });
}

File Descriptors and Child Processes

When creating child processes, be mindful of file descriptor inheritance behavior:

const { spawn } = require('child_process');
const fs = require('fs');

// Open a file descriptor
fs.open('input.txt', 'r', (err, fd) => {
  if (err) throw err;
  
  // Create child process and pass the file descriptor
  const child = spawn('node', ['child.js'], {
    stdio: ['inherit', 'inherit', 'inherit', fd]
  });
  
  child.on('exit', () => {
    fs.close(fd, () => {});
  });
});

Choosing Between Async and Sync APIs

Node.js provides both synchronous and asynchronous versions of file descriptor operations:

const fs = require('fs');

// Synchronous approach
try {
  const fd = fs.openSync('sync.txt', 'w');
  fs.writeSync(fd, 'Synchronously written data');
  fs.closeSync(fd);
} catch (err) {
  console.error(err);
}

// Asynchronous approach
fs.open('async.txt', 'w', (err, fd) => {
  if (err) return console.error(err);
  
  fs.write(fd, 'Asynchronously written data', (err) => {
    if (err) return console.error(err);
    
    fs.close(fd, (err) => {
      if (err) console.error(err);
    });
  });
});

File Descriptors and Error Handling

Proper error handling for file descriptor operations is crucial:

const fs = require('fs');

function safeFileOperation() {
  let fd;
  
  fs.open('important.data', 'r+', (openErr, fileDescriptor) => {
    if (openErr) {
      console.error('Failed to open file:', openErr);
      return;
    }
    fd = fileDescriptor;
    
    fs.write(fd, 'New data', (writeErr) => {
      if (writeErr) {
        console.error('Failed to write file:', writeErr);
        // Still need to attempt closing the file descriptor
        return fs.close(fd, () => {});
      }
      
      fs.close(fd, (closeErr) => {
        if (closeErr) {
          console.error('Failed to close file:', closeErr);
        }
      });
    });
  });
}

File Descriptors and Network Sockets

In Node.js, network sockets also use the file descriptor abstraction:

const net = require('net');
const fs = require('fs');

const server = net.createServer((socket) => {
  // Get underlying file descriptor
  const fd = socket._handle.fd;
  console.log(`New connection, socket file descriptor: ${fd}`);
  
  socket.on('data', (data) => {
    // Use file descriptor to write logs
    fs.write(fd, `[${new Date()}] ${data}\n`, (err) => {
      if (err) console.error('Failed to write log:', err);
    });
  });
});

server.listen(3000);

本站部分内容来自互联网,一切版权均归源网站或源作者所有。

如果侵犯了你的权益请来信告知我们删除。邮箱:cc@cccx.cn

Front End Chuan

Front End Chuan, Chen Chuan's Code Teahouse 🍵, specializing in exorcising all kinds of stubborn bugs 💻. Daily serving baldness-warning-level development insights 🛠️, with a bonus of one-liners that'll make you laugh for ten years 🐟. Occasionally drops pixel-perfect romance brewed in a coffee cup ☕.