首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> C語言 >> 關於C語言

用 Visual C# 實作 ICMP 協議與 Ping 功能(七):按鈕點擊事件完整發送/接收邏輯

     編輯:關於C語言

button1_Click 核心流程:建立 Raw Socket → 組裝封包 → 發送 → 接收回應 → 計算 RTT

以下為完整事件處理程式碼 (已修正大小寫與語法),逐段解析關鍵步驟:

1. 參數準備與主機解析

private void button1_Click(object sender, EventArgs e)
{
    listBox1.Items.Clear();
    string hostName = textBox1.Text;

    for (int k = 0; k < 3; k++) // 發送 3 次
    {
        Socket socket = new Socket(AddressFamily.InterNetwork, SocketType.Raw, ProtocolType.Icmp);

        IPHostEntry hostInfo;
        try { hostInfo = Dns.GetHostEntry(hostName); }
        catch { listBox1.Items.Add("找不到此主機!"); return; }

        IPEndPoint remoteEP = new IPEndPoint(hostInfo.AddressList[0], 30); // 任意埠號 30
        IPHostEntry localInfo = Dns.GetHostEntry(Dns.GetHostName());
        IPEndPoint localEP = new IPEndPoint(localInfo.AddressList[0], 30);

2. 組裝 ICMP 封包與計算檢驗和


        const int ICMP_ECHO = 8;
        int dataSize = 4;
        int packetSize = dataSize + 8; // ICMP 標頭 8 bytes

        IcmpPacket packet = new IcmpPacket(ICMP_ECHO, 0, 0, 45, 0, dataSize);
        byte[] buffer = new byte[packetSize];
        int index = packet.CountByte(buffer);
        if (index != packetSize) { listBox1.Items.Add("封包組裝錯誤!"); return; }

        // 計算檢驗和
        int cksumBufLen = (int)Math.Ceiling((double)index / 2);
        ushort[] cksumBuffer = new ushort[cksumBufLen];
        int offset = 0;
        for (int i = 0; i < cksumBufLen; i++)
        {
            cksumBuffer[i] = BitConverter.ToUInt16(buffer, offset);
            offset += 2;
        }
        packet.CheckSum = IcmpPacket.SumOfCheck(cksumBuffer);

        // 重新序列化含正確檢驗和的封包
        byte[] sendData = new byte[packetSize];
        index = packet.CountByte(sendData);
        if (index != packetSize) { listBox1.Items.Add("封包組裝錯誤!"); return; }

3. 發送、接收與超時控制


        int startTime = Environment.TickCount;
        if (socket.SendTo(sendData, packetSize, SocketFlags.None, remoteEP) == -1)
        { listBox1.Items.Add("無法傳送封包!"); }

        byte[] recvBuffer = new byte[256];
        EndPoint senderEP = localEP;
        int timeout = 0;

        while (true)
        {
            int nBytes = socket.ReceiveFrom(recvBuffer, 256, SocketFlags.None, ref senderEP);
            if (nBytes == -1) { listBox1.Items.Add("主機無回應!"); break; }
            if (nBytes > 0)
            {
                int elapsed = Environment.TickCount - startTime;
                listBox1.Items.Add($"Reply from {hostInfo.AddressList[0]}: time={elapsed}ms bytes={nBytes}");
                break;
            }
            timeout = Environment.TickCount - startTime;
            if (timeout > 1000) { listBox1.Items.Add("Request timed out."); break; }
        }
        socket.Close();
    }
}

關鍵技術點提醒

  • Raw Socket 需管理員權限:Vista 以後執行需「以系統管理員身分執行」,否則 SocketType.Raw 會擲出 SocketException (存取被拒)。
  • 埠號 30 為任意值:ICMP 不使用傳統埠號,但 IPEndPoint 建構式需填入,任何未衝突數值皆可。
  • 檢驗和計算必須在序列化後:先將欄位寫入緩衝區 → 計算檢驗和 → 回填 CheckSum 欄位 → 再序列化一次送出。
  • 接收緩衝區 256 bytes 足夠:IP 標頭 20 + ICMP 標頭 8 + 資料 4 + 邊際 = 32 bytes 左右,256 綽綽有餘。
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved