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

# 目的

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

## 実装

まず、Clientサイド（Node.js）ですが、[node-ipc](https://www.npmjs.com/package/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](https://github.com/RIAEvangelist/node-ipc/tree/master/example/unixWindowsSocket/)があります。
その中から、[基本的なクライアントコード](https://github.com/RIAEvangelist/node-ipc/blob/master/example/unixWindowsSocket/basic/hello-client.js)を参考にさせていただきました。

```javascript
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"を送信しています。
[この部分](https://github.com/RIAEvangelist/node-ipc/blob/master/dao/client.js#L84)を見ると書いてありますが、windowsの場合は、NamedPipeのpipe名に変換されます。
上記コードの場合、connectToの第2引数に文字列で指定した場合の`Path`パラメータが省略されています。
そのため、Unix Domain Socketのパスだと、`/tmp/app.world`になり、
名前付きパイプの場合、`\\.\pipe\tmp-app.world`に変換されます。
C#で`NamedPipeServerStream`を生成する際は、`tmp-app.world`となります。

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

```C#
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](https://www.newtonsoft.com/json)を用いて、パースしましょう。
対して、`"pong"`メッセージを送り返す際は、下記の構造のデータをJSON文字列にシリアライズし送信する必要があります。
typeは、node-ipcにおける、メッセージIDに当たります。
dataは、もちろんオブジェクトでもよく、JSONであれば何でもいいです。

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

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