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

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

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

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

Q&A

解決済

4回答

15096閲覧

TcpClient接続

退会済みユーザー

退会済みユーザー

総合スコア0

C#

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

0グッド

3クリップ

投稿2017/06/06 01:42

編集2017/06/06 02:43

C#の勉強でサーバーとクライアント、2つのアプリを作りクライアントからtextBoxに入力した文字をサーバーに送信するというものです。
一通り完成し少しづつ改良しているのですが、行き詰ったので質問させていただきます。
今まで正常に動いていたクライアントのVer.はtextBoxに文字を入力し、送信ボタンを押す度に

C#

1 public void Send() 2{ 3TcpClient client = new TcpClient(); 4 client.Connect("127.0.0.1", 9000); 5・・・省略・・・ 6client.Close() 7}

のようにTcpClientをnewしてIPアドレスの指定をしてソケットをClose()していました。
サーバー側は、接続を受け付けて、クライアントから文字の受信が終わればソケットをClose()としていました。
その後、無限ループでまた接続を受け付け→処理→Close()の繰り返しです。
これでは何度も同じ作業をすることになるので、クライアントのTcpClientのnewとIPアドレスの指定を一度だけして2回目以降は文字の送受信だけを行いたいと思い、下記のようにしてみました。

C#

1//メンバ変数 2 TcpClient client = new TcpClient(); 3 4 public void Send(byte num, string text) 5 { 6 if(client.Connected != true) 7 { 8 client.Connect("127.0.0.1", 9000); //TCP/IP接続を行う 9 } 10 11 //通信ストリームの取得 12 NetworkStream stream = client.GetStream(); 13 byte[] SendBuffer = Encoding.Unicode.GetBytes(num + text); 14 stream.Write(SendBuffer, 0, SendBuffer.Length);

引数などは気にしないでください。
変更箇所は、メンバ変数としてTcpClientをnewして、IPアドレスの指定はif文で分岐、処理の最後にClose()をしない、という3か所です。
サーバー側の変更箇所はClose()をしない、という1か所だけです。

そしていざ送信をすると、1回目は普通に送信出来ますが、2回目以降が反応してくれません。
デバックしながら見てみると、クライアント側はちゃんと送信までできています。今回のソースコードでいうSendBufferにはちゃんとコードが収まっています。
しかし、サーバー側は受信した際に処理が行われる場所にデバックポイントを貼っているにもかかわらず、まったく反応していません。

送信はできているハズなのですが、どうにもわかりません。
調べてみると使い捨て???みたいな事が書いてありましたが私にはよくわかりませんでした。

以上、どうすれば2回目以降も送受信ができるのか教えてください。
よろしくお願いいたします。

サーバーのコードです。(今回必要と思う箇所だけ抜粋)

C#

1 //// 接続待ち開始ボタンのクリックイベント 2 private void button1_Click(object sender, EventArgs e) 3 { 4 if (SLTAlive == false) 5 { 6 // スレッド終了指示フラグを未終了に設定 7 SLTAlive = true; 8 9 // 接続待ち用スレッドを作成 10 ListeningCallbackThread =new Thread(Start); 11 12 // 接続待ち用スレッドを開始 13 ListeningCallbackThread.Start(); 14 15 } 16 } 17 18 19 //========================================= 20 21//接続要求受け入れ開始 22 public void Start() 23 { 24 25 // 接続要求受け入れ開始 26 server.Start(); 27 form.label1.Text = "サーバー開始"; 28 29 try 30 { 31 // 受信の受付を行なうための無限ループ 32 33 while (form.SLTAlive == true) // スレッド終了指示フラグでの終了指示がある場合はループ終了 34 { 35 // 受信接続キュー内で、接続待ちがあるか判断 36 if (server.Pending() == true) 37 { 38 // クライアントからの接続を受け付ける 39 TcpClient ClientSocket = server.AcceptTcpClient(); 40 41 // 通信ストリームの取得 42 NetworkStream stream = ClientSocket.GetStream(); 43 44 //Receiveメソッドの呼び出し 45 Receive(stream); 46 47 } 48 49 // 短時間だけ待機 50 51 Thread.Sleep(100); 52 53} 54 } 55 catch (Exception ex) 56 { 57 form.label1.Text = "サーバー終了"; 58 } 59 60 } 61 62 //========================================= 63 64//クライアントからの受信 65 public void Receive(NetworkStream stream) 66 { 67 // クライアントからの電文の受信 68 byte[] ReceiveDate = new byte[2000]; 69 stream.Read(ReceiveDate, 0, ReceiveDate.Length); 70 71 string str = new string(Encoding.Unicode.GetString(ReceiveDate, 0, ReceiveDate.Length).ToCharArray()); //オブジェクトの生成 72 str = str.TrimEnd("\0".ToCharArray()); 73 74・・・以下省略・・・ 75 }

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

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

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

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

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

YAmaGNZ

2017/06/06 02:24

送受信なのでクライアント側だけではなくサーバ側のソースもないと判断できません。
退会済みユーザー

退会済みユーザー

2017/06/06 02:43

サーバーのソースを追加しました。よろしくお願いいたします。
guest

回答4

0

ベストアンサー

サーバ側ですが

C#

1 // 受信接続キュー内で、接続待ちがあるか判断 2 if (server.Pending() == true) 3 { 4 // クライアントからの接続を受け付ける 5 TcpClient ClientSocket = server.AcceptTcpClient(); 6 7 // 通信ストリームの取得 8 NetworkStream stream = ClientSocket.GetStream(); 9 10 //Receiveメソッドの呼び出し 11 Receive(stream); 12 13 } 14

とありますが、この状態では1回目のReceiveが呼び出された後、接続要求が来ない限り次のReceiveが
呼び出されません。
今回のクライアント側がCloseしないよう修正されたことで、接続要求は最初の1回目しか来ないことになります。

今回のようなコネクションを維持するような修正を行う場合、現状の
コネクション接続要求受付→受信→送信
というような順次処理を行うようなロジックはやめたほうがいいと思います。
イベントやコールバックで状態遷移をしっかり把握し接続要求や受信等を処理するようにしたほうがいいと思います。

投稿2017/06/06 03:09

YAmaGNZ

総合スコア10242

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

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

退会済みユーザー

退会済みユーザー

2017/06/06 04:11

そういう事だったんですね。ありがとうございます。 また、簡単に再現しようとすればどうするのが一番良いでしょうか? お手数をおかけしますが、よろしくお願いいたします。
YAmaGNZ

2017/06/06 04:48

エラー処理も何も考えずに現状のロジックで行うとすれば // 受信接続キュー内で、接続待ちがあるか判断 if (server.Pending() == true) { // クライアントからの接続を受け付ける TcpClient ClientSocket = server.AcceptTcpClient(); connected = true; } // 接続済みか判断 if (connected == true) // 通信ストリームの取得 NetworkStream stream = ClientSocket.GetStream(); //Receiveメソッドの呼び出し Receive(stream); } という感じで、現在接続状態か判断するフラグを用意する等で判断し 受信処理を行うという感じでしょうか ただ、この場合、クライアントが終了してコネクションが切断される等 意図しない異常が発生した場合に対応できていません。 本来であれば、現在のコネクション状態と起こった事象によりどういう状態遷移をすべきか 決定し、プログラムすべきです。
退会済みユーザー

退会済みユーザー

2017/06/06 05:11

こちらを参考にやってみます。 ありがとうございました。
退会済みユーザー

退会済みユーザー

2017/06/06 05:46

度々失礼致します。 先ほど記載していただいた方法でやってみましたが、一度目しか送信できませんでした。 そして、とりあえず受信ができるかどうかのテストで、 public void Start() { // 接続要求受け入れ開始 server.Start(); form.label1.Text = "サーバー開始"; try { // 受信の受付を行なうための無限ループ while (form.SLTAlive == true) // スレッド終了指示フラグでの終了指示がある場合はループ終了 { // クライアントからの接続を受け付ける TcpClient ClientSocket = server.AcceptTcpClient(); // 通信ストリームの取得 NetworkStream stream = ClientSocket.GetStream(); if (stream != null) { //Receiveメソッドの呼び出し Receive(stream); } // 短時間だけ待機 Thread.Sleep(100); } } catch (Exception ex) { form.label1.Text = "サーバー終了"; } } としてみましたがダメでした。 原因がイマイチわかりません。 ご教授していただけると幸いです。 よろしくお願いいたします。
YAmaGNZ

2017/06/06 06:04

AcceptTcpClientはブロッキングメソッドなので、接続要求がある場合のみ行うべきです。 この場合、1回目は接続要求がある為、AcceptTcpClientが実行されてTcpClientを返してきます。 2回目の実行時には接続要求がない為、接続要求が来るまでAcceptTcpClientは返ってきません。 ですので、それ以降の処理が実行されない状況になります。
退会済みユーザー

退会済みユーザー

2017/06/06 06:26

つまりこのソースではできないということですよね・・ クライアント側で送信する度にnewする→接続要求がある→AcceptTcpClientが返ってくる。 クライアント側で送信する度にnewをしない→接続要求がない→AcceptTcpClientが返ってこない。 BeginAcceptTcpClientを使えばこの問題は解決できますでしょうか?
YAmaGNZ

2017/06/06 06:45 編集

接続要求がある時のみAcceptTcpClientを実行すればいいのです。 先ほど私が書いたように // 受信接続キュー内で、接続待ちがあるか判断 if (server.Pending() == true) { // クライアントからの接続を受け付ける TcpClient ClientSocket = server.AcceptTcpClient(); } とすればいいはずです。 ただ、これだけだと、接続要求が来ていない時に受信ロジックに入ってエラーとなってしまう為、 先ほど書いたように接続した時にフラグを立てて、そのフラグで受信処理を行うようにすれば いいはずです。 途中での切断等を考慮しないのであれば     // 接続要求受け入れ開始   server.Start();   form.label1.Text = "サーバー開始";   // クライアントからの接続を受け付ける   TcpClient ClientSocket = server.AcceptTcpClient();   try   {     // 受信の受付を行なうための無限ループ     while (form.SLTAlive == true) // スレッド終了指示フラグでの終了指示がある場合はループ終了     { // 通信ストリームの取得 NetworkStream stream = ClientSocket.GetStream(); とAcceptTcpClientをループの外に出してしまうのも手ではあります。
退会済みユーザー

退会済みユーザー

2017/06/06 07:02

できました!! 何度も私の依頼にご対応していただき、ありがとうございました。 これを基盤にして改良していきます。
guest

0

TCPの遷移のいい画像が無かったので、適当に貼っておきますが
コネクションの状態遷移
通信をする前に、通信を始めるシーケンスがあるのですが、そのTcpClientでは、newする時にその処理をするのではないでしょうか?

投稿2017/06/06 02:43

yoorwm

総合スコア1305

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

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

0

Closeはした方がいいのでは…
単純に1回しか待ち受けてないので1回目受け取ったら処理が終わってるだけのような気がしますが。

投稿2017/06/06 02:10

yy_tn

総合スコア299

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

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

退会済みユーザー

退会済みユーザー

2017/06/06 02:18

記載不足でした。すみません。 サーバー側は無限ループをしていますのでクライアントからの受付を終了し処理が終われば、また受信するようにしています。
退会済みユーザー

退会済みユーザー

2017/06/06 02:21

Closeをしてしまうと使い捨てになってしまうようなのでCloseをわざとしないようにしているのですが、できないのでしょうか?
yy_tn

2017/06/06 02:31 編集

すみません、回答ではないのに回答の所に書いてしまった上に ちゃんと読めていなかったです、申し訳ない。 TcpClient自体は closeしない で合ってるかと思います。 NetworkStreamのことと勘違いしました。
guest

0

常にnewする処理を書くのが手間なら、newする処理を共通メソッド化してみたらどうでしょうか。
そうすれば、共通メソッドをcallするだけで開通できるので、コードもスッキリし、開発量も減ると思います。

投稿2017/06/06 01:58

koizumi

総合スコア230

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

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

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

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

ただいまの回答率
85.49%

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

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

質問する

関連した質問