7
7

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

C#とNode.jsでIPC通信してみた

Posted at

目的

デスクトップアプリを作る上で、Node.jsからC#コードの結果を取得することが目的です。
色々調べると、Edge.jsというライブラリが見つかり、使ってみました。
もちろんインラインコードなど書きたくなかったので、DLLを読み込む方式にしたのですが、私のやり方が悪いのかVisual Studioでのコードデバッグができず、途方に暮れていました。
また、Edge.jsの場合、C#コンパイラーがすでにnpmパッケージにBundleされているようで、.NET4.5に依存しているようでした。
Node.js -> C#の呼び出し部分のオーバーヘッドが少ないようですが、私の目的としては、C#で作ったデータをもらって、Node.js(実際は、ElectronCarlo)で扱う程度のものだったので、C#で作った関数を、秒間何千何万と呼び出したいわけではなかったのです。
そこでIPC通信を使うことにしました。

実装

まず、Clientサイド(Node.js)ですが、node-ipcを使わせて頂きました。
node-ipcのIPCソケットには、いくつかタイプがあります。

  1. Unix Socket or Windows Socket
  2. TCP Socket
  3. TLS Socket
  4. UDP Sockets

結論からいうと、1.を採用しました。
なぜかというと、ネットワーク上の別のPCのプロセスと通信したいわけでもなく、localhost上のポートを使ってまで動かしたくはなかった為です。
また、C#から送られてくるデータは欠損が許されない為、UDPは使えないだろうと。
その中で、最も高速と思われる1.を採用した次第です。
node-ipcが提供しているExampleがあります。
その中から、基本的なクライアントコードを参考にさせていただきました。

const ipc = require("../../../node-ipc");

ipc.config.id = "hello";
ipc.config.retry = 1000;

ipc.connectTo("world", function() {
  ipc.of.world.on("connect", function() {
    ipc.log("## connected to world ##", ipc.config.delay);
    ipc.of.world.emit("app.message", "ping");
  });
  ipc.of.world.on("disconnect", function() {
    ipc.log("disconnected from world");
  });
  ipc.of.world.on("app.message", function(data) {
    ipc.log("got a message from world : ", data);
  });

  console.log(ipc.of.world.destroy);
});

サーバーに対して、"ping"を送信しています。
この部分を見ると書いてありますが、windowsの場合は、NamedPipeのpipe名に変換されます。
上記コードの場合、connectToの第2引数に文字列で指定した場合のPathパラメータが省略されています。
そのため、Unix Domain Socketのパスだと、/tmp/app.worldになり、
名前付きパイプの場合、\\.\pipe\tmp-app.worldに変換されます。
C#でNamedPipeServerStreamを生成する際は、tmp-app.worldとなります。

それを踏まえ、サーバーサイドのコードを書きます。
C#側のコードはこんな感じにしてみました。
※ ちなみに今回は、サーバーもクライアントも同一PCである為、名前付きパイプに伴うimpersonationのようなセキュリティ対策は考慮していません。

using System;
using System.IO;
using System.IO.Pipes;
using System.Text;

namespace nodeIpc
{
    public class Entry
    {
        static void Main(string[] args)
        {
            // サーバーの作成
            using (var server = new NamedPipeServerStream("tmp-app.world", PipeDirection.InOut))
            {
                Console.WriteLine("waiting for client connection...");
                // クライアント接続待ち
                server.WaitForConnection();

                Console.WriteLine("connected!");

                // 受信
                char c;
                string message = string.Empty;

                while ((c = (char)server.ReadByte()) != '\f')
                {
                    message += c;
                }

                // typeとdataが設定されたJSON文字列が送信されてくる
                Console.WriteLine(message);

                var writer = new StreamWriter(server);

                // 返信
                var data = "pong";
                // node-ipcが解釈できる形のJSON文字列 + \fを返す。
                writer.Write("{ \"type\": \"app.message\", \"data\": \"" + data + "\" }" + "\f");
                writer.Flush();
                // クライアント側が送信メッセージを読み終わるまで待つ
                server.WaitForPipeDrain();
            }
        }
    }
}

まずは、サーバーを作成し、接続時に送られてくる"ping"という文字列を取得します。
node-ipcのDefaultのメッセージ終端である\fが来るまでループし、メッセージを取得します。
メッセージは、JSONになっているので、適宜Json.NETを用いて、パースしましょう。
対して、"pong"メッセージを送り返す際は、下記の構造のデータをJSON文字列にシリアライズし送信する必要があります。
typeは、node-ipcにおける、メッセージIDに当たります。
dataは、もちろんオブジェクトでもよく、JSONであれば何でもいいです。

{
  "type": "app.message",
  "data": "pong"
}

稚拙なMyサンプルゆえに、サーバーが1回のメッセージ送受信しかしてくれない為、接続後リトライエラーが多発しますが、通信は可能です。
是非お試しを。

7
7
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
7
7

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?