質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.50%
C#

C#はマルチパラダイムプログラミング言語の1つで、命令形・宣言型・関数型・ジェネリック型・コンポーネント指向・オブジェクティブ指向のプログラミング開発すべてに対応しています。

Q&A

解決済

1回答

3968閲覧

APIのラッパーを作成したい

aglkjggg

総合スコア769

C#

C#はマルチパラダイムプログラミング言語の1つで、命令形・宣言型・関数型・ジェネリック型・コンポーネント指向・オブジェクティブ指向のプログラミング開発すべてに対応しています。

0グッド

0クリップ

投稿2017/05/12 18:50

編集2017/05/13 04:15

Ⅰ. 現在の環境

#機能、役割
1WebSocketのみで通信可能なAPIサーバ
21と3の仲介を行うサーバ。1にある機能をHTTPアクセス可能なAPIとして実装します。いわゆる1のラッパーとなるサーバ。
3HTTPのみで通信可能なクライアント

2の開発・動作環境
Windows 10 64bit
Visual Studio 2017
C# 7.0
ASP.NET としてプロジェクトを作成しました。

Ⅱ. 作りたい物(やりたい事)

1と3を通信させたいです。

しかし、以下の2つの理由の為2のサーバを作成しました。

  • 通信プロトコルが異なる

1と3はそれぞれ WebSocket と HTTP でしか通信できません。
1でHTTP通信, 3でWebSocket通信といった事が許されていません。

  • WebSocket は一定時間ごとにPingしないと切断されてしまう

2のサーバは常駐するタイプのアプリケーションとして作成しました。
よって、一定時間ごとにPingする事に対応できました。

Ⅲ. 発生している問題

同時に複数リクエストがあった場合失敗してしまう

  • 「3のクライアント」から「2のサーバ」に対して同時にリクエストされた場合、

同時にリクエストされた物が全て失敗し、クライアントに正しくレスポンスを返す事ができません。

  • エラー内容は無く、WebSocket.csの無限ループを無限に実行している形となってしまう為、クライアントに正しくレスポンスを返すことができていない状態です。
  • 「1の上位サーバ」と「2のサーバ」間は複数同時リクエストに問題が無いことは確認がとれています。

以下のプログラムが「2のサーバ」のプログラムです。

UserProfileController.cs

cs

1[Route("api/[controller]")] 2public class UserProfileController : Controller 3{ 4 // 以下のようなリクエストでこの関数が呼ばれる 5 // http://example.com/api/userprofile/1000 6 [HttpGet] 7 public async Task<JsonResult> Get(int userId) 8 { 9 return Json(WebSocket.Instance.GetUserProfile(userId)); 10 } 11}

WebSocket.cs
WebSocket クラスはシングルトンとして作っています。
「2のサーバ」の起動と同時に「WebSocket クラス」のインスタンスが作成(初期化)されます。

cs

1public class WebSocket 2{ 3 private WebSocket websocket; 4 private int messageId = 0; 5 private static Api instance; 6 public static Api Instance 7 { 8 get 9 { 10 if (instance == null) 11 { 12 instance = new Api(); 13 } 14 15 return instance; 16 } 17 } 18 19 private WebSocket() 20 { 21 websocket = new WebSocket($"ws://example.com"); 22 websocket.AutoSendPingInterval = 60; // 60 秒ごとに自動的にPingを送信する 23 websocket.EnableAutoSendPing = true; 24 } 25 26 public async Task<dynamic> GetUserProfile(int userId) 27 { 28 // WebSocket の接続確立後は messageId は常にユニークな数値 29 var tmpMessageId = this.messageId++; 30 31 // ユーザーID 1000のプロファイルを取得する 32 // { "messageId": 1, "action" : "GetUserProfile", "userId" : 1000 } 33 var message = new 34 { 35 messageId = tmpMessageId, 36 action = "GetUserProfile", 37 userId = userId 38 }; 39 40 dynamic response = null; 41 EventHandler<MessageReceivedEventArgs> handler = null; 42 handler = (sender, e) => 43 { 44 // e.Message にはレスポンスのJSONが含まれている 45 // { "messageId" : 1, "result" : { "user_name" : "hoge", "age" : 10 }} 46 var json = (dynamic)JsonConvert.DeserializeObject(e.Message); 47 if (json.messageId == tmpMessageId) 48 { 49 // レスポンスを変数に保存する 50 // この匿名関数ではreturnできない。 51 response = json; 52 } 53 54 // 受信が成功したのでイベントハンドラを消す 55 websocket.MessageReceived -= handler; 56 }; 57 58 // リクエストする前にイベントハンドラを設定する 59 websocket.MessageReceived += handler; 60 61 // リクエストする 62 websocket.send(JsonConvert.SerializeObject(message)); 63 64 // 受信できるまで待つ 65 while(true) 66 { 67 if(response != null) 68 { 69 break; 70 } 71 await Task.Delay(100); 72 } 73 74 return JsonConvert.SerializeObject(response); 75 } 76}

Ⅳ. 聞きたいこと

どの点を改善すれば同時複数リクエストでも失敗しないサーバになりますでしょうか?
また、現在の構成以外でも1と3をうまく通信させる方法はありますでしょうか?

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答1

0

ベストアンサー

最初のリクエストで messageId が 1 になります。
次のリクエストで 2 になります。

最初のレスポンスでハンドラが呼ばれます。tmpMessageId は 1 です。messageId は 2 です。
レスポンスは捨てられます。

来ないレスポンスをずっと待ち続けます。

あと、シングルトンならコンストラクタは private にしてください。

投稿2017/05/12 22:17

Zuishin

総合スコア28656

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

aglkjggg

2017/05/13 04:14

ご回答ありがとうございます。 説明不足で申し訳ありません。 「messageId は常にユニークな数値」と書いておりましたが、正しくは messageIdが1でリクエストしたものにはmessageIdが1でレスポンスが帰ってきます。 messageIdが2でリクエストしたものにはmessageIdが2でレスポンスが帰ってきます。 複数リクエストが問題ないことは確認が取れています。 messageIdが1,2を同時にリクエストしてもmessageId1,2をそれぞれ返してくれます。 「messageIdが異なっていればmessageIdが同じものが来るまで待機」という意味合いでプログラムを書いていました。 シングルトンの件指摘ありがとうございます。 修正しました。
Zuishin

2017/05/13 04:39

私が読み間違えていたようですね。handler が呼ばれるかどうか、呼ばれたときに json.messageId と tmpMessageId がどうなっているか、ログをとってみてください。無限ループになっているということは、response がずっと null なのではないかと思います。
aglkjggg

2017/05/13 05:28 編集

確認した所、messageIdが異なる別のイベントハンドラでレスポンスを受け取っていた為、本来受け取りたいイベントハンドラがずっと待ち続ける動きをしていました。 何度も検証した所タイミング次第という部分があります。 タイミングが良ければそれぞれのイベントハンドラでレスポンスを受信できますが、 タイミングが悪ければ期待するイベントハンドラでレスポンスを受信できません。 この点を解決する為に調べた所イベントの処理が得意というRx(Reactive Extensions)を知ったのですが、 HTTPのリクエストがイベントではなく単なるメソッドなので、 HTTP側の処理とWebSocket側の処理をRxでうまく記述する方法が分かりませんでした。
Zuishin

2017/05/13 05:45

他のイベントハンドラで受け取っていても登録されたハンドラ全てが呼ばれるのではないかと思うのですが、そうはならなかったということですか?
aglkjggg

2017/05/13 06:24 編集

ありがとうございます! 頂いたコメントで気づき、解決しました! messageId の一致する、しないに関わらずイベントハンドラが削除されていたのが問題でした。 以下のように変更するとうまくいきました。 websocket.MessageReceived -= handler; ↓ if(response != null) { websocket.MessageReceived -= handler; }
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.50%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問