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

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

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

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

TCP

TCP(Transmission Control Protocol)とは、トランスポート層のプロトコルで、コネクション型のデータサービスです。

サーバ

サーバは、 クライアントサーバモデルにおいてクライアントからの要求に対し 何らかのサービスを提供するプログラムを指す言葉です。 また、サーバーソフトウェアを稼動させているコンピュータ機器そのもののことも、 サーバーと呼ぶ場合もあります。

Q&A

解決済

2回答

18138閲覧

C# TCP通信の複数クライアント

退会済みユーザー

退会済みユーザー

総合スコア0

C#

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

TCP

TCP(Transmission Control Protocol)とは、トランスポート層のプロトコルで、コネクション型のデータサービスです。

サーバ

サーバは、 クライアントサーバモデルにおいてクライアントからの要求に対し 何らかのサービスを提供するプログラムを指す言葉です。 また、サーバーソフトウェアを稼動させているコンピュータ機器そのもののことも、 サーバーと呼ぶ場合もあります。

0グッド

0クリップ

投稿2018/04/10 01:45

編集2018/04/10 02:33

現在Visualstudioにてwindowsフォームアプリケーションでチャットアプリを作成しています。
下記は1対1の通信のサーバ側のコードです。
そこから複数クライアントに対応したいと思っています。
イメージでは接続できたクライアントをスレッドに渡し、待ち受け側はそのままループで頭に戻り受付再開するのかと思い、取得したポートナンバを参照渡し(ref)で渡そうと考えたのですがうまくいきません。
詳細->うまくいかない内容ですが、
非同期メソッドにて参照渡しができない点。ですのでいきなり行き詰りました。。

なにか良い方法はないでしょうか?
よろしくお願いします!

C#

1 /// <summary> 2 /// 待ち受けボタンクリック処理 3 /// </summary> 4 /// <param name="sender">オブジェクト</param> 5 /// <param name="e">イベント</param> 6 private void WaitServerBtn_Click(object sender, EventArgs e) 7 { 8 try 9 { 10 if (status != SeverStatus.IDLE) 11 { 12 StopSever(); 13 } 14 else 15 { 16 string ipAddress = this.getIpAdd1.IpAddress; 17 string portString = this.myPortEdit.Text; 18 port = Convert.ToInt32(portString); 19 20 StartServer(port); 21 } 22 } 23 catch (Exception exept) 24 { 25 MessageBox.Show(exept.Message); 26 } 27 } 28/// <summary> 29 /// サーバー側スレッド処理 30 /// </summary> 31 /// <param name="PortnumSV">ポート番号</param> 32 void StartServer(int PortnumSV) 33 { 34 Task.Factory.StartNew(() => 35 { 36 RunRecvMessageAsync(PortnumSV); 37 }); 38 } 39 /// <summary> 40 /// 非同期受信メッセージ処理 41 /// </summary> 42 public async void RunRecvMessageAsync(int PortnumSV) 43 { 44 status = SeverStatus.CONNECT_INIT; 45 46 // クライアント接続&ネットストリームの初期化 47 TcpClient client = null; 48 NetworkStream stream = null; 49 50 try 51 { 52 IPAddress localAddr = IPAddress.Parse("127.0.0.1"); // IPアドレスの設定*今回はローカルしか使わないので直値 53 54 // サーバーを開始 55 server = new TcpListener(localAddr, PortnumSV); 56 server.Start(); 57 58 Byte[] bytes = new Byte[17]; 59 60 //待機中の間ループして探し続ける 61 while (true) 62 { 63 switch (status) 64 { 65 case SeverStatus.CONNECT_INIT: 66 SafeSetLable(Properties.Resources.ConnectWait); //接続待機中のラベル表示 67 this.WaitServerBtn.Invoke(new Action(() => 68 { 69 this.WaitServerBtn.Text = Properties.Resources.Cutting; //待ち受けボタンを切断に変更 70 })); 71 72 status = SeverStatus.CONNECT_ACCEPT_WAIT; 73 break; 74 75 // ステータス = 待ち受けの時 76 case SeverStatus.CONNECT_ACCEPT_WAIT: 77 if (server.Pending()) 78 { 79 client = server.AcceptTcpClient(); // クライアント接続待ち //クライアント接続前にサーバ切断するとerr! 80 stream = client.GetStream(); // ストリーム取得 81 SafeSetLable(Properties.Resources.Connected); // 接続されましたのラベル表示 82 83 status = SeverStatus.CONNECTED; //ステータス = 接続中 84 } 85 break; 86 87 // ステータス = 接続中の時 88 case SeverStatus.CONNECTED: 89 // メッセージを受信 90 // 読み込むデータが存在する? 91 if (stream.DataAvailable) 92 { 93 // データを読み込む 94 int readlen = stream.Read(bytes, 0, bytes.Length); 95 if (readlen != 0) 96 { 97 String data = System.Text.Encoding.UTF8.GetString(bytes, 0, readlen); 98 // UIスレッド以外から呼び出された時のためにinvokeする 99 this.ChatText.Invoke(new Action(() => 100 { 101 this.ChatText.Text += (Properties.Resources.Partner + ":" + data + "\r\n"); 102 })); 103 } 104 } 105 // クライアントから切断された? 106 if (client.Client.Poll(1000, SelectMode.SelectRead) && (client.Client.Available == 0)) 107 { 108 stream.Dispose(); // ストリームの開放 109 stream = null; 110 client.Close(); // クライアントとの接続終了 111 client = null; 112 113 status = SeverStatus.CONNECT_ACCEPT_WAIT; 114 } 115 break; 116 // ステータス = アイドルの時 117 case SeverStatus.IDLE: 118 default: 119 break; 120 } // ループ終わり 121 122 // 続きの実行は適当な空いているスレッドに割り当てるようにする 123 await Task.Delay(1).ConfigureAwait(false); 124 } 125 } 126 catch (SocketException socketEx) 127 { 128 // MessageBox.Show(except.Message); 129 if (socketEx.ErrorCode == WSAEINTR) 130 { 131 MessageBox.Show("クライアント側で通信が切断されました。\n"); 132 } 133 134 } 135 finally 136 { 137 if (stream != null) 138 { 139 stream.Dispose(); 140 stream = null; 141 } 142 if (client != null) 143 { 144 client.Close(); 145 client = null; 146 } 147 if (server != null) 148 { 149 server.Stop(); 150 server = null; 151 SafeSetLable("切断されました"); 152 this.WaitServerBtn.Invoke(new Action(() => 153 { 154 this.WaitServerBtn.Text = "待ち受け"; 155 this.WaitServerBtn.Enabled = true; 156 })); 157 } 158 status = SeverStatus.IDLE; // ステータス = アイドル 159 } 160 } 161 /// <summary> 162 /// サーバー側スレッド処理 163 /// </summary> 164 /// <param name="PortnumSV">ポート番号</param> 165 void StartServer(int PortnumSV) 166 { 167 Task.Factory.StartNew(() => 168 { 169 RunRecvMessageAsync(PortnumSV); 170 }); 171 } 172 private async void StopSever() 173 { 174 if (status != SeverStatus.IDLE) 175 { 176 this.WaitServerBtn.Enabled = false; 177 server.Stop(); 178 179 while (status != SeverStatus.IDLE) 180 { 181 await Task.Delay(100); //Task.waitで待つとデッドロックする 182 } 183 } 184 } 185

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

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

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

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

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

y_waiwai

2018/04/10 02:25

うまくいかないとは、なにがどう、うまくいかないんでしょうか
guest

回答2

0

ベストアンサー

◇ベストカレントプラティクス
WebSocketで作成されるのが一番だと思います。

◇以下は処理案です。
1,あまりコードをよく見ていませんが、server.AcceptTcpClientの戻り値をスレッドプールで処理するのも一つの手ですが、クライアントのメッセージを受信部分は非同期処理のserver.BeginAcceptTcpClientを使うのも一つの手かと。
2,RunRecvMessageAsync内で、 server = new TcpListener(localAddr, PortnumSV);を行うのではなく。StartServerStopSeverで行ってください。

3,TcpListenerを使っても最終的には独自の通信プロトコルを作成する必要があるため、TcpListenerではなくHttpListenerを使うと、プロトコルのデバック作業が楽になります。
動作確認してませんが、サンプルソースです雰囲気を感じ取ってくださいな。

C#

1private HttpListener server = new HttpListener(); 2private readonly object locker = new object(); 3public Action<string> OnLogWrite;// ログ出力用 4public void Start() 5{ 6 lock (this.locker) 7 { 8 this.server.Prefixes.Add(ConfigurationManager.AppSettings["prefix"]); 9 this.server.Start(); 10 this.server.BeginGetContext(this.OnRequested, this.server); 11 } 12} 13public void Stop() 14{ 15 lock (this.locker) 16 { 17 this.server.Close(); 18 this.server = new HttpListener(); 19 } 20} 21public void OnRequested(IAsyncResult res){ 22 var listener = res.AsyncState as HttpListener; 23 if (!listener.IsListening) 24 { 25 // 受信開始→終了でOnRequestedイベントが発火するため、受信待機状態でない時はSkip 26 return; 27 } 28 var context = listener.EndGetContext(res); 29 30 listener.BeginGetContext(this.OnRequested, listener); 31  // 以下はやりたいこと。 32}

4.RunRecvMessageAsyncメソッド内で、変数:statusをクライアントの接続の度に変更しているのも複数接続で、破綻しているのではないでしょうか。
5,画面やログの書き出しはactionを定義すると便利です。スレッドセーフを意識してくださいな。

◇参考情報
0. TcpListener#BeginAcceptTcpClient

  1. HttpListener クラス

コンパイル通してませんが、TcpListener の場合はこのような形です。

C#

1private TcpListener server = new TcpListener(); 2private readonly object locker = new object(); 3public Action<string> OnLogWrite;// ログ出力用 4public void Start() 5{ 6 lock (this.locker) 7 { 8 // ここらへんでlistenとbindが必要なはず。 9 // start 10 this.server.Start(); 11 this.server.BeginAcceptTcpClient(this.OnRequested, this.server); 12 } 13} 14public void Stop() 15{ 16 lock (this.locker) 17 { 18 this.server.Close(); 19 this.server = new TcpListener(); 20 } 21} 22public void OnRequested(IAsyncResult res){ 23 var listener = res.AsyncState as TcpListener; 24 var client = listener.EndAcceptTcpClient(res); 25 26 listener.BeginAcceptTcpClient(this.OnRequested, listener); 27 Task.Factory.StartNew(() => 28 { 29   //これ以降は各クライアントで行いたい処理。 30 client.Client.RemoteEndPoint; 31 var stream = client.GetStream(); 32 }); 33}

投稿2018/04/10 02:35

編集2018/04/10 06:48
umyu

総合スコア5846

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

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

退会済みユーザー

退会済みユーザー

2018/04/10 05:09 編集

わかりやすいコメントありがとうございます! BeginAcceptTcpClient なんですが調べmsdnなどで見てもいまいち理解できなかったのですが、実際につかっていく流れはどのようになりますか? .... BeginAcceptTcpClient(終わるときに呼ぶコールバックメソッド , オブジェクト) この第一引数にいれるのは private async void asynccallback() { await Task.Run(() => { // メッセージを受信 // 読み込むデータが存在する? if (stream.DataAvailable) { // データを読み込む int readlen = stream.Read(bytes, 0, bytes.Length); if (readlen != 0) { String data = System.Text.Encoding.UTF8.GetString(bytes, 0, readlen); // UIスレッド以外から呼び出された時のためにinvokeする this.ChatText.Invoke(new Action(() => { this.ChatText.Text += (Properties.Resources.Partner + ":" + data + "\r\n"); })); } } }); } =========================================================== これでよいのでしょうか?
umyu

2018/04/10 05:14 編集

>_Takuyaさんへ まず、async/awaitの世界は残念ながら忘れてください、現状混在させるのは修羅の道です。 きちんと一通り動作するものを作成してから、ソースコードをバージョン管理しつつasync/await対応をしてくださいな。再度説明しますが、TcpListenerを使うということは、独自の通信プロトコルを自力で作成する必要があります。
退会済みユーザー

退会済みユーザー

2018/04/10 05:21

はい。 今回このプロジェクトを勉強するにあたり、 自分でもかなりいろいろなことが混ざっていることがわかります、、、。 ひとつずつ整理して頑張りたいと思います! HttpListenerですが調べると自分が今作ろうとしているWINDOWSフォームアプリとは違うような説明があるのでが、同じ感じでロジック組んでも問題ないのですか?
umyu

2018/04/10 05:34 編集

>_Takuyaさんへ WINDOWSフォームアプリでもサーバー起動時に管理者権限が必要ですが、可能です。 最終的に貴方がやりたいことによるのですが、チャットを自力作成したいのなら、WebSocketのライブラリを使うのが一番手間がかからないと思います。 TCPListenerは単純な送信には便利なのですが、チャットルームを作るに当たり、例えば誰がこのチャットの発言しているのかの情報を送信する必要がありますが、クライアントから送られてきたチャットの文字と区別する必要があります。 そのために通信プロトコル HTTPをご存知でしょうか?HTTPの場合はGET URL HTTP 1.0 の文字列をサーバーに送信してます。例えば文字列の先頭に発言者のIDを付けるなどをして貴方が全部定義する必要があります。そして、この通信プロトコルをデバックする必要があります。HTTPを使うとブラウザの開発者ツールや他のHTTP Clientをデバック時に使えるので、開発負荷が軽減されるのです。
退会済みユーザー

退会済みユーザー

2018/04/10 07:12

>umyu 様 細かいところまでわかりやすく教えていただきありがとうございます!! WebSocket 調べながらやっていくとうまくいきました! これから教えていただいたTCPListenerやHttpListenerもチャレンジしようと思います。 ほんとに助かりました。 また機会がありましたらよろしくお願いいたします!!
umyu

2018/04/10 07:49

>_Takuyaさんへ 解決してよかったです。
guest

0

非同期メソッドにて参照渡しができない点。ですのでいきなり行き詰りました。。

というのであるなら、ThreadLocal を使うとか。

投稿2018/04/10 03:14

y_waiwai

総合スコア87774

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

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

退会済みユーザー

退会済みユーザー

2018/04/10 07:02

コメントありがとうございます! y_waiwai 様 ThreadLocal 使ってみるとうまくいきました。 いつも助けていただいてありがとうございます!!
y_waiwai

2018/04/10 07:07

ああ、これはあくまで別解としてみてください。 他の回答のほうがまっとうな対処だと思いますよw
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

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

ただいまの回答率
85.48%

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

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

質問する

関連した質問