Nodejs предоставляет довольно скромные возможности IPC в принципе, а для windows уж и подавно. Конечно, можно для коммуникации в пределах одной машины делать клиент-сервера на сокетах, если это windows. Так же можно юзать UNIX Sockets для линукса и прочих производных, которые это дело поддерживают. Так же известны костыли с mkfifo, но это *nix и потому нам не интересны. В официальной документации (на данный момент для версии v0.10.26) ничего про IPC винде не сказано в принципе. Можно юзать модуль CLUSTER, но мне это решение показалось топорным, так как идеальным для меня было бы некая IPC, от которой мог бы абстрагироваться класс Socket.
/* * Initialize a pipe. The last argument is a boolean to indicate if * this pipe will be used for handle passing between processes. */ UV_EXTERN int uv_pipe_init(uv_loop_t*, uv_pipe_t* handle, int ipc); /* * Opens an existing file descriptor or HANDLE as a pipe. */ UV_EXTERN int uv_pipe_open(uv_pipe_t*, uv_file file); /* * Bind the pipe to a file path (UNIX) or a name (Windows.) * * Paths on UNIX get truncated to `sizeof(sockaddr_un.sun_path)` bytes, * typically between 92 and 108 bytes. */ UV_EXTERN int uv_pipe_bind(uv_pipe_t* handle, const char* name);
открываем uv_pipe_open (src/win/pipe.c), видим классическую картину :
/* Creates a pipe server. */ int uv_pipe_bind(uv_pipe_t* handle, const char* name) { uv_loop_t* loop = handle->loop; int i, err, nameSize; uv_pipe_accept_t* req; // ---------8<-------------------- handle->accept_reqs[0].pipeHandle = CreateNamedPipeW(handle->name, PIPE_ACCESS_DUPLEX | FILE_FLAG_OVERLAPPED | FILE_FLAG_FIRST_PIPE_INSTANCE, PIPE_TYPE_BYTE | PIPE_READMODE_BYTE | PIPE_WAIT, PIPE_UNLIMITED_INSTANCES, 65536, 65536, 0, NULL); // ---------8<-------------------- return uv_translate_sys_error(err); }
То что надо. Все уже написано до нас. Осталось найти как это использовать.
В ноде этот код вызывается из pipe_wrap.cc :
Handle<Value> PipeWrap::Open(const Arguments& args) { HandleScope scope; UNWRAP(PipeWrap) int fd = args[0]->IntegerValue(); uv_pipe_open(&wrap->handle_, fd); return scope.Close(v8::Null()); }
Сам же PipeWrap проврапплен в жс под видом нативного модуля pipe_wrap и может быть найден простым вызовом require(‘pipe_wrap’) :
NODE_MODULE(node_pipe_wrap, node::PipeWrap::Initialize)
Следующий шаг — поиск pipe_wrap по модулям в папке node/lib/ Модуль используется по своему прямому назначению, в net.js для создания пайпа
// constructor for lazy loading function createPipe() { var Pipe = process.binding('pipe_wrap').Pipe; return new Pipe(); } // constructor for lazy loading function createTCP() { var TCP = process.binding('tcp_wrap').TCP; return new TCP(); }
и (ура!) он же используется в классе Socket, который так хотелось использовать в IPC :
Socket.prototype.connect = function(options, cb) { if (this.write !== Socket.prototype.write) this.write = Socket.prototype.write; if (typeof options !== 'object') { // Old API: // connect(port, [host], [cb]) // connect(path, [cb]); var args = normalizeConnectArgs(arguments); return Socket.prototype.connect.apply(this, args); } if (this.destroyed) { this._readableState.reading = false; this._readableState.ended = false; this._writableState.ended = false; this._writableState.ending = false; this._writableState.finished = false; this.destroyed = false; this._handle = null; } var self = this; var pipe = !!options.path; if (!this._handle) { this._handle = pipe ? createPipe() : createTCP(); initSocketHandle(this); }
Но это для коннекта. Что касаемо серверной части кода, то тут не обойтись без костылей. Дело в том, что просто так не заставить ноду слушать именованный пайп, так как,
1) для начала, имя пайпа не очевидно. Оно по всем канонам windows обязано выглядеть как .pipepipe_name
2) Сервер (net.XXX) создает экземпляр пайпа только если в качестве порта и типа адреса (addressType) передано -1, о чем, опять таки можн узнать из кода :
var createServerHandle = exports._createServerHandle = function(address, port, addressType, fd) { var r = 0; // assign handle in listen, and clean up if bind or listen fails var handle; if (typeof fd === 'number' && fd >= 0) { try { handle = createHandle(fd); } catch (e) { // Not a fd we can listen on. This will trigger an error. debug('listen invalid fd=' + fd + ': ' + e.message); process._errno = 'EINVAL'; // hack, callers expect that errno is set return null; } handle.open(fd); handle.readable = true; handle.writable = true; return handle; } else if (port == -1 && addressType == -1) { //!!! <------------------------------ handle = createPipe(); if (process.platform === 'win32') { var instances = parseInt(process.env.NODE_PENDING_PIPE_INSTANCES); if (!isNaN(instances)) { handle.setPendingInstances(instances); } } } else { handle = createTCP(); } if (address || port) { debug('bind to ' + address); if (addressType == 6) { r = handle.bind6(address, port); } else { r = handle.bind(address, port); } } if (r) { handle.close(); handle = null; } return handle; };
Пришло время экспериментов и итогов. Так как мы можем создать пайп и юзать его в Socket, то нам доступна вся сетевая подсистема ноды. То есть на том конце пайпа может быть, например, веб-сервер или просто сервер, ожидающий команд. Вот пример использования в качестве сервера:
var net = require('net'); var util = require('util'); var http = require('http'); var http = require('http'); http.createServer(function (req, res) { res.writeHead(200, { 'Content-Type': 'text/plain' }); res.end('Hello World'); }).listen('\.pipeteeest2', -1 /*!!!!!!*/, -1 /*!!!!!!*/); console.log('Server running at ', '\.pipeteeest2');
И по все тойже причины (Socket), нам доступны и клиенты для такого локального хттп сервера. Для хттп нам заботливо предоставили Agent API, позволяя хукать открытие соединения и направлять сокет куда угодно:
var a = new http.Agent(); options = { host: 'www.localhost.com', port: 80, path: '/index.html', method: 'GET' }; a.createConnection = function () { return ((new net.Socket()).connect('\.pipeteeest2')); }; var req = http.request(options, function (res) { console.log('STATUS: ' + res.statusCode); console.log('HEADERS: ' + JSON.stringify(res.headers)); res.setEncoding('utf8'); res.on('data', function (chunk) { console.log('BODY: ' + chunk); }); }); req.on('error', function (e) { console.log("Got error " + e.message); }); req.end();
На этом все. тему виндозного IPC считаю раскрытой.
1 comments On NodeJS : Недокументированные IPC в nodejs для windows
a — чет почти не используется, наверно вместо «http.request» хотели написать «a.request»…