-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathmulti-server.html
More file actions
397 lines (304 loc) · 23.4 KB
/
multi-server.html
File metadata and controls
397 lines (304 loc) · 23.4 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>マルチサーバーの構成 | SOCKET-MANAGER Framework For PHP</title>
<meta name="description" content="SOCKET-MANAGERフレームワークでのマルチサーバー構築方法を解説。サーバー(プロセス)間通信のイメージやWebSocket/TCP混在環境の実装例を図解と共に詳しく紹介。" />
<meta content="PHP,ソケット通信,フレームワーク,IPC,マルチサーバー,ソケットマネージャー" name="keywords">
<link rel="canonical" href="https://socket-manager.github.io/document/multi-server.html" />
<script async src="https://www.googletagmanager.com/gtag/js?id=G-LF9W695NNW"></script>
<script>
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
gtag('js', new Date());
gtag('config', 'G-LF9W695NNW');
</script>
<link rel="icon" href="https://socket-manager.github.io/document/favicon.ico" type="image/x-icon" />
<link type="text/css" rel="stylesheet" href="./css/common.css" media="all" />
<script src="./js/jquery-3.7.1.min.js"></script>
<script type="text/javascript" src="./js/common.js"></script>
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "TechArticle",
"headline": "SOCKET-MANAGER Framework - マルチサーバー通信の実装ガイド",
"description": "SOCKET-MANAGERフレームワークでのマルチサーバー構築方法を解説。サーバー(プロセス)間通信のイメージやWebSocket/TCP混在環境の実装例を図解と共に詳しく紹介。",
"keywords": "PHP, ソケット通信, マルチサーバー, WebSocket, ソケットマネージャー",
"articleSection": ["Technical Documentation", "Server Development", "PHP Programming"],
"image": "https://socket-manager.github.io/document/img/multi-server/outline.png",
"author": {
"@type": "Person",
"name": "SOCKET-MANAGER開発チーム"
},
"publisher": {
"@type": "Organization",
"name": "SOCKET-MANAGER",
"logo": {
"@type": "ImageObject",
"url": "https://socket-manager.github.io/document/logo.png",
"width": 355,
"height": 50
}
},
"mainEntityOfPage": {
"@type": "WebPage",
"@id": "https://socket-manager.github.io/document/multi-server.html"
},
"url": "https://socket-manager.github.io/document/multi-server.html",
"breadcrumb": {
"@type": "BreadcrumbList",
"itemListElement": [{
"@type": "ListItem",
"position": 1,
"name": "Framework Top",
"item": "https://socket-manager.github.io/document/"
},{
"@type": "ListItem",
"position": 2,
"name": "マルチサーバーの構成",
"item": "https://socket-manager.github.io/document/multi-server.html"
}]
},
"isPartOf": {
"@type": "WebSite",
"name": "フレームワークのご紹介",
"url": "https://socket-manager.github.io/document/"
}
}
</script>
</head>
<body>
<div class="layout">
<div class="menu" role="navigation" aria-label="ページメニュー">
<h2 class="menu-title">SOCKET-MANAGER</h2>
<h4 class="menu-reference menu-page-title-bottom"><a href="./reference/" target="_blank">>> Reference</a></h4>
<h2 class="menu-label">MAIN-MENU</h2>
<div class="menu-text">
<h3 class="menu-page-title-link"><a href="./">▶フレームワークのご紹介</a></h3>
<h3 class="menu-page-title-link"><a href="./event-handler.html">▶イベントハンドラについて</a></h3>
</div>
<h3 class="menu-label-sub">IMPLEMENT</h3>
<div class="menu-text">
<h3 class="menu-page-title-link"><a href="./init-class.html">▶初期化クラス</a></h3>
<h3 class="menu-page-title-link"><a href="./unit-parameter.html">▶UNITパラメータクラス</a></h3>
<h3 class="menu-page-title-link"><a href="./protocol-unit.html">▶プロトコルUNITクラス</a></h3>
<h3 class="menu-page-title-link"><a href="./command-unit.html">▶コマンドUNITクラス</a></h3>
<h3 class="menu-page-title-link"><a href="./main.html">▶メイン処理クラス</a></h3>
<h3 class="menu-page-title-link"><a href="./setting.html">▶設定ファイル</a></h3>
<h3 class="menu-page-title-link"><a href="./message.html">▶メッセージファイル</a></h3>
</div>
<div class="menu-line"></div>
<div class="menu-text">
<h3 class="menu-page-title-link-for-runtime-manager"><a href="./runtime-manager/" target="_blank">>> ランタイムライブラリ</a></h3>
<h3 class="menu-page-title-link-for-runtime-manager"><a href="./simple-socket/" target="_blank">>> シンプルソケット機能</a></h3>
</div>
<h3 class="menu-label-sub">ADVANCED</h3>
<div class="menu-text">
<h3 class="menu-page-title-link"><a href="./architecture.html">▶アーキテクチャ</a></h3>
<h3 class="menu-page-title-link"><a href="./event.html">▶イベント駆動アーキテクチャ</a></h3>
<h3 class="menu-page-title-link"><a href="./ipc.html">▶IPC(プロセス間通信)</a></h3>
<h3 class="menu-page-title">▼マルチサーバーの構成</h3>
<ul>
<li><a href="./multi-server.html#begin">はじめに</a></li>
</ul>
<ul>
<li><a href="./multi-server.html#outline">サーバー構成</a></li>
</ul>
<ul>
<li><a href="./multi-server.html#source">ソースで確認</a></li>
</ul>
<ul>
<li><a href="./multi-server.html#last">おわりに</a></li>
</ul>
<h3 class="menu-page-title-link"><a href="./tcp-and-udp.html">▶TCP/UDP通信について</a></h3>
<h3 class="menu-page-title-link"><a href="./laravel.html">▶Laravelと連携する</a></h3>
<h3 class="menu-page-title-link"><a href="./system-setting.html">▶システム設定ファイル</a></h3>
<h3 class="menu-page-title-link"><a href="./custom-command.html">▶カスタムコマンド作成機能</a></h3>
</div>
<h3 class="menu-label-sub">OTHER-PROJECT</h3>
<div class="menu-text">
<h3 class="menu-page-title-link"><a href="./new-project.html">▶新規開発環境</a></h3>
<h3 class="menu-page-title-link"><a href="./websocket.html">▶Websocketサーバー開発環境</a></h3>
<h3 class="menu-page-title-link"><a href="./dev-ops.html">▶フレームワークのDevOps環境</a></h3>
</div>
<div class="menu-line"></div>
<div class="menu-text">
<h3 class="menu-page-title-link-for-minecraft"><a href="./minecraft-contents/" target="_blank">>> マインクラフト専用環境</a></h3>
<h3 class="menu-page-title-link-for-launcher"><a href="./launcher/" target="_blank">>> GUI & CLI ランチャー</a></h3>
<h3 class="menu-page-title-link-for-rest-api"><a href="./rest-api/" target="_blank">>> REST-APIサーバー開発環境</a></h3>
</div>
<h2 class="menu-label">EXTRA-MENU</h2>
<div class="menu-text">
<h3 class="menu-page-title-link"><a href="./extra-demo.html">▶デモサーバーの種類</a></h3>
<h3 class="menu-page-title-link"><a href="./extra-demo-command.html">▶デモのコマンド仕様</a></h3>
<h3 class="menu-page-title-link"><a href="./extra-demo-setting.html">▶デモの設定ファイル</a></h3>
<h3 class="menu-page-title-link"><a href="./extra-minecraft.html">▶マインクラフトの通信仕様</a></h3>
<h3 class="menu-page-title-link"><a href="./extra-close-frame.html">▶切断フレームの検証</a></h3>
</div>
<h2 class="menu-label">PHP-TECHNIQUE</h2>
<div class="menu-text">
<h3 class="menu-page-title-link"><a href="./php-pass-by-reference.html">▶参照渡し</a></h3>
<h3 class="menu-page-title-link"><a href="./php-phpdoc.html">▶PHPDocのフォーマット</a></h3>
</div>
<div class="menu-dummy-for-framework"></div>
</div>
<div class="main" role="main">
<h1>【マルチサーバーの構成】</h1>
<a id="begin"></a>
<h2 class="subtitle">はじめに</h2>
<div class="text-block">
ここで述べているマルチサーバーというのは、親子関係がある複数のサーバー同士でサーバー(プロセス)間通信(IPC)を通して連携し合っているサーバーの事を指しています。<br />
このページでは<code>クライアント⇔サーバー</code>間と<code>サーバー⇔サーバー</code>間で並行処理を行いながらプロトコル部とコマンド(ビジネスロジック)部を分割管理する手段を提供します。<br /><br />
以降ではデモのソースを例に挙げて話を進めます。
</div>
<a id="outline"></a>
<h2 class="subtitle">サーバー構成</h2>
<div class="text-block">
マルチサーバーの構成には色々なパターンが考えられると思いますが、大きく分けると以下の2パターンになるかと思います。<br />
<ul>
<li>一つのホスト上で複数のプロセスを配置する(スケールアップ)</li>
<li>二つ以上のホストで複数のプロセスを分散配置する(スケールアウト)</li>
</ul>
プロセス間通信だけの利用でソケット通信を使っている場合は、基本的にホスト名とポート番号を使って振り分けができていれば特に問題はありませんが、一つのサーバー上で<code>クライアント⇔サーバー</code>間と<code>サーバー⇔サーバー</code>間でやり取りを行う場合、プロトコル部とコマンド部を切り分けて使う事に課題がありました。<br /><br />
そこでデモ用として使っている今回のマルチサーバーには、一つのサーバープロセスの中で待ち受けポートをWebsocket用とマルチサーバー用の2つに分けて実装(SocketManagerモジュールを2つ使用)する事で切り分けができるようにしています。<br /><br />
これを図にすると以下のようになります。<br />
<div class="img-block">
<a href="./img/multi-server/outline.png" target="_blank"><img class="img-zoomout" src="./img/multi-server/outline.png" loading="lazy" alt="SOCKET-MANAGERのマルチサーバー構成図" /></a>
</div>
※プロセスをフォークしているわけではないので"親プロセス/子プロセス"ではなく敢えて"親サーバー/子サーバー"と表記しています。<br /><br />
上の図では、SOCKET-MANAGER Frameworkのエントリポイントをクライアント通信側とサーバー間通信側に分けた上で、UNITパラメータクラスによるグローバル共有(コンテキスト)を介して通信データを共有できるように構成しています。<br /><br />
また、<code>SocketManager</code>のエントリポイントが分かれる事によってサーバー間通信側と分離した管理が可能になるため、以下のメリットが生まれます。<br />
<ul>
<li>クライアント通信側と同じインターフェースでサーバー間通信の開発が可能</li>
<li>INETソケット(オリジナルプロトコルを含む)を使ったサーバー間通信が可能</li>
<li>物理サーバーが分かれていても同じインターフェース(INETソケット通信)が利用できるのでシステム変更が不要</li>
<li>別途モジュールの追加実装を必要としない</li>
</ul>
結果、単一ソリューションによるアーキテクチャレベルでの実装が容易になり、外部ライブラリや外部メディア(あるいはサーバー)などの管理作業が省略できる事で中長期的な運用の手間が軽減されます。<br /><br />
アーキテクチャページのメイン処理クラスの表記を代用し、仮に1つのサーバープロセス上のエントリポイント分割にフォーカスを当てたイメージが以下のようになります。<br />
(UNITパラメータクラスでグローバル共有しています)<br />
<div class="img-block">
<a href="./img/multi-server/main-image.png" target="_blank"><img class="img-zoomout" src="./img/multi-server/main-image.png" loading="lazy" alt="クライアント通信用/サーバー間通信用それぞれのメイン処理クラスの構成図" /></a>
</div>
UNITパラメータクラスのグローバル共有の部分にフォーカスを当てると以下のようなイメージになります。<br />
<div class="img-block">
<a href="./img/multi-server/global-link.png" target="_blank"><img class="img-zoomout" src="./img/multi-server/global-link.png" loading="lazy" alt="クライアント通信用/サーバー間通信用それぞれのUNITパラメータクラスをグローバル共有としてリソースを交換するイメージ" /></a>
</div>
そして実際にはノンブロッキングループで並走する事になるので以下のようなイメージになります。<br />
<div class="img-block">
<a href="./img/multi-server/main-para.png" target="_blank"><img class="img-zoomout" src="./img/multi-server/main-para.png" loading="lazy" alt="クライアント通信用/サーバー間通信用それぞれのノンブロッキングループ並走イメージ" /></a>
</div>
</div>
<br />
<a id="source"></a>
<h2 class="subtitle">ソースで確認</h2>
<div class="text-block">
ここでは上記で説明したサーバー構成を<code>ChatServerForTcpMulti.php</code>のソースを追いながらみていきます。<br /><br />
以下の実装では Websocket 用のUNITパラメータクラス <code>ParameterForWebsocket</code> とマルチサーバー用のUNITパラメータクラス <code>ParameterForTcpMulti</code> のインスタンスを生成し、それぞれの設定メソッドを使って互いのインスタンスを交換設定しています。<br />
<pre color-change="php" aria-label="デモ用:app/MainClass/ChatServerForTcpMulti.php内のグローバルエリア交換設定">
// UNITパラメータのインスタンス化
$websocket_param = new ParameterForWebsocket(); // Websocket用
$tcpmulti_param = new ParameterForTcpMulti(); // サーバー間通信(TCP)用
// UNITパラメータの交換設定
$websocket_param->setChatParameterForServer($tcpmulti_param); // サーバー間通信(TCP)用インスタンスをWebsocket用インスタンスへ
$tcpmulti_param->setChatParameterForWebsocket($websocket_param); // Websocket用インスタンスをサーバー間通信(TCP)用インスタンスへ
</pre><br />
以下の Websocket 用の実装ではノーマルな初期設定ブロックになっています。<br />
<pre color-change="php" aria-label="デモ用:app/MainClass/ChatServerForTcpMulti.php内のWebsocket用の初期設定ブロック">
// SocketManagerのインスタンス設定
$websocket_manager = new SocketManager($this->host, $this->port);
// SocketManagerの初期設定
$init = new InitForWebsocket($websocket_param, $this->port);
$websocket_manager->setInitSocketManager($init);
// プロトコルUNITの設定
$protocol_units = new ProtocolForWebsocket();
$websocket_manager->setProtocolUnits($protocol_units);
// コマンドUNITの設定
$command_units = new CommandForWebsocket();
$websocket_manager->setCommandUnits($command_units);
</pre><br />
以下のマルチサーバー用の実装が Websocket 用と異なるのは冒頭の <code>SocketManager</code> のインスタンス生成時に条件判断が入っているところです。<br />
マルチサーバーには親子関係がありますから、自身が親サーバーなのか、あるいは子サーバーなのかを判断する必要があります。<br />
また、同じTCP通信同士では Websocket 用とマルチサーバー用で同じポートが使えないので異なるポート番号を使用する必要があります。<br />
(マルチサーバー用のポートをUDP通信で使う場合は Websocket のTCP通信とはリソースが異なるので同じポート番号でも問題ありません)<br /><br />
そこでこのデモでは Websocket 用で使うポート番号+10の値を親サーバー用のポート番号として使うというルールにしています。<br />
つまりコマンドライン引数の指定で Websocket 用のポート番号が 10000、親サーバーのポート番号 10010 で指定されていれば、自身は親サーバーだと判断するようにしています。<br />
(UDP通信の場合は Websocket 用のポート番号と親サーバー用のポート番号の指定が同じであれば親だと判断しています)<br /><br />
自身が子サーバーだと判断した場合、待ち受けホストもポート番号も共に設定する必要がないので <code>SocketManager</code> の引数を空にしています。<br />
<pre color-change="php" aria-label="デモ用:app/MainClass/ChatServerForTcpMulti.php内のマルチサーバー用の初期設定ブロック">
// SocketManagerのインスタンス設定
$tcpmulti_manager = null;
if($this->parent === true)
{
$tcpmulti_manager = new SocketManager($this->host, $this->parent_port);
}
else
{
$tcpmulti_manager = new SocketManager();
}
// SocketManagerの初期設定
$init = new InitForTcpMulti($tcpmulti_param, $this->port, $this->parent, $this->parent_port);
$tcpmulti_manager->setInitSocketManager($init);
// プロトコルUNITの設定
$protocol_units = new ProtocolForTcpMulti();
$tcpmulti_manager->setProtocolUnits($protocol_units);
// コマンドUNITの設定
$command_units = new CommandForTcpMulti();
$tcpmulti_manager->setCommandUnits($command_units);
</pre><br />
Websocket 用の方は Listen するだけでいいのですが、マルチサーバーの場合は親の時は Listen、子の時は Connect にする必要があるので条件分岐しています。<br />
<pre color-change="php" aria-label="デモ用:app/MainClass/ChatServerForTcpMulti.php内のポート設定ブロック">
$ret = $websocket_manager->listen();
if($ret === false)
{
goto finish; // リッスン失敗
}
if($this->parent === true)
{
$ret = $tcpmulti_manager->listen();
if($ret === false)
{
goto finish; // リッスン失敗
}
}
else
{
$w_ret = $tcpmulti_manager->connect($this->host, $this->parent_port);
if($w_ret === false)
{
goto finish; // 接続失敗
}
}
</pre><br />
以下のソースでは Websocket 用の <code>SocketManager</code> とマルチサーバー用の <code>SocketManager</code> 共に <code>cycleDriven</code> メソッドを呼んで周期ドリブンの処理で並走しています。<br />
<pre color-change="php" aria-label="デモ用:app/MainClass/ChatServerForTcpMulti.php内のノンブロッキングループブロック">
while(true)
{
// 周期ドリブン
$ret = $websocket_manager->cycleDriven($this->cycle_interval, $this->alive_interval);
if($ret === false)
{
goto finish;
}
$ret = $tcpmulti_manager->cycleDriven($this->cycle_interval);
if($ret === false)
{
goto finish;
}
}
</pre><br />
</div>
<br />
<a id="last"></a>
<h2 class="subtitle">おわりに</h2>
<div class="text-block">
サーバーをまたがってユーザーを検索するようなケースではデータベースで串刺し検索を行った方が早いように思えますが、トランザクションロックを起こす可能性がある限り同時接続している他のユーザーに影響が出る可能性を考慮しないといけません。<br /><br />
データの永続化が必要な場合は、起動時に必要な情報を読み込んでおくようにしたり、今回のようなマルチサーバーでデータベースアクセス専用のサーバーを起ててリクエストを投げるだけにするなど、工夫次第でパフォーマンスへの影響を軽減できる方法はあると思います。<br />
その上でデータベース上のデータ共有が必要であればサーバー間通信で賄えばいいでしょう。<br /><br />
デモ環境のマルチサーバーの起動方法については<font><a href="./extra-demo.html#multi" target="_blank">▶デモサーバーの種類⇒マルチサーバー</a></font>のページをご覧ください。
</div>
</div>
</div>
</body>
</html>