かってきままに!

わたくし、とんちゃんが日々の話題をかってきままに記していきます。

非同期ソケット通信(TCP) サーバー

2010-12-17 17:05:13 | C++/CLI

using namespace System;
using namespace System::IO;
using namespace System::Text;
using namespace System::Threading;
using namespace System::Diagnostics;
using namespace System::Net;
using namespace System::Net::Sockets;
using namespace System::Collections::Generic;


//AsyncAcceptCallback用Stateオブジェクト
ref class AsyncAcceptObject  {

public: Socket^  Listener;                               //リスナー用ソケット
          ManualResetEvent^  ConnectedEvent;   //接続完了イベント(接続 : true)

        //コンストラクタ
        AsyncAcceptObject()  {
            this->ConnectedEvent = gcnew ManualResetEvent(false);
        }

};

//AsyncRecieveCallback用Stateオブジェクト
ref class AsyncRecieveObject  {

public: Socket^  Soc;                           //通信用ソケット
          array^  ReceiveBuffer;     //受信用バッファ
          MemoryStream^  ReceivedData;  //受信データ連結用バッキングストア

        //接続中の通信用ソケットコレクション
        static initonly SynchronizedCollection^ SocketList;

        //staticなため複数のスレッドから同時にアクセス可能性があるのでスレッドセーフのものを使う
        //ただし For Eachなどの複合処理ではスレッドセーフは保証されないのでロックする必要がある
        //System.ServiceModelアセンブリへの参照設定が必要

        //コンストラクタ
        AsyncRecieveObject(Socket^ s)  {
            this->Soc = s;
            this->ReceiveBuffer = gcnew array(1024);
            this->ReceivedData = gcnew MemoryStream();
        }

        //スタティックコンストラクタ
private: static AsyncRecieveObject()  {
            SocketList = gcnew SynchronizedCollection();
         }

};

//BeginSend用非同期コールバック
void AsyncSendCallback(IAsyncResult^ ar)
{

    Socket^ socket = safe_cast(ar->AsyncState);

    //データの送信
    try  {

        //送信完了
        int bytesSent = socket->EndSend(ar); 
        Console::WriteLine("クライアントに {0} bytes 送信しました", bytesSent);

        //例外処理 (簡易的に全て System::Exception で受けています)
    }  catch(System::Exception^ e)  {  
        Debug::WriteLine("\n" + e + "\n");
    }

}

//BeginRecieve用非同期コールバック
void AsyncReceiveCallback(System::IAsyncResult^ ar)
{

    AsyncRecieveObject^ aro = safe_cast(ar->AsyncState);
    int length = 0;

    //データの受信
    try  {

        //受信完了
        length = aro->Soc->EndReceive(ar);
       
    }  catch (System::Exception^ e)  {
        //例外が発生したソケットをリストから削除
        aro->SocketList->Remove(aro->Soc);

        //ソケットを閉じる
        if(aro->Soc->Connected)
            aro->Soc->Shutdown(SocketShutdown::Both);

        aro->Soc->Close();

        Debug::WriteLine("\n" + e + "\n");
        Console::WriteLine(e->Message);
        return;

    }

    //クライアントから切断されたか調べる
    if (length <= 0)  {

        //切断したソケットをリストから削除
        aro->SocketList->Remove(aro->Soc);

        if(aro->Soc->Connected)
            aro->Soc->Shutdown(SocketShutdown::Both);
        aro->Soc->Close();

        Console::WriteLine("クライアントが切断しました");
        return;

    }

    //受信データの蓄積
    aro->ReceivedData->Write(aro->ReceiveBuffer, 0, length);

    //受信データの処理

    //ネットワークバッファのキューにデータがもうない場合(最後まで受信した時)
    if (aro->Soc->Available == 0)  {           

        //データの表示
        String^ str = Encoding::UTF8->GetString(aro->ReceivedData->ToArray());

        System::Console::WriteLine("クライアントから受信\n送信元アドレス {0}, ポート{1}\n受信内容 : {2}",
            safe_cast(aro->Soc->RemoteEndPoint)->Address,
            safe_cast(aro->Soc->RemoteEndPoint)->Port, str);

        //接続中の全てのソケットに応答メッセージを返す
        array^ SendBuffer = Encoding::UTF8->GetBytes(String::Format("サーバー受信完了\n送信元アドレス {0}, ポート{1}\n受信内容 : ",
            safe_cast(aro->Soc->RemoteEndPoint)->Address,
            safe_cast(aro->Soc->RemoteEndPoint)->Port));
  
         //コレクションのロック
        Monitor::Enter(AsyncRecieveObject::SocketList->SyncRoot);        

        for each (Socket^ socket in aro->SocketList) {
            socket->BeginSend(SendBuffer, 0, SendBuffer->Length,
                SocketFlags::None, gcnew AsyncCallback(&AsyncSendCallback), socket);
            socket->BeginSend(aro->ReceivedData->ToArray(), 0, aro->ReceivedData->ToArray()->Length,
                SocketFlags::None, gcnew AsyncCallback(&AsyncSendCallback), socket);
        }

        //アンロック
        Monitor::Exit(AsyncRecieveObject::SocketList->SyncRoot);

        //バッキングストアの初期化
        aro->ReceivedData->Close();
        aro->ReceivedData = gcnew MemoryStream();

    }

    //再び非同期受信の開始
    aro->Soc->BeginReceive(aro->ReceiveBuffer, 0, aro->ReceiveBuffer->Length,
        SocketFlags::None, gcnew AsyncCallback(&AsyncReceiveCallback), aro);

}

//BeginAccept用非同期コールバック
void AsyncAcceptCallback(System::IAsyncResult^ ar)
{

    AsyncAcceptObject^ aao = safe_cast(ar->AsyncState);
    Socket^ listener = aao->Listener;

    //接続処理
    try  {

        //接続完了&ソケットの取得
        Socket^ socket = listener->EndAccept(ar);

        Console::WriteLine("クライアントが接続しました。");
        Console::WriteLine("クライアント情報 : アドレス{0}, ポート{1}",
            safe_cast(socket->RemoteEndPoint)->Address,
            safe_cast(socket->RemoteEndPoint)->Port);

        //接続したソケットをリスト追加
        AsyncRecieveObject::SocketList->Add(socket);

        //非同期受信の開始処理

        AsyncRecieveObject^ aro = gcnew AsyncRecieveObject(socket);

        //非同期受信の開始
        socket->BeginReceive(aro->ReceiveBuffer, 0, aro->ReceiveBuffer->Length,
            SocketFlags::None, gcnew AsyncCallback(&AsyncReceiveCallback), aro);

    }  catch(System::Exception^ e)  {
        Debug::WriteLine("\n" + e + "\n");
        return;

    }

    //再び非同期にで接続受け入れを開始
    listener->BeginAccept(gcnew AsyncCallback(&AsyncAcceptCallback), aao);

    //接続イベントをセット
    aao->ConnectedEvent->Set();

}

//メイン
int main(array ^args)
{
   
    //ホスト名前を取得する場合
    //System::String^ host = System::Net::Dns::GetHostName();

    //使用するIPアドレスとポートの設定

    //ローカルホストを指定(適宜変更してください)
    String^ host = "localhost";

    //ホストのIPアドレスリストを取得
    array^ adressList = Dns::GetHostAddresses(host);

    //IPアドレスの列挙
    for each (System::Net::IPAddress^ ip in adressList) 
        System::Console::WriteLine(ip);

    IPAddress^ ipAdd;
 
    //ホストの持つIPアドレスリストからIPV4のアドレスを取得
    for each (IPAddress^ ip in adressList)
        if(ip->AddressFamily == AddressFamily::InterNetwork)         
            ipAdd = ip;

    //ポートの設定(適宜変更してください)
    int port = 2001; 

    //Listenの開始処理

    AsyncAcceptObject^ aao = gcnew AsyncAcceptObject();

    //リスナーの作成
    aao->Listener =
        gcnew Socket(AddressFamily::InterNetwork, SocketType::Stream, ProtocolType::Tcp);

    //使用するIPアドレス&ポートを設定
    aao->Listener->Bind(gcnew IPEndPoint(ipAdd, port));           
    aao->Listener->Listen(10);

    //Listen開始
    aao->Listener->BeginAccept(gcnew AsyncCallback(&AsyncAcceptCallback), aao);


    //1つの接続が確立するまで待機
    aao->ConnectedEvent->WaitOne();


    //終了処理

    Console::ReadLine();
    Console::WriteLine("終了します");

    //コレクションのロック
    Monitor::Enter(AsyncRecieveObject::SocketList->SyncRoot);

    try  {

        //接続中のソケットを全て切断
        for each (Socket^ socket in AsyncRecieveObject::SocketList)  {
            if(socket->Connected)
                socket->Shutdown(SocketShutdown::Both);

            socket->Close();
        }

    }  catch(System::Exception^ e)  {
        Debug::WriteLine("\n" + e + "\n");

    }

    //アンロック
    Monitor::Exit(AsyncRecieveObject::SocketList->SyncRoot);         

    Console::ReadLine();
    return 0;

}


別スレッドからフォーム上のコントロールを操作する

2010-12-14 17:54:47 | C++/CLI


        

         //別スレッドからメインスレッドのフォーム上のコントロールのメソッドを呼ぶためにデリゲートを定義

         //引数が必要な場合
        delegate void SetFocusDelegate1(int);

        //引数が必要ない場合  
        delegate void SetFocusDelegate2();


        //別スレッドからメインスレッドのフォーム上のコントロールのメソッドを呼ぶためのデリゲートに登録するメソッドを定義
        
        //引数有の場合
        void SetFocus1(int i)  {

            //別スレッドから呼ばれた場合は if文内に入る
            if (this->InvokeRequired)  {
              
                MessageBox::Show("別スレッドから呼び出し" + i, "InvokeRequired");
               
                //別スレッドからコントロールのメソッドをそのまま呼ぶとエラー
                //this->textBox2->Focus();
               
                //別スレッドからの呼び出しによる、メインスレッドのフォーム上のコントロールのメソッドを呼ぶためのデリゲートに、実行するメソッドを登録
                SetFocusDelegate1^ focusDel = gcnew SetFocusDelegate1(this, &Form1::SetFocus1);
               
                //メインスレッドから SetFocus1() を実行してもらうために SetFocus1() が登録されたデリゲートを実行
                //二番目の引数にデリゲートに渡す引数を渡す
                this->Invoke(focusDel, i+1);

                //ここで return しないと下に流れて別スレッドから this->textBox2->Focus() が呼ばれてエラーとなる
                return;

            }


            //以下メインスレッド上から呼ばれた時の処理
            MessageBox::Show("メインスレッドから呼び出し" + i, "InvokeRequired");

            //メインスレッドからの呼び出しなのでエラーはでない
            this->textBox2->Focus();

            return;

        }


        //引数無の場合
        void SetFocus2()  {

            //別スレッドから呼ばれた場合は if文内に入る
            if (this->InvokeRequired)  {
             
                MessageBox::Show("別スレッドから呼び出し", "InvokeRequired");
               
                //別スレッドからコントロールのメソッドをそのまま呼ぶとエラー
                //this->textBox2->Focus();

                //メインスレッドから SetFocus1() を実行してもらうために SetFocus1() が登録されたデリゲートを実行

                //*デリゲートの定義も一度に行うこともできる
                this->Invoke(gcnew SetFocusDelegate2(this, &Form1::SetFocus2));

                return;

            }


            //以下メインスレッド上から呼ばれた時の処理
            MessageBox::Show("メインスレッドから呼び出し", "InvokeRequired");

            //メインスレッドからの呼び出しなのでエラーはでない
            this->textBox2->Focus();
           
            return;

        }

 

        //別スレッドで実行されるメソッドを定義
        //このメソッドは引数を取れない

        //SetFocusが引数有の場合
        void worker1(System::Object^ i)  {

            SetFocus1(safe_cast(i));

            return;

        }


        //引数無の場合
        void worker2()  {

            SetFocus2();

            return;

        }

 

    //「別スレッドでfocus引数有」ボタンが押されたとき
    private: System::Void button1_Click(System::Object^  sender, System::EventArgs^  e) {

                 //別スレッドで実行するメソッドをデリゲートに登録(スレッドとして実行するメソッドに引数を渡す場合)
                 System::Threading::ParameterizedThreadStart^ s = gcnew System::Threading::ParameterizedThreadStart(this, &Form1::worker1);

                 //別スレッドで実行するデリゲートをスレッドとして作成
                 System::Threading::Thread^ t = gcnew System::Threading::Thread(s);

                 //スレッドスタート  引数は適当
                 t->Start(15);

             }


    //「メインスレッドでfocus引数有」ボタンが押されたとき
    private: System::Void button2_Click(System::Object^  sender, System::EventArgs^  e) {

                 this->SetFocus1(10);  //引数は適当

             }

   
    //「別スレッドでfocus引数無」ボタンが押されたとき
    private: System::Void button4_Click(System::Object^  sender, System::EventArgs^  e) {

                 //別スレッドで実行するメソッドをデリゲートに登録(スレッドとして実行するメソッドに引数を渡さない場合)
                 System::Threading::ThreadStart^ s = gcnew System::Threading::ThreadStart(this, &Form1::worker2);

                 //別スレッドで実行するデリゲートをスレッドとして作成
                 System::Threading::Thread^ t = gcnew System::Threading::Thread(s);

                 t->Start();

             }

   
    //「メインスレッドでfocus引数無」ボタンが押されたとき
    private: System::Void button3_Click(System::Object^  sender, System::EventArgs^  e) {
                
                 this->SetFocus2();

             }