ソケット通信のプログラム C#版(PC側)


今回はマイコンのLinuxアプリケーションと通信するためのPC側クライアントプログラムをC#で実装してみました。
C#で実装するメリットはやはりWindowsフォームアプリケーションに組み込めることです。
マイコンに実装したアプリのGUIを簡単に作ることができます。

制御対象となるのは、以前から作成しているDCモータがついたロボットなどです。
wpid-wp-1467103068647.jpeg
これらとイーサネットで接続し、WindowsPC上のグラフィカルなGUIでコントロールしたいと思います。

通信相手となるマイコン側のプログラム(サーバー)は、依然このブログで書いたものがそのまま使えます。
・ソケット通信のプログラム(マイコン側)

今回のC#のプログラムはDOBON.NETさんのこちらのサイトを参考にさせて頂きました。

以下がC#版のクライアント側プログラムです。
とりあえず動作確認用に、Visual Studioでコンソールアプリケーションとしてプロジェクトを作成しています。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.InteropServices;

namespace dotnet_scket_sample
{
    //構造体宣言
    //StructLayout属性にして、メンバが宣言された順番にメモリ上に配置されるようにする
    [StructLayout(LayoutKind.Sequential)]
    struct Transfer_data
    {
        //MarshalAsで宣言時に配列サイズを固定
        [MarshalAs(UnmanagedType.ByValArray, SizeConst = 20)]
        public float[] prm_pc_to_soc;                               //PC→SoCへの転送パラメータ

        [MarshalAs(UnmanagedType.ByValArray, SizeConst = 20)]
        public float[] prm_soc_to_pc;								//SoC→PChへの転送パラメータ
    }

    class Program
    {
        static void Main(string[] args)
        {
            
            //構造体インスタンス生成
            Transfer_data td = new Transfer_data();
            td.prm_pc_to_soc = new float[20];
            td.prm_soc_to_pc = new float[20];

            //サーバーのIPアドレス
            string ipOrHost = "127.0.0.1";

            //接続先ポート番号
            int port = 12345;

            //TcpClientを作成し、サーバーと接続する
            System.Net.Sockets.TcpClient tcp =
                new System.Net.Sockets.TcpClient(ipOrHost, port);

            //接続ログ表示
            Console.WriteLine("サーバー({0}:{1})と接続しました({2}:{3})。",
            ((System.Net.IPEndPoint)tcp.Client.RemoteEndPoint).Address,
            ((System.Net.IPEndPoint)tcp.Client.RemoteEndPoint).Port,
            ((System.Net.IPEndPoint)tcp.Client.LocalEndPoint).Address,
            ((System.Net.IPEndPoint)tcp.Client.LocalEndPoint).Port);

            //NetworkStreamを取得する
            System.Net.Sockets.NetworkStream ns = tcp.GetStream();

            //読み取り、書き込みのタイムアウトを10秒にする
            ns.ReadTimeout = 10000;
            ns.WriteTimeout = 10000;

            //転送データ設定
            td.prm_pc_to_soc[0] = 321;
            td.prm_pc_to_soc[1] = 333;
            td.prm_pc_to_soc[2] = 456;
            td.prm_pc_to_soc[3] = 777;

            //送信処理            
            //送信用バッファを作成してデータをコピー
            int tdsize = Marshal.SizeOf(td);
            IntPtr sdptr = Marshal.AllocHGlobal(tdsize);

            byte[] sendBuffer = new byte[tdsize];                   //メモリ確保

            //ポインタ生成
            Marshal.StructureToPtr(td, sdptr, false);

            //ポインタ→バイト配列
            Marshal.Copy(sdptr, sendBuffer, 0, tdsize);             //データをコピー
            
            //データ送信
            ns.Write(sendBuffer, 0, tdsize);

            //受信処理
            //サーバーから送られたデータを受信する
            int rvsize = Marshal.SizeOf(td);
            IntPtr rvptr = Marshal.AllocHGlobal(rvsize);

            byte[] recvBuffer = new byte[rvsize];

            //データ受信
            ns.Read(recvBuffer, 0, rvsize);

            //バイト配列→ポインタへコピー
            Marshal.Copy(recvBuffer, 0, rvptr, rvsize);

            //ポインタ→構造体
            GCHandle gch = GCHandle.Alloc(recvBuffer, GCHandleType.Pinned);
            td = (Transfer_data)Marshal.PtrToStructure(gch.AddrOfPinnedObject(), typeof(Transfer_data));
            gch.Free();

            //受信データコンソール出力
            Console.Write("受信データ" + Environment.NewLine);
            Console.Write("recv01 = {0}", (object)td.prm_soc_to_pc[0] + Environment.NewLine);
            Console.Write("recv02 = {0}", (object)td.prm_soc_to_pc[1] + Environment.NewLine);
            Console.Write("recv03 = {0}", (object)td.prm_soc_to_pc[2] + Environment.NewLine);
            Console.Write("recv04 = {0}", (object)td.prm_soc_to_pc[3] + Environment.NewLine);
            Console.Write("recv05 = {0}", (object)td.prm_soc_to_pc[4] + Environment.NewLine);
            Console.Write("recv06 = {0}", (object)td.prm_soc_to_pc[5] + Environment.NewLine);
            Console.Write("recv07 = {0}", (object)td.prm_soc_to_pc[6] + Environment.NewLine);
            Console.Write("recv08 = {0}", (object)td.prm_soc_to_pc[7] + Environment.NewLine);
            Console.Write("recv09 = {0}", (object)td.prm_soc_to_pc[8] + Environment.NewLine);
            Console.Write("recv10 = {0}", (object)td.prm_soc_to_pc[9] + Environment.NewLine);

            //通信終了
            ns.Close();
            tcp.Close();

            //終了メッセージ出力
            Console.Write("通信終了");

            //確認用入力待ち
            Console.ReadLine();

        }
    }
}

今回も、前回同様float型のデータを送信、受信各20個づつやり取りする形になっています。

1カ所はまったところとしては、C#では構造体メンバに配列を使用する場合、宣言時に「MarshalAs」を使ってあらかじめ配列のサイズを定義していないと、92行目でAllocを使用する際に例外が発生します。

このソースコードをWindows側の通信スレッドに実装して、Windowsフォームとデータをやり取りすれば簡単にGUIが作成できるかと思います。
実際に入出力フォームまで実装して動作確認できたら、続きを載せたいと思います。

Add a Comment

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です