< Service Registration Program Example | RnR Main | Querying DNS >
What do we have in this chapter 8 part 4?
|
WSALookupServiceBegin() and WSALookupServiceNext() Example
Create a new empty Win32 console mode application and add the project/solution name.
Add the following code.
// Standard I/O #include <stdio.h> // Link to ws2_32.lib #include <winsock2.h> // For guids #include <svcguid.h> |
int main(int argc, char * argv[])
{
WSADATA wsaData;
DWORD dwResult;
HANDLE hLookup = 0;
WSAQUERYSET lpRestrictions;
GUID guid = SVCID_HOSTNAME;
dwResult = WSAStartup(MAKEWORD(2,2), &wsaData);
if (dwResult != 0)
{
printf("Cannot startup Winsock, error code %d\n", dwResult);
exit(1);
}
else
printf("WSAStartup() is OK!\n");
ZeroMemory(&lpRestrictions, sizeof(WSAQUERYSET));
lpRestrictions.dwSize = sizeof(WSAQUERYSET);
lpRestrictions.lpServiceClassId = &guid;
dwResult = WSALookupServiceBegin(&lpRestrictions, LUP_RETURN_NAME, &hLookup);
if (dwResult != SOCKET_ERROR)
{
DWORD dwLength = 0;
WSAQUERYSET * pqs = NULL;
printf("WSALookupServiceBegin() is OK!\n");
// Picking an arbitrary value works fine
// COMMENT/UNCOMMENT below for success on Windows 2000, XP
pqs = (WSAQUERYSET *) malloc(sizeof(WSAQUERYSET) + 100);
dwLength = sizeof(WSAQUERYSET) + 100;
do
{
if (WSALookupServiceNext(hLookup, 0, &dwLength, pqs) != 0)
{
dwResult = WSAGetLastError();
if((dwResult == WSA_E_NO_MORE) || (dwResult == WSAENOMORE))
{
printf("No more record found!\n");
break;
}
printf("WSALookupServiceNext() failed with error code %d\n", WSAGetLastError());
}
else
{
dwResult = 0;
printf("WSALookupServiceNext() looks fine!\n");
}
if (dwResult == WSAEFAULT)
{
if (pqs)
{
printf("Freeing pqs...\n");
free(pqs);
}
// Reallocate
pqs = (WSAQUERYSET *) malloc(dwLength);
if (!pqs)
{
printf("Could not allocate memory: %d\n", GetLastError());
exit(2);
}
else
{
printf("Memory allocated for pqs successfully!\n");
continue;
}
}
// Output it since we have it now
if ((dwResult == 0) && (pqs))
{
printf(" Service instance name: %S\n", pqs->lpszServiceInstanceName);
printf(" Name space num: %d\n", pqs->dwNameSpace);
printf(" Num of protocols: %d\n", pqs->dwNumberOfProtocols);
printf(" Version: %d\n", pqs->lpVersion);
}
} while ((dwResult != WSA_E_NO_MORE) && (dwResult != WSAENOMORE));
// clean-up
free(pqs);
if(WSALookupServiceEnd(hLookup) == 0)
printf("hLookup handle was released!\n");
else
printf("WSALookupServiceEnd(hLookup) failed with error code %d\n", WSAGetLastError());
}
else
{
printf("Error on WSALookupServiceBegin: %d\n", WSAGetLastError());
exit(3);
}
if(WSACleanup() == 0)
printf("WSACleanup() is OK!\n");
else
printf("WSACleanup() failed with error code %d\n", WSAGetLastError());
return 0;
}
Build and run the project.
----------------------------------------------------------
Now that you know how to register a service within a name space, let's look at how a client can query the name space for a given service so it can get information about the service for communication purposes. Name resolution is quite a bit simpler than service registration, even though name resolution uses three functions for querying: WSALookupServiceBegin(), WSALookupServiceNext(), and WSALookupServiceEnd().
The first step when performing a query is to call WSALookupServiceBegin(), which initiates the query by setting up the constraints within which the query will act. The function prototype is as follows:
INT WSALookupServiceBegin (
LPWSAQUERYSET lpqsRestrictions,
DWORD dwControlFlags,
LPHANDLE lphLookup
);
The first parameter is a WSAQUERYSET structure that places constraints on the query, such as limiting the name spaces to query. The second parameter, dwControlFlags, determines the depth of the search. Table 8-7 contains the various possible flags and their meanings. These flags affect how the query behaves as well as which data is returned from the query. The last parameter is of type HANDLE and is initialized upon function return. The return value is 0 on success; otherwise, SOCKET_ERROR is returned. If one or more parameters are invalid, WSAGetLastError() returns WSAEINVAL. If the name is found in the name space but no data matches the given restrictions, the error is WSANO_DATA. If the given service does not exist, WSASERVICE_NOT_FOUND is the error.
Table 8-7 Control Flags
|
|
Flag |
Meaning |
LUP_DEEP |
In hierarchical name spaces, query deep as opposed to the first level. |
LUP_CONTAINERS |
Retrieve container objects only. This flag pertains to hierarchical name spaces only. |
LUP_NOCONTAINERS |
Do not return any containers. This flag pertains to hierarchical name spaces only. |
LUP_FLUSHCACHE |
Ignore cached information, and query the name space directly. Note that not all name providers cache queries. |
LUP_FLUSHPREVIOUS |
Instruct the name provider to discard the information set previously returned. This flag is typically used after WSALookupServiceNext() returns WSA_NOT_ENOUGH_MEMORY. The information that was too big for the supplied buffer is discarded, and the next information set is to be retrieved. |
LUP_NEAREST |
Retrieve the results in order of distance. Note that it is up to the name provider to calculate this distance metric, as there is no specific provision for this information when a service is registered. Name providers are not required to support this concept. |
LUP_RES_SERVICE |
Specifies that the local address be returned in the CSADDR_INFO structure. |
LUP_RETURN_ADDR |
Retrieve the addresses as lpcsaBuffer. |
LUP_RETURN_ALIASES |
Retrieve only alias information. Each alias is returned in successive calls to WSALookupServiceNext() and will have the RESULT_IS_ALIAS flag set. |
LUP_RETURN_ALL |
Retrieve all available information. |
LUP_RETURN_BLOB |
Retrieve the private data as lpBlob. |
LUP_RETURN_COMMENT |
Retrieve the comment as lpszComment. |
LUP_RETURN_NAME |
Retrieve the name as lpszServiceInstanceName. |
LUP_RETURN_TYPE |
Retrieve the type as lpServiceClassId. |
LUP_RETURN_VERSION |
Retrieve the version as lpVersion. |
When you make a call to WSALookupServiceBegin(), a handle for the query is returned that you pass to WSALookupServiceNext(), which returns the data to you. The function is defined as:
INT WSALookupServiceNext (
HANDLE hLookup,
DWORD dwControlFlags,
LPDWORD lpdwBufferLength,
LPWSAQUERYSET lpqsResults
);
The handle hLookup is returned from WSALookupServiceBegin(). The dwControlFlags parameter has the same meaning as in WSALookupServiceBegin() except that only LUP_FLUSHPREVIOUS is supported. The parameter lpdwBufferLength is the length of the buffer passed as lpqsResults. Because the WSAQUERYSET structure could contain binary large object (BLOB) data, it is often required that you pass a buffer larger than the structure itself. If the buffer size is insufficient for the data to be returned, the function call fails with WSA_NOT_ENOUGH_MEMORY.
Once you have initiated the query with WSALookupServiceBegin(), call WSALookupServiceNext() until the error WSA_E_NO_MORE (10110) is generated. Caution: in earlier implementations of Winsock, the error code for no more data is WSAENOMORE (10102), so robust code should check for both error codes. Once all the data has been returned or you have finished querying, call WSALookupServiceEnd() with the HANDLE variable used in the queries. The function is:
INT WSALookupServiceEnd (HANDLE hLookup);
Let's look at how you can query the service you registered in the previous section. The first thing to do is set up a WSAQUERYSET structure that defines the query. Look at the following code.
WSAQUERYSET qs;
GUID guid = SVCID_NETWARE(200);
AFPROTOCOLS afp[2] = {{AF_IPX, NSPROTO_IPX}, {AF_INET, IPPROTO_UDP}};
HANDLE hLookup;
int ret;
memset(&qs, 0, sizeof(qs));
qs.dwSize = sizeof (WSAQUERYSET);
qs.lpszServiceInstanceName = "Widget Server";
qs.lpServiceClassId = &guid;
qs.dwNameSpace = NS_ALL;
qs.dwNumberOfProtocols = 2;
qs.lpafpProtocols = afp;
ret = WSALookupServiceBegin(&qs, LUP_RETURN_ADDR │ LUP_RETURN_NAME, &hLookup);
if (ret == SOCKET_ERROR)
// Error
Remember that all service lookups are based on the service class GUID that the service you are searching for is based on. The variable guid is set to the service class ID of our server. You first initialize qs to 0 and set the dwSize field to the size of the structure. The next step is to give the name of the service you are searching for. The service name can be the exact name of the server, or you can specify a wildcard (*) that will return all services of the given service class GUID. Next, tell the query to search all name spaces by using the NS_ALL constant. Last, set up the protocols that the client is capable of connecting with, which are IPX and UDP/IP. This is done by using an array of two AFPROTOCOLS structures.
To begin the query, you must call WSALookupServiceBegin(). The first parameter is our WSAQUERYSET structure, and the next parameters are flags defining which data should be returned if a matching service is found. Here you specify that you want addressing information and the service name by logically ORing the two flags LUP_RETURN_ADDR and LUP_RETURN_NAME. The flag LUP_RETURN_NAME is necessary only if you're specifying the wildcard (*) for the service name; otherwise, you already know the service name. The last parameter is a HANDLE variable that identifies this particular query. It will be initialized upon successful return.
Once the query is successfully opened, you call WSALookupServiceNext() until WSA_E_NO_MORE is returned. Each successful call returns information about a service that matches our criteria. Here is what the code looks like for this step:
char buff[sizeof(WSAQUERYSET) + 2000];
DWORD dwLength, dwErr;
WSAQUERYSET *pqs = NULL;
SOCKADDR *addr;
int i;
pqs = (WSAQUERYSET *)buff;
dwLength = sizeof(WSAQUERYSET) + 2000;
while (1)
{
ret = WSALookupServiceNext(hLookup, 0, &dwLength, pqs);
if (ret == SOCKET_ERROR)
{
if ((dwErr = WSAGetLastError()) == WSAEFAULT)
{
printf("Buffer too small; required size is: %d\n", dwLength);
break;
}
else if ((dwErr == WSA_E_NO_MORE) ││ (dwErr = WSAENOMORE))
break;
else
{
printf("Failed with error: %d\n", dwErr);
break;
}
}
for (i = 0; i < pqs->dwNumberOfCsAddrs; i++)
{
addr = (SOCKADDR *)pqs->lpcsaBuffer[i].RemoteAddr.lpSockaddr;
if (addr->sa_family == AF_INET)
{
SOCKADDR_IN *ipaddr = (SOCKADDR_IN *)addr;
printf("IP address:port = %s:%d\n", inet_ntoa(ipaddr->sin_addr), ipaddr->sin_port);
}
else if (addr->sa_family == AF_IPX)
{
CHAR IPXString[64];
DWORD IPXStringSize = sizeof(IPXString);
if (WSAAddressToString((LPSOCKADDR) addr, sizeof(SOCKADDR_IPX), NULL, IPXString, &IPXStringSize) == SOCKET_ERROR)
printf("WSAAddressToString returned error %d\n", WSAGetLastError());
else
printf("IPX address: %s\n", IPXString);
}
}
}
WSALookupServiceEnd(hLookup);
The example code is straightforward, although a bit simplified. Calling WSALookupServiceNext() requires only a valid handle to a query, the length of the return buffer, and the return buffer. You don't need to specify any control flags because the only valid flag for this function is LUP_FLUSHPREVIOUS. If our supplied buffer is too small and this flag is set, the results from this call are discarded. However, in this example we don't use LUP_FLUSHPREVIOUS, and if our buffer is too small, the WSAEFAULT error is generated. If this occurs, lpdwBufferLength is set to the required size. Our example uses a fixed-size buffer equal to the size of the WSAQUERYSET structure plus 2000 bytes. Because all you are asking for are service names and addresses, this should be a sufficient size. Of course, in production code, your applications should be prepared to handle the WSAEFAULT error.
Once you successfully call WSALookupServiceNext(), the buffer is filled with a WSAQUERYSET structure containing the results. In our query, you asked for names and addresses; the fields of WSAQUERYSET that are of most interest are lpszServiceInstanceName and lpcsaBuffer. The former contains the name of the service, the latter is an array of CSADDR_INFO structures that contains addressing information for the service. The parameter dwNumberOfCsAddrs tells us exactly how many addresses have been returned. In the example code, all we do is simply print out the addresses. Check for only IPX and IP addresses to print because those are the only address families you requested when you opened the query.
If our query used a wildcard (*) for the service name, each call to WSALookupServiceNext() would return a particular instance of that service running somewhere on the network, provided, of course, that multiple instances are actually registered and running. Once all instances of the service have been returned, the error WSA_E_NO_MORE is generated, and you break out of the loop. The last thing you do is call WSALookupServiceEnd() on the query handle. This releases any resources allocated for the query.
< Service Registration Program Example | RnR Main | Querying DNS >