< NetBIOS Client Example | Winsock2 Supported Protocols Main | AppleTalk Receiver-Sender Example >
What do we have in this chapter 4 part 7?
|
AppleTalk
Note: The AppleTalk Protocol Is Not Available in latest version of Windows OS. However, here we make it available on Windows XP using the following 'quick' and 'dirty' :) steps just for program example testing. First of all copy the following six files from Windows 2000 machine or installation CD. In this case we extract these files form Windows 2000 server installation CD using the EXPAND command, expanding and copying to their respective folders.
C:\WINDOWS\INF\netatlk.inf C:\ WINDOWS \SYSTEM32\DRIVERS\sfmatalk.sys C:\ WINDOWS \SYSTEM32\sfmatmsg.dll C:\ WINDOWS \SYSTEM32\sfmmon.dll C:\ WINDOWS \SYSTEM32\sfmwshat.dll C:\ WINDOWS \SYSTEM32\SPOOL\PRTPROCS\W32X86\sfmpsprt.dll
Those files should be under the X:\i386 folder in old dos compressed file format.
|
If you copy those files from the existing Windows 2000 machine, paste them to their respective folders (the WINNT should be replaced with WINDOWS for Windows XP). Next, restart your machine.
Open the Local Area Connection property page. Click the Install button.
Select the Protocol network component and click the Add button.
Select AppleTalk Protocol from the Network Protocol pane and click OK.
The AppleTalk Protocol should be visible as shown below.
AppleTalk support in Winsock has been around for a while, although few people are aware of it. You probably will not choose AppleTalk unless you are communicating with Macintosh computers. AppleTalk is somewhat similar to NetBIOS in that it is name-based on a per-process basis. That is, a server dynamically registers a particular name that it will be known as. Clients use this name to establish a connection. However, AppleTalk names are substantially more complicated than NetBIOS names. The next section will discuss how computers using the AppleTalk protocol are addressed on the network.
An AppleTalk name is actually based on three separate names: name, type, and zone. Each name can be up to 32 characters long. The name identifies the process and its associated socket on a machine. The type is a subgrouping mechanism for zones. Traditionally, a zone is a network of AppleTalk-enabled computers physically located on the same loop. Microsoft's implementation of AppleTalk allows a Windows machine to specify the default zone it is located within. Multiple networks can be bridged together. These human-friendly names map to a socket number, a node number, and a network number. An AppleTalk name must be unique within the given type and zone. This requirement is enforced by the Name Binding Protocol (NBP), which broadcasts a query to see if the name is already in use. Under the hood, AppleTalk uses the Routing Table Maintenance Protocol (RTMP) to dynamically discover routes to the different AppleTalk networks linked together. The following structure provides the basis for addressing AppleTalk hosts from Winsock:
typedef struct sockaddr_at
{
USHORT sat_family;
USHORT sat_net;
UCHAR sat_node;
UCHAR sat_socket;
} SOCKADDR_AT, *PSOCKADDR_AT;
Notice that the address structure contains only characters or short integers and not friendly names. The SOCKADDR_AT structure is passed into Winsock calls such as bind(), connect(), and WSAConnect(), but to translate the human-readable names you must query the network to either resolve or register that name first. This is done by using a call to getsockopt() or setsockopt(), respectively.
A server that wants to register a particular name so that clients can easily connect to it calls setsockopt() with the SO_REGISTER_NAME option. For all socket options involving AppleTalk names, use the WSH_NBP_NAME structure, which is defined as:
typedef struct
{
CHAR ObjectNameLen;
CHAR ObjectName[MAX_ENTITY];
CHAR TypeNameLen;
CHAR TypeName[MAX_ENTITY];
CHAR ZoneNameLen;
CHAR ZoneName[MAX_ENTITY];
} WSH_NBP_NAME, *PWSH_NBP_NAME;
A number of types, which include WSH_REGISTER_NAME, WSH_DEREGISTER_NAME, and WSH_REMOVE_NAME are defined based on the WSH_NBP_NAME structure. Using the appropriate type depends on whether you look up a name, register a name, or remove a name. The following code sample illustrates how to register an AppleTalk name:
#define MY_ZONE *
#define MY_TYPE Winsock-Test-App
#define MY_OBJECT AppleTalk-Server
WSH_REGISTER_NAME atname;
SOCKADDR_AT ataddr;
SOCKET s;
// Fill in the name to register
strcpy(atname.ObjectName, MY_OBJECT);
atname.ObjectNameLen = strlen(MY_OBJECT);
strcpy(atname.TypeName, MY_TYPE);
atname.TypeNameLen = strlen(MY_TYPE);
strcpy(atname.ZoneName, MY_ZONE);
atname.ZoneNameLen = strlen(MY_ZONE);
s = socket(AF_APPLETALK, SOCK_STREAM, ATPROTO_ADSP);
if (s == INVALID_SOCKET)
{
// Error
}
ataddr.sat_socket = 0;
ataddr.sat_family = AF_APPLETALK;
if (bind(s, (SOCKADDR *)&ataddr, sizeof(ataddr)) == SOCKET_ERROR)
{
// Unable to open an endpoint on the AppleTalk network
}
if (setsockopt(s, SOL_APPLETALK, SO_REGISTER_NAME, (char *)&atname, sizeof(WSH_NBP_NAME)) == SOCKET_ERROR)
{
// Name registration failed!
}
The first thing you'll notice is the MY_ZONE, MY_TYPE, and MY_OBJECT strings. Remember that an AppleTalk name is three-tiered. Notice that the zone is an asterisk (*). This is a special character used in the zone field to specify the “current” zone the computer is located in. Next, we create a socket of type SOCK_STREAM of the AppleTalk Data Stream Protocol (ADSP). Following socket creation, you'll notice a call to the bind() function with an address structure that has a zeroed-out sat_socket field and only the protocol family field set. This is important because it creates an endpoint on the AppleTalk network for your application to make requests from. Note that although this call to bind() allows you to perform simple actions on the network, by itself it doesn't allow your application to accept incoming connection requests from clients. To accept client connections, you must register your name on the network, which is the next step.
Registering an AppleTalk name is simple. Make the call to setsockopt by passing SOL_APPLETALK as the level parameter and SO_REGISTER_NAME as the optname parameter. The last two parameters are a pointer to our WSH_REGISTER_NAME structure and its size. If the call succeeds, our server name was successfully registered. If the call fails, the name is probably already in use. The Winsock error returned is WSAEADDRINUSE (10048). Note that for both datagram-oriented and stream-oriented AppleTalk protocols, a process that wants to receive data must register a name that clients can either send datagrams to or connect to.
On the client side of the equation, an application usually knows a server by its friendly name and must resolve that into the network, node, and socket numbers Winsock calls use. This is accomplished by calling getsockopt() with the SO_LOOKUP_NAME option. Performing a name lookup relies on the WSH_LOOKUP_NAME structure. This structure and its dependent structure are defined as:
typedef struct
{
WSH_ATALK_ADDRESS Address;
USHORT Enumerator;
WSH_NBP_NAME NbpName;
} WSH_NBP_TUPLE, *PWSH_NBP_TUPLE;
typedef struct _WSH_LOOKUP_NAME
{
// Array of NoTuple WSH_NBP_TUPLEs
WSH_NBP_TUPLE LookupTuple;
ULONG NoTuples;
} WSH_LOOKUP_NAME, *PWSH_LOOKUP_NAME;
When we call getsockopt() with the SO_LOOKUP_NAME option, we pass a buffer cast as a WSH_LOOKUP_NAME structure and fill in the WSH_NBP_NAME field within the first LookupTuple member. Upon a successful call, getsockopt() returns an array of WSH_NBP_TUPLE elements containing physical address information for that name.
The following sample illustrates how to look up a name. In addition, it shows how to list all “discovered” AppleTalk zones and how to find your default zone. Zone information can be obtained by using the getsockopt() options SO_LOOKUP_ZONES and SO_LOOKUP_MYZONE.
-----------------------------------------------------------
Add the following source code.
#include <winsock.h>
#include <atalkwsh.h>
#include <stdio.h>
#define DEFAULT_ZONE *
#define DEFAULT_TYPE Windows Sockets
#define DEFAULT_OBJECT AppleTalk-Server_Bodo
char szZone[MAX_ENTITY], szType[MAX_ENTITY], szObject[MAX_ENTITY];
BOOL bFindName = FALSE, bListZones = FALSE, bListMyZone = FALSE;
void usage()
{
printf(Usage: appletalkzone [options]\n);
printf( Name Lookup:\n);
printf( -z:ZONE-NAME\n);
printf( -t:TYPE-NAME\n);
printf( -o:OBJECT-NAME\n);
printf( List All Zones:\n);
printf( -lz\n);
printf( List My Zone:\n);
printf( -lm\n);
ExitProcess(1);
}
void ValidateArgs(int argc, char **argv)
{
int i;
strcpy_s(szZone, sizeof(szZone),DEFAULT_ZONE);
strcpy_s(szType, sizeof(szType),DEFAULT_TYPE);
strcpy_s(szObject, sizeof(szObject),DEFAULT_OBJECT);
for(i = 1; i < argc; i++)
{
if (strlen(argv[i]) < 2)
continue;
if ((argv[i][0]=='-')||(argv[i][0]=='/'))
{
switch (tolower(argv[i][1]))
{
case 'z': // Specify a zone name
if (strlen(argv[i]) > 3)
strncpy_s(szZone, sizeof(szZone),&argv[i][3], MAX_ENTITY);
bFindName = TRUE;
break;
case 't': // Specify a type name
if (strlen(argv[i]) > 3)
strncpy_s(szType, sizeof(szType),&argv[i][3], MAX_ENTITY);
bFindName = TRUE;
break;
case 'o': // Specify an object name
if (strlen(argv[i]) > 3)
strncpy_s(szObject, sizeof(szObject),&argv[i][3], MAX_ENTITY);
bFindName = TRUE;
break;
case 'l': // List zones information
if (strlen(argv[i]) == 3)
// List all zones
if (tolower(argv[i][2]) == 'z')
bListZones = TRUE;
// List my zone
else if (tolower(argv[i][2]) == 'm')
bListMyZone = TRUE;
break;
default:
usage();
}
}
}
}
int main(int argc, char **argv)
{
WSADATA wsd;
char cLookupBuffer[16000], *pTupleBuffer = NULL;
PWSH_NBP_TUPLE pTuples = NULL;
PWSH_LOOKUP_NAME atlookup;
PWSH_LOOKUP_ZONES zonelookup;
SOCKET s;
DWORD dwSize = sizeof(cLookupBuffer);
SOCKADDR_AT ataddr;
int i;
// Load the Winsock library
if (WSAStartup(MAKEWORD(2, 2), &wsd) != 0)
{
printf(Unable to load Winsock library!\n);
return 1;
}
printf(WSAStartup() is OK! Library loaded...\n);
ValidateArgs(argc, argv);
atlookup = (PWSH_LOOKUP_NAME)cLookupBuffer;
zonelookup = (PWSH_LOOKUP_ZONES)cLookupBuffer;
if (bFindName)
{
// Fill in the name to look up
strcpy_s(atlookup->LookupTuple.NbpName.ObjectName, sizeof(atlookup->LookupTuple.NbpName.ObjectName), szObject);
atlookup->LookupTuple.NbpName.ObjectNameLen = strlen(szObject);
strcpy_s(atlookup->LookupTuple.NbpName.TypeName, sizeof(atlookup->LookupTuple.NbpName.TypeName),szType);
atlookup->LookupTuple.NbpName.TypeNameLen = strlen(szType);
strcpy_s(atlookup->LookupTuple.NbpName.ZoneName, sizeof(atlookup->LookupTuple.NbpName.ZoneName),szZone);
atlookup->LookupTuple.NbpName.ZoneNameLen = strlen(szZone);
}
// Create the AppleTalk socket
s = socket(AF_APPLETALK, SOCK_STREAM, ATPROTO_ADSP);
if (s == INVALID_SOCKET)
{
printf(socket() failed with error code %d\n, WSAGetLastError());
return 1;
}
else
printf(socket() is OK for AppleTalk!\n);
// We need to bind in order to create an endpoint on the
// AppleTalk network to make our query from
ZeroMemory(&ataddr, sizeof(ataddr));
ataddr.sat_family = AF_APPLETALK;
ataddr.sat_socket = 0;
if (bind(s, (SOCKADDR *)&ataddr, sizeof(ataddr)) == INVALID_SOCKET)
{
printf(bind() failed with error code %d\n, WSAGetLastError());
return 1;
}
else
printf(bind() is OK!\n);
if (bFindName)
{
printf(Looking up: %s:%s@%s\n, szObject, szType, szZone);
if (getsockopt(s, SOL_APPLETALK, SO_LOOKUP_NAME, (char *)atlookup, &dwSize) == INVALID_SOCKET)
{
printf(getsockopt(SO_LOOKUP_NAME) failed with error code %d\n, WSAGetLastError());
return 1;
}
else
printf(getsockopt(SO_LOOKUP_NAME) is OK!\n);
printf(Lookup returned: %d entries\n, atlookup->NoTuples);
// Our character buffer now contains an array of
// WSH_NBP_TUPLE structures after our WSH_LOOKUP_NAME structure
pTupleBuffer = (char *)cLookupBuffer + sizeof(WSH_LOOKUP_NAME);
pTuples = (PWSH_NBP_TUPLE) pTupleBuffer;
for(i = 0; i<(int)atlookup->NoTuples; i++)
{
ataddr.sat_family = AF_APPLETALK;
ataddr.sat_net = pTuples[i].Address.Network;
ataddr.sat_node = pTuples[i].Address.Node;
ataddr.sat_socket = pTuples[i].Address.Socket;
printf(server address = %lx.%lx.%lx.\n,
ataddr.sat_net,
ataddr.sat_node,
ataddr.sat_socket);
}
}
else if (bListZones)
{
// It is very important to pass a sufficiently big buffer
// for this option. Windows NT 4 SP3 blue screens if it is too small.
if (getsockopt(s, SOL_APPLETALK, SO_LOOKUP_ZONES, (char *)atlookup, &dwSize) == INVALID_SOCKET)
{
printf(getsockopt(SO_LOOKUP_NAME) failed with error code %d\n, WSAGetLastError());
return 1;
}
else
printf(getsockopt(SO_LOOKUP_NAME) looks fine!\n);
printf(Lookup returned: %d zones\n, zonelookup->NoZones);
// The character buffer contains a list of null-separated
// strings after the WSH_LOOKUP_ZONES structure
pTupleBuffer = (char *)cLookupBuffer + sizeof(WSH_LOOKUP_ZONES);
for(i = 0; i<(int)zonelookup->NoZones; i++)
{
printf(%3d: '%s'\n, i+1, pTupleBuffer);
while (*pTupleBuffer++);
}
}
else if (bListMyZone)
{
// This option returns a simple string
if (getsockopt(s, SOL_APPLETALK, SO_LOOKUP_MYZONE, (char *)cLookupBuffer, &dwSize) == INVALID_SOCKET)
{
printf(getsockopt(SO_LOOKUP_NAME) failed with error code %d\n, WSAGetLastError());
return 1;
}
else
printf(getsockopt(SO_LOOKUP_NAME) is OK lor!\n);
printf(My Zone: '%s'\n, cLookupBuffer);
}
else
usage();
WSACleanup();
return 0;
}
Build and run the project.
When you are using most of the AppleTalk socket options, such as SO_LOOKUP_MYZONE, SO_LOOKUP_ZONES, and SO_LOOKUP_NAME, you need to provide a large character buffer to the getsockopt() call. If you call an option that requires you to provide a structure, that structure needs to be at the start of the supplied character buffer. If the call to getsockopt() is successful, the function places the returned data in the character buffer after the end of the supplied structure. Take a look at the SO_LOOKUP_NAME section in the above code sample. The variable, cLookupBuffer, is a simple character array used in the call to getsockopt(). First, cast it as a PWSH_LOOKUP_NAME and fill in the name information you want to find. Pass the buffer into getsockopt(), and upon return, increment the character pointer pTupleBuffer so that it points to the character after the end of the WSH_LOOKUP_NAME structure. Next, cast that pointer to a variable of PWSH_NBP_TUPLE because the data returned from a lookup name call is an array of WSH_NBP_TUPLE structures. Once you have the proper starting location and type of the tuples, you can walk through the array.
< NetBIOS Client Example | Winsock2 Supported Protocols Main | AppleTalk Receiver-Sender Example >