重载成员函数使CAsyncSocket类支持IPXSPX协议

    技术2022-05-11  119

    本问系作者原创,转载请注明出处。重载成员函数使CAsyncSocket类支持IPX/SPX协议 --------------------------------------------------------------------------------     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》


    最新回复(0)