分享
 
 
 

重载成员函数使CAsyncSocket类支持IPX/SPX协议

王朝vc·作者佚名  2006-01-08
窄屏简体版  字體: |||超大  

今天竟然发现了一篇我2002年初的时候写的一篇文档,呵呵,现在看起来有点幼稚,不过还是贴上来收藏做纪念,毕竟当初是初学者。

本文系作者原创,转载请注明出处。

CAsyncSocket类是VC++的MFC里一个对WinSock API封装得很低级的一个类。适合于既想利用WinSock API的灵活性,又想享受MFC里的消息事件机制带来的方便情况下使用,但是CAsyncSocket不支持IPX/SPX协议。笔者最近在开发一个基于IPX/SPX协议传送SPX包的程序时候,重载了CAsyncSocket类的Create,Bind,Connect,Socket,Accept这五个成员函数,成功地使CAsyncSocket类支持了IPX/SPX协议。下面谈谈具体实现。

我们先看看AsyncSocket::Create()这个函数的定义

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);

第一个参数是套接字的端口,第二个参数是套接字的类型,默认为流式套接字。第三个参数是针对套接字产生的事件,第四个参数是套接字的地址,默认为NULL。下面是这个函数的实现:

BOOL CAsyncSocket::Create(UINT nSocketPort, int nSocketType,

long lEvent, LPCTSTR lpszSocketAddress)

{

if (Socket(nSocketType, lEvent))

{

if (Bind(nSocketPort,lpszSocketAddress))

return TRUE;

int nResult = GetLastError();

Close();

WSASetLastError(nResult);

}

return FALSE;

}

请仔细看上面的代码,其实Create函数就调用了两个函数,一个是Socket,一个是Bind.从上面这段代码里,我们还看不出是CAsyncSocket类对TCP/IP和IPX/SPX协议的处理不同之处。

那么我们就接下来看看Socket函数

BOOL Socket(int nSocketType=SOCK_STREAM, long lEvent =

FD_READ | FD_WRITE | FD_OOB | FD_ACCEPT | FD_CONNECT | FD_CLOSE,

int nProtocolType = 0, int nAddressFormat = PF_INET);

接下来是Socket函数的实现

BOOL CAsyncSocket::Socket(int nSocketType, long lEvent,

int nProtocolType, int nAddressFormat)

{

ASSERT(m_hSocket == INVALID_SOCKET);

m_hSocket = socket(nAddressFormat,nSocketType,nProtocolType);

if (m_hSocket != INVALID_SOCKET)

{

CAsyncSocket::AttachHandle(m_hSocket, this, FALSE);

return AsyncSelect(lEvent);

}

return FALSE;

}

看清楚了,里面有一句

m_hSocket=socket(nAddressFormat,nSocketType,nProtocolType);

我想熟悉WinSock API编程的朋友一定会知道,其实这就是WinSock API里创建一个新的套接字。第一个参数nAddressFormat就是套接字使用的协议族,在winsock2.h里我们可以找到

#define PF_INET AF_INET

#define PF_IPX AF_IPX

这两个定义,PF_INET就是指我们新创建的套接字使用TCP/IP协议,PF_IPX就是使用IPX/SPX协议。

而第二个参数nSocketType默认的就是流式套接字,第三个参数nProtocolType是指定使用的协议类型。

在winsock2.h里有这样的定义

#define IPPROTO_IP 0

默认的参数0就是IP包。

在使用默认参数调用Create函数情况下,即

CAsyncSocket::Create();

到了CAsyncSocket::Socket函数

m_hSocket=socket(nAddressFormat,nSocketType,nProtocolType);

这一句这里就变成了

m_hSocket=socket(AF_INET,SOCK_STREAM,IPROTO_IP);

我想现在大家都已经很清楚了CAsyncSocket类之所以不支持IPX/SPX协议问题就在这一句上面,因为他默认的创建一个基于TCP/IP的流式或者数据包套接字。我们要让CAsyncSocket类支持IPX/SPX,首先就要修改这里。

而在WinSock API里,创建一个基于IPX/SPX协议传送SPX包的流式套接字应该是这样

SOCKET sdServer;

SOCKADDR_IPX IPXAddr;

int addrlen=sizeof(SOCKADDR_IPX);

sdServer=socket(AF_IPX,SOCK_STREAM,NSPROTO_SPX);

ZeroMemory(&IPXAddr,sizeof(SOCKADDR_IPX));

IPXAddr.safamily=AF_IPX;

IPXAddr.sa_socket=hotns(9000);

bind(sdServer,(PSOCKADDR)&IPXAddr,sizeof(SOCKADDR_IPX);

IPX应用程序通过bind把本地地址与套接字绑定在一起,绑定端口是9000。我们不需要在SOCKADDR_IPX里指定网络号和节点地址,bind函数会自动利用系统上第一个可用IPX网络接口来填充这些SOCKADDR_IPX结构里的相应字段。

所以我们在自己的类里重载的Socket函数应该是这样

BOOL Socket(int flg,int nSocketType=SOCK_STREAM, long lEvent =

FD_READ | FD_WRITE | FD_OOB | FD_ACCEPT | FD_CONNECT | FD_CLOSE,

int nProtocolType = NSPROTO_SPX, int nAddressFormat = PF_IPX);

BOOL CMySocket::Socket(int flg,int nSocketType, long lEvent,

int nProtocolType, int nAddressFormat)

{

ASSERT(m_hSocket == INVALID_SOCKET);

m_hSocket = socket(nAddressFormat,nSocketType,nProtocolType);

if (m_hSocket != INVALID_SOCKET)

{

CAsyncSocket::AttachHandle(m_hSocket, this, FALSE);

return AsyncSelect(lEvent);

}

return FALSE;

}

这个重载的函数和原函数相比,前面多了一个参数flg,用来区分重载后的函数和原函数。别的参数都没有改变,请大家注意函数声明里的默认参数。nProtocolType这个参数的默认值改成了NSPROTO_SPX,nAddressFormat这个参数的默认值也改成了PF_IPX.

接下来我们看CAsyncSocket类的Bind函数,CAsyncSocket类定义了两个Bind函数

BOOL Bind(UINT nSocketPort, LPCTSTR lpszSocketAddress = NULL);

BOOL Bind (const SOCKADDR* lpSockAddr, int nSockAddrLen);

MFC的原代码里只提供第一个Bind函数的代码。

BOOL CAsyncSocket::Bind(UINT nSocketPort, LPCTSTR lpszSocketAddress)

{

USES_CONVERSION;

SOCKADDR_IN sockAddr;

memset(&sockAddr,0,sizeof(sockAddr));

LPSTR lpszAscii = T2A((LPTSTR)lpszSocketAddress);

sockAddr.sin_family = AF_INET;

if (lpszAscii == NULL)

sockAddr.sin_addr.s_addr = htonl(INADDR_ANY);

else

{

DWORD lResult = inet_addr(lpszAscii);

if (lResult == INADDR_NONE)

{

WSASetLastError(WSAEINVAL);

return FALSE;

}

sockAddr.sin_addr.s_addr = lResult;

}

sockAddr.sin_port = htons((u_short)nSocketPort);

return Bind((SOCKADDR*)&sockAddr, sizeof(sockAddr));

}

不知道大家注意到了没有,在这个函数的最后一句里又调用了一个Bind函数,这个函数显然是

BOOL Bind (const SOCKADDR* lpSockAddr, int nSockAddrLen);

这个函数的具体原代码我们不知道,但是他的两个参数,第一个参数传进去了SOCKADDR这个结构,第二个参数传进去了这个结构的长度。

而WinSock API的bind函数原型是

int bind(SOCKET S,const struct sockaddr FAR* name,int namelen);

我们可以想象,

Bind((SOCKADDR*)&sockAddr, sizeof(sockAddr));

这个函数里肯定调用了WinSock API里的bind函数。具体怎样实现的我们不用管,我们只要管好

Bind((SOCKADDR*)&sockAddr, sizeof(sockAddr));

这里传递进去的参数就OK了。所以我们也就只要重载

BOOL Bind(UINT nSocketPort, LPCTSTR lpszSocketAddress = NULL);

这个函数,下面是重载的第一个Bind函数

BOOL Bind(int flg,UINT nSocketPort, LPCTSTR lpszSocketAddress = NULL);

BOOL CMySocket::Bind(int flg,UINT nSocketPort, LPCTSTR lpszSocketAddress)

{

USES_CONVERSION;

SOCKADDR_IPX sockAddr;

memset(&sockAddr,0,sizeof(sockAddr));

sockAddr.sa_family=AF_IPX;

sockAddr.sa_socket=htons((u_short)nSocketPort);

return CAsyncSocket::Bind((PSOCKADDR)&sockAddr, sizeof(sockAddr));

}

现在我们在

CAsyncSocket::Bind((PSOCKADDR)&sockAddr, sizeof(sockAddr));

这里传递进去的是支持IPX的PSOCKADDR结构,该结构定义在wsipx.h头文件里

typedef struct sockaddr_ipx {

short sa_family; //协议族

char sa_netnum[4];//网络地址

char sa_nodenum[6];//节点地址

unsigned short sa_socket;//端口

} SOCKADDR_IPX, *PSOCKADDR_IPX,FAR *LPSOCKADDR_IPX;

现在这些函数都重载好了,我们再回头看看Create函数,重载后的Create函数应该如下

BOOL Create(int flg,UINT nSocketPort = 0, int nSocketType=SOCK_STREAM,

long lEvent = FD_READ | FD_WRITE | FD_OOB | FD_ACCEPT | FD_CONNECT | FD_CLOSE,

LPCTSTR lpszSocketAddress = NULL);

BOOL CMySocket::Create(int flg,UINT nSocketPort, int nSocketType,

long lEvent, LPCTSTR lpszSocketAddress)

{

if (Socket(flg,nSocketType, lEvent))

{

if (Bind(flg,nSocketPort,lpszSocketAddress))

return TRUE;

int nResult = GetLastError();

Close();

WSASetLastError(nResult);

}

return FALSE;

}

以后我们在MFC程序里创建支持IPX/SPX协议的套接字的时候,就只要像先面这样就可以了。

MySocket TestSocket;

TestSocket.Create(0);

这样就创建了一个支持IPX/SPX协议的客户端套接字,如果创建服务器端的套接字,只要改成下面的代码

MySocket TestSocket;

TestSocket.Create(0,10000);

这样就创建了一个监听端口是10000的,支持IPX/SPX传送SPX包的服务器端流式套接字。

先面我们还要对Connect和Accept函数进程重载。首先还是来看看他们的MFC代码,Connect函数也有两个,对于第二个Connect函数我们同样不用管,只要重载第一个,。

BOOL Connect(LPCTSTR lpszHostAddress, UINT nHostPort);

BOOL Connect(const SOCKADDR* lpSockAddr, int nSockAddrLen);

BOOL CAsyncSocket::Connect(LPCTSTR lpszHostAddress, UINT nHostPort)

{

USES_CONVERSION;

ASSERT(lpszHostAddress != NULL);

SOCKADDR_IN sockAddr;

memset(&sockAddr,0,sizeof(sockAddr));

LPSTR lpszAscii = T2A((LPTSTR)lpszHostAddress);

sockAddr.sin_family = AF_INET;

sockAddr.sin_addr.s_addr = inet_addr(lpszAscii);

if (sockAddr.sin_addr.s_addr == INADDR_NONE)

{

LPHOSTENT lphost;

lphost = gethostbyname(lpszAscii);

if (lphost != NULL)

sockAddr.sin_addr.s_addr = ((LPIN_ADDR)lphost->h_addr)->s_addr;

else

{

WSASetLastError(WSAEINVAL);

return FALSE;

}

}

sockAddr.sin_port = htons((u_short)nHostPort);

return Connect((SOCKADDR*)&sockAddr, sizeof(sockAddr));

}

这里我们要考虑一个问题,就是在IPX/SPX协议下,地址和TCP/IP下表示方法是不同的,IPX/SPX下地址是由4位的网络地址和6位的节点地址来表示的,比如00000000.0000000000

上面的Connect函数重载后的代码如下:

BOOL Connect(int flg,LPCTSTR lpszHostAddress,LPCTSTR lpszHostAddressse, UINT nHostPort);

BOOL CMySocket::Connect(int flg,LPCTSTR lpszHostAddress,LPCTSTR lpszHostAddressse, UINT nHostPort)

{

USES_CONVERSION;

ASSERT(lpszHostAddress != NULL);

ASSERT(lpszHostAddressse!=NULL);

SOCKADDR_IPX sockAddr;

memset(&sockAddr,0,sizeof(sockAddr));

sockAddr.sa_family=AF_IPX;

char* num=(LPSTR)(LPCSTR)lpszHostAddress;

char* node=(LPSTR)(LPCSTR)lpszHostAddressse;

AtoH(sockAddr.sa_netnum,num,4);

AtoH(sockAddr.sa_nodenum,node,6);

sockAddr.sa_socket=htons((u_short)nHostPort);

return CAsyncSocket::Connect((PSOCKADDR)&sockAddr, sizeof(sockAddr));

}

这里的AtoH函数是转换IPX/SPX地址用的。

UCHAR BtoH(char ch)

{

if ((ch >= ?') && (ch <= ?'))

{

return (ch - ?');

}

if ((ch >= 'A') && (ch <= 'F'))

{

return (ch - 'A' + 0xA);

}

if ((ch >= 'a') && (ch <= 'f'))

{

return (ch - 'a' + 0xA);

}

}

void AtoH(char *szDest, char *szSource, int iCount)

{

while (iCount--)

{

*szDest++ = (BtoH(*szSource++) << 4) + BtoH(*szSource++);

}

return;

}

最后我们重载Accept函数,MFC里的Accept函数代码如下:

virtual BOOL Accept(CAsyncSocket& rConnectedSocket,

SOCKADDR* lpSockAddr = NULL, int* lpSockAddrLen = NULL);

BOOL CAsyncSocket::Accept(CAsyncSocket& rConnectedSocket,

SOCKADDR* lpSockAddr, int* lpSockAddrLen)

{

ASSERT(rConnectedSocket.m_hSocket == INVALID_SOCKET);

ASSERT(CAsyncSocket::FromHandle(INVALID_SOCKET) == NULL);

CAsyncSocket::AttachHandle(INVALID_SOCKET, &rConnectedSocket);

SOCKET hTemp = accept(m_hSocket, lpSockAddr, lpSockAddrLen);

if (hTemp == INVALID_SOCKET)

{

DWORD dwProblem = GetLastError();

CAsyncSocket::DetachHandle(rConnectedSocket.m_hSocket, FALSE);

rConnectedSocket.m_hSocket = INVALID_SOCKET;

SetLastError(dwProblem);

}

else if (CAsyncSocket::FromHandle(INVALID_SOCKET) != NULL)

{

rConnectedSocket.m_hSocket = hTemp;

CAsyncSocket::DetachHandle(INVALID_SOCKET, FALSE);

CAsyncSocket::AttachHandle(hTemp, &rConnectedSocket);

}

return (hTemp != INVALID_SOCKET);

}

看了MFC的源代码后,我们发现只要改变第二个参数的类型就可以了。重载后的函数如下

virtual BOOL Accept(CAsyncSocket& rConnectedSocket,

PSOCKADDR lpSockAddr = NULL, int* lpSockAddrLen = NULL);

BOOL CMySocket::Accept(CAsyncSocket& rConnectedSocket,

PSOCKADDR lpSockAddr, int* lpSockAddrLen)

{

ASSERT(rConnectedSocket.m_hSocket == INVALID_SOCKET);

ASSERT(CAsyncSocket::FromHandle(INVALID_SOCKET) == NULL);

CAsyncSocket::AttachHandle(INVALID_SOCKET, &rConnectedSocket);

SOCKET hTemp = accept(m_hSocket, lpSockAddr, lpSockAddrLen);

if (hTemp == INVALID_SOCKET)

{

DWORD dwProblem = GetLastError();

CAsyncSocket::DetachHandle(rConnectedSocket.m_hSocket, FALSE);

rConnectedSocket.m_hSocket = INVALID_SOCKET;

SetLastError(dwProblem);

}

else if (CAsyncSocket::FromHandle(INVALID_SOCKET) != NULL)

{

rConnectedSocket.m_hSocket = hTemp;

CAsyncSocket::DetachHandle(INVALID_SOCKET, FALSE);

CAsyncSocket::AttachHandle(hTemp, &rConnectedSocket);

}

return (hTemp != INVALID_SOCKET);

}

OK,现在所有的函数重载完毕,我们从CAsyncSocket类继承来的MySocket类已经是一个支持IPX/SPX的类了。

现在就请尽情地享受MFC里的事件消息机制带给你的方便吧。

参考书籍:《WINDOWS网络编程技术》

《深入浅出MFC》

 
 
 
免责声明:本文为网络用户发布,其观点仅代表作者个人观点,与本站无关,本站仅提供信息存储服务。文中陈述内容未经本站证实,其真实性、完整性、及时性本站不作任何保证或承诺,请读者仅作参考,并请自行核实相关内容。
2023年上半年GDP全球前十五强
 百态   2023-10-24
美众议院议长启动对拜登的弹劾调查
 百态   2023-09-13
上海、济南、武汉等多地出现不明坠落物
 探索   2023-09-06
印度或要将国名改为“巴拉特”
 百态   2023-09-06
男子为女友送行,买票不登机被捕
 百态   2023-08-20
手机地震预警功能怎么开?
 干货   2023-08-06
女子4年卖2套房花700多万做美容:不但没变美脸,面部还出现变形
 百态   2023-08-04
住户一楼被水淹 还冲来8头猪
 百态   2023-07-31
女子体内爬出大量瓜子状活虫
 百态   2023-07-25
地球连续35年收到神秘规律性信号,网友:不要回答!
 探索   2023-07-21
全球镓价格本周大涨27%
 探索   2023-07-09
钱都流向了那些不缺钱的人,苦都留给了能吃苦的人
 探索   2023-07-02
倩女手游刀客魅者强控制(强混乱强眩晕强睡眠)和对应控制抗性的关系
 百态   2020-08-20
美国5月9日最新疫情:美国确诊人数突破131万
 百态   2020-05-09
荷兰政府宣布将集体辞职
 干货   2020-04-30
倩女幽魂手游师徒任务情义春秋猜成语答案逍遥观:鹏程万里
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案神机营:射石饮羽
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案昆仑山:拔刀相助
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案天工阁:鬼斧神工
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案丝路古道:单枪匹马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:与虎谋皮
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:李代桃僵
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:指鹿为马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:小鸟依人
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:千金买邻
 干货   2019-11-12
 
推荐阅读
 
 
 
>>返回首頁<<
 
靜靜地坐在廢墟上,四周的荒凉一望無際,忽然覺得,淒涼也很美
© 2005- 王朝網路 版權所有