この記事は株式会社富士通システムズウェブテクノロジーが企画するいのべこ夏休みアドベントカレンダー 2020の21日目の記事です。本記事の掲載内容は私自身の見解であり、所属する組織を代表するものではありません。
はじめに
趣味で使っているD言語でWebSocketを取り扱う話です。
D言語はマイナー(少なくとも私の周りで利用者を見かけない)なので、環境構築手順も載せました。
環境
利用するOSやツールのバージョンは以下の通りです。
バージョン | |
---|---|
OS | Windows 10 Pro 1909 |
コンパイラ | DMD 2.093.0 |
IDE | Visual Studio Community 2019 / Visual D 1.0.0 |
ブラウザ | Google Chrome 84.0.4147.135 |
D言語について
C/C++の流れを汲む言語で、ネイティブコードを生成します。詳細に興味があれば公式サイトを見てみてください。
仕事でよく使うJava/.NET系に比べると標準ライブラリは少ないですが、機能がいろいろあって楽しい言語です。
この記事には文法の説明は含まれませんが、C++/Java/C#を知っていれば何となくサンプルコードが読めると思います。
vibe.dについて
D言語で実装されたウェブアプリケーションフレームワークです。
HTMLテンプレートやREST APIの機能もありますが、今回はWebSocketのみを扱います。
ThreadではなくFiberで処理を行うという特徴があります。
開発環境の構築
コーディングを行うための環境を構築します。
以下の順で進めます。
- DMD(Digital Mars D compiler)のインストール
- Visual Studio Community 2019と関連ツールのインストール
- Visual D(Visual Studio向けD言語パッケージ)のインストール
1.DMDのインストール
公式サイトのダウンロードページからDMDのインストーラー(exe版)をダウンロードして実行します。執筆時点では2.093.0が最新ですのでその前提で進めます。
以下、気を付けるポイントだけ明記します。
- Choose Componentsでは
Extras
の下のDownload Visual D
をチェックしないでください。DMD 2.093.0に付属のVisual Dは少し古くVisual Studio 2019に対応していませんので、後で最新版をインストールします。
- Choose Visual Studio Installationでは
Download and install Visual Studio 2019
を選択してください。
Install
ボタンをクリックすると、DMDのインストールが始まるとともにVisual Studio Installerのダウンロードが始まります。
2.Visual Studio Community 2019と関連ツールのインストール
Visual Studio Installerのダウンロードが完了すると、インストール画面が表示されます。
そのままインストールするとツールが足りず、後々エラーが出るので、以下のようにC++ によるデスクトップ開発
を追加します。
インストールが終わるとVisual Studio 2019が起動しますが、このままではD言語を扱うことはできませんので、終了させます。
3.Visual Dのインストール
Visual DのサイトからVisual Dのインストーラーをダウンロードします。Download Visual D extension only
版をダウンロードして実行してください。
すでにVisual Studio 2019がインストールされているため、以下のように表示されるはずです。
そのまま進めてインストールが完了すると、Visual Studio 2019でVisual D
の機能を利用できるようになります。正常にインストールされていれば、Visual Studio 2019のメニューの拡張機能
にVisual D
が表示されます。
これで環境構築は完了です。次はコーディングに向けてプロジェクトを作成します。
プロジェクトの作成
DMDに付属しているdubを利用してプロジェクトを作成します。dub
はD言語の公式パッケージマネージャーです。
適当な場所にプロジェクト用のフォルダ(今回はC:\myproject
)を作り、作業場所とします。
コマンドプロンプトを起動してフォルダに移動し、dub init
を実行すると、対話モードになります。以下のように入力していきます。
設定項目 | 入力 |
---|---|
Package recipe format | sdl |
Name | お好みで変更 |
Description | お好みで変更 |
Author name | お好みで変更 |
License | そのままEnter |
Copyright string | お好みで変更 |
Add dependency | vibe-dを追加 |
C:\myproject>dub init
Package recipe format (sdl/json) [json]: sdl
Name [myproject]:
Description [A minimal D application.]:
Author name [Nobuyuki]: godmyoh
License [proprietary]:
Copyright string [Copyright ツゥ 2020, godmyoh]:
Add dependency (leave empty to skip) []: vibe-d
Adding dependency vibe-d ~>0.9.0
Add dependency (leave empty to skip) []:
Successfully created an empty project in 'C:\myproject'.
Package successfully created in .
C:\myproject>
以下のように空のアプリケーションが生成されます。
myproject
│ .gitignore
│ dub.sdl
│
└─source
app.d
dub.sdl
には対話で入力した内容が設定値として保存されています。今後dub
コマンドを実行するとdub.sdl
の内容に沿って処理が行われます。
app.d
の中を見ると、以下のように単純なmain
関数が生成されています。
import std.stdio;
void main()
{
writeln("Edit source/app.d to start your project.");
}
空のアプリケーションのビルドと実行
生成した空のアプリケーションを、コマンドラインとVisual Studioそれぞれからビルド、実行してみます。
コマンドラインからのビルドと実行
dub build
コマンドでビルドできます。
C:\myproject>dub build
Performing "debug" build using C:\D\dmd2\windows\bin\dmd.exe for x86_64.
taggedalgebraic 0.11.16: building configuration "library"...
eventcore 0.9.7: building configuration "winapi"...
stdx-allocator 2.77.5: building configuration "library"...
vibe-core 1.9.3: building configuration "winapi"...
(中略)
vibe-d 0.9.0: building configuration "vibe-core"...
myproject ~master: building configuration "application"...
Linking...
To force a rebuild of up-to-date targets, run again with --force.
Copying files for vibe-d:tls...
C:\myproject>
ビルドの過程でvibe-d
及びvibe-d
が依存するライブラリのソースコードがダウンロードされ、コンパイルされます。
ビルドの結果、myproject.exe
が生成されていますので、実行してみます。
C:\myproject>myproject.exe
Edit source/app.d to start your project.
C:\myproject>
Visual Studioからのビルドと実行
Visual Studioでプロジェクトを開くために、.sln
等のファイルを生成します。
dub generate visuald
コマンドで生成できます。
C:\myproject>dub generate visuald
Solution 'myproject.sln' generated.
Copying files for vibe-d:tls...
C:\myproject>
これでVisual Studioのソリューションファイルmyproject.sln
が生成されました。Visual Studio 2019で開くと以下のように表示されます。
WebSocketプログラミング
WebSocketを使ったプログラムを作成します。
実はvibe-dの公式チュートリアルにも似たようなネタがあるのですが、気にせず書いていきます。
WebSocketの受け口を作る
サーバ側でWebSocketの接続を待ち受けるための最低限の処理を実装します。
ポート8080
のパス/ws
でWebSocketの接続ができるよう、vibe-d
の設定を行います。
WebSocketの処理の本体はmyWebSocketHandler
で、クライアントから送られてきたデータを単にログに出すだけです。
module app;
import vibe.vibe;
void main()
{
auto settings = new HTTPServerSettings;
settings.port = 8080;
auto router = new URLRouter;
router.get("/ws", handleWebSockets(&myWebSocketHandler));
listenHTTP(settings, router);
runApplication();
}
void myWebSocketHandler(scope WebSocket socket)
{
while (socket.waitForData)
{
auto received = socket.receiveText;
logInfo("RECEIVED:" ~ received);
}
logInfo("DISCONNECTED");
}
これをビルドして実行します。dub run
コマンドを使います。
C:\myproject>dub run
Performing "debug" build using C:\D\dmd2\windows\bin\dmd.exe for x86_64.
(中略)
Running .\myproject.exe
[main(----) INF] Listening for requests on http://[::]:8080/
[main(----) INF] Listening for requests on http://0.0.0.0:8080/
ちゃんと動いているはずですが、これだけでは動作確認ができないので、クライアントを実装します。
WebSocketのクライアントを作る
クライアントはHTMLで簡単なものを作成します。
テキストボックスにメッセージを入力して送るだけです。
<html>
<body bgcolor="#e0e0e0">
<script>
var ws = new WebSocket('ws://localhost:8080/ws');
</script>
<img src="https://dlang.org/images/d3.png">
<br><br>
<input type="text" id="message">
<br>
<button type="button" id="send" onclick="ws.send(document.getElementById('message').value)">Send</button>
<body>
</html>
見た目が寂しかったので、D言語の公式マスコットキャラクターであるD言語くん
/D-man
の画像を埋め込みました。Chromeで表示すると以下のようになります。
[main(----) INF] Listening for requests on http://[::]:8080/
[main(----) INF] Listening for requests on http://0.0.0.0:8080/
[main(3oGb) INF] RECEIVED:hogehoge
双方向通信をする
WebSocketは双方向通信が可能なので、サーバ側からクライアント側に向けてメッセージを送ってみます。
ここでは簡単に、一定時間ごとにクライアントに対してメッセージを送ります。せっかく画面上にD言語くん
がいますので、D言語くん
がしゃべっているかのように見せかけて表示させます。
以下はclient.html
の修正版です。かなり雑ですが、D言語くん
の右にテキストボックスを置いて、サーバからメッセージが来たらそこに表示するように修正します。
<html>
<body bgcolor="#e0e0e0">
<script>
var ws = new WebSocket('ws://localhost:8080/ws');
// ↓追加:サーバからメッセージを受け取ったらテキストボックスに表示
ws.onmessage = (msg) => { document.getElementById('server_message').value = msg.data; };
</script>
<img src="https://dlang.org/images/d3.png">
<!-- ↓追加:D言語くんの右にテキストボックス(背景が透明&枠無し)を置く -->
<input type="text" id="server_message" style="position:relative; top:-130px; background-color:transparent; border-style:none; width:300px; font-size:50px;">
<br><br>
<input type="text" id="message">
<br>
<button type="button" id="send" onclick="ws.send(document.getElementById('message').value)">Send</button>
<body>
</html>
サーバ側も処理を追加します。
クライアントからのメッセージと無関係なタイミングで処理を行うため、vibe-d
のTask
機能を利用します。
module app;
import vibe.vibe;
void main()
{
auto settings = new HTTPServerSettings;
settings.port = 8080;
auto router = new URLRouter;
router.get("/ws", handleWebSockets(&myWebSocketHandler));
// ↓追加:D言語くんをしゃべらせる処理をTaskとして実行
runTask(&startDman);
listenHTTP(settings, router);
runApplication();
}
// ↓追加:接続済みのWebSocketの一覧を保持しておく配列
WebSocket[] sockets;
void myWebSocketHandler(scope WebSocket socket)
{
// ↓追加:接続したWebSocketを配列に追加
sockets ~= socket;
while (socket.waitForData)
{
auto received = socket.receiveText;
logInfo("RECEIVED:" ~ received);
}
logInfo("DISCONNECTED");
// ↓追加:切断したWebSocketを配列から削除
foreach (i, s; sockets)
{
if (s is socket)
{
sockets = sockets[0..i] ~ sockets[i+1..$];
break;
}
}
}
// ↓追加:D言語くんをしゃべらせる処理
void startDman()
{
// 接続中の全クライアントにメッセージを送る
void send(string message)
{
foreach (s; sockets)
s.send(message);
}
// 2秒間隔で "< Hello!" と "" を交互に送る
while (true)
{
send("< Hello!");
sleep(2.seconds);
send("");
sleep(2.seconds);
}
}
dub run
で最新版を実行し、client.html
をF5でリフレッシュします。
画像だと分かりませんが、< Hello!
というメッセージが2秒ごとに出たり消えたりしています。
これで、双方向通信ができました。
終わりに
ここまで読んでいただきありがとうございました。
記事の中ではほとんど紹介できませんでしたが、D言語は楽しい言語ですので興味があればぜひ調べてみてください。仲間募集中です。