C#によるソケット通信3(クライアント編)

ネットワーク

この記事では、C#のソケット通信におけるクライアントの実装を説明します。

クライアントはリスナーに対して接続、データ送信、データ受信、切断を行います。

ソケット通信におけるクライアントの役割と実装について紹介します。
サンプルプログラムは以下になります。

GitHub - nomux2/SampleSocketConnection
Contribute to nomux2/SampleSocketConnection development by creating an account on GitHub.

リスナーに接続する(Connect)

クライアントは、リスナーのIPアドレス、ポートを指定して接続処理を行います。

クライアントを利用する際は、接続したいリスナーのIPアドレスとポート番号をあらかじめ確認しておく必要があります。

以下のConnetの処理を記載します。

private Socket _socket = null;
private IPEndPoint _localEndPoint;
private IPEndPoint _remoteEndPoint;
private bool _recving_flg = false;

BeginConnectという接続するための非同期メソッドがありますが、どのみちクライアントは接続できなければ何もできないということと、接続処理中に余計な処理を割り込ませてエラーにしたくない、などの理由からあえて接続に関しては非同期ではありません。

public bool Connect(string host, int port)
{
    try
    {
        //コネクションが閉じているときは新しく作成する
        if (this.IsClosed)
        {
            _socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
            _recving_flg = false;
        }

        //既に接続されている場合は、接続処理から抜ける
        if (_socket.Connected)
        {
            throw new ApplicationException("すでに接続されています。");
        }

        //接続する
        IPEndPoint ipEnd = new IPEndPoint(Dns.GetHostAddresses(host)[0], port);
        _socket.Connect(ipEnd);

        _localEndPoint = (IPEndPoint)_socket.LocalEndPoint;
        _remoteEndPoint = (IPEndPoint)_socket.RemoteEndPoint;

        //非同期データ受信を開始する
        this.StartReceive();

    }
    catch (Exception ex)
    {
        _errmsg = ex.Message;
        _errstack = ex.StackTrace;

        return false;
    }

    return true;

}

データ受信

データ受信をする際は、BeginReciveを実行します。この関数は非同期で動きますので実行すると終了を待たず処理を抜けます。

その後1度だけ受信処理を行うとパラメータで設定したコールバック関数を呼び出します。

データ受信待ち状態をそのまま続ける場合は、再度BeginReceiveを実行します。

/// <summary>
/// データの非同期受信を開始する
/// </summary>
public void StartReceive()
{
    if (this.IsClosed)
        throw new ApplicationException("閉じています。");
    if (_recving_flg)
        throw new ApplicationException("StartReceiveがすでに呼び出されています。");

    //初期化
    byte[] recv_buffer = new byte[1024];
    _recving_flg = true;

    SocketStateClass state = new SocketStateClass()
    {
        Buffer = recv_buffer,
        Encoding = this.Encoding,
        WorkSocket = _socket
    };

    //非同期受信を開始
    this._socket.BeginReceive(recv_buffer, 0, recv_buffer.Length, SocketFlags.None, new AsyncCallback(RecvCallback), state);
}

CallBackで呼び出されたとき、引数にIAsyncResult arがあります。この中にリスナー側がBeginSendで渡したstateオブジェクト(object型)が入っています。

ここでは受け渡し用にSocketStateClassクラス(以下にリンク貼っておきます)を作成し、リスナー側もクライアント側も共通で利用しています。

受信したSocketStateClassオブジェクトを取り出したらデータを処理します。
ここでは受信することを説明しています。
受信したデータの扱いについては割愛します。

サンプルプログラム側では、受信したデータはフォームの方へ渡しています。

//BeginReceiveのコールバック
private void RecvCallback(IAsyncResult ar)
{
    int len = -1;
    //読み込んだ長さを取得
    try
    {
        if (_socket == null) return;

        len = _socket.EndReceive(ar);

        //切断されたか調べる
        if (len <= 0)
        {
            this.Close();
            return;
        }

        //受信したデータを取得する
        SocketStateClass state = (SocketStateClass)ar.AsyncState;

        //受信データを処理する
        //割愛します。

        if (_socket != null)
        {
            //再び受信開始
            _socket.BeginReceive(state.Buffer, 0, state.Buffer.Length, SocketFlags.None, new AsyncCallback(RecvCallback), state);

        }
    }
    catch(Exception ex)
    {
        _errmsg = ex.Message;
        _errstack = ex.StackTrace;
    }

}

データ送信

データを送る際はBeginSendを使用します。ここでパラメータとして設定したstateが送信先のsocket(今回の場合、リスナー)に送信されます。

/// <summary>
/// メッセージ送信
/// </summary>
/// <param name="msg">メッセージ</param>
public void Send(string msg)
{
    //バイト配列にする
    byte[] buffer = this.Encoding.GetBytes(msg);

    //送信
    Send(buffer);
}

/// <summary>
/// データ送信
/// </summary>
/// <param name="buffer">データ</param>
public void Send(byte[] buffer)
{
    if (this.IsClosed)
        throw new ApplicationException("閉じています。");

    SocketStateClass state = new SocketStateClass()
    {
        Buffer = buffer,
        Encoding = this.Encoding,
        WorkSocket = _socket
    };

    //データを送信する
    _socket.BeginSend(buffer, 0, buffer.Length, 0, new AsyncCallback(CallBackSend), state);

}

CallBack関数にCallBackSendという関数を呼び出しています。特にCallBackしても行う処理が無いのでここでは説明は省きます。サンプルでは呼び出し元に送信完了のイベントを発生させています。

切断

クライアントの切断処理です。リスナーとの接続を切断します。

利用が終わったら切断しておかないとポートを使用したままになります。
もうソケットオブジェクトを使わない場合には、CloseとDisposeして破棄してしまいましょう

//切断
_socket.Shutdown(SocketShutdown.Both);

//ソケットを破棄
_socket.Close();
_socket.Dispose();

まとめ

やってみて、サンプルプログラムを作るのに時間がかかってしまいました。それを1記事でまとめようとしたら量が多すぎて3記事になってしまい、それでも1記事がとても長くなってしまい・・・。

なかなか文章って難しいなぁって思いました。誤字脱字多いし・・・。

この記事が皆様のお役に立てたら幸いです。

タイトルとURLをコピーしました