WebSocketとgRPC
中央線ミートアップ #2
2018/11/30 tashxii@TIS
Web socket
• Webアプリケーションで双方向通信を行うためのプロトコル
• Websocketをnewして、
onmessageで受信、
sendで送信
Melody
• Go言語のWeb socket用ライブラリ(Githubスター数 1200)
• APIは、Send(一人に送る)、BroadCastOthers(他の人に送る)、
BroadCastAll(全員に送る)の3通りだけ。
MelodyMelody
Melody
パクって作ってみた - Gopher-war
• https://github.com/olahol/melody/blob/master/examples/gopher
s/demo.gif
• しかし、弾が出ない、勝ち負けがわからないなどの不満も・・・
弾は出ないです
ご要望1
• 弾が出ます!!
ご要望1
• 弾が出ます!!
弾
ご要望2
ためうちは?
ためうちはできるの?
できます!
ためうち
ミサイル
ためうちの実装
• チャージ  setTimeout を2回使います。
if (missileload === true) {
if (mycharge === "none") {
chargingTimerid = setTimeout(()=>{
mycharge = "charging";
show(myid, e.pageX, e.pageY, mylife, myname, mycharge);
ws.send(["show", e.pageX, e.pageY, mycharge].join(" "));
chargedTimerid = setTimeout(()=>{
mycharge = "charged"
show(myid, e.pageX, e.pageY, mylife, myname, mycharge);
ws.send(["show", e.pageX, e.pageY, mycharge].join(" "));
}, 300);
}, 700);
gRPC
• Googleが開発したRPC(Remote Procedure Call)を行うためのプロトコル
• protoファイルと呼ばれるIDL(Interface Definition Language)でAPIを定義
• 単方向と双方向の通信が可能。複数のリクエスト・レスポンスを捌ける
syntax = "proto3";
package message;
option go_package ="message";
import "github.com/mwitkow/go-proto-validators/validator.proto";
service MessageService {
rpc Connect (ConnectRequest) returns (Empty);
rpc Disconnect (DisconnectRequest) returns (Empty);
rpc ReceiveMessage (ReceiveMessageRequest) returns (stream ReceiveMessageResponse);
rpc SendMessageOthers (SendMessageRequest) returns (Empty);
rpc SendMessageAll (SendMessageRequest) returns (Empty);
}
message Empty {
}
message ConnectRequest {
string id = 1 [(validator.field) = {msg_exists : true}];
string name = 2 [(validator.field) = {msg_exists : true}];
int64 x = 3;
int64 y = 4;
}
message DisconnectRequest {
string id = 1 [(validator.field) = {msg_exists : true}];
}
message ReceiveMessageRequest {
string from_id = 1 [(validator.field) = {msg_exists : true}];
}
message ReceiveMessageResponse {
string from_id = 1 [(validator.field) = {msg_exists : true}];
string type = 2 [(validator.field) = {msg_exists : true}];
repeated string params = 3;
}
message SendMessageRequest {
string from_id = 1 [(validator.field) = {msg_exists : true}];
string type = 2 [(validator.field) = {msg_exists : true}];
repeated string params = 3;
}
Electron
• Githubが開発したクロスプラットフォーム対応のアプリケーションビルド環境
• HTML, CSS, Javascriptでディスクトップアプリケーションを開発できる
• Visual Studio Code、Slack、Atom Editorなど、採用実績も多数
React + Redux
• React … Facebook製のJSライブラリ。
• ざっくりいうと、MVCのVを担当するフレームワーク。Githubスター数11万強
• Redux … アプリケーションの状態管理を行うためのFluxアーキテクチャを実装し
たライブラリ。
• React向けに開発されているが他のライブラリにも適用可能。
Redux-Saga
• React+Reduxで非同期処理を手続的に記述できるミドルウェア
• takeで待受、callで、非同期呼び出し、putでディスパッチ
function* handleConnect() {
while (true) {
const action = yield take(CONNECT_START_EVENT)
console.log("handleConnect: action=" + JSON.stringify(action))
const { error } = yield call(
MessageApi.connectAsync,
action.payload.id,
action.payload.name,
)
if (error) {
console.log(error)
yield put(connectFailureEvent())
} else {
yield put(connectSuccessEvent(action.payload.id, action.payload.name, "/main"))
}
}
}
gRPCとElectronで双方向通信を
検証してみた - Gopher-Let’s-Go
Atomic Designの採用
• コンポーネントを化学にたとえAtom(原子),
Molecules(分子), Organisms(組織), Template,
Pageの5階層に分けて設計する手法。
• 今回は、原子分子は1つにまとめ、4階層にしている
。
Redux-Saga双方向の実装
• eventChanelを作成し、emitでactionを返し、putします。
function* watchRefresh() {
const action = yield take(REFRESH_START_EVENT)
const channel = yield call(createRefreshChannel, action.payload.id)
while (true) {
const payload = yield take(channel)
yield put(payload)
}
}
function createRefreshChannel(id) {
return eventChannel(emit => {
const iv = setInterval(() => {
emit(sendMessageAllStartEvent(id,"refresh",[]))
}, 1)
return () => {
clearInterval(iv)
}
})
}
gPRCでのPush通知
• ChannelをQueueとして
• 使って実現しました。
// ReceiveMessage method
func (s *MessageServer) ReceiveMessage(req
*message.ReceiveMessageRequest, stream
message.MessageService_ReceiveMessageServer) (err error) {
fmt.Println("ReceiveMessage is called")
connection, ok := s.connections[req.FromId]
if !ok {
msg := fmt.Sprintf("FromId:%s does not exist", req.FromId)
return errors.New(msg)
}
loop:
for {
select {
case msg := <-connection.queue:
params := strings.Split(msg, " ")
stream.Send(&message.ReceiveMessageResponse{
FromId: connection.id,
Type: params[0],
Params: params[1:],
})
case <-connection.disconnect:
break loop
}
}
return nil
}
//Show new player to other players
for _, connection := range s.connections {
msg := fmt.Sprintf("show %s %s %d %d %d %d %s",
player.id,
player.name,
player.life,
player.x,
player.y,
player.size,
player.charge)
connection.queue <- msg
}
圧倒的スケール!
• Web socket ファイル数 2 600行
• gRPC + React ファイル数 40 1450行
0
10
20
30
40
ファイル数
0
375
750
1125
1500
1875
Line
まとめ(あくまで個人の感想です)
• Go言語で書くのは楽しい、かんたん
• すごくいっぱいライブラリがあり、選定に迷う
• Web socketをやるならMelodyは使いやすい
• gPRC
• 簡単に単方向、双方向のAPIを定義、実装できる。
• ストリーミングのAPI作成は扱い方に少し癖がある。クライアントからの呼び出し
方など
• Electron
• クライアントとサーバーのAPIを使えることが強み。

Web socket and gRPC