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

CAsyncSocket 擴展支援 IPX/SPX 協議:重載 Create/Socket/Bind/Connect/Accept 完整實戰

     編輯:關於C語言

背景:CAsyncSocket 預設僅支援 TCP/IP (AF_INET)

MFC CAsyncSocket 封裝 WinSock,但其 Create() → Socket() 預設參數硬編碼 PF_INET (AF_INET),導致無法直接使用 IPX/SPX (Novell NetWare 協議族)。本文展示如何透過繼承並重載關鍵虛擬函數,實現 IPX/SPX 支援。

原始碼分析:問題根源


// CAsyncSocket::Socket 預設實作 (簡化)
BOOL CAsyncSocket::Socket(int nSocketType, long lEvent,
                          int nProtocolType = 0, int nAddressFormat = PF_INET)
{
    m_hSocket = socket(nAddressFormat, nSocketType, nProtocolType); // 關鍵行!
    // ...
}

nAddressFormat = PF_INET 固定為 IPv4。要支援 IPX,需傳入 PF_IPX (定義於 winsock2.h:#define PF_IPX AF_IPX)。

IPX/SPX Socket 參數對照

參數TCP/IPIPX/SPX (SPX 傳輸層)
Address FamilyAF_INET (PF_INET)AF_IPX (PF_IPX)
Socket TypeSOCK_STREAM / SOCK_DGRAMSOCK_STREAM (SPX 為連線導向可靠傳輸)
ProtocolIPPROTO_TCP / IPPROTO_UDPNSPROTO_SPX (定義於 wsipx.h)
地址結構sockaddr_inSOCKADDR_IPX
埠號16-bit port16-bit socket number (網路序 htons)

自訂類別 CIPXAsyncSocket 完整實作

Header


// IPXAsyncSocket.h
#pragma once
#include 
#include  // SOCKADDR_IPX, NSPROTO_SPX

class CIPXAsyncSocket : public CAsyncSocket
{
public:
    // 重載 Create:新增 addressFormat 參數,預設 IPX
    virtual BOOL Create(UINT nSocketPort = 0, int nSocketType = SOCK_STREAM,
                        long lEvent = FD_READ | FD_WRITE | FD_OOB | FD_ACCEPT | FD_CONNECT | FD_CLOSE,
                        LPCTSTR lpszSocketAddress = NULL, int nAddressFormat = PF_IPX);

protected:
    // 重載 Socket:透傳 addressFormat
    virtual BOOL Socket(int nSocketType, long lEvent,
                        int nProtocolType = NSPROTO_SPX, int nAddressFormat = PF_IPX);

    // 重載 Bind:處理 SOCKADDR_IPX
    virtual BOOL Bind(UINT nSocketPort, LPCTSTR lpszSocketAddress = NULL);

    // 重載 Connect:接受 IPX 地址
    virtual BOOL Connect(LPCTSTR lpszHostAddress, UINT nHostPort);

    // 重載 Accept:處理 IPX 客戶端地址
    virtual BOOL Accept(CAsyncSocket& rConnectedSocket, LPCTSTR lpszHostAddress = NULL, UINT* lpHostPort = NULL);
};

CPP 核心實作


// IPXAsyncSocket.cpp
#include "stdafx.h"
#include "IPXAsyncSocket.h"

#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif

// 1. Socket:核心修改點
BOOL CIPXAsyncSocket::Socket(int nSocketType, long lEvent,
                             int nProtocolType, int nAddressFormat)
{
    ASSERT(m_hSocket == INVALID_SOCKET);
    // 直接傳入 addressFormat (PF_IPX) 與 protocol (NSPROTO_SPX)
    m_hSocket = socket(nAddressFormat, nSocketType, nProtocolType);
    if (m_hSocket != INVALID_SOCKET) {
        CAsyncSocket::AttachHandle(m_hSocket, this, FALSE);
        return AsyncSelect(lEvent);
    }
    return FALSE;
}

// 2. Bind:填充 SOCKADDR_IPX
BOOL CIPXAsyncSocket::Bind(UINT nSocketPort, LPCTSTR lpszSocketAddress)
{
    SOCKADDR_IPX ipxAddr = {};
    ipxAddr.sa_family = AF_IPX;
    ipxAddr.sa_socket = htons((u_short)nSocketPort); // 埠號轉網路序
    // sa_netnum, sa_nodenum, sa_netnum[4], sa_nodenum[6] 填 0
    // bind 會自動使用系統第一張可用 IPX 介面填充網路/節點號

    int nResult = bind(m_hSocket, (SOCKADDR*)&ipxAddr, sizeof(ipxAddr));
    if (nResult == SOCKET_ERROR) return FALSE;
    return TRUE;
}

// 3. Connect:目標地址解析 (簡化版,假設 lpszHostAddress 為 "網路號.節點號" 格式)
BOOL CIPXAsyncSocket::Connect(LPCTSTR lpszHostAddress, UINT nHostPort)
{
    SOCKADDR_IPX ipxAddr = {};
    ipxAddr.sa_family = AF_IPX;
    ipxAddr.sa_socket = htons((u_short)nHostPort);

    // 解析 lpszHostAddress: "00000001.000000000001" (網路號.節點MAC)
    // 實際專案建議使用 SAP (Service Advertising Protocol) 或 GIPX 解析服務名
    // 此處略過解析邏輯,假設外部已填充 ipxAddr.sa_netnum / sa_nodenum

    return connect(m_hSocket, (SOCKADDR*)&ipxAddr, sizeof(ipxAddr)) != SOCKET_ERROR;
}

// 4. Accept:取得客戶端 IPX 地址
BOOL CIPXAsyncSocket::Accept(CAsyncSocket& rConnectedSocket, LPCTSTR lpszHostAddress, UINT* lpHostPort)
{
    SOCKADDR_IPX ipxAddr; int nAddrLen = sizeof(ipxAddr);
    SOCKET hNewSocket = accept(m_hSocket, (SOCKADDR*)&ipxAddr, &nAddrLen);
    if (hNewSocket == INVALID_SOCKET) return FALSE;

    // 將新 Socket 附加到衍生類物件 (需 rConnectedSocket 也是 CIPXAsyncSocket)
    if (!rConnectedSocket.Attach(hNewSocket)) {
        closesocket(hNewSocket); return FALSE;
    }
    // 可選:將客戶端地址轉為字串返回
    return TRUE;
}

// 5. Create:串聯 Socket + Bind
BOOL CIPXAsyncSocket::Create(UINT nSocketPort, int nSocketType, long lEvent,
                             LPCTSTR lpszSocketAddress, int nAddressFormat)
{
    if (Socket(nSocketType, lEvent, NSPROTO_SPX, nAddressFormat)) {
        if (Bind(nSocketPort, lpszSocketAddress))
            return TRUE;
        int nErr = GetLastError();
        Close();
        WSASetLastError(nErr);
    }
    return FALSE;
}

使用範例:IPX 伺服器/客戶端


// Server
CIPXAsyncSocket sockServer;
sockServer.Create(9000); // PF_IPX, SOCK_STREAM, NSPROTO_SPX
sockServer.Listen(5);
CIPXAsyncSocket sockClient;
sockServer.Accept(sockClient); // 阻塞等待

// Client
CIPXAsyncSocket sockClient;
sockClient.Create(0); // 系統分配埠
sockClient.Connect(_T("00000001.000000000001"), 9000); // 目標網路.節點

注意事項與避坑

  • 標頭檔依賴:需包含 wsipx.h、連結 ws2_32.lib (含 IPX 支援)。
  • 系統支援:Windows 內建 NWLink (IPX/SPX 相容協議),需在網路連線屬性安裝「NWLink IPX/SPX/NetBIOS 相容傳輸協議」。
  • 地址解析:IPX 無 DNS,通常透過 SAP 廣播尋找服務,或硬編碼網路/節點號。生產環境需整合 SAP 查詢。
  • 防火牆/路由:IPX 為非 IP 協議,不經 TCP/IP 防火牆,但需網路設備支援 IPX 路由。
  • Unicode/ANSI:lpszHostAddress 處理需注意編碼,建議統一用 ANSI 字串解析十六進位。

現代替代視角

IPX/SPX 已為淘汰技術 (Novell NetWare 退出歷史舞台)。現代跨平台網路開發請直接使用 TCP/IP (IPv4/IPv6)。本技術僅適用於:

  • 維護舊有 NetWare 環境應用。
  • 特殊工控/嵌入式系統仍使用 IPX。
  • 教學理解 Socket Address Family 機制。

總結

透過重載 Socket() (修改 nAddressFormat/nProtocolType)、Bind() (填充 SOCKADDR_IPX)、Connect/Accept (處理 IPX 地址),成功將 CAsyncSocket 擴展為支援 IPX/SPX。關鍵在於理解 socket() 三參數與地址結構的協議族差異。此模式同樣適用於擴展 AF_IRDA (紅外)、AF_BTH (藍牙) 等其他位址家族。

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