由于项目需要lpd服务,但是windows的lpd服务开启比较麻烦所以决定自己开发lpd服务,浏览了各种网站和资源,各种总结,终于算简单完成了lpd服务的开发。确实感觉lpd/lpr这块的开发资源太少了,有必要分享分享。
515端口的lpd/lpr打印服务主要要解决的问题就是socket接收数据解析数据问题,以前在这里徘徊了好久。后来慢慢琢磨,终于琢磨了一些路子出来。当然肯定不敢和window本身自带的lpd服务稳定行比较。不过经过一两天的测试,基本没啥问题。
下面是各种代码,包括将nodejs安装到window服务。
LPD.js
const net = require('net');
let HOST = '0.0.0.0';
let PORT = 515;
let CACHE_SIZE = 2_097_152; //文件缓存大小 2MB
const fs = require('fs');
// const _dir = process.execPath;
const _dir = process.cwd();
let socketPool = [];
let server = net.createServer(function (sock) {
sock.on('data', async function (data) {
// 回发该数据,客户端将收到来自服务端的数据
try {
let socketObj = socketPool.find(i => i.ip === sock.remoteAddress);
if (socketObj) {
if (socketObj.step === '0') {
//接收到打印列队名 在此处可以限制lpr打印的队列名
sock.write('\x00\n');
socketObj.step = '1';
} else if (socketObj.step === '1') {
sock.write('\x00\n');
socketObj.step = '2';
} else if (socketObj.step === '2') {
sock.write('\x00\n');
socketObj.step = '3';
} else if (socketObj.step === '3') {
//控制文件 解析拿到打印内容的总长度
let dcount = [];
for (let i = 0; i < data.length; i++) {
let item = data[i];
if (0 !== i) {
if (item.toString(16) === '20') break;
dcount.push(item);
}
}
socketObj.dataLen = Buffer.from(dcount).toString();
sock.write('\x00\n');
socketObj.step = '4';
} else if (socketObj.step === '4') {
//判断接收数据是否完成(实时接收数据长度:控制文件解析的数据长度)
if (socketObj.receiveLen.toString() !== socketObj.dataLen) {
socketObj.receiveLen += data.length;
socketObj.cache = Buffer.concat([socketObj.cache, data]);
socketObj.cacheSize += data.length;
//判断缓存大小,若过大则先写入文件,防止内存溢出
/*if (socketObj.cacheSize >= CACHE_SIZE && !(socketObj.receiveLen >= Number(socketObj.dataLen))) {
//达到缓存设置大小
await writeFile(socketObj, false);
//重置数据缓存
socketObj.cacheSize = 0;
socketObj.cache = Buffer.alloc(0);
} else {*/
if (socketObj.receiveLen >= Number(socketObj.dataLen)) {
await writeFile(socketObj, true);
}
// }
sock.write('\x00\n');
} else {
await writeFile(socketObj, true);
sock.write('\x00\n');
socketObj.step = '5';
}
} else {
sock.end();
}
} else {
sock.end();
}
} catch (e) {
console.log('<ondata error>', e);
await writeLog(e);
}
});
//数据错误事件
sock.on('error', function (exception) {
console.log('服务端错误socket error:' + exception);
writeLog('服务端错误socket error:' + exception).then(() => {
});
sock.end();
});
// 为这个socket实例添加一个"close"事件处理函数
sock.on('close', function (data) {
socketPool = socketPool.filter(i => i.socket !== sock);
console.log('socketPool Length:' + socketPool.length);
});
}).on('connection', (sock) => {
console.log('新连接 >>>> :' + sock.remoteAddress);
writeLog('新连接 >>>> :' + sock.remoteAddress).then(() => {
});
//新连接 往连接池添加用于后续接收数据使用
socketPool.push({
ip: sock.remoteAddress,
socket: sock,
step: '0',
dataLen: '0',
receiveLen: 0,
//客户端发送的数据不是一次发送完毕,每次接收3000字节不等,接受一次添加cache里面,最后一并写入文件(用于监控文件创建需要一次写入);当然也可以接收一点写入一点
cache: Buffer.alloc(0),
cacheSize: 0,
isWriteEnd: false,
filename: new Date().getTime().toString()
});
});
/**
* 写入pcl文件内容
* @param socketObj 缓存数据对象
* @param isWriteEnd 是否结束本次文件对象写入
* @returns {Promise<void>}
*/
async function writeFile(socketObj, isWriteEnd) {
if (!socketObj.isWriteEnd) {
try {
console.log('写入文件,结束:' + isWriteEnd);
let path = _dir + `\\Temp\\${socketObj.filename}.pcl`;
await fs.writeFileSync(path, socketObj.cache, {flag: 'a+'});
socketObj.isWriteEnd = isWriteEnd;
} catch (e) {
await writeLog(`写入文件失败。isWriteEnd:${isWriteEnd}. ` + e);
}
}
}
/**
* 判断端口是否被占用
* @param port
* @returns {Promise<unknown>}
*/
function portUsed(port) {
return new Promise((resolve, reject) => {
try {
let server = net.createServer().listen(port);
server.on('listening', function () {
server.close();
resolve();
});
server.on('error', function (err) {
if (err.code === 'EADDRINUSE') {
reject(err);
}
});
} catch (e) {
reject(e);
}
});
}
/**
* 读取配置文件
* @returns {Promise<unknown>}
*/
function getConfig() {
return new Promise(async (resolve, reject) => {
try {
if (await fs.existsSync(_dir + '\\LPDConfig.json')) {
let config = await fs.readFileSync(_dir + '\\LPDConfig.json');
if (config) {
config = JSON.parse(config);
HOST = config.HOST;
CACHE_SIZE = config.CACHE_SIZE;
if (CACHE_SIZE == null || isNaN(CACHE_SIZE) || CACHE_SIZE < 0) CACHE_SIZE = 200 * 1024;
resolve();
} else reject(`未找到配置文件{${_dir}\\config.json}`);
} else reject(`未找到配置文件{${_dir}\\config.json}`);
} catch (e) {
reject(`读取配置文件{${_dir}\\config.json}错误`);
}
})
}
/**
* 写入日志文件
* @param data
* @returns {Promise<void>}
*/
async function writeLog(data) {
try {
let now = new Date();
data = `[${now.getFullYear()}-${now.getMonth() + 1}-${now.getDate()} ${now.getHours()}:${now.getMinutes()}:${now.getSeconds()}] ` + data + '\r\n';
await fs.writeFileSync(_dir + `\\Log\\lpdServer_${now.getFullYear()}-${now.getMonth() + 1}-${now.getDate()}_log.log`, data, {flag: 'a+'});
} catch (e) {
}
}
//读取配置文件,检测端口冲突,启动服务
try {
getConfig()
.then(v => {
portUsed(PORT)
.then(v => {
//真正启动服务监听
server.listen(PORT, HOST);
console.log('Server listening on ' + HOST + ':' + PORT);
writeLog('Server listening on ' + HOST + ':' + PORT)
.then(() => {
})
})
.catch(e => {
console.log('端口{515}被占用!');
writeLog('端口{515}被占用!')
.then(() => {
process.exit(1);
});
})
}).catch(e => {
console.log(e);
writeLog(e).then(() => {
setTimeout(() => {
process.exit(1);
}, 2000);
})
})
} catch (e) {
writeLog(e)
.then(() => {
setTimeout(() => {
process.exit(1);
}, 2000);
})
}
LPDConfig.json 其实这里本身用不着配置文件。
{
"HOST": "0.0.0.0",
"CACHE_SIZE": 2097152
}
install.js 安装到window服务,需要用到node-windows库。
let Service = require('node-windows').Service;
try {
let svc = new Service({
id: 'LPDServer',
name: 'LPDServer', //服务名称
description: 'LPD服务', //描述
script: process.cwd() + '\\LPD.js', //nodejs项目要启动的文件路径
workingDirectory: process.cwd(),
execPath: process.cwd() + '\\node.exe'
});
svc.on('install', (e) => {
svc.start();
});
svc.install();
} catch (e) {
console.log(e);
}
uninstall.js 卸载window服务。
let Service = require('node-windows').Service;
try {
let svc = new Service({
id: 'LPDServer',
name: 'LPDServer', //服务名称
description: 'LPD服务', //描述
script: process.cwd() + '\\LPD.js', //nodejs项目要启动的文件路径
workingDirectory: process.cwd(),
execPath: process.cwd() + '\\node.exe'
});
svc.on('uninstall', function () {
console.log('Uninstall complete.');
console.log('The service exists: ', svc.exists);
});
svc.uninstall();
} catch (e) {
console.log(e);
}
当然肯定不完善,但是能跑就行了。