なべひろBlog

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

C#でSystem.Net.SocketsのxxxAsyncを使ったTCPクライアントを作ってみる

前回はxxxAsyncを用いたTCPサーバを作ってみましたがTCPクライアントを作ってみます。
以前作ったTCPクライアントはこちらになりますが、これよりはコード数が少なくなります。
但し、前記事のTCPサーバを作った時と同じような注意点と、TCPサーバとは違う注意点もあります。
TCPサーバ編と重複する部分もありますが、そちらを見てない方のため必要な部分を全て解説します。
ちなみにサンプルはこちらになります。
プログラム全体像としてはTCPサーバに接続しデータを送信した後受信待ちになり、受信が完了すると切断します。
また、今回は非同期なusing ステートメントも追加してみました。
SocketAsyncEventArgs
このクラスを用いて非同期通信を行います。
インスタンスのイベントハンドラにイベント発生時のメソッドを定義したり送受信バッファの設定を行います。
終了時には破棄する必要がありますので相手から切断された時や自分で切断した時は破棄を忘れないでください。
接続のConnectAsyncメソッドでも解説しますが、ConnectAsyncメソッド実行前にSetBufferメソッドを実行し送信データをセットするとConnectAsyncメソッドが実行されると自動的にデータが送信されます。
AcceptAsync
TCPサーバへの接続を行います。
using SocketAsyncEventArgs e = new();
// 送信データをセット
e.SetBuffer(sendData, 0, sendData.Length);
// 接続先をセット
e.RemoteEndPoint = new IPEndPoint(serverAddress, port);
// 各種動作が完了した時のイベントハンドラ
e.Completed += new EventHandler(Completed);
// TCPサーバへ接続 
if (client.ConnectAsync(e) == true)
{
    // 接続がタイムアウトなら
    if (connectDone.Wait(timeOut) == false)
    {
        throw new InvalidOperationException("PLC 接続タイムアウト");
    }
}
ConnectAsyncメソッドの戻り値がfalseの場合、同期的に接続が完了した事になります。
falseの場合は特に何もせず次の処理を実行します。
戻り値がtrueなら非同期な接続になるので適度なタイムアウトを用いて接続できなかった時の処理が必要です。
SocketAsyncEventArgsでも解説しましたがConnectAsyncメソッド前にSetBufferメソッドが実行され送信データをセットし、ConnectAsyncメソッドでTCPサーバへ接続されると自動的にデータが送信されます。
尚、この場合登録されたイベントハンドラには接続完了のイベントは発生しますが送信完了のイベントは発生しません。
送信完了イベントが必要だったり任意のタイミングでデータを送信したい場合はConnectAsyncメソッド実行前にSetBufferメソッドを実行しないでください。
SendAsync
非同期送信です。
これも同様に戻り値がfalseなら同期的に処理が完了、trueなら非同期に処理が完了した事を示します。
e.SetBuffer(sendData, 0, sendData.Length);
if (client.SendAsync(e) == true)
{
    if (sendDone.Wait(timeOut) == false)
    {
        throw new InvalidOperationException("PLC データ送信タイムアウト");
    }
}
SendAsyncの戻り値がtrueでもfalseでも特に処理の違いはありません。
戻り値がtrueの場合任意の時間でタイムアウトを入れ、何かしら問題が発生した場合の処理だけ入れておけば大丈夫です。
ReceiveAsync
非同期受信です。
これもAcceptAsyncと同様に戻り値がtrueの場合は非同期に、falseの場合は同期的に処理が行われた事を示します。
e.SetBuffer(receiveData, 0, receiveData.Length);
if (client.ReceiveAsync(e) == true)
{
    if (receiveDone.Wait(timeOut) == false)
    {
        throw new InvalidOperationException("PLC データ受信タイムアウト");
    }
}
受信前に受信データを入れる入れ物を用意しSetBufferメソッドを実行すれば受信が完了すると入れ物に受信したデータが入ります。
また、受信処理が終わったらSocketAsyncEventArgs.BytesTransferredを参照する事で実際に相手から送られてきたデータ量が確認できます。
イベントハンドラ
SocketAsyncEventArgs.Completedに追加されたイベントハンドラは、接続や送信完了や受信完了でイベントが発生します。
イベント発生時に何か処理を行いたい時はイベントの要因を参照すれば可能です。
private void Completed(object sender, SocketAsyncEventArgs e)
{
    switch (e.LastOperation)
    {
        // 接続のイベントが発生
        case SocketAsyncOperation.Connect:
            connectDone.Set();
            break;
        // 送信完了のイベントが発生
        case SocketAsyncOperation.Send:
            sendDone.Set();
            break;
        // 受信完了のイベントが発生
        case SocketAsyncOperation.Receive:
            receiveDone.Set();
            break;
    }
}
関連記事