なべひろBlog

プログラミングをメインに仕事に関するアレコレを発信しています。

C#で今時な書き方の非同期なTCPサーバを作ってみる 概要編

もくじ
C#を用いたTCPサーバのサンプルソースは多くありますが、かなり以前の手法だったりして効率的な記述じゃないのもあります。
(効率的な記述だからと言って高速に動作する訳ではありませんが...)
今回はMicrosoftが公開している非同期TCPサーバのサンプルソースをベースに今時な書き方をして、かつ簡単に使い回しができるようにアレンジしてみます。
これを書いている時点ではC#の最新バージョンは8です。
ちなみに今回紹介するサンプルソースは私が数年前に作ったプログラムを今風に若干変更したプログラムで、基本的な動作は変わっておらず幾つかの業務で使用しても問題の出てないプログラムです。
但し、使い方によっては何かしら問題が出るかもしれませんがご了承ください。
また他にもコード数が少なく非同期で行える手法もありますが、今回の手法は一番高速に動作する手法です。(たぶん)
コード数が多くなるのがネックですが、一度汎用的なClassを作って使い回しすれば最初の労力だけで済みますので最初はがんばりましょう。
サンプルソースは下記となります。
尚、今回はBeginxxxメソッドを使用していますがxxxAsyncを使用した非同期TCPサーバの事例は下記リンクとなります。
サンプルソースで使用しているローカル変数は理解しやすいように型名を記述していますがvarでも全然OKです。
準備
今回はWPFアプリケーションとなりますが、NuGetで必要なのをインストールします。
ReactiveProperty.WPF
以前はReactivePropertyという名称でしたが、新しくなりました。
ここで問題になるのが前のソースコードからコピペするとxamlファイルでエラーになる場合があります。
検索してもヒットしなかったので模索して動作させつつ作者様へ問い合わせしたらOKとの事でした。
その違いは
ReactivePropertyのバージョンが7より前は
xmlns:ri="clr-namespace:Reactive.Bindings.Interactivity;assembly=ReactiveProperty.NETCore"
ReactivePropertyのバージョンが7からは
xmlns:ri="clr-namespace:Reactive.Bindings.Interactivity;assembly=ReactiveProperty.WPF"
となります。
最後の「NETCore」が「WPF」に変わります。
作者様が提供しているドキュメントには掲載されていましたので私が探し足りなかったようです。
System.Text.Encoding.CodePages
TCP通信で送受信データが文字列でShift-jisの場合に必要となります。
そしてどこか1箇所でいいので
System.Text.Encoding.RegisterProvider(System.Text.CodePagesEncodingProvider.Instance);
と記述する事で
Encoding.GetEncoding("shift_jis")
などで例外が出なくなります。
System.ServiceModel.Primitives
SynchronizedCollectionを使う時必要になります。
今回は接続してきたクライアント一覧情報で使用します。
詳細な検証まではできてませんが(実質不可能?)、非同期で接続や切断、データ送受信を行うのでスレッドセーフを心がけて例外が発生しないようにしています。
主なメソッド
Server
今回主役のコンストラクタです。
ここでエラー表示の設定、エラーログ、データ受信に実行されるメソッドを引数で渡します。
尚、データ受信時に行う処理はアプリケーションによってさまざまなので、このClassにあるデータ受信時のコールバック関数で処理をしてもかまいませんし、どこかにアクセス可能なメソッドを用意してデータ受信時のコールバック関数でメソッドを実行してもかまいません。
~Server
デストラクタです。
特に触る事はないです。
message
エラーログに追記したい時、追記する文字列を引数として実行します。
但し、ここではスレッドセーフなBlockingCollectionに追加するだけで実際ファイルに書き込むのは別タスクで行います。
ファイルアクセスは遅い動作なので実際にファイルに書き込むのは別タスクで行い速い処理の邪魔はしないようにしています。
Open
ここでTCPサーバのポートがオープンされTCPクライアントの接続を許可します。
引数は自分のIPアドレス(PCに有線LANと無線LANがある時にどれを使うかの指定)、ポート番号、接続可能なTCPクライアントの最大数、受信データの最大容量(バイト単位)となります。
accept
ここでTCPクライアントの接続を別タスクで待機します。
これは触る必要はなく、ユーザープログラムで呼び出す必要もありません。
acceptCallback
TCPクライアントから接続要求が来た時に動作するコールバック関数です。
これも触る必要はなく、ユーザープログラムで呼び出す必要はありません。
readCallback
唯一?編集する必要があるメソッドです。
ただしユーザープログラムから呼び出したりする必要はありません。
TCPクライアントからデータが来た時に動作するコールバック関数となります。
writeCallback
送信が終了すると呼び出されるコールバック関数です。
これも特別な事がない限り触る必要もなく、ユーザープログラムで呼び出す必要もありません。
Close
意図的にポートを閉じたい時に実行します。
デストラクタでも呼び出されるので必ずしも呼び出す必要はありません。
StateObject
TCPクライアントごとにインスタンスが生成され、受信したデータを入れる入れ物があったります。
これも特に触る必要はありません。
結構多くのメソッドがありますが、実際に触るのはreadCallbackだけで、他のメソッドやコールバック関数は触る必要はないです。
また、汎用性を考えコンストラクタで受信時に実行されるメソッドを指定すれば、このTCPサーバClassはほぼ無編集で使えます。
内容が長くなりそうなのでプログラムの詳細は次回にします。
関連記事