349 lines
9.6 KiB
JavaScript
349 lines
9.6 KiB
JavaScript
// named-pipe-server.js
|
||
const net = require('net');
|
||
const fs = require('fs');
|
||
const path = require('path');
|
||
const { EventEmitter } = require('events');
|
||
const async_hooks = require('async_hooks');
|
||
const { v4: uuidv4 } = require('uuid');
|
||
|
||
const DEFAULT_PIPE_PATH = process.platform === 'win32'? '\\\\.\\pipe\\rwlock' : '/tmp/rwlock.sock';
|
||
|
||
/**
|
||
* 基于命名管道的读写锁服务器
|
||
* 提供跨进程的读写锁功能,支持多个客户端同时请求对同一资源的读锁或写锁
|
||
* 实现读写锁的基本语义:
|
||
* 1. 多个读操作可以同时进行
|
||
* 2. 写操作是独占的,不允许同时进行读或其他写操作
|
||
* 3. 锁请求按先进先出(FIFO)方式处理
|
||
*/
|
||
class NamedPipeLockServer extends EventEmitter {
|
||
/**
|
||
* 创建一个NamedPipeLockServer实例
|
||
* @param {string} pipePath - 命名管道路径,默认值根据操作系统确定
|
||
*/
|
||
constructor(pipePath = DEFAULT_PIPE_PATH) {
|
||
super();
|
||
this.pipePath = pipePath;
|
||
this.server = null;
|
||
this.locks = new Map(); // resource -> { readers: Set, writer: null, queue: [] }
|
||
this.clients = new Map(); // clientId -> socket
|
||
}
|
||
|
||
/**
|
||
* 启动服务器
|
||
* 创建并监听命名管道,处理客户端连接和消息
|
||
*/
|
||
start() {
|
||
// 确保管道文件不存在(Windows)
|
||
if (process.platform === 'win32') {
|
||
try {
|
||
if (fs.existsSync(this.pipePath)) {
|
||
fs.unlinkSync(this.pipePath);
|
||
}
|
||
} catch (error) {
|
||
// 忽略错误,可能管道正在使用
|
||
}
|
||
}
|
||
|
||
this.server = net.createServer(socket => {
|
||
const clientId = uuidv4();
|
||
socket.clientId = clientId;
|
||
this.clients.set(clientId, socket);
|
||
|
||
console.log(`Client connected: ${clientId}`);
|
||
|
||
socket.on('data', data => {
|
||
try {
|
||
const messageStrs = data.toString().split('\n');
|
||
messageStrs.forEach(messageStr => {
|
||
if (messageStr) {
|
||
const message = JSON.parse(messageStr);
|
||
this.handleMessage(clientId, message);
|
||
}
|
||
});
|
||
// const message = JSON.parse();
|
||
// this.handleMessage(clientId, message);
|
||
} catch (error) {
|
||
console.error('Error parsing message:', error);
|
||
this.sendError(clientId, 'Invalid message format');
|
||
}
|
||
});
|
||
|
||
socket.on('close', () => {
|
||
this.handleClientDisconnect(clientId);
|
||
this.clients.delete(clientId);
|
||
console.log(`Client disconnected: ${clientId}`);
|
||
});
|
||
|
||
socket.on('error', error => {
|
||
console.error(`Client error (${clientId}):`, error);
|
||
this.handleClientDisconnect(clientId);
|
||
this.clients.delete(clientId);
|
||
});
|
||
});
|
||
|
||
// 启动命名管道服务器
|
||
this.server.listen(this.pipePath, () => {
|
||
console.log(`Lock server listening on named pipe: ${this.pipePath}`);
|
||
});
|
||
|
||
this.server.on('error', error => {
|
||
console.error('Server error:', error);
|
||
});
|
||
}
|
||
|
||
/**
|
||
* 处理来自客户端的消息
|
||
* @param {string} clientId - 客户端标识符
|
||
* @param {Object} message - 消息对象
|
||
*/
|
||
handleMessage(clientId, message) {
|
||
const { type, resource, requestId } = message;
|
||
|
||
if (!this.locks.has(resource)) {
|
||
this.locks.set(resource, {
|
||
readers: new Set(),
|
||
writer: null,
|
||
queue: []
|
||
});
|
||
}
|
||
|
||
const lock = this.locks.get(resource);
|
||
|
||
switch (type) {
|
||
case 'readLock':
|
||
this.handleReadLock(clientId, resource, requestId, lock);
|
||
break;
|
||
case 'writeLock':
|
||
this.handleWriteLock(clientId, resource, requestId, lock);
|
||
break;
|
||
case 'unlock':
|
||
this.handleUnlock(clientId, resource, lock);
|
||
break;
|
||
default:
|
||
this.sendError(clientId, `Unknown message type: ${type}`);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 处理读锁请求
|
||
* @param {string} clientId - 客户端标识符
|
||
* @param {string} resource - 资源名称
|
||
* @param {string} requestId - 请求ID
|
||
* @param {Object} lock - 锁对象
|
||
*/
|
||
handleReadLock(clientId, resource, requestId, lock) {
|
||
if (!lock.writer) {
|
||
// 可以立即获取读锁
|
||
lock.readers.add(clientId);
|
||
this.sendToClient(clientId, {
|
||
type: 'lockGranted',
|
||
requestId,
|
||
resource,
|
||
lockType: 'read'
|
||
});
|
||
console.log(`Read lock granted to ${clientId} for ${resource}`);
|
||
} else {
|
||
// 加入等待队列
|
||
lock.queue.push({ clientId, type: 'read', requestId });
|
||
console.log(`Read lock queued for ${clientId} for ${resource}`);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 处理写锁请求
|
||
* @param {string} clientId - 客户端标识符
|
||
* @param {string} resource - 资源名称
|
||
* @param {string} requestId - 请求ID
|
||
* @param {Object} lock - 锁对象
|
||
*/
|
||
handleWriteLock(clientId, resource, requestId, lock) {
|
||
if (lock.readers.size === 0 && !lock.writer) {
|
||
// 可以立即获取写锁
|
||
lock.writer = clientId;
|
||
this.sendToClient(clientId, {
|
||
type: 'lockGranted',
|
||
requestId,
|
||
resource,
|
||
lockType: 'write'
|
||
});
|
||
console.log(`Write lock granted to ${clientId} for ${resource}`);
|
||
} else {
|
||
// 加入等待队列
|
||
lock.queue.push({ clientId, type: 'write', requestId });
|
||
console.log(`Write lock queued for ${clientId} for ${resource}`);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 处理解锁请求
|
||
* @param {string} clientId - 客户端标识符
|
||
* @param {string} resource - 资源名称
|
||
* @param {Object} lock - 锁对象
|
||
*/
|
||
handleUnlock(clientId, resource, lock) {
|
||
let released = false;
|
||
|
||
// 移除读锁
|
||
if (lock.readers.has(clientId)) {
|
||
lock.readers.delete(clientId);
|
||
released = true;
|
||
console.log(`Read lock released by ${clientId} for ${resource}`);
|
||
}
|
||
|
||
// 移除写锁
|
||
if (lock.writer === clientId) {
|
||
lock.writer = null;
|
||
released = true;
|
||
console.log(`Write lock released by ${clientId} for ${resource}`);
|
||
}
|
||
|
||
if (released) {
|
||
// 处理等待队列
|
||
this.processQueue(resource, lock);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 处理等待队列中的锁请求
|
||
* 根据读写锁规则,尽可能多地授权锁请求
|
||
* @param {string} resource - 资源名称
|
||
* @param {Object} lock - 锁对象
|
||
*/
|
||
processQueue(resource, lock) {
|
||
const granted = [];
|
||
|
||
for (let i = 0; i < lock.queue.length; i++) {
|
||
const request = lock.queue[i];
|
||
|
||
if (request.type === 'read') {
|
||
if (!lock.writer) {
|
||
// 可以授予读锁
|
||
lock.readers.add(request.clientId);
|
||
this.sendToClient(request.clientId, {
|
||
type: 'lockGranted',
|
||
requestId: request.requestId,
|
||
resource,
|
||
lockType: 'read'
|
||
});
|
||
granted.push(i);
|
||
console.log(`Queued read lock granted to ${request.clientId} for ${resource}`);
|
||
} else {
|
||
// 有写锁等待,读锁必须等待
|
||
break;
|
||
}
|
||
} else { // write
|
||
if (lock.readers.size === 0 && !lock.writer) {
|
||
// 可以授予写锁
|
||
lock.writer = request.clientId;
|
||
this.sendToClient(request.clientId, {
|
||
type: 'lockGranted',
|
||
requestId: request.requestId,
|
||
resource,
|
||
lockType: 'write'
|
||
});
|
||
granted.push(i);
|
||
console.log(`Queued write lock granted to ${request.clientId} for ${resource}`);
|
||
} else {
|
||
// 写锁必须等待前面的锁释放
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
|
||
// 移除已处理的请求
|
||
if (granted.length > 0) {
|
||
lock.queue = lock.queue.filter((_, index) => !granted.includes(index));
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 处理客户端断开连接
|
||
* 释放该客户端持有的所有锁,并处理相关队列
|
||
* @param {string} clientId - 客户端标识符
|
||
*/
|
||
handleClientDisconnect(clientId) {
|
||
console.log(`Processing disconnect for client: ${clientId}`);
|
||
|
||
// 释放该客户端持有的所有锁
|
||
for (const [resource, lock] of this.locks) {
|
||
let released = false;
|
||
|
||
if (lock.readers.has(clientId)) {
|
||
lock.readers.delete(clientId);
|
||
released = true;
|
||
}
|
||
|
||
if (lock.writer === clientId) {
|
||
lock.writer = null;
|
||
released = true;
|
||
}
|
||
|
||
// 如果释放了锁,重新处理队列
|
||
if (released) {
|
||
this.processQueue(resource, lock);
|
||
}
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 向指定客户端发送消息
|
||
* @param {string} clientId - 客户端标识符
|
||
* @param {Object} message - 要发送的消息对象
|
||
*/
|
||
sendToClient(clientId, message) {
|
||
const socket = this.clients.get(clientId);
|
||
if (socket && !socket.destroyed) {
|
||
try {
|
||
socket.write(JSON.stringify(message));
|
||
} catch (error) {
|
||
console.error(`Error sending to client ${clientId}:`, error);
|
||
}
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 向客户端发送错误消息
|
||
* @param {string} clientId - 客户端标识符
|
||
* @param {string} errorMessage - 错误信息
|
||
*/
|
||
sendError(clientId, errorMessage) {
|
||
this.sendToClient(clientId, {
|
||
type: 'error',
|
||
message: errorMessage
|
||
});
|
||
}
|
||
|
||
/**
|
||
* 停止服务器
|
||
*/
|
||
stop() {
|
||
if (this.server) {
|
||
this.server.close();
|
||
console.log('Lock server stopped');
|
||
}
|
||
}
|
||
}
|
||
|
||
// // 启动服务器
|
||
// const pipePath = process.platform === 'win32'
|
||
// ? '\\\\.\\pipe\\rwlock-server'
|
||
// : '/tmp/rwlock.sock';
|
||
|
||
// const server = new NamedPipeLockServer(pipePath);
|
||
// server.start();
|
||
|
||
// // 优雅关闭
|
||
// process.on('SIGINT', () => {
|
||
// console.log('Shutting down lock server...');
|
||
// server.stop();
|
||
// process.exit(0);
|
||
// });
|
||
|
||
// process.on('SIGTERM', () => {
|
||
// console.log('Shutting down lock server...');
|
||
// server.stop();
|
||
// process.exit(0);
|
||
// });
|
||
|
||
module.exports = NamedPipeLockServer; |