2013/11/14

C# Tips4 TCP/IP通信 クライアント編

クライアントはサーバに接続し、tcpオブジェクトを生成し、ネットワークストリームにwriteしたりまたネットワークストリームからreadしたりすることで作成できます。サンプルは以下の通り。




using System;
using System.Net;
using System.Net.Sockets;

class Client {
#region private property
   public delegate void OnReceive(string message);
   public event OnReceive onreceive;
#endregion

#region private property
   private NetworkStream ns;
   private TcpClient tcp;
   private IPAddress ipaddress;
   private int port;
#endregion

#region public method
   public Client(string host, int port) {
      try {
         ipaddress = null;

         foreach(IPAddress ipa in Dns.GetHostAddresses(host)) {
            if(ipa.AddressFamily == AddressFamily.InterNetwork) {
               ipaddress = ipa;
               Console.WriteLine("起動 : {0}", ipa);
               break;
            }
         }

         if(ipaddress == null) {
            throw new CannotFindHost(CannotFindHost.Error.CANNOT_FIND_IPV4);
         }
      }
      catch(SocketException) {
         throw new IPAddressException();
      }
      catch(CannotFindHost e) {
         throw e;
      }

      tcp = new System.Net.Sockets.TcpClient();
      this.port = port;
   }

   public void connect() {
      try {
         tcp.Connect(ipaddress, port);

         try {
            ns = tcp.GetStream();
         }
         catch(InvalidOperationException) {
            throw new CannotFindHost(CannotFindHost.Error.FAILD_GET_STREAM);
         }
      }
      catch(SocketException e) {
         if(e.ErrorCode == 10061) {
            throw new CannotFindHost(CannotFindHost.Error.TIMEOUT);
         }
         else {
            throw new IPAddressException();
         }
      }

      recieve();
   }

   public void sendMessage(string message) {
      byte[] buffer = System.Text.Encoding.GetEncoding("Shift_JIS").GetBytes(message);
      ns.Write(buffer, 0, buffer.Length);
   }

   public void close() {
      ns.Close();
      tcp.Close();
   }
#endregion

#region private method
   async System.Threading.Tasks.Task recieve() {
      var ms = new System.IO.MemoryStream();
      byte[] buffer = new byte[256];

      do {
         int size = await ns.ReadAsync(buffer, 0, buffer.Length);

         if(size == 0) {
            close();
            return;
         }

         ms.Write(buffer, 0, size);
      } while(ns.DataAvailable);

      onreceive(System.Text.Encoding.GetEncoding("Shift_JIS").GetString(ms.ToArray()));

      recieve();
   }
#endregion

#region excepiton
   public class IPAddressException : Exception {
      public override string ToString() {
         return "※ IPアドレスの変換あるいは取得に失敗しました。";
      }
   }

   public class CannotFindHost : Exception {
      public enum Error {
         CANNOT_FIND_IPV4,
         TIMEOUT,
         FAILD_GET_STREAM
      };
      public Error error;

      public CannotFindHost(Error error) {
         this.error = error;
      }

      public override string ToString() {
         return "※ 接続先が見付かりません。(code : " + error + ")";
      }
   }
#endregion
}
mainから使うには次のようにします。
using System;

class Program {
   public static void Main() {
      Client client;

      try {
         client = new Client("127.0.0.1", 9000);
         client.onreceive += callback;
         client.connect();

         client.sendMessage("abc");

         Console.ReadLine();
         client.close();
      }
      catch(Client.CannotFindHost e) {
         Console.WriteLine(e);
      }
      catch(Client.IPAddressException e) {
         Console.WriteLine(e);
      }
   }

   public static void callback(string message) {
      Console.WriteLine("受信 : {0}", message);
   }
}
前回と同様にasync/await機構を使っています。receiveメソッドは別スレッドになっていると言うことです。 そのほか例外処理が多いだけで中身は本当に少ないですね。
SocketExceptionはいろいろな種類の例外をキャッチしてしまうため、ErrorCodeを取得してタイムアウトだけ判別しています。しかし、そのほかのエラーコードは分かりません。ドキュメントがあるらしいですがめんどくさい;
ちなみに#region NAMEから#endregionで囲うことで、IDE上で-ボタンをクリックすることでブロックとなって最小化できるようになります。

 おまけ
送受信プログラムを起動して実行してみた様子です。

0 件のコメント:

コメントを投稿