今天竟然发现了一篇我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》