TcpListenerについて

タグの編集
投稿者 ポムNNN  (社会人) 投稿日時 2018/3/10 15:55:48
visual studio2015 で arduinoとパソコン をTCP/IPで通信するソフトを作っています。
arduinoがクライント、PCがサーバになるようにプログラムしています。

動作としましては、ボタン1でサーバを開始し、ボタン2でサーバ停止というプログラムになっています。

arduino側で 接続→送信→切断 のループのプログラムで動かすと上手くいくのですが、
初回接続後、接続しっぱなしで、arduinoからデータを送り続けるというパターンが上手くいきません。

下記プログラムの    Client = server.AcceptTcpClient   の部分が悪さをしている気がしました。
理由としては、arduinoからの接続要求が来ないのでサーバはダンマリ停止している状況だと判断しました。
使用するメソッドが間違っているのでしょうか?アドバイス頂けたらと思います。

Imports System.Net.Sockets
Imports System.Net
Public Class Form1
    Private Stop_Flg As Boolean
    Private server As TcpListener

    Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
        TextBox1.Text = ""
        Task.Run(Sub() DoWork())
    End Sub

    Private Sub Button2_Click(sender As Object, e As EventArgs) Handles Button2.Click
        Stop_Flg = True
    End Sub

    Public Sub DoWork()

        server = Nothing
        Stop_Flg = False

        Try

            Dim port As Integer = 9000
            Dim localAddr As IPAddress = IPAddress.Parse("192.168.250.19")

            server = New TcpListener(localAddr, port)
            server.Start()

            Invoke(New Action(Sub()
                                  TextBox1.Text = "サーバー開始"
                              End Sub))

            Dim data As String = Nothing
            Dim Client As TcpClient = Nothing
            Dim NetStream As NetworkStream

            While Stop_Flg <> True 

                If server.Pending Then 

                    While Stop_Flg <> True

                        Client = server.AcceptTcpClient
                        NetStream = Client.GetStream
                        Threading.Thread.Sleep(100)  '相手からデータ到着までの待機時間

                        If NetStream.DataAvailable = True Then

                            Dim reader As New IO.BinaryReader(NetStream)
                            Dim bytes As Byte() = reader.ReadBytes(3)

                            For Each i In bytes
                                Console.Write(Chr(i)) ' "123" と表示される
                            Next

                            Console.Write(vbNewLine)
                            reader.Close()

                        End If

                    End While

                    Client.Close()

                Else
                    Threading.Thread.Sleep(0)
                End If

            End While

        Catch e As Exception
            MsgBox(e.ToString)
        Finally

            server.Stop()
            TextBox1.Text = "終了"

        End Try

    End Sub
End Class
投稿者 kiku  (社会人) 投稿日時 2018/3/13 09:49:27
現在のソースでは、下記のような動作をします。
 1.サーバは、AcceptTcpClientメソッドでブロックして接続待機
 2.クライアントは、接続要求
 3.サーバは、クライアントから接続要求があると、
   AcceptTcpClientメソッドが終了する。
 4.クライアントは、接続完了を受け取る。
 5.クライアントは、データ送信する。
 6.サーバは、Sleepでデータ受信するまで待つ。
 7.サーバは、ReadBytesで受信データを取得する。
 8.サーバは、受信データを出力する。
 9.サーバは、whileで処理が戻る。
 10.サーバは、AcceptTcpClientメソッドでブロックして接続待機
 11.クライアントは、2回目のデータ送信をする。
 12.サーバは、接続待ちしているので、AcceptTcpClientで止まっている状態がつづく。
 13.サーバは、ClientとNetStream共に破棄していないため、
    クライアントからの2回目のデータ送信は受信できているが、
    サーバは、その受信データを読みだしていない。

AcceptTcpClientで接続を待つのではなく、
2回目のデータ受信に備えて、
ReadBytesなどを実施する必要があります。

参考になるURLを張っておきます。
http://nonsoft.la.coocan.jp/SoftSample/CS.NET/SampleTcpIpSvr.html


ここまでが質問に対する答えになります。

そのほか懸念点。
 ・サーバは、複数クライアントからの同時接続に対応していない。
  ※1対1の通信であるならば問題ありません。
 ・Sleepで待つのは良くないです。
 ・Client、NetStreamは、disposeをもっているので使わなくなったら破棄しましょう。


投稿者 ポムNNN  (社会人) 投稿日時 2018/3/13 23:12:53
kiku様 情報ありがとうございます。

現状としましては、いくつか疑問はありますが、目的通り動いている状況です。毎回違う値をクライアントからサーバに送って、値が同じことを確認できました。

>・サーバは、複数クライアントからの同時接続に対応していない。※1対1の通信であるならば問題ありません。
  今回の1対1の通信用のアプリを作っています。いつかは複数台を受けることができるようにする予定です。

>・Sleepで待つのは良くないです。
過去の投稿にありました。勉強不足でした。ご指摘ありがとうございます。

> 13.サーバは、ClientとNetStream共に破棄していないため、クライアントからの2回目のデータ送信は受信できているが、サーバは、その受信データを読みだしていない。
についてですが、1回目の読み出し後、ClientとNetStreamを破棄してしまうと、2回目の読み出し前にAcceptTcpClient か それに代わる事をしなければないといけない気がするのですが、どうなのでしょうか?私の解釈間違いだったら、すみません。
クライントとサーバが接続しっぱなしでデータをやり取りする場合でも、読み込み終了の度に Closeするもんなのでしょうか?
なので Client と NetStreamのCloseはコメント扱いになっています・・・・。

他の部分は、前回の投稿と同じなので今回は、サーバ部分のみ記載しています。

 Public Sub DoWork()
        Dim server As TcpListener
        Dim Client As TcpClient
        Dim netstream As NetworkStream

        server = Nothing
        Stop_Flg = False

        Try

            Dim port As Integer = 9000
            Dim localAddr As IPAddress = IPAddress.Parse("192.168.250.19")

            server = New TcpListener(localAddr, port)
            server.Start()

            Invoke(New Action(Sub()
                                  TextBox1.Text = "サーバー開始"
                              End Sub))

            Client = server.AcceptTcpClient

            While Stop_Flg <> True

                Dim bytes(2) As Byte

                netstream = Client.GetStream
                Dim ReadSize As Integer = netstream.Read(bytes, 0, bytes.Length)

                If ReadSize > 0 Then
                    For Each i In bytes
                        Console.Write(Chr(i))
                    Next
                    Console.Write(vbNewLine)

                End If

            End While

            'netstream.Close()
            'Client.Close()

        Catch e As Exception
            MsgBox(e.ToString)
        Finally
            server.Stop()
        End Try
投稿者 kiku  (社会人) 投稿日時 2018/3/14 09:15:19
Client と NetStreamの破棄のタイミングですが、
クライアントとの通信を終了したらになります。
では、通信が終了したらとはどういう条件かというと
どんなアプリケーションを作りたいかによって変わってきますが、
例えば下記が考えられます。

1.クライアントがデータ送信をする必要がなくなったため、
  クライアントから接続を切断したとき。
2.サーバはクライアントとの通信を終了したいとき。

●上記1に対応
netstream.Readは、ブロックする命令で
受信できたら受信できたデータ数を返しますが
クライアントが接続を切断すると、負の値(うる覚え)を返します。

負の値だったら、クライアントは接続を切断しているため、
サーバはClient と NetStreamを破棄して、
AcceptTcpClientでクライアントからの新しい接続を待ちます。

●上記2の対応
Stop_Flgがtrueになったら、
サーバはClient と NetStreamを破棄して、
アプリケーションを終了します。
投稿者 ポムNNN  (社会人) 投稿日時 2018/3/16 20:46:12
kiku様

完全に私の勘違いでした・・・。
もっと勉強いたします。

質問なのですが、複数台のクライアントを接続する場合は、TcpListenerクラスでも可能なのでしょうか?
より細かい設定ができるSocketクラスを使うべきなのでしょうか?

投稿者 kiku  (社会人) 投稿日時 2018/3/17 09:54:36
TcpListenerでも複数クライアントに対応できます。
すでにご案内している下記のサンプルも、そのような作りになっています。

http://nonsoft.la.coocan.jp/SoftSample/CS.NET/SampleTcpIpSvr.html

概要だけ説明しますと、
AcceptTcpClientの返り値は、TcpClientなのですが、
これは、TCP接続毎に作られます。

つまり、2つのクライアントから接続があった場合、
2つ作られることになるわけです。
2つできたらそれぞれを独立して処理していけば良いということです。

それぞれを独立させて処理するために、
スレッドなどを使っている感じです。

ご案内したサンプルは、まだサンプルレベルですが、
非常に良くできているので、良く解析し、理解されると良いかもです。
投稿者 ポムNNN  (社会人) 投稿日時 2018/3/17 19:04:51
kiku様

頂いたサンプルをよく理解して複数台対応のサーバーを作ってみたいと思います。
色々アドバイス頂けて、大変助かりました。ありがとうございました。