-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathevent-handler.html
More file actions
466 lines (337 loc) · 28.3 KB
/
event-handler.html
File metadata and controls
466 lines (337 loc) · 28.3 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
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
<!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フレームワークにおけるイベントハンドラの実装方法を解説。キューとステータスUNITを使った非同期処理、再利用UNIT、ポーリングUNIT、リトライUNITの実装例を具体的に紹介。" />
<meta content="PHP,ソケット通信,サーバー開発,framework,IPC,ソケットマネージャー" name="keywords">
<link rel="canonical" href="https://socket-manager.github.io/document/event-handler.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フレームワークにおけるイベントハンドラの実装方法を解説。キューとステータスUNITを使った非同期処理、再利用UNIT、ポーリングUNIT、リトライUNITの実装例を具体的に紹介。",
"articleSection": ["Technical Documentation", "Server Development", "PHP Programming"],
"keywords": "PHP, ソケット通信, イベントハンドラ, 非同期処理, ソケットマネージャー",
"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/event-handler.html"
},
"url": "https://socket-manager.github.io/document/event-handler.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/event-handler.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">▼イベントハンドラについて</h3>
<ul>
<li><a href="./event-handler.html#begin">はじめに</a></li>
</ul>
<ul>
<li><a href="./event-handler.html#certain">排他制御・実行順序の保障</a></li>
</ul>
<ul>
<li><a href="./event-handler.html#base">イベント処理の基本構成</a></li>
</ul>
<ul>
<li><a href="./event-handler.html#reuse">再利用UNITとして使う</a></li>
</ul>
<ul>
<li><a href="./event-handler.html#polling">ポーリングUNITとして使う</a></li>
</ul>
<ul>
<li><a href="./event-handler.html#retry">リトライUNITとして使う</a></li>
</ul>
<ul>
<li><a href="./event-handler.html#group">UNIT集合の種類</a></li>
</ul>
<ul>
<li><a href="./event-handler.html#last">おわりに</a></li>
</ul>
</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-link"><a href="./multi-server.html">▶マルチサーバーの構成</a></h3>
<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">
このフレームワークでは通信状態のステータス管理を可能にするため、イベントハンドラをキューとステータスUNIT(単にUNITとも言います)という単位に分けて最適化を行います。<br />
また、各UNITは非同期で動作しますので、他のクライアントの通信を妨げる事なく複雑で柔軟なシーケンスに対応できるようになっていて、ここではその概念的なものを押さえておきます。<br /><br />
キューとUNITの事をざっくり説明すると、以下のような関係になります。<br />
<pre aria-label="キューとUNITの関係">
■UNIT(ステータスUNIT)
イベント処理の最小単位
■キュー
1つのイベントを処理するためのUNITの集合(言わば静的に配置されたタスクの待ち行列のようなもの)
</pre><br />
このアーキテクチャは、後に出てくるプロトコルやサーバーコンテンツを実装する際の共通インターフェースとして機能し、その組み合わせ次第で様々なプロトコルやコンテンツに対応できる設計になっています。<br />
</div><br />
<a id="certain"></a>
<h2 class="subtitle">排他制御・実行順序の保障</h2>
<div class="text-block">
このキューとステータスUNITの仕組みにより、一般的なイベント駆動型プログラミングとは異なる以下の特長を実現しています。<br />
<br />
<h3>排他実行の保障</h3>
各イベントハンドラが処理中であっても、同じハンドラが重複して呼ばれることはありません。<br />
これは、キューに登録されている全ステータスUNITの処理完了を監視しながら、イベントの発生源となる送受信バッファがFIFO(先入れ先出し)形式で管理されており、未処理イベントが順番にキューへ投入されるためです。<br />
そのため、同一のハンドラ処理が重複して並行実行されることなく、排他的に順次処理されます。<br />
<br />
<h3>UNIT処理順序の保障</h3>
各ステータスUNITは他クライアントの処理とは非同期で実行されますが、デベロッパーにより意図的に「スローブレイク」(フレームワーク内でステータスを維持したままハンドラ処理を中断する機能)を実行したとしても、登録されたステータスUNITの処理順序は厳密に保証されます。<br />
これにより、非同期環境下でも意図した通りのイベント順序と整合性を保つことが可能です。<br /><br />
以降では、チャットサーバーの実装を仮定して、イベントが発生してからキューとUNITを使ってどのように処理されるのか、その内容を見ていきます。<br />
</div><br />
<a id="base"></a>
<h2 class="subtitle">イベント処理の基本構成</h2>
<div class="text-block">
チャットサーバーと言えば、クライアントからメッセージを受信して全員に配信するという実装が一番シンプルですが、ここでは特定の相手に対するプライベートメッセージの扱いも兼ねて、以下のようなイベント処理を基本構成として見ていきます。<br />
<pre aria-label="イベント処理の構成">
■チャットメッセージの受信
【処理】受信したメッセージを全員に配信する
■プライベートメッセージの受信
【処理】配信先の相手を検索して、受信したメッセージをその相手に配信する
</pre><br />
これをキューとUNITで構成すると以下のようになります。<br />
<pre aria-label="キューとUNITを使った置き換えイメージ">
■キュー名:CHAT_MESSAGE
①ステータス名:start
【処理】受信したメッセージを全員に配信する
【戻り値】nullを返して処理終了
■キュー名:PRIVATE_MESSAGE
①ステータス名:start
【処理】配信先の相手を検索して、受信したメッセージをその相手に配信する
【戻り値】nullを返して処理終了
</pre><br />
上記の例ではチャットメッセージとプライベートメッセージの各イベントにキュー名を割り当て、個々の処理単位にUNITとしてのステータス名を1つだけ割り当てています。<br />
(最初に実行されるステータス名は<code>start</code>と決まっています)<br />
そしてUNITの戻り値で次に呼び出すUNITを決定し、nullを返すまでUNIT処理を繰り返すルールとなっています。<br /><br />
以降では、これを基本構成としてUNITの代表的な使用例を見ていきます。<br />
</div><br />
<a id="reuse"></a>
<h2 class="subtitle">再利用UNITとして使う</h2>
<div class="text-block">
チャットコンテンツはサーバー側でログを記録しているケースが多いと思います。<br /><br />
そこで、受信したメッセージをログに記録する部分を冒頭の基本構成に組み込んでUNITを再構成してみます。<br />
<pre aria-label="再構成イメージ">
■キュー名:CHAT_MESSAGE
①ステータス名:start
【処理】受信したメッセージをログに記録して次のステータス(send)へ遷移する
【戻り値】send
②ステータス名:send
【処理】受信したメッセージを全員に配信する
【戻り値】nullを返して処理終了
■キュー名:PRIVATE_MESSAGE
①ステータス名:start
【処理】受信したメッセージをログに記録して次のステータス(send)へ遷移する
【戻り値】send
②ステータス名:send
【処理】配信先の相手を検索して、受信したメッセージをその相手に配信する
【戻り値】nullを返して処理終了
</pre><br />
上記のように、<code>start</code>ステータスで受信したメッセージをログに記録する処理を行い、<code>send</code>ステータスでメッセージを配信するようにUNITを構成しました。<br />
御覧の通り、<code>start</code>ステータスはチャットメッセージとプライベートメッセージの両方で同じ内容になるので、一つの共通処理UNITとして利用する事が可能です。<br />
UNITの使い方をこのように工夫する事で、イベント処理の構造化プログラミングが可能になります。<br /><br />
※UNIT内ではどのキューで呼ばれたのかを判断できるメソッドがあるので、キューの種類に応じてログの内容を仕分ける事が可能です。<br />
</div><br />
<a id="polling"></a>
<h2 class="subtitle">ポーリングUNITとして使う</h2>
<div class="text-block">
通常のチャットメッセージは基本的に同プロセス内での配信を想定していますが、プライベートメッセージを送信する場合は相手が常に同じプロセス内に居るとは限りません。<br />
そのためマルチサーバー構成の場合は、相手を検索するためにサーバー間通信(IPC)を使うケースが出てきます。<br /><br />
そこで、サーバー間通信部分を冒頭の基本構成にあるプライベートメッセージの処理に組み込んでUNITを再構成してみます。<br />
<pre aria-label="再構成イメージ">
■キュー名:PRIVATE_MESSAGE
①ステータス名:start
【処理】
・配信先の相手が同プロセス内で検索して見つかった場合、受信したメッセージをその相手に配信して終了する
・配信先の相手が同プロセス内で検索して見つからなかった場合、次のステータス(search_request)へ遷移する
【戻り値】search_request or null(処理終了)
②ステータス名:search_request
【処理】他のプロセスに配信先相手の検索とメッセージ配信を依頼して次のステータス(search_response)へ遷移する
【戻り値】search_response
③ステータス名:search_response
【処理】
・他のプロセスからのレスポンスを受信するまで、自身のステータス(search_response)を返してポーリングを継続する
→メッセージが無事に送信できた場合、送信結果成功として送信元へレスポンスを返す
→メッセージが送信できなかった場合、送信結果失敗として送信元へレスポンスを返す
【戻り値】search_response or nullを返して処理終了
</pre><br />
上記のように、<code>search_request</code>ステータスで配信先相手の検索とメッセージ配信を依頼して、<code>search_response</code>ステータスで他のプロセスからのレスポンスを受信するようにUNITを構成しました。<br />
御覧の通り、<code>search_request</code>と<code>search_response</code>ステータスに分ける事で、非同期処理を維持したままサーバー間通信を行い、<code>search_response</code>UNIT内で自身のステータスを返し続ける事でポーリングを行う事ができます。<br />
</div><br />
<a id="retry"></a>
<h2 class="subtitle">リトライUNITとして使う</h2>
<div class="text-block">
無線通信を含む通信データのやりとりでは、ノイズや電波障害などの影響で通信が途切れたり、データが欠損して通信に失敗する事があります。<br />
それを補うためにリトライ処理を施して通信の安定性を向上させる事でリカバリーできる事があります。<br /><br />
そこで、リトライ処理を先ほどのポーリングUNITの処理に組み込んで再構成してみます。<br />
<pre aria-label="再構成イメージ">
■キュー名:PRIVATE_MESSAGE
①ステータス名:start
【処理】
・配信先の相手が同プロセス内で検索して見つかった場合、受信したメッセージをその相手に配信して終了する
・配信先の相手が同プロセス内で検索して見つからなかった場合、次のステータス(search_request)へ遷移する
【戻り値】search_request or null(処理終了)
②ステータス名:search_request
【処理】他のプロセスに配信先相手の検索とメッセージ配信を依頼して次のステータス(search_response)へ遷移する
【戻り値】search_response
③ステータス名:search_response
【処理】
・他のプロセスからのレスポンスを受信するまで、自身のステータス(search_response)を返してポーリングを継続する
→メッセージが無事に送信できた場合、送信結果成功として次のステータス(source_response)へ遷移する
→メッセージ送信が失敗、かつリトライ回数が3回に到達していない場合、前のステータス(search_request)へ遷移してリトライする
→メッセージ送信が失敗、かつリトライ回数が3回に到達した場合、送信結果失敗として次のステータス(source_response)へ遷移する
【戻り値】search_request or source_response
④ステータス名:source_response
【処理】前のステータスから受け取った送信結果を添えて送信元へレスポンスを返す
【戻り値】nullを返して処理終了
</pre><br />
上記のように、<code>search_response</code>ステータスでリトライ回数の判定を行い、リトライ時には<code>search_request</code>ステータスへ遷移させる事で繰り返し処理が行えるようUNITを構成しました。<br /><br />
※UNIT内ではリトライ回数などの任意のデータをディスクリプタ(クライアント接続子)に保持できるメソッドがあります。<br />
</div><br />
<a id="group"></a>
<h2 class="subtitle">UNIT集合の種類</h2>
<div class="text-block">
これまでの説明で使ってきたキューやUNIT群は、サーバーのコンテンツ部分にあたるコマンドUNIT(あるいはコマンド部)というカテゴリに分類されますが、これに台頭するプロトコルUNIT(あるいはプロトコル部)というものも存在します。<br /><br />
これら二つのカテゴリの概要は次の通りです。<br />
<pre aria-label="カテゴリの概要">
■コマンドUNIT(コマンド部)
プロトコルUNITで受信したデータを元にディスパッチされるUNITの集合。
チャットサーバーで言えばチャットメッセージやプライベートメッセージの配信などのコンテンツ部分に相当します。
■プロトコルUNIT(プロトコル部)
クライアントと直接通信する時に呼び出されるUNITの集合。
有名なプロトコルで言えばHTTPやWebsocket等。オリジナルプロトコルも含みます。
</pre><br />
これを踏まえると、以下の関係が成り立ちます。<br />
<pre aria-label="UNITの関係">
■UNIT(ステータスUNIT)
イベント処理の最小単位
■キュー
一つのイベントを処理するためのUNITの集合(言わば静的に配置されたタスクの待ち行列のようなもの)
■コマンドUNIT(コマンド部)
サーバーのコンテンツ部分にあたるキューの集合
■プロトコルUNIT(プロトコル部)
クライアントとの通信部分にあたるキューの集合
</pre><br />
キューの集合が2種類に分かれているのは、同じサーバーコンテンツ(コマンドUNIT)であってもプロトコルを変更したり、同じプロトコルであってもサーバーコンテンツを入れ替えたりする事が可能になるからです。<br />
このようにそれぞれを独立して実装する事で、サーバーの組み合わせのバリエーションが選べるようになっています。<br /><br />
このフレームワーク環境ではコマンドUNIT(<font><a href="./command-unit.html" target="_blank">▶コマンドUNITクラス</a></font>参照)やプロトコルUNIT(<font><a href="./protocol-unit.html" target="_blank">▶プロトコルUNITクラス</a></font>参照)のそれぞれを独自のクラスで管理しています。<br />
メイン処理クラス(<font><a href="./main.html" target="_blank">▶メイン処理クラス</a></font>参照)を生成する場合と同じように、これらのクラスもコマンドでひな形を生成できるので、デベロッパーはクラス内のキューとUNITの開発に集中して取り組む事ができます。<br />
</div><br />
<a id="last"></a>
<h2 class="subtitle">おわりに</h2>
<div class="text-block">
今回は触れていませんが、例えばチャットメッセージのログをデータベースへ記録するケースも考えられます。<br />
特にトランザクションが発生する書き込みの場合は少なからず処理のブロッキングが発生します。<br /><br />
データ量が少ない時にはあまり問題にならないかもしれませんが、トラフィックのボトルネックとして影響が出てくるようであれば、データベースオペレーション専用のサーバーを起てておき、ポーリングUNITの項でご紹介したようなサーバー間通信を使う方法で対処した方がいいでしょう。<br /><br />
このフレームワークは非同期処理を基本としているので、動的なプロセスのフォークやスレッドの生成は必要ありません。<br />
そのためマルチサーバー環境を構築する際は、スケーラビリティと安定したリソースを確保するため、プロセスの絶対数による管理を推奨しています。<br />
</div>
</div>
</div>
</body>
</html>