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

Visual C++ 實作區域網路多播通訊完整指南

     編輯:關於VC++

最近研讀《Unix 網路程式設計》一書,深感這本經典著作極適合 Socket 程式設計入門者。書中關於多播的介紹讓我豁然開朗:原來在程式碼中實作基本的多播功能竟然如此直觀簡單。本文不打算照本宣科地探討理論概念,而是專注於實際程式碼中如何落地多播功能。

開發與測試環境

  • 開發環境:Visual C++ 6.0 (SP5),使用 Winsock 2 (ws2_32.lib)
  • 測試環境:三台位於兩個不同 AD 網域的 Windows 2000 (SP4) 機器

區域網路多播位址範圍

針對區域網路,可用的多播位址範圍為 224.0.0.0 至 224.0.0.255(詳細原因請參考專業網路程式設計書籍)。

核心機制:Socket 選項設定

多播功能透過設定 UDP Socket 選項來實現,主要涉及以下選項:

選項功能說明
IP_ADD_MEMBERSHIP加入多播群組
IP_DROP_MEMBERSHIP離開多播群組
IP_MULTICAST_IF指定輸出多播資料報的介面
IP_MULTICAST_TTL設定 TTL (Time To Live) 值
IP_MULTICAST_LOOP控制是否接收自己發送的多播資料(回環)

關鍵結構體:ip_mreq

設定 IP_ADD_MEMBERSHIP 和 IP_DROP_MEMBERSHIP 時需使用 ip_mreq 結構體,須包含 <Ws2tcpip.h> 並確保其在 <winsock2.h> 之後引入。

程式功能與完整實作碼

程式讀取使用者輸入的文字行並發送至多播群組;所有加入該群組的客戶端均可接收並顯示來自群組的資料。

// 專案設定:C/C++ -> Code Generation -> 勾選 "Debug Multithreaded"
#include <iostream>
#include <winsock2.h>  // 注意引入順序
#include <Ws2tcpip.h>
#include <process.h>   // _beginthread 需求
#pragma comment(lib, "ws2_32.lib")
using namespace std;

const char* MULTICAST_IP = "224.0.0.99";  // 多播群組位址
const int MULTICAST_PORT = 2002;          // 多播群組埠號
const int BUFFER_SIZE = 1024;

void do_send(void* arg);  // 讀取使用者輸入並發送至多播群組的執行緒函式
void do_read(void* arg);  // 讀取多播群組資料的函式

int main() {
    WSAData wsaData;
    if (WSAStartup(MAKEWORD(2,2), &wsaData) != 0) {
        cout << "Error in WSAStartup" << endl;
        return 0;
    }

    SOCKET server = socket(AF_INET, SOCK_DGRAM, 0);  // 建立 UDP Socket
    cout << "create socket: " << server << endl;

    int ret;
    const int on = 1;  // 允許同一機器執行多個程式實例
    ret = setsockopt(server, SOL_SOCKET, SO_REUSEADDR, (char*)&on, sizeof(on));
    if (ret == SOCKET_ERROR) {
        WSACleanup();
        cout << "Error in setsockopt(SO_REUSEADDR): " << WSAGetLastError() << endl;
        return 0;
    }

    // 設定 TTL
    const int routenum = 10;
    ret = setsockopt(server, IPPROTO_IP, IP_MULTICAST_TTL, (char*)&routenum, sizeof(routenum));
    if (ret == SOCKET_ERROR) {
        WSACleanup();
        cout << "Error in setsockopt(IP_MULTICAST_TTL): " << WSAGetLastError() << endl;
        return 0;
    }

    // 禁止回環
    const int loopback = 0;
    ret = setsockopt(server, IPPROTO_IP, IP_MULTICAST_LOOP, (char*)&loopback, sizeof(loopback));
    if (ret == SOCKET_ERROR) {
        WSACleanup();
        cout << "Error in setsockopt(IP_MULTICAST_LOOP): " << WSAGetLastError() << endl;
        return 0;
    }

    // 綁定本地位址
    sockaddr_in local;
    memset(&local, 0, sizeof(local));
    local.sin_family = AF_INET;
    local.sin_port = htons(MULTICAST_PORT);
    local.sin_addr.S_un.S_addr = INADDR_ANY;
    ret = bind(server, (sockaddr*)(&local), sizeof(local));
    if (ret == SOCKET_ERROR) {
        WSACleanup();
        cout << "Error in bind: " << WSAGetLastError() << endl;
        return 0;
    }

    // 加入多播群組
    ip_mreq mreq;
    memset(&mreq, 0, sizeof(mreq));
    mreq.imr_interface.S_un.S_addr = INADDR_ANY;
    mreq.imr_multiaddr.S_un.S_addr = inet_addr(MULTICAST_IP);
    ret = setsockopt(server, IPPROTO_IP, IP_ADD_MEMBERSHIP, (char*)&mreq, sizeof(mreq));
    if (ret == SOCKET_ERROR) {
        WSACleanup();
        cout << "Error in setsockopt(IP_ADD_MEMBERSHIP): " << WSAGetLastError() << endl;
        return 0;
    }

    // 建立兩個執行緒:一個負責發送,一個負責接收
    HANDLE hHandle[2];
    hHandle[0] = (HANDLE)_beginthread(do_send, 0, (void*)server);
    hHandle[1] = (HANDLE)_beginthread(do_read, 0, (void*)server);

    // 等待發送執行緒結束(使用者輸入 "end" 時)
    WaitForSingleObject(hHandle[0], INFINITE);
    WSACleanup();
    return 0;
}

// 發送執行緒:讀取使用者輸入直到 "end"
void do_send(void* arg) {
    SOCKET server = (SOCKET)arg;
    char sendline[BUFFER_SIZE + 1];
    sockaddr_in remote;
    memset(&remote, 0, sizeof(remote));
    remote.sin_addr.s_addr = inet_addr(MULTICAST_IP);
    remote.sin_family = AF_INET;
    remote.sin_port = htons(MULTICAST_PORT);

    for (;;) {
        cin.getline(sendline, BUFFER_SIZE);
        if (strncmp(sendline, "end", 3) == 0) break;
        sendto(server, sendline, strlen(sendline), 0, (sockaddr*)(&remote), sizeof(remote));
    }
    cout << "do_send end..." << endl;
}

// 接收執行緒:持續讀取多播資料
void do_read(void* arg) {
    SOCKET server = (SOCKET)arg;
    char buf[BUFFER_SIZE + 1];
    int ret;
    sockaddr_in client;
    int clientLen;

    for (;;) {
        clientLen = sizeof(client);
        memset(&client, 0, clientLen);
        ret = recvfrom(server, buf, BUFFER_SIZE, 0, (sockaddr*)(&client), &clientLen);
        if (ret == 0) continue;  // 空字串
        else if (ret == SOCKET_ERROR) {
            if (WSAGetLastError() == WSAEINTR) break;  // 主執行緒終止導致的錯誤
            cout << "Error in recvfrom: " << WSAGetLastError() << endl;
            break;
        }
        buf[ret] = '\0';
        cout << "received: " << buf << endl;
    }
    cout << "do_read end..." << endl;
}

總結

雖然實際專案中仍需更多錯誤處理與健壯性設計,但核心多播功能的實作確實相當簡潔明瞭。掌握 Socket 選項與 ip_mreq 結構體的運用,即可快速建立區域網路多播通訊應用。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved