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)。
| 參數 | TCP/IP | IPX/SPX (SPX 傳輸層) |
|---|---|---|
| Address Family | AF_INET (PF_INET) | AF_IPX (PF_IPX) |
| Socket Type | SOCK_STREAM / SOCK_DGRAM | SOCK_STREAM (SPX 為連線導向可靠傳輸) |
| Protocol | IPPROTO_TCP / IPPROTO_UDP | NSPROTO_SPX (定義於 wsipx.h) |
| 地址結構 | sockaddr_in | SOCKADDR_IPX |
| 埠號 | 16-bit port | 16-bit socket number (網路序 htons) |
// 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);
};
// 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;
}
// 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 支援)。lpszHostAddress 處理需注意編碼,建議統一用 ANSI 字串解析十六進位。IPX/SPX 已為淘汰技術 (Novell NetWare 退出歷史舞台)。現代跨平台網路開發請直接使用 TCP/IP (IPv4/IPv6)。本技術僅適用於:
透過重載 Socket() (修改 nAddressFormat/nProtocolType)、Bind() (填充 SOCKADDR_IPX)、Connect/Accept (處理 IPX 地址),成功將 CAsyncSocket 擴展為支援 IPX/SPX。關鍵在於理解 socket() 三參數與地址結構的協議族差異。此模式同樣適用於擴展 AF_IRDA (紅外)、AF_BTH (藍牙) 等其他位址家族。