File descriptor
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