C#C#でTCPサーバーを安定して動作出来るように実装したので、その時のほぼ個人用メモ。(2020/02/07追記)ルーティングで激ハマリしたので、その対応を追加。Cに比べると滅茶苦茶分かりにくい。ネットで調べるとSocketとかTcpListenerとかNetworkStreamとか似たようなのが一杯出てきて、何が正解なのか分からんかったのと、Cでは分かりやすかったSelect(受信タイムアウト)の仕組みが、C#ではかなり分かりにくい。受信タイムアウトも「例外」扱いとか違和感がすごいよね・・・。考えても「例内」なんだが...。あとは、ネットのサンプルでは「一回受信できれば終わり」みたいなのが多くて、常時稼働させる場合の例が無く、再接続(リトライ)とか、その辺の仕組みが全然見当たらなかったので書いた。実装しようとしているのは以下の様な構成です。本記事で解説するのは主に赤枠破線部(C#版TCPサーバー)です。メイン画面からの起動→送受信スレッド起動→送受信処理あたりを記載します。破線部以外、特に分かりにくかった、クラスインスタンス側から画面への通知(コールバック)もまとめて記事にしておこうと思います。(2020/02/07追記)ルーティングに対応。見つかったが、使いづらく、異常処理に弱かった。画面からの送受信終了指示は、少々強引に割り込ます。送信と受信はスレッドを分けて、送信・受信間は同期させない。受信とは関係なく、定周期に送信をおこなえるようにするため。:リスナーは「TCPListener」、接続されるクライアントハンドルは「TCPClient」、TCP送受信は「NetworkStream」を使うと、通信が出来る。受信用バッファ「m_recv_buffer」は、通信相手とのプロトコルが決まっていない場合は、メンバにする必要はない。毎回の受信で受信バッファを用意すればよい。筆者の場合、受信電文のフォーマットが規定されていた(ヘッダ部12byte+データ部[可変])だったので、ヘッダ部受信とデータ部受信の2回に分けて受信する必要がある。いう場合はメンバにした方が簡単。0;}サーバースレッドを起動する処理。TcpListener」をtry-catchで囲むこと。CのTCPサーバで組むbindがこれに当たるらしく、bindに失敗するとCなら「-1」が返るが、C#だと例外を吐いて死ぬ。クライアントと切断後に同じソケットを使ってすぐに再接続を行う場合は、Cの場合はsetsockopt()で「SO_REUSEADDR」を指定するが、C#の場合はSetSocketOption()で「SocketOptionName.ReuseAddress」を指定する。(2020/02/07追記)そして、クライアント→サーバへの接続が、L3SWやルータ等を介して異なるネットワークになっている場合は、クライアントに対してSYN-ACKを返すことが出来ない現象が発生した。C#ではTTL(Time-To-Live)を明示的に設定してやらんとデフォルト「1」になるらしい(試した所、Cのwinsockではデフォルト128)。255)」で指定する。ネットに転がっているほとんどのサンプルにも書いておらず、苦戦した。WireSharkでSYN-ACKのパケットを見ることで解決できた。スレッドのハンドルは、メイン画面側からコントロールする必要があるため、内部に保持っておく。知らん人が好きにいじれないようにprivateにしておいて、ハンドル操作を行う関数を別途作るべし。(ServerStartがそうだね)「m_listener.Start();」でリッスン開始する。}}サーバー受信スレッドの実体。ポイントは、「m_listener.AcceptTcpClient()」でAcceptを行うが、クライアントからconnectされるまではブロックするということ。基本方針に書いたが、画面の処理、送信処理とは別スレッドのため、本スレッドがブロッキングしようが問題ないが、connect待ちの状態の時に停止させようとすると、割り込ませる必要があり、その方法が「m_listener.Stop()」である。ブロッキング中に割り込んでcatch句の方に飛ぶことが出来るため、そこで受信ループを抜けて、終了処理を行えばよい。(別に受信タイムアウトを待ってから終了でもいいけどね。その場合はbreakの先でlistener.StopすればOK)ちなみにノンブロッキングのBeginAcceptTcpClientというものがあるが、Accept時にイベントハンドルして処理を行うため、タイミングが制御しにくく、やめた方がよい。素直にスレッド化した方が安心。接続状態「m_connected」は、自前で管理した方がよい。m_clientに「connected」というメンバがいるが、closeした後もtrueのままだったり?、とてもじゃないけど仕様が謎で使い道がよく分からなかった。受信処理は別関数にしておいた。そんなに処理が大きくない場合は一緒でもいいけど。あと、受信バッファの初期化は、プロトコルが決まっていない場合は不要な処理。ポイントはNetworkStreamの「Read()」である。これは接続時に指定した「ReadTimeout」の時間、受信を待ち(ブロッキング)、タイムアウトすると例外(IOException)を吐く。これがC言語ユーザには違和感満載である。本処理は「ヘッダ部受信」→「ヘッダ部から残りサイズ計算」→「残りサイズ受信」という流れで組んでいるが、プロトコルが不明な場合は、Readの第三引数(最大受信byte数)を大きな値にしてしまえばよい。何秒周期かにヘルスチェック(生存確認)を投げるだけ。任意のタイミング(画面からのボタン操作)で投げたい場合は、送信処理をpublicで作成し、それをメイン画面から呼ぶ仕組みにするとよい。まとめReadは受信タイムアウト時に例外IOExceptionを吐くから気を付けよう。間違いなどあれば、受け付けます。Unity/ゲームプログラミング】初心者向け画面処理のメモ【右肩下がり】アクセス数(PV)、アドセンス収益が急減したので分析してみた【Unity】Androidビルドが突然できなくなった場合の対処法(SDKの設定項目が消えた)最近は通販で買うほうが安い時代。マップ自動生成のアルゴリズムリンクてぃってぃのゲームチャンネル月別アーカイブ検索にほんブログ村ブログパーツにほんブログ村はてなブログをはじめよう!kktt0312さんは、はてなブログを使っています。ブログを報告するスターをつけました引用をストックしましたストック一覧を見る閉じる引用するにはまずログインしてくださいログイン閉じる引用をストックできませんでした。再度お試しください閉じる限定公開記事のため引用できません。