ブログ

割とコンピュータよりの情報をお届けします。

2018/07

Node.jsでtcpサーバとクライアント作成の試行

前の記事でSocket.ioばかり使用していたが,とりあえずsocketでもよいのではないかと思って調べていたら,参考になりそうな記事があった。

参考をもとに,ほとんど構造を変えないように注意しながらサーバ側とクライアント側を作成させてもらった。

サーバ側 こっちは作りやすい。

var net = require('net');
var port = 3000;
var server = net.createServer(function (connection) {
  console.log('server: created');
  connection.on('data', function(data){
    connection.write('server: Repeating: ' + data);
  });
  connection.on('close', function(){
    console.log('server: client closed connection');
  });
  connection.on('error', function(){
    console.log('server: client made error');
  });
}).listen(port);
console.log('listening on port 3000');

クライアント側はもしNode.jsなら以下のようになるが,本来socket.connect()にはcallbackを渡すことができる。そこでwrite()を入れていたが,このプログラムはいったん接続が切れたら再接続を図ろうとしている。ところが,connect()に渡したcallbackは次に接続ができるようになった時に1回実行されるので,ずーっと接続ができない状態であるとき接続が始まると,一気に大量のパケットが流れ出す。非常に問題の多いコードになっているので一応コメントアウトしている。

最も目的はPythonなどとの接続であろうからこのクライアント側コードは使わなくてもよい。

var net = require('net');

var port = 3000;
var client = new net.Socket();

client.setEncoding('utf8');

var connect = function () {
  client.connect(port, 'localhost'/*, function() {
    console.log('client: connected to server');
    //client.write('connect');
  }*/);
}

connect();

process.stdin.resume();
process.stdin.on('data', function (data) {
  client.write(data);
});

client.on('data', function (data) {
  console.log('client: ' + data);
});

client.on('close', function () {
  console.log('client: connection is closed');
  setTimeout(connect, 6000);
});

client.on('error', function () {
  console.log('client: made error');
});

もっともこのコードでも,connect()の対応は不完全で接続が確立できないまま何度もconnect()の実行を繰り返すと次第にメモリ使用量が増えていく。

クライアント側でNode.jsで再接続を実装する場合には,node-net-reconnectなどの使用を検討された方がよいかもしれない。

≫ Read More

2018/07/29 コンピュータ   TakeMe

vue-cliでインストールしたwebpackでnpm run buildで用意したコンテンツをCefSharpで使用

vue-cliでインストールしたwebpackでnpm run buildで用意したコンテンツをCefSharpで使用するときの注意点。

まずCefSharpの方だが,最近CefSharpのバージョンが65に上がったが概ねここに書いた記事のまま使用できる(というかまだパッケージ自体はAny CPU対応を謳っているが,これをインストールしただけでAny CPU対応の記述をプロジェクトファイルに追記していないと使えもしない状態が続いている)。

public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();

            var browser = new CefSharp.Wpf.ChromiumWebBrowser();
            // アドレスの取得
            String page = string.Format(@"{0}\dist\index.html", StartupPath);
            BrowserSettings browserSettings = new BrowserSettings();
            browserSettings.FileAccessFromFileUrls = CefState.Enabled;
            browserSettings.UniversalAccessFromFileUrls = CefState.Enabled;
            browser.BrowserSettings = browserSettings;
            // アドレス設定
            browser.BrowserSettings.AcceptLanguageList = "ja-JP";
            browser.Address = page;
            Content = browser;
        }

        // 実行ファイルのディレクトリ取得
        public string StartupPath
        {
            get
            {
                return System.IO.Path.GetDirectoryName(System.IO.Path.GetFullPath(Environment.GetCommandLineArgs()[0]));
            }
        }
    }

Windows Formsが必要ならここに書いた(差分が必要)。

そして,vue-cliでコンテンツを用意する方法だが,コマンドプロンプトでnpm run devを実行するとdistフォルダにコンテンツが生成されるが,ここにあるようにconfig/index.jsのbuildのオブジェクトのassetsPublicPathを'./'に変更する必要がある。

≫ Read More

2018/07/29 コンピュータ   TakeMe

Pythonでsocket.io 2.0のクライアントはsocketIO-client-nexusに限定されているかもしれない

Python用のsocket.ioのクライアントは結構更新が遅くなっていて,今ではsocket.io 2.xに対応していないものばかりになってしまった。

この前の記事にあるsocket.ioの例につなげるクライアントだけを書いてみた。

まず,socketIO-client-nexusにならsocket.io 2.xの対応が盛り込まれている。(リンク先にもあるが,それ以外のsocket.ioのクライアントライブラリはsocket.io 1.xにしか対応してないことが多いよう。)

ただし,すべてが実装されているかどうかはわからない。

とりあえず,pip install socketIO-client-nexusを実行してから,つくってみた例だが,以下のようになる。

from socketIO_client_nexus import SocketIO, LoggingNamespace

def on_connect():
    print('connect')

def on_disconnect():
    print('disconnect')

def on_reconnect():
    print('reconnect')

def on_date_response(*args):
    print('on_date', args)

socketIO = SocketIO('localhost', 8081, LoggingNamespace)
socketIO.on('connect', on_connect)
socketIO.on('disconnect', on_disconnect)
socketIO.on('reconnect', on_reconnect)

# Listen
socketIO.on('date', on_date_response)
socketIO.emit('test', {'value': 'test3'})
socketIO.wait(seconds=10)

≫ Read More

2018/07/28 コンピュータ   TakeMe

vue-cliでインストールしたwebpackなどでnpm run buildでできたファイルイをfile://で使う

npm run build
を実行した後,Opening index.html over file:// won't work
と表示が出ていたのでできないのかと思っていたが,

これは,config/index.jsのbuildのオブジェクトのassetsPublicPathを'./'に変更すると回避できる
つまり,distのindex.htmlがドキュメントルートに来なくなても,file://であっても使用できるようになる。

最近のChromeはfile://〇〇であってもhttpsを使用していない旨の警告「このサイトへの接続は保護されていません」と表示される。

ちなみにwepack-dev-serverを使用する場合の設定はconfig/index.jsの上の方に devというオブジェクトに記述する。

ポート番号は標準は8080だが,変更できる。

 

ところで標準ではwebpack-dev-serverにネットワーク内の他のクライアントからアクセスできない。おそらくhost: 'localhost'が原因なのではと思っているが,うまく回避できていない。

≫ Read More

2018/07/26 コンピュータ   TakeMe

vue-cliをnpm install -D vue-cliでインストールに失敗

vue-cliのインストールの説明ではnpm install -g vue-cliでインストールと書かれているが,npm install -D vue-cliでできないのかを確認していてハマった。

先にうまくいった手順の例を説明すると(node-v10.2.1-win-x64)

npm init -y
npm install -D vue-cli
npm install -D vue
npm install -D webpack
npm install -D webpack-dev-server
npx vue init webpack my-project
cd my-project
npm install
npm run dev

たぶん npm install -D vue,npm install -D webpack,npm install -D webpack-dev-serverなどは関係ないと思われるが念のためここで実行しておいた。

npm init -y

の後にpackage.jsonの内容をいじってからnpm install -D vue-cliを実行しているとnpx vue init webpack my-projectで警告がでてnpm installで失敗してしまっていた。(3週間考えた挙句棚上げにしていた。)

ただし,うまくいった理由はpackage.jsonをいじらずに次の操作に行ったからか?
vueやwebpackを明示的にインストールしないといけないのかネットワークの都合なのかは定かではない。

インストール自体は数十分間で結構時間がかかる操作だった。(回線速度による)

≫ Read More

2018/07/26 コンピュータ   TakeMe

MongoDBの周辺スクリプトの作成の続き

MongoDB(特に4.0.0のWindows用)の設定関係のファイルを引き続き作っていた。

サンプル作成が異常に面倒である。
今回もファイルサイズが大きすぎて本文に埋め込めなくなってしまった。

環境変数の読み込み用ファイルenv.bat
(cmdのショートカットのリンク先にC:\Windows\System32\cmd.exe /k "C:\mongodb-4.0.0\env.bat"などと設定して使うのも)

@echo off
set "PATH=%~dp0;%PATH%"
SET PATH=%PATH%;C:\mongodb-4.0.0\bin

setlocal enabledelayedexpansion
pushd "%~dp0"

popd
endlocal

 

MongoDBをスタートするスクリプト mongodb_start.cmd

mongod --config "C:\mongodb-4.0.0\mongod.cfg"

 

MongoDBをWindowsサービスにするスクリプト mongodb_install_service.cmd

set service_name=MONGODB_TAKEME_APP
mongod --install --config "C:\mongodb-4.0.0\mongod.cfg" --serviceName %service_name% --serviceDisplayName "MONGODB FOR TAKEME_APP" --serviceDescription "MongoDB 4.0.0 for TakeMe Applications."
net start %service_name%

 

MongoDBをWindowsサービスから削除する mongodb_remove_service.cmd

set service_name=MONGODB_TAKEME_APP
net stop %service_name%
mongod --remove --serviceName "MONGODB_TAKEME_APP"

 

MongoDBの設定ファイル mongod.cfg
ユーザ入れかえ mongodb_change_username.cmd
(username.jsに新しいユーザ名usernameOld.jsに古いユーザ名を入れて実行)

set service_name=MONGODB_TAKEME_APP
mongo localhost:27017 Init0.js
net stop %service_name%
net start %service_name%
mongo localhost:27017 Init1.js

管理ユーザで実行しないと使えないけど参考のファイルなど添付しておきます(Search consoleで警告が出たので中止)。

 

特にユーザ交換スクリプトは以下のようにInit0.jsを実行する。

conn = new Mongo("localhost:27017");
db  = conn.getDB("admin");
load('usernameOld.js');

db.auth(username, password);

var lstUsers = db.getUsers();
var users = [];
for (var i = 0; i < lstUsers.length; i++) {
  users.push(lstUsers[i].user);
}

db = conn.getDB('TAKEME_APP');
db.auth(username, password);
for (var i = 0; i <users.length; i++) {
  db.dropUser(users[i]);
}

db = conn.getDB('admin');
db.auth(username, password);
for (var i = 0; i <users.length; i++) {
  db.dropUser(users[i]);
}

 

設定を行う本体スクリプトInit1.js

conn = new Mongo("localhost:27017");
db  = conn.getDB("admin");
load('username.js');

db.createUser(
  {
    user: username,
    pwd: password,
    roles: [
      {
        "role": "root",
        "db" : "admin"
      },
      {
        "role": "userAdmin",
        "db" : "admin"
      },
      {
        "role": "hostManager",
        "db" : "admin"
      }
    ]
  }
);

db.auth(username, password);

var lstUsers = db.getUsers();
var users = [];
for (var i = 0; i < lstUsers.length; i++) {
  if (lstUsers[i].user !== username)
    users.push(lstUsers[i].user);
}

for (var i = 0; i <users.length; i++) {
  db.dropUser(users[i]);
}


db = conn.getDB('TAKEME_APP');
for (var i = 0; i < users.length; i++) {
  db.dropUser(users[i]);
}

db.createUser(
  {
    user: username,
    pwd: password,
    roles: [
      {
        "role": "readWrite",
        "db" : "TAKEME_APP"
      }
    ]
  }
);


注意はmongo shellでJavaScriptを実行する場合,conn = new Mongo("localhost:27017"); db = conn.getDB("admin");などを使ってdbを指定してこないといけない。use admin;などでdbが指定できるコンソールと若干異なる。

≫ Read More

2018/07/22 コンピュータ   TakeMe

MongoDBの設定ファイルを作らされた

先週からMongoDBの設定ファイルを作らされていた。enableLocalhostAuthBypassってローカルなら認証いらないのでは?と思っていたら違うらしい。

作ったファイルの例(Windows用に直し)は以下のようなもの。

systemLog:
    destination: file
    path: "C:/mongodb/logs/log.txt"
net:
    bindIp: 127.0.0.1
    port: 27017
storage:
    dbPath: "C:/mongodb/data"
    journal:
        enabled: true
        commitIntervalMs: 500
    engine: wiredTiger
    wiredTiger:
      engineConfig:
         cacheSizeGB: 1.0
         journalCompressor: zlib
      collectionConfig:
         blockCompressor: zlib    
setParameter:
    enableLocalhostAuthBypass: true
#    enableLocalhostAuthBypass: false
security:
    authorization: enabled
#    authorization: disabled
   

設定の仕方何度も確認したが,enableLocalhostAuthBypass: trueをいれてもコマンド実行になると認証が省けない。
読んでみるとadminデータベースの最初のユーザが作成されるまでの限定的操作では有効だがその後は関係ないらしい…

基本的には,一度目のcreateUserが完了するまで一部コマンドのみ有効である(getUsersなどは使えない)。普通はadminデータベースにユーザを追加する。
ところが,いったんdropUserでこのユーザを削除してしまうとmongodbを再起動するまではこの設定は無効である。

また,createUserで複数のユーザを作成している場合(adminデータベース以外のデータベースに対するユーザもカウントされるようである)には,一つだけdropUserで削除したところでまだユーザが残っているのでmongodbを再起動してもこの設定は無効である。

≫ Read More

2018/07/22 コンピュータ   TakeMe

普通のアプリをWindowsサービス化するnssmで32bitと64bit混ぜこぜ

普通のアプリをWindowsサービス化するnssmは便利なツールだが,32bit版になっていても実行するファイルが64bit版であっても構わないみたい。

nssmは64bitビルドでなければ64 bitアプリを動かせないのかと思っていたが,問題ないようだ。
Wix Toolsetでインストーラを作っていた時に気付いた。

例えば32 bitビルドのnssmでnodeが64 bit版の場合には,タスクマネージャーで確認するとnssmだけ「(32 ビット)」という表記がついているがnode.jsにはその表記がないので64 bitで動いているようだ。

Wix Toolsetでインストールする先のフォルダProgram FilesにするかProgram Files(x86)の違いには注意する必要があるようだが,単体の時は混ぜこぜが可能だ。

Wix Toolsetの場合にはインストール先ディレクトリのIDを標準のProgramFilesFolderにするか,意図的にProgramFiles64Folderにするかの差である。nssmファイルの選択を気を付けないといけない。

≫ Read More

2018/07/15 コンピュータ   TakeMe

ntp-clientとsocket.ioでサービス作成を試してWix Toolsetでインストールしてみた

ntp-clientとsocket.ioでサービス作成を試してみる」で作成したファイルをnssmでWindowsサービスとして動かすアプリをWix Toolsetでインストールファイルを作成してみた。

nssmをダウンロードしてくることが最初。
本当はnode.jsのインストーラも同梱した方がよいような気がするが,ここでは前の記事でインストールしたようにnode.exeはzipで展開してcmdファイルに
[node.exeパス] serv.js
を記述したserve.cmdファイルを用意て同梱した。

wxsファイルの例は添付します。

<?xml version="1.0" encoding="utf-8"?>
<!−−
    # This comment is generated by WixEdit, the specific commandline
    # arguments for the WiX Toolset are stored here.

    candleArgs: 
    lightArgs: "<projectname>.wixobj" -out "<projectname>.msi" <extensions> -cultures:ja-JP
−−>
<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi">
    <Product Id="E5C926F4-5511-480D-BDCE-0222B5C6A1A1" Name="SocketIO_TEST 作品" Language="1041" Version="0.0.0.1" Manufacturer="Take3's Web" UpgradeCode="F8DE7934-6B08-42E4-A0A0-2086A5612F23" Codepage="932">
        <Package Description="Test file in a Product" Comments="Simple test" InstallerVersion="200" Compressed="yes" />
        <Media Id="1" Cabinet="simple.cab" EmbedCab="yes" />
        <Directory Id="TARGETDIR" Name="SourceDir">
            <Directory Id="ProgramFilesFolder" Name="PFiles">
                <Directory Name="SocketIO_TEST" Id="SOCKETIO_TEST">
                    <Directory Id="NODE_MODULES" Name="node_modules">
... 容量制限のため省略 ...
                    <Component Id="Serv.js">
                        <File Id="SERV.JS" Name="serv.js" Source=".\serv.js" />
                    </Component>
                    <Component Id="GetNtp.js">
                        <File Id="GETNTP.JS" Name="GetNtp.js" Source=".\GetNtp.js" />
                    </Component>
                    <Component Id="SocketIOHandler.js">
                        <File Id="SOCKETIOHANDLER.JS" Name="SocketIOHandler.js" Source=".\SocketIOHandler.js" />
                    </Component>
                    <Component Id="nssm.exe">
                        <File Id="NSSM.EXE" Name="nssm.exe" Source=".\nssm.exe" />
                        <RegistryKey Key="SYSTEM\CurrentControlSet\Services\SocketIO_TEST" Root="HKLM" ForceCreateOnInstall="yes" ForceDeleteOnUninstall="yes">
                            <RegistryValue Value="16" Type="integer" Name="Type" />
                            <RegistryValue Name="Start" Type="integer" Value="2" />
                            <RegistryValue Name="ErrorControl" Type="integer" Value="1" />
                            <RegistryValue Name="WOW64" Type="integer" Value="332" />
                            <RegistryValue Name="ObjectName" Type="string" Value="LocalSystem" />
                            <RegistryValue Name="DelayedAutostart" Type="integer" Value="0" />
                            <RegistryValue Name="FailureActionsOnNonCrashFailures" Type="integer" Value="1" />
                            <RegistryKey Id="Parameters" Key="Parameters" ForceCreateOnInstall="yes" ForceDeleteOnUninstall="yes">
                                <RegistryValue Name="Application" Type="expandable" Value="[SOCKETIO_TEST]serve.cmd" />
                                <RegistryValue Name="AppParameters" Type="expandable" Value="" />
                                <RegistryValue Name="AppDirectory" Type="expandable" Value="[SOCKETIO_TEST]" />
                                <RegistryKey Id="AppExit" Key="AppExit" ForceCreateOnInstall="yes" ForceDeleteOnUninstall="yes">
                                    <RegistryValue Action="write" Type="string" Value="Restart" />
                                </RegistryKey>
                            </RegistryKey>
                        </RegistryKey>
                        <ServiceInstall Name="SocketIO_TEST" Type="ownProcess" Start="auto" ErrorControl="normal" Description="SocketIO_TEST作品" DisplayName="SocketIO_TEST" />
                        <ServiceControl Id="StartService" Name="SocketIO_TEST" Start="install" Stop="both" Remove="uninstall" Wait="yes" />
                    </Component>
                    <Component Id="serve.cmd">
                        <File Id="SERVE.CMD" Name="serve.cmd" Source="serve.cmd" />
                    </Component>
                    <Component Id="index.html">
                        <File Id="INDEX.HTML_2" Name="index.html" Source="index.html" />
                    </Component>
                    <Component Id="package.json">
                        <File Id="PACKAGE.JSON_40" Name="package.json" Source="package.json" />
                    </Component>
                    <Component Id="package_lock.json">
                        <File Id="PACKAGE_LOCK.JSON" Name="package-lock.json" Source="package-lock.json" />
                    </Component>
                </Directory>
            </Directory>
        </Directory>
        <Feature Id="DefaultFeature" Title="Main Feature" Level="1">
            <ComponentRef Id="NODE_NTP_CLIENT" />
            <ComponentRef Id="NODE_NTP_CLIENT.CMD" />
            <ComponentRef Id="HISTORY.MD" />
... 容量制限のため省略...

≫ Read More

2018/07/15 コンピュータ   TakeMe

ntp-clientとsocket.ioでサービス作成を試してみる

ntp-clientとsocket.ioを使ってみた。
学習を始めたJavaScriptのクラス使い方も試してみた。

以下に示す例はnode.jsでntp-clientを使用して自コンピュータ時刻(サーバ側)とntpサーバーの時刻の差を6分置きに取得する。
そして10秒置きに自コンピュータ時刻(サーバ側)を取得してオフセットを足して,socket.ioを通して送信する。
サンプルにしては長いものになっている。

まずアプリのスタートポイントの(本体)serv.js

const GetNtp = require('./GetNtp')

var getNtp = new GetNtp();
var ntpServer = 'ntp.nict.jp';

var task = new Promise(function(resolve, reject) {
  getNtp.getNetworkTime(ntpServer, 123, resolve, reject);
})

task.then(function(value) {
  // 6分置きにオフセットmsを更新
  setInterval(function () {
    getNtp.getNetworkTime(ntpServer, 123);
  }, 360000);
  
  // 10秒置きに時刻を送信 
  setInterval(function() {
    var value = new Date();
    value = new Date(value.valueOf() + getNtp.timeOffset);
    socketIOHandler.io.sockets.emit('date', {'date': value});
  }, 10000);
});

var SocketIOHandler = require('./SocketIOHandler');
var socketIOHandler = new SocketIOHandler(8081, getNtp);
socketIOHandler.start();

クラスの勉強中につきGetNtpというクラス風のオブジェクトを作成するGetNtp.js

var GetNtp = function() {
  this.ntpClient = require('ntp-client');
  this.last = null;
  this.timeOffset = 0;
}

GetNtp.prototype.getNetworkTime = function(uri, port, resolve, reject) {
  var before = new Date();
  this.ntpClient.getNetworkTime(uri, port, (err, date)=> {
    if(err) {
      console.error(err);
      if (typeof reject !== "undefined")
        reject();
      return;
    }
    var after = new Date();
    var computerDate = (before.valueOf() + after.valueOf()) / 2.0;
    this.timeOffset = date.valueOf() - computerDate.valueOf(); // コンピュータ時刻が進んでいたら負
    console.log('Time Offset Updated: ' + this.timeOffset.toFixed(1) + " ms");
    this.last = date;
    if (typeof resolve !== "undefined")
      resolve(date)
  });
}

module.exports = GetNtp

同様にクラス風オブジェクトを作成するSocketIOHandler.js

var SocketIOHandler = function(port, getNtp) {
  this.getNtp = getNtp;
  this.fs = require('fs');
  this.handler = function(req, res) {
    this.fs.readFile(__dirname + '/index.html',
    function (err, data) {
      if (err) {
        res.writeHead(500);
        return res.end('Error loading index.html');
      }
      res.writeHead(200);
      res.end(data);
    });
  }.bind(this);

  this.app = require('http').createServer(this.handler);
  this.io = require('socket.io')(this.app);
  
  this.start = () => {
    this.app.listen(port);
  }

  this.io.on('connection', (socket) => {
    socket.emit('connected', { hello: 'world' });
    {
      var value = new Date();
      value = new Date(value.valueOf() + this.getNtp.timeOffset);
      socket.emit('date', {'date': value});
    }
    socket.on('test', function (data) {
      console.log(data);
    });
  });
}

module.exports = SocketIOHandler;

クライアント側のコードindex.html。(httpでクライアントに読ませて使用する)

<html>
<head>
  <title>Sample App</title>
  <script
    src="https://code.jquery.com/jquery-3.3.1.min.js"
    integrity="sha256-FgpCb/KJQlLNfOu91ta32o/NMZxltwRo8QtmkMRdAu8="
    crossorigin="anonymous"></script>
</head>
<body>
<div id="test"></div>
<script src="/socket.io/socket.io.js"></script>
<script>
  var socket = io('http://localhost:8081');
  socket.on('connected', function (data) {
    console.log(data);
    socket.emit('test', { value: 'data' });
  });
  socket.on('date', function(data) {
    console.log(data);
    $('#test').html(data.date);
  });
</script>
</body>
</html>

≫ Read More

2018/07/15 コンピュータ   TakeMe