15
5

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 3 years have passed since last update.

D言語/vibe.dでWebSocketプログラミング

Posted at

この記事は株式会社富士通システムズウェブテクノロジーが企画するいのべこ夏休みアドベントカレンダー 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で処理を行うという特徴があります。

開発環境の構築

コーディングを行うための環境を構築します。
以下の順で進めます。

  1. DMD(Digital Mars D compiler)のインストール
  2. Visual Studio Community 2019と関連ツールのインストール
  3. 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++ によるデスクトップ開発を追加します。
5.png

インストールが終わると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が表示されます。
7.png
これで環境構築は完了です。次はコーディングに向けてプロジェクトを作成します。

プロジェクトの作成

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関数が生成されています。

app.d
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で開くと以下のように表示されます。

`vibe-d`に依存するすべてのライブラリがプロジェクトとして表示されていますが、手を入れるのはmyprojectだけです。以下は、試しにソリューションエクスプローラーから`app.d`を開いたところです。 ![9.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/679067/bf0f4144-4900-bbb1-65a7-56e1600cb0cb.png) ソリューションエクスプローラーからソリューションを右クリックして`ソリューションのビルド`を行うと、依存関係がすべてコンパイルされ、`myproject.exe`がビルドされます。 ![10.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/679067/cd13a806-38a4-fda9-1064-a2a7b90f7b42.png) 実行やデバッグも、一般的なVisual Studioの操作と同様に行えます。以下はブレークポイントを設定してデバッグ実行した場合です。 ![11.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/679067/32ea7f64-b6e9-4ea4-645a-c2205005cae0.png)

WebSocketプログラミング

WebSocketを使ったプログラムを作成します。
実はvibe-dの公式チュートリアルにも似たようなネタがあるのですが、気にせず書いていきます。

WebSocketの受け口を作る

サーバ側でWebSocketの接続を待ち受けるための最低限の処理を実装します。
ポート8080のパス/wsでWebSocketの接続ができるよう、vibe-dの設定を行います。
WebSocketの処理の本体はmyWebSocketHandlerで、クライアントから送られてきたデータを単にログに出すだけです。

app.d
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で簡単なものを作成します。
テキストボックスにメッセージを入力して送るだけです。

client.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で表示すると以下のようになります。

試しにhogehogeと入力してSendをクリックすると、期待通りコマンドプロンプト側にもhogehogeとログが表示されました。
[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言語くんの右にテキストボックスを置いて、サーバからメッセージが来たらそこに表示するように修正します。

client.html
<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-dTask機能を利用します。

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言語は楽しい言語ですので興味があればぜひ調べてみてください。仲間募集中です。

15
5
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
15
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?