719 lines
18 KiB
C++
719 lines
18 KiB
C++
/******************************************************************************\
|
|
* rmmc.cpp
|
|
*
|
|
* This IPv6 sample demonstrates the use of WSARecvMsg() and the IPV6_PKTINFO option
|
|
* to determine multicast reception on a datagram socket.
|
|
*
|
|
* WSARecvMsg is new to Windows Sockets in Windows XP.
|
|
*
|
|
* This sample requires that TCP/IP version 6 be installed on the system.
|
|
*
|
|
* A IPv6 datagram socket is created and bound and the IPV6_PKTINFO option is set.
|
|
* The socket is joined to multicast group and an overlapped WSARecvMsg is posted.
|
|
* MSG_MCAST flag is checked upon completion, upon where further
|
|
* processing can be made.
|
|
*
|
|
*
|
|
* This is a part of the Microsoft Source Code Samples.
|
|
* Copyright 1996 - 2006 Microsoft Corporation.
|
|
* All rights reserved.
|
|
* This source code is only intended as a supplement to
|
|
* Microsoft Development Tools and/or WinHelp documentation.
|
|
* See these sources for detailed information regarding the
|
|
* Microsoft samples programs.
|
|
\******************************************************************************/
|
|
|
|
#ifdef _IA64_
|
|
#pragma warning (disable: 4311)
|
|
#pragma warning (disable: 4312)
|
|
#endif
|
|
|
|
#ifndef WIN32_LEAN_AND_MEAN
|
|
#define WIN32_LEAN_AND_MEAN
|
|
#endif
|
|
|
|
#include <winsock2.h>
|
|
#include <ws2ipdef.h>
|
|
#include <ws2tcpip.h>
|
|
#include <mstcpip.h>
|
|
#include <mswsock.h>
|
|
#include <iphlpapi.h>
|
|
#include <stdio.h>
|
|
|
|
// "safe" macros
|
|
|
|
#define MALLOC(x) \
|
|
HeapAlloc(GetProcessHeap(),HEAP_ZERO_MEMORY,x)
|
|
|
|
#define FREE(p) \
|
|
if(NULL != p) {HeapFree(GetProcessHeap(),0,p); p = NULL;}
|
|
|
|
#define MSIZE(p) \
|
|
HeapSize(GetProcessHeap(),0,p)
|
|
|
|
#define CLOSESOCK(s) \
|
|
if(INVALID_SOCKET != s) {closesocket(s); s = INVALID_SOCKET;}
|
|
|
|
#define CLOSESOCKEVENT(h) \
|
|
if(WSA_INVALID_EVENT != h) {WSACloseEvent(h); h = WSA_INVALID_EVENT;}
|
|
|
|
#define ERR(e) \
|
|
{ \
|
|
printf("%s:%s failed: %d [%s@%ld]\n",__FUNCTION__,e,WSAGetLastError(),__FILE__,__LINE__); \
|
|
}
|
|
|
|
//constants
|
|
|
|
#define WS_VER 0x0202
|
|
|
|
#define DEFAULT_PORT 12345
|
|
|
|
#define DEFAULT_WAIT 5000
|
|
|
|
#define MCAST_V6 "ff1f::1"
|
|
|
|
#define TST_MSG "Hello\0"
|
|
|
|
//Helper functions
|
|
|
|
VOID SET_PORT(PSOCKADDR pAddr,USHORT port)
|
|
{
|
|
if (AF_INET6 == pAddr->sa_family)
|
|
{
|
|
((SOCKADDR_IN6*)pAddr)->sin6_port = htons(port);
|
|
}
|
|
else
|
|
{
|
|
((SOCKADDR_IN*)pAddr)->sin_port = htons(port);
|
|
}
|
|
}
|
|
|
|
VOID InitOverlap(LPWSAOVERLAPPED pOver)
|
|
{
|
|
CLOSESOCKEVENT(pOver->hEvent);
|
|
|
|
ZeroMemory(pOver,sizeof(WSAOVERLAPPED));
|
|
|
|
if (WSA_INVALID_EVENT == (pOver->hEvent = WSACreateEvent()))
|
|
{
|
|
ERR("WSACreateEvent");
|
|
}
|
|
}
|
|
|
|
VOID InitMcastAddr(SOCKADDR* pmcaddr,int size)
|
|
{
|
|
WSAStringToAddressA(MCAST_V6,
|
|
AF_INET6,
|
|
NULL,
|
|
pmcaddr,
|
|
&size
|
|
);
|
|
|
|
((SOCKADDR_IN6*)pmcaddr)->sin6_port = htons(DEFAULT_PORT);
|
|
|
|
|
|
}
|
|
|
|
BOOL SetIpv6PktInfoOption(SOCKET sock)
|
|
{
|
|
DWORD dwEnableOption = 1;
|
|
|
|
if (SOCKET_ERROR == setsockopt(sock,
|
|
IPPROTO_IPV6,
|
|
IPV6_PKTINFO,
|
|
(CHAR*)&dwEnableOption,
|
|
sizeof dwEnableOption
|
|
))
|
|
{
|
|
ERR("setsockopt IPV6_PKTINFO");
|
|
return FALSE;
|
|
}
|
|
|
|
return TRUE;
|
|
|
|
}
|
|
|
|
BOOL AllocAndInitIpv6PktInfo(LPWSAMSG pWSAMsg)
|
|
{
|
|
PBYTE CtrlBuf = (PBYTE)MALLOC(WSA_CMSG_SPACE(sizeof IN6_PKTINFO)); //caller frees heap allocated CtrlBuf
|
|
|
|
if(NULL == CtrlBuf)
|
|
{
|
|
ERR("HeapAlloc");
|
|
return FALSE;
|
|
}
|
|
|
|
pWSAMsg->Control.buf = (CHAR*)CtrlBuf;
|
|
pWSAMsg->Control.len = (ULONG)MSIZE(CtrlBuf);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
BOOL ProcessIpv6Msg(LPWSAMSG pWSAMsg)
|
|
{
|
|
PWSACMSGHDR pCtrlInfo = NULL;
|
|
PIN6_PKTINFO pPktInfo = NULL;
|
|
|
|
pCtrlInfo = WSA_CMSG_FIRSTHDR(pWSAMsg);
|
|
|
|
if ((IPPROTO_IPV6 == pCtrlInfo->cmsg_level) &&
|
|
(IPV6_PKTINFO == pCtrlInfo->cmsg_type))
|
|
{
|
|
pPktInfo = (PIN6_PKTINFO)WSA_CMSG_DATA(pCtrlInfo);
|
|
|
|
//do something with pPktInfo
|
|
|
|
return TRUE;
|
|
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
BOOL RouteLookup(SOCKADDR *destAddr,
|
|
int destLen,
|
|
SOCKADDR *localAddr,
|
|
int localLen
|
|
)
|
|
{
|
|
DWORD dwBytes = 0;
|
|
BOOL bRet = FALSE;
|
|
CHAR szAddr[MAX_PATH] = {0};
|
|
SOCKET s = INVALID_SOCKET;
|
|
|
|
|
|
__try
|
|
{
|
|
|
|
if (INVALID_SOCKET == (s = socket(destAddr->sa_family,SOCK_DGRAM,0)))
|
|
{
|
|
ERR("socket");
|
|
__leave;
|
|
}
|
|
|
|
if (SOCKET_ERROR == WSAIoctl(s,
|
|
SIO_ROUTING_INTERFACE_QUERY,
|
|
destAddr,
|
|
destLen,
|
|
localAddr,
|
|
localLen,
|
|
&dwBytes,
|
|
NULL,
|
|
NULL
|
|
))
|
|
{
|
|
ERR("WSAIoctl");
|
|
__leave;
|
|
}
|
|
|
|
dwBytes = sizeof(szAddr);
|
|
|
|
ZeroMemory(szAddr,dwBytes);
|
|
|
|
WSAAddressToStringA(destAddr,
|
|
(DWORD)destLen,
|
|
NULL,
|
|
szAddr,
|
|
&dwBytes
|
|
);
|
|
|
|
dwBytes = sizeof(szAddr);
|
|
|
|
ZeroMemory(szAddr,dwBytes);
|
|
|
|
WSAAddressToStringA(localAddr,
|
|
(DWORD)localLen,
|
|
NULL,
|
|
szAddr,
|
|
&dwBytes
|
|
);
|
|
|
|
bRet = TRUE;
|
|
}
|
|
__finally
|
|
{
|
|
CLOSESOCK(s);
|
|
}
|
|
|
|
return bRet;
|
|
}
|
|
|
|
|
|
DWORD
|
|
GetInterfaceIndexForAddress(
|
|
SOCKADDR *pAddr
|
|
)
|
|
{
|
|
IP_ADAPTER_UNICAST_ADDRESS *pTmpUniAddr = NULL;
|
|
IP_ADAPTER_ADDRESSES *pAdaptAddr = NULL,
|
|
*pTmpAdaptAddr = NULL;
|
|
MIB_IPADDRTABLE *pMibTable = NULL;
|
|
DWORD dwRet = 0,
|
|
dwReturn = (DWORD) SOCKET_ERROR,
|
|
dwSize = 0,
|
|
Family = AF_UNSPEC,
|
|
dwIndex = 0;
|
|
BOOL bFound = FALSE;
|
|
|
|
typedef ULONG (*LPFN_GETADAPTERSADDRESSES) (ULONG Family,DWORD Flags,PVOID Reserved,PIP_ADAPTER_ADDRESSES pAdapterAddresses,PULONG pOutBufLen);
|
|
LPFN_GETADAPTERSADDRESSES fnGetAdaptersAddresses = (LPFN_GETADAPTERSADDRESSES)GetProcAddress(GetModuleHandleA("iphlpapi"),"GetAdaptersAddresses");
|
|
|
|
__try
|
|
{
|
|
if (NULL == pAddr)
|
|
{
|
|
WSASetLastError(ERROR_INVALID_PARAMETER);
|
|
ERR("GetInterfaceIndexForAddress: invalid parameter passed (pAddr == NULL)");
|
|
__leave;
|
|
}
|
|
|
|
switch (pAddr->sa_family)
|
|
{
|
|
case AF_INET:
|
|
Family = AF_INET;
|
|
break;
|
|
case AF_INET6:
|
|
Family = AF_INET6;
|
|
break;
|
|
default:
|
|
WSASetLastError(WSAEAFNOSUPPORT);
|
|
__leave;
|
|
break;
|
|
}
|
|
|
|
if (fnGetAdaptersAddresses)
|
|
{
|
|
|
|
|
|
if (ERROR_BUFFER_OVERFLOW != (dwRet = fnGetAdaptersAddresses(Family,
|
|
GAA_FLAG_SKIP_ANYCAST|GAA_FLAG_SKIP_MULTICAST|GAA_FLAG_SKIP_DNS_SERVER,
|
|
NULL,
|
|
NULL,
|
|
&dwSize
|
|
)))
|
|
{
|
|
WSASetLastError(dwRet);
|
|
ERR("GetAdaptersAddresses");
|
|
__leave;
|
|
}
|
|
|
|
if (NULL == (pAdaptAddr = (IP_ADAPTER_ADDRESSES*)MALLOC(dwSize)))
|
|
{
|
|
ERR("HeapAlloc");
|
|
__leave;
|
|
}
|
|
|
|
if (ERROR_SUCCESS != (dwRet = fnGetAdaptersAddresses(Family,
|
|
GAA_FLAG_SKIP_ANYCAST|GAA_FLAG_SKIP_MULTICAST|GAA_FLAG_SKIP_DNS_SERVER,
|
|
NULL,
|
|
pAdaptAddr,
|
|
&dwSize
|
|
)))
|
|
{
|
|
WSASetLastError(dwRet);
|
|
ERR("GetAdaptersAddresses");
|
|
__leave;
|
|
}
|
|
|
|
//look at each IP_ADAPTER_ADDRESSES node
|
|
|
|
pTmpAdaptAddr = pAdaptAddr;
|
|
|
|
while (pTmpAdaptAddr)
|
|
{
|
|
|
|
//look at each IP_ADAPTER_UNICAST_ADDRESS node
|
|
|
|
pTmpUniAddr = pTmpAdaptAddr->FirstUnicastAddress;
|
|
|
|
|
|
while (pTmpUniAddr)
|
|
{
|
|
|
|
|
|
|
|
if (AF_INET == pTmpUniAddr->Address.lpSockaddr->sa_family)
|
|
{
|
|
|
|
if (IN4_ADDR_EQUAL(&((SOCKADDR_IN*)pAddr)->sin_addr,&((SOCKADDR_IN*)pTmpUniAddr->Address.lpSockaddr)->sin_addr))
|
|
{
|
|
dwReturn = pTmpAdaptAddr->IfIndex;
|
|
bFound = TRUE;
|
|
|
|
break;
|
|
}
|
|
|
|
}
|
|
else
|
|
{
|
|
if (IN6_ADDR_EQUAL(&((SOCKADDR_IN6*)pAddr)->sin6_addr, &((SOCKADDR_IN6*)pTmpUniAddr->Address.lpSockaddr)->sin6_addr ))
|
|
{
|
|
dwReturn = pTmpAdaptAddr->Ipv6IfIndex;
|
|
bFound = TRUE;
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
pTmpUniAddr = pTmpUniAddr->Next;
|
|
|
|
}
|
|
|
|
if (bFound)
|
|
break;
|
|
|
|
pTmpAdaptAddr = pTmpAdaptAddr->Next;
|
|
|
|
}
|
|
} //end GetAdaptersAddresses
|
|
else //use mibtable
|
|
{
|
|
//call with NULL to get size needed to alloc
|
|
if (ERROR_INSUFFICIENT_BUFFER != (dwRet = GetIpAddrTable(NULL,
|
|
&dwSize,
|
|
TRUE
|
|
)))
|
|
{
|
|
WSASetLastError(dwRet);
|
|
ERR("GetIpAddrTable");
|
|
__leave;
|
|
}
|
|
|
|
if (NULL == (pMibTable = (MIB_IPADDRTABLE*)MALLOC(dwSize)))
|
|
{
|
|
ERR("HeapAlloc");
|
|
__leave;
|
|
}
|
|
|
|
if (NO_ERROR != (dwRet = GetIpAddrTable(pMibTable,
|
|
&dwSize,
|
|
TRUE
|
|
)))
|
|
{
|
|
WSASetLastError(dwRet);
|
|
ERR("GetIpAddrTable");
|
|
__leave;
|
|
}
|
|
|
|
for (dwIndex = 0; dwIndex < pMibTable->dwNumEntries; dwIndex++)
|
|
{
|
|
if( ((SOCKADDR_IN*)pAddr)->sin_addr.s_addr == pMibTable->table[dwIndex].dwAddr)
|
|
{
|
|
dwReturn = pMibTable->table[dwIndex].dwIndex;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
}//end use mibtable
|
|
|
|
}
|
|
__finally
|
|
{
|
|
FREE(pAdaptAddr);
|
|
FREE(pMibTable);
|
|
}
|
|
|
|
return dwReturn;
|
|
}
|
|
|
|
int SetSendInterface(SOCKET s, SOCKADDR *iface)
|
|
{
|
|
char *optval=NULL;
|
|
int optlevel = 0,
|
|
option = 0,
|
|
optlen = 0,
|
|
rc = NO_ERROR;
|
|
DWORD dwIPv6Index = 0;
|
|
|
|
|
|
if (iface->sa_family == AF_INET)
|
|
{
|
|
// Setup the v4 option values
|
|
optlevel = IPPROTO_IP;
|
|
option = IP_MULTICAST_IF;
|
|
optval = (char *) &((SOCKADDR_IN *)iface)->sin_addr.s_addr;
|
|
optlen = sizeof(((SOCKADDR_IN *)iface)->sin_addr.s_addr);
|
|
}
|
|
else if (iface->sa_family == AF_INET6)
|
|
{
|
|
// Setup the v6 option values
|
|
optlevel = IPPROTO_IPV6;
|
|
option = IPV6_MULTICAST_IF;
|
|
if (SOCKET_ERROR == (dwIPv6Index = GetInterfaceIndexForAddress(iface)))
|
|
{
|
|
rc = SOCKET_ERROR;
|
|
}
|
|
optval = (char *) &dwIPv6Index;
|
|
optlen = sizeof(dwIPv6Index);
|
|
}
|
|
else
|
|
{
|
|
WSASetLastError(WSAEAFNOSUPPORT);
|
|
rc = SOCKET_ERROR;
|
|
}
|
|
// Set send IF
|
|
if (rc != SOCKET_ERROR)
|
|
{
|
|
// Set the send interface
|
|
rc = setsockopt(
|
|
s,
|
|
optlevel,
|
|
option,
|
|
optval,
|
|
optlen
|
|
);
|
|
if(SOCKET_ERROR == rc)
|
|
{
|
|
ERR("setsockopt");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return rc;
|
|
}
|
|
|
|
LPFN_WSARECVMSG GetWSARecvMsgFunctionPointer()
|
|
{
|
|
LPFN_WSARECVMSG lpfnWSARecvMsg = NULL;
|
|
GUID guidWSARecvMsg = WSAID_WSARECVMSG;
|
|
SOCKET sock = INVALID_SOCKET;
|
|
DWORD dwBytes = 0;
|
|
|
|
sock = socket(AF_INET6,SOCK_DGRAM,0);
|
|
|
|
if(SOCKET_ERROR == WSAIoctl(sock,
|
|
SIO_GET_EXTENSION_FUNCTION_POINTER,
|
|
&guidWSARecvMsg,
|
|
sizeof(guidWSARecvMsg),
|
|
&lpfnWSARecvMsg,
|
|
sizeof(lpfnWSARecvMsg),
|
|
&dwBytes,
|
|
NULL,
|
|
NULL
|
|
))
|
|
{
|
|
ERR("WSAIoctl SIO_GET_EXTENSION_FUNCTION_POINTER");
|
|
return NULL;
|
|
}
|
|
|
|
CLOSESOCK(sock);
|
|
|
|
return lpfnWSARecvMsg;
|
|
}
|
|
|
|
|
|
int __cdecl main()
|
|
{
|
|
WSADATA wsd;
|
|
INT i = 0,
|
|
nErr = 0,
|
|
nStartup = 0,
|
|
rc = 0;
|
|
SOCKET sock = INVALID_SOCKET;
|
|
SOCKADDR_STORAGE addr = {0},
|
|
mcaddr = {0},
|
|
remoteaddr = {0};
|
|
WSAOVERLAPPED over = {0};
|
|
WSABUF wsabuf = {0};
|
|
DWORD dwBytes = 0,
|
|
dwFlags = 0,
|
|
dwRet = 0;
|
|
IPV6_MREQ mreq = {0};
|
|
WSAMSG wsamsg = {0};
|
|
LPFN_WSARECVMSG WSARecvMsg = NULL;
|
|
|
|
|
|
__try
|
|
{
|
|
//Initialize Winsock
|
|
|
|
nErr = WSAStartup(WS_VER,&wsd);
|
|
if (nErr)
|
|
{
|
|
WSASetLastError(nErr);
|
|
ERR("WSAStartup");
|
|
__leave;
|
|
}
|
|
else
|
|
nStartup++;
|
|
|
|
|
|
// bind socket and register multicast
|
|
|
|
mcaddr.ss_family = AF_INET6;
|
|
|
|
InitMcastAddr((SOCKADDR*)&mcaddr,sizeof mcaddr);
|
|
|
|
if (INVALID_SOCKET == (sock = socket(AF_INET6,SOCK_DGRAM,0)))
|
|
{
|
|
ERR("socket");
|
|
__leave;
|
|
}
|
|
|
|
if(!RouteLookup((SOCKADDR*)&mcaddr,
|
|
sizeof mcaddr,
|
|
(SOCKADDR*)&addr,
|
|
sizeof addr
|
|
))
|
|
{
|
|
ERR("RouteLookup");
|
|
__leave;
|
|
}
|
|
|
|
SET_PORT((SOCKADDR*)&addr,DEFAULT_PORT);
|
|
|
|
if (SOCKET_ERROR == bind(sock,(SOCKADDR*)&addr,sizeof addr))
|
|
{
|
|
ERR("bind");
|
|
__leave;
|
|
}
|
|
|
|
mreq.ipv6mr_multiaddr = ((SOCKADDR_IN6*)&mcaddr)->sin6_addr;
|
|
|
|
if (SOCKET_ERROR == setsockopt(sock,
|
|
IPPROTO_IPV6,
|
|
IPV6_ADD_MEMBERSHIP,
|
|
(char*)&mreq,
|
|
sizeof mreq
|
|
))
|
|
{
|
|
ERR("setsockopt IPV6_ADD_MEMBRESHIP");
|
|
__leave;
|
|
}
|
|
|
|
// PktInfo
|
|
|
|
if (!SetIpv6PktInfoOption(sock))
|
|
{
|
|
ERR("SetIpv6PktInfoOption");
|
|
__leave;
|
|
}
|
|
|
|
if(!AllocAndInitIpv6PktInfo(&wsamsg))
|
|
{
|
|
ERR("AllocAndInitIpv6PktInfo");
|
|
__leave;
|
|
}
|
|
|
|
// data buffer
|
|
|
|
wsabuf.buf = (CHAR*)MALLOC(100);
|
|
|
|
if(NULL == wsabuf.buf)
|
|
{
|
|
ERR("HeapAlloc");
|
|
__leave;
|
|
}
|
|
|
|
wsabuf.len = (ULONG)MSIZE(wsabuf.buf);
|
|
wsamsg.lpBuffers = &wsabuf;
|
|
wsamsg.dwBufferCount = 1;
|
|
|
|
// packet source address
|
|
wsamsg.name = (SOCKADDR*)&remoteaddr;
|
|
wsamsg.namelen = sizeof remoteaddr;
|
|
|
|
//Post overlapped WSARecvMsg
|
|
InitOverlap(&over);
|
|
|
|
if (NULL == (WSARecvMsg = GetWSARecvMsgFunctionPointer()))
|
|
{
|
|
ERR("GetWSARecvMsgFunctionPointer");
|
|
__leave;
|
|
}
|
|
|
|
if (SOCKET_ERROR == WSARecvMsg(sock,
|
|
&wsamsg,
|
|
&dwBytes,
|
|
&over,
|
|
NULL
|
|
))
|
|
{
|
|
if (WSA_IO_PENDING != WSAGetLastError())
|
|
{
|
|
ERR("WSARecvMsg");
|
|
__leave;
|
|
}
|
|
}
|
|
|
|
//set send interface
|
|
|
|
if (SOCKET_ERROR == SetSendInterface(sock,(SOCKADDR*)&addr))
|
|
{
|
|
ERR("SetSendInterface");
|
|
__leave;
|
|
}
|
|
|
|
//send msg to multicast
|
|
|
|
|
|
SET_PORT((SOCKADDR*)&mcaddr,DEFAULT_PORT);
|
|
|
|
//send a few packets
|
|
for (i=0;i<5;i++)
|
|
{
|
|
|
|
if (SOCKET_ERROR == (rc = sendto(sock,
|
|
TST_MSG,
|
|
lstrlenA(TST_MSG),
|
|
0,
|
|
(SOCKADDR*)&mcaddr,
|
|
sizeof (mcaddr)
|
|
)))
|
|
{
|
|
ERR("sendto");
|
|
__leave;
|
|
}
|
|
|
|
printf("Sent %d bytes\n",rc);
|
|
}
|
|
|
|
dwRet = WaitForSingleObject(over.hEvent,DEFAULT_WAIT);
|
|
|
|
if (dwRet)
|
|
{
|
|
printf("%s\n",gai_strerror(dwRet));
|
|
__leave;
|
|
}
|
|
|
|
if (!WSAGetOverlappedResult(sock,
|
|
&over,
|
|
&dwBytes,
|
|
TRUE,
|
|
&dwFlags
|
|
))
|
|
{
|
|
ERR("WSAGetOverlappedResult");
|
|
__leave;
|
|
}
|
|
|
|
printf("WSARecvMsg completed with %d bytes\n",dwBytes);
|
|
|
|
// if multicast packet do further processing
|
|
if (MSG_MCAST & wsamsg.dwFlags)
|
|
{
|
|
if (ProcessIpv6Msg(&wsamsg))
|
|
{
|
|
//do something more interesting here
|
|
printf("Recvd multicast msg.\n");
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
__finally
|
|
{
|
|
|
|
|
|
CLOSESOCK(sock);
|
|
FREE(wsabuf.buf);
|
|
FREE(wsamsg.Control.buf);
|
|
CLOSESOCKEVENT(over.hEvent);
|
|
if(nStartup) WSACleanup();
|
|
}
|
|
|
|
return 0;
|
|
}
|