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

2020/06/24

C# ソケット通信

アイキャッチ

C#を用いたTCPサーバのサンプルソースは多くありますが、かなり以前の手法だったりして効率的な記述じゃないのもあります。

(効率的な記述だからと言って高速に動作する訳ではありませんが...)

今回はMicrosoftが公開している非同期TCPサーバのサンプルソースをベースに今時な書き方をして、かつ簡単に使い回しができるようにアレンジしてみます。

これがMicrosoftが公開している非同期サーバー ソケットの例です。

尚、これを書いている時点ではC#の最新バージョンは8です。

ちなみに今回紹介するサンプルソースは私が数年前に作ったプログラムを今風に若干変更したプログラムで、基本的な動作は変わっておらず幾つかの業務で使用しても問題の出てないプログラムです。

但し、使い方によっては何かしら問題が出るかもしれませんがご了承ください。

また他にもコード数が少なく非同期で行える手法もありますが、今回の手法は一番高速に動作する手法です。(たぶん)

コード数が多くなるのがネックですが、一度汎用的なClassを作って使い回しすれば最初の労力だけで済みますので最初はがんばりましょう。

サンプルソースで使用しているローカル変数は理解しやすいように型名を記述していますがvarでも全然OKです。

尚、今回はBeginxxxメソッドを使用していますがxxxAsyncを使用した非同期TCPサーバの事例は下記リンクとなります。

準備

今回は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はほぼ無編集で使えます。

内容が長くなりそうなのでプログラムの詳細は次回にします。

自己紹介

自分の写真



新潟県のとある企業で働いてます。
【できる事】
電子回路設計
基板パターン設計
マイコンプログラム
C#(WinForms WPF)を使ったWindowsアプリケーション作成
PLCラダー
自動化装置アドバイザー
にほんブログ村 IT技術ブログ ソフトウェアへ

カテゴリ

このブログを検索

QooQ