C# のネットワーク通信ソケットのタイムアウト設定

ソケットに対して、タイムアウト設定を行う。

// 接続待ち
Socket client = tcplistener.AcceptSocket();

// 2秒データを受信しなかったらタイムアウトになるように設定
client.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReceiveTimeout, 2000);

このコードでタイムアウトした場合

System.IO.IOException: 転送接続からデータを読み取れません。 ---> 
System.Net.Sockets.SocketException: 接続済みの呼び出し先が一定の時間を過ぎても正しく応答しなかったため、接続できませんでした。
または接続済みのホストが応答しなかったため、確立された接続は失敗しました。

という内容の Exception が投げられる。

TcpClient でタイムアウト設定を行うなら

TcpClient client = new TcpClient("127.0.0.1", 12345);
client.ReceiveTimeout = 2000;
client.SendTimeout = 2000;

こんな感じで出来る。

送信タイムアウト(データを通信相手に一定時間送れなかった、受け取ってもらえなかった)
受信タイムアウト(通信相手から一定時間、データが届かなかった)
接続タイムアウト(通信相手に、ネットワーク的に接続できなかった、IPアドレス・ホスト名が間違っている、ポート番号が違う、相手のファイアウォールに阻まれた、など)

といった理由でタイムアウトが発生する事がある。

C# の TcpListener で接続してきたクライアントのIPアドレスを取得

// TcpListener で接続をうけ、Socket として取る。
Socket client = tcplistener.AcceptSocket();

// エンドポイントとかいうのをもらう
IPEndPoint endpoint = (IPEndPoint)client.RemoteEndPoint;

// そこから接続している相手の IPAddress が取れる。
IPAddress address = endpoint.Address;

// NetworkStream を作成。
NetworkStream stream = new NetworkStream(client);

C# の UDP通信をSocketで行う。TTL の指定 & ブロードキャスト

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

TTL の指定

// 送信先
IPEndPoint remoteIP = new IPEndPoint(IPAddress.Parse("192.168.11.2"), 80);

// 送信データ
byte[] data = new byte[16];

// UDP ソケットの作成
Socket s = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);

// TTL を設定
// TTLとは…→ http://e-words.jp/w/TTL-1.html
s.SetSocketOption(SocketOptionLevel.IP, SocketOptionName.IpTimeToLive, 255);

// データを送信
s.SendTo(data, 0, data.Length, SocketFlags.None, remoteIP);

ブロードキャスト

IPEndPoint remoteIP = new IPEndPoint(IPAddress.Broadcast, 10002);

byte[] data = new byte[16];
Socket s = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
s.SetSocketOption(SocketOptionLevel.IP, SocketOptionName.IpTimeToLive, 16);
// ブロードキャストはこれで許可を入れないといけないっぽい
s.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.Broadcast, 1);

s.SendTo(data, data.Length, SocketFlags.None, remoteIP);

C# で マジックパケット(Wake On Lan、WOL)の送信

遠隔からコンピュータを起動する機能です。
起動されるコンピュータ側の設定も必要。

オーバーロードしてるから、長く見えるけど、要は一番下のメソッドが重要。

using System.Net;
using System.Net.Sockets;
using System.IO;
/// <summary>
/// マジックパケットを送信します。
/// </summary>
/// <param name="address">IPアドレス</param>
/// <param name="subnetmask">サブネットマスク</param>
/// <param name="physicalAddress">起動するマシンのMACアドレス</param>
private void SendMagicPacket(string address, string subnetmask, byte[] physicalAddress)
{
    SendMagicPacket(IPAddress.Parse(address), IPAddress.Parse(subnetmask), physicalAddress);
}

/// <summary>
/// マジックパケットを送信します。
/// </summary>
/// <param name="address">IPアドレス</param>
/// <param name="subnetmask">サブネットマスク</param>
/// <param name="physicalAddress">起動するマシンのMACアドレス</param>
private void SendMagicPacket(IPAddress address, IPAddress subnetmask, byte[] physicalAddress)
{
    uint uip = BitConverter.ToUInt32(address.GetAddressBytes(), 0);
    uint usub = BitConverter.ToUInt32(subnetmask.GetAddressBytes(), 0);

    uint result = uip | (usub ^ 0xFFFFFFFF);

    SendMagicPacket(new IPAddress(result), physicalAddress);
}

/// <summary>
/// ブロードキャストアドレス(255.255.255.255)に対してマジックパケットを送信します。
/// </summary>
/// <param name="physicalAddress">起動するマシンのMACアドレス</param>
private void SendMagicPacket(byte[] physicalAddress)
{
    SendMagicPacket(IPAddress.Broadcast, physicalAddress);
}

/// <summary>
/// 指定されたアドレスに対してマジックパケットを送信します。
/// 送信先のアドレスはブロードキャストアドレスである必要があります。
/// </summary>
/// <param name="broad">ブロードキャストアドレス</param>
/// <param name="physicalAddress">起動するマシンのMACアドレス</param>
private void SendMagicPacket(string broad, byte[] physicalAddress)
{
    SendMagicPacket(IPAddress.Parse(broad), physicalAddress);
}

/// <summary>
/// 指定されたアドレスに対してマジックパケットを送信します。
/// 送信先のアドレスはブロードキャストアドレスである必要があります。
/// </summary>
/// <param name="broad">ブロードキャストアドレス</param>
/// <param name="physicalAddress">起動するマシンのMACアドレス</param>
private void SendMagicPacket(IPAddress broad, byte[] physicalAddress)
{
    MemoryStream stream = new MemoryStream();
    BinaryWriter writer = new BinaryWriter(stream);
    for (int i = 0; i < 6; i++)
    {
        writer.Write((byte)0xff);
    }
    for (int i = 0; i < 16; i++)
    {
        writer.Write(physicalAddress);
    }

    UdpClient client = new UdpClient();
    client.EnableBroadcast = true;
    client.Send(stream.ToArray(), (int)stream.Position, new IPEndPoint(broad, 0));
}

C# でコンピュータのネットワークカードの名前の一覧を列挙

まぁ、2.0 ならこれで一覧がとれるかな?と。

using System.Net.NetworkInformation;
NetworkInterface[] nic = NetworkInterface.GetAllNetworkInterfaces();
foreach (NetworkInterface var in nic)
{
    Console.WriteLine(var.Description);
}

「MS TCP Loopback interface」 ってやつは、リストアップする際に除いたほうがイイ。
調べたら出てくると思うけど、localhost 接続用の仮想 NIC だったと思う。

WMI を使って、NIC一覧を取得するなら、こんな感じ。

using System.Management; //参照設定に System.Management を追加
ManagementClass mc = new ManagementClass("Win32_PerfRawData_Tcpip_NetworkInterface");
ManagementObjectCollection moc = mc.GetInstances();

foreach (ManagementObject mo in moc)
{
    // 情報を表示
    Console.WriteLine("名前     = {0}", mo["Name"]);
    Console.WriteLine("接続速度 = {0} Mbps", Convert.ToInt32(mo["CurrentBandwidth"]) / 1000 / 1000);

    Console.WriteLine("------");
}

無効になってる NIC は拾えないので(今は未検証だけど、たしかそうだった)、ご注意。

C# の TcpClient をハブだけの環境で使うと遅い気がする

.Net Framework には、TcpClient という Socket のラッパっぽい、便利クラスがある。

だが、このクラスを使って、ハブだけのクローズドな環境で利用すると、接続開始してから、何かのタイムアウト待ちで、通信できるまで15秒かかってしまう。
デフォルトゲートウェイがある環境(ルーターがある環境)ではすぐ通信できるが、無い環境では15秒後に通信が始まる。

Socket クラスを使用すると、すぐに通信できるのを見ると TcpClient は恐らく、何らかの処理(名前解決とか?)を行おうとし、そのタイムアウトが15秒に設定してあるためと思われる。

なので、TcpClient と同じようなメソッドを実装したクラスで、既存の TcpClient の実装部分を置き換えることでコレが解決する。
具体的には、以下のようなクラスを作成した。

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

namespace Util
{
    public class TcpClient2
    {
        private IPEndPoint end;
        private Socket socket;
        private NetworkStream stream;
        
        public TcpClient2(string host, int port) : this(IPAddress.Parse(host), port)
        {
        }
        public TcpClient2(IPAddress addr, int port)
        {
            end = new IPEndPoint(addr, port);
        }
        public NetworkStream GetStream()
        {
            if(socket == null)
            {
                socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
                socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.SendTimeout, SendTimeout);
                socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReceiveTimeout, ReceiveTimeout);
                socket.Connect(end);
                stream = new NetworkStream(socket);
            }
            return stream;
        }
        public void Close()
        {
            socket.Close();
        }
        public int ReceiveTimeout = 0;
        public int SendTimeout    = 0;
    }
}