Datagram Operations
Datagrams are connectionless methods of communication. A sender merely addresses each packet with its destination NetBIOS name and sends it on its way. No checking is performed to ensure data integrity, order of arrival, or reliability.
There are three ways to send a datagram. The first is to direct the datagram at a specific (unique or group) name. This means that only the process that registered the destination name can receive that datagram. The second method is to send a datagram to a group name. Only those processes that registered the given group name will be able to receive the message. Finally, the third way to send a datagram is to broadcast it to the entire network. Any process on any workstation on the LAN can receive the datagram. Sending a datagram to either a unique or a group name uses the NCBDGSEND command, whereas broadcasts use the NCBDGSENDBC command.
Using any of the datagram send commands is a simple process. Set the ncb_num field to the name number returned from an NCBADDNAME command or using events. For each LANA, the code posts an asynchronous NCBDGRECV (or NCBDGRECVBC) and waits until one succeeds, at which point it checks all posted commands, prints the messages for those that succeed, and cancels those commands that are still pending. The following example provides functions for both directed and broadcast sends and receives. The program can be compiled into a sample application that can be configured to send or receive datagrams. The program accepts several command-line parameters that allow the user to specify the number of datagrams to send or receive, the delay between sends, the use of broadcasts instead of directed datagrams, the receipt of datagrams for any name, and so on.
// Nbdgram.c #include <windows.h> #include <stdio.h> #include <stdlib.h> #include "..\Common\nbcommon.h" #define MAX_SESSIONS 254 #define MAX_NAMES 254 #define MAX_DATAGRAM_SIZE 512 BOOL bSender = FALSE, // Send or receive datagrams bRecvAny = FALSE, // Receive for any name bUniqueName = TRUE, // Register my name as unique? bBroadcast = FALSE, // Use broadcast datagrams? bOneLana = FALSE; // Use all LANAs or just one? char szLocalName[NCBNAMSZ + 1], // Local NetBIOS name szRecipientName[NCBNAMSZ + 1]; // Recipient's NetBIOS name DWORD dwNumDatagrams = 25, // Number of datagrams to send dwOneLana, // If using one LANA, which one? dwDelay = 0; // Delay between datagram sends // // Function: ValidateArgs // // Description: // This function parses the command line arguments // and sets various global flags indicating the selections // void ValidateArgs(int argc, char **argv) { int i; 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 'n': // Use a unique name bUniqueName = TRUE; if (strlen(argv[i]) > 2) strcpy(szLocalName, &argv[i][3]); break; case 'g': // Use a group name bUniqueName = FALSE; if (strlen(argv[i]) > 2) strcpy(szLocalName, &argv[i][3]); break; case 's': // Send datagrams bSender = TRUE; break; case 'c': // # of datagrams to send or receive if (strlen(argv[i]) > 2) dwNumDatagrams = atoi(&argv[i][3]); break; case 'r': // Recipient's name for datagrams if (strlen(argv[i]) > 2) strcpy(szRecipientName, &argv[i][3]); break; case 'b': // Use broadcast datagrams bBroadcast = TRUE; break; case 'a': // Receive datagrams on any name bRecvAny = TRUE; break; case 'l': // Operate on this LANA only bOneLana = TRUE; if (strlen(argv[i]) > 2) dwOneLana = atoi(&argv[i][3]); break; case 'd': // Delay (millisecs) between sends if (strlen(argv[i]) > 2) dwDelay = atoi(&argv[i][3]); break; default: printf("usage: nbdgram ?\n"); break; } } } return; } // // Function: DatagramSend // // Description: // Send a directed datagram to the specified recipient on the // specified LANA number from the given name number to the // specified recipient. Also specified is the data buffer and // the number of bytes to send. // int DatagramSend(int lana, int num, char *recipient, char *buffer, int buflen) { NCB ncb; ZeroMemory(&ncb, sizeof(NCB)); ncb.ncb_command = NCBDGSEND; ncb.ncb_lana_num = lana; ncb.ncb_num = num; ncb.ncb_buffer = (PUCHAR)buffer; ncb.ncb_length = buflen; memset(ncb.ncb_callname, ' ', NCBNAMSZ); strncpy(ncb.ncb_callname, recipient, strlen(recipient)); if (Netbios(&ncb) != NRC_GOODRET) { printf("Netbios: NCBDGSEND failed: %d\n", ncb.ncb_retcode); return ncb.ncb_retcode; } return NRC_GOODRET; } // // Function: DatagramSendBC // // Description: // Send a broadcast datagram on the specified LANA number from the // given name number. Also specified is the data buffer and the number // of bytes to send. // int DatagramSendBC(int lana, int num, char *buffer, int buflen) { NCB ncb; ZeroMemory(&ncb, sizeof(NCB)); ncb.ncb_command = NCBDGSENDBC; ncb.ncb_lana_num = lana; ncb.ncb_num = num; ncb.ncb_buffer = (PUCHAR)buffer; ncb.ncb_length = buflen; if (Netbios(&ncb) != NRC_GOODRET) { printf("Netbios: NCBDGSENDBC failed: %d\n", ncb.ncb_retcode); return ncb.ncb_retcode; } return NRC_GOODRET; } // // Function: DatagramRecv // // Description: // Receive a datagram on the given LANA number directed toward the // name represented by num. Data is copied into the supplied buffer. // If hEvent is not 0, the receive call is made asynchronously // with the supplied event handle. If num is 0xFF, listen for a // datagram destined for any NetBIOS name registered by the process. // int DatagramRecv(PNCB pncb, int lana, int num, char *buffer, int buflen, HANDLE hEvent) { ZeroMemory(pncb, sizeof(NCB)); if (hEvent) { pncb->ncb_command = NCBDGRECV ASYNCH; pncb->ncb_event = hEvent; } else pncb->ncb_command = NCBDGRECV; pncb->ncb_lana_num = lana; pncb->ncb_num = num; pncb->ncb_buffer = (PUCHAR)buffer; pncb->ncb_length = buflen; if (Netbios(pncb) != NRC_GOODRET) { printf("Netbos: NCBDGRECV failed: %d\n", pncb->ncb_retcode); return pncb->ncb_retcode; } return NRC_GOODRET; } // // Function: DatagramRecvBC // // Description: // Receive a broadcast datagram on the given LANA number. // Data is copied into the supplied buffer. If hEvent is not 0, // the receive call is made asynchronously with the supplied // event handle. // int DatagramRecvBC(PNCB pncb, int lana, int num, char *buffer, int buflen, HANDLE hEvent) { ZeroMemory(pncb, sizeof(NCB)); if (hEvent) { pncb->ncb_command = NCBDGRECVBC ASYNCH; pncb->ncb_event = hEvent; } else pncb->ncb_command = NCBDGRECVBC; pncb->ncb_lana_num = lana; pncb->ncb_num = num; pncb->ncb_buffer = (PUCHAR)buffer; pncb->ncb_length = buflen; if (Netbios(pncb) != NRC_GOODRET) { printf("Netbios: NCBDGRECVBC failed: %d\n", pncb->ncb_retcode); return pncb->ncb_retcode; } return NRC_GOODRET; } // // Function: main // // Description: // Initialize the NetBIOS interface, allocate resources, and then // send or receive datagrams according to the user's options // int main(int argc, char **argv) { LANA_ENUM lenum; int i, j; char szMessage[MAX_DATAGRAM_SIZE], szSender[NCBNAMSZ + 1]; DWORD *dwNum = NULL, dwBytesRead, dwErr; ValidateArgs(argc, argv); // // Enumerate and reset the LANA numbers // if ((dwErr = LanaEnum(&lenum)) != NRC_GOODRET) { printf("LanaEnum failed: %d\n", dwErr); return 1; } if ((dwErr = ResetAll(&lenum, (UCHAR)MAX_SESSIONS, (UCHAR)MAX_NAMES, FALSE)) != NRC_GOODRET) { printf("ResetAll failed: %d\n", dwErr); return 1; } // // This buffer holds the name number for the NetBIOS name added // to each LANA // dwNum = (DWORD *)GlobalAlloc(GMEM_FIXED GMEM_ZEROINIT, sizeof(DWORD) * lenum.length); if (dwNum == NULL) { printf("out of memory\n"); return 1; } // // If we're going to operate on only one LANA, register the name // on only that specified LANA; otherwise, register it on all // LANAs // if (bOneLana) { if (bUniqueName) AddName(dwOneLana, szLocalName, &dwNum[0]); else AddGroupName(dwOneLana, szLocalName, &dwNum[0]); } else { for(i = 0; i < lenum.length; i++) { if (bUniqueName) AddName(lenum.lana[i], szLocalName, &dwNum[i]); else AddGroupName(lenum.lana[i], szLocalName, &dwNum[i]); } } // We are sending datagrams // if (bSender) { // Broadcast sender // if (bBroadcast) { if (bOneLana) { // Broadcast the message on the one LANA only // for(j = 0; j < dwNumDatagrams; j++) { wsprintf(szMessage, "[%03d] Test broadcast datagram", j); if (DatagramSendBC(dwOneLana, dwNum[0], szMessage, strlen(szMessage)) != NRC_GOODRET) return 1; Sleep(dwDelay); } } else { // Broadcast the message on every LANA on the local // machine // for(j = 0; j < dwNumDatagrams; j++) { for(i = 0; i < lenum.length; i++) { wsprintf(szMessage, "[%03d] Test broadcast datagram", j); if (DatagramSendBC(lenum.lana[i], dwNum[i], szMessage, strlen(szMessage)) != NRC_GOODRET) return 1; } Sleep(dwDelay); } } } else { if (bOneLana) { // Send a directed message to the one LANA specified // for(j = 0; j < dwNumDatagrams; j++) { wsprintf(szMessage, "[%03d] Test directed datagram", j); if (DatagramSend(dwOneLana, dwNum[0], szRecipientName, szMessage, strlen(szMessage)) != NRC_GOODRET) return 1; Sleep(dwDelay); } } else { // Send a directed message to each LANA on the // local machine // for(j = 0; j < dwNumDatagrams; j++) { for(i = 0; i < lenum.length; i++) { wsprintf(szMessage, "[%03d] Test directed datagram", j); printf("count: %d.%d\n", j,i); if (DatagramSend(lenum.lana[i], dwNum[i], szRecipientName, szMessage, strlen(szMessage)) != NRC_GOODRET) return 1; } Sleep(dwDelay); } } } } else // We are receiving datagrams { NCB *ncb=NULL; char **szMessageArray = NULL; HANDLE *hEvent=NULL; DWORD dwRet; // Allocate an array of NCB structure to submit to each recv // on each LANA // ncb = (NCB *)GlobalAlloc(GMEM_FIXED GMEM_ZEROINIT, sizeof(NCB) * lenum.length); // // Allocate an array of incoming data buffers // szMessageArray = (char **)GlobalAlloc(GMEM_FIXED, sizeof(char *) * lenum.length); for(i = 0; i < lenum.length; i++) szMessageArray[i] = (char *)GlobalAlloc(GMEM_FIXED, MAX_DATAGRAM_SIZE); // // Allocate an array of event handles for // asynchronous receives // hEvent = (HANDLE *)GlobalAlloc(GMEM_FIXED GMEM_ZEROINIT, sizeof(HANDLE) * lenum.length); for(i = 0; i < lenum.length; i++) hEvent[i] = CreateEvent(0, TRUE, FALSE, 0); if (bBroadcast) { if (bOneLana) { // Post synchronous broadcast receives on // the one LANA specified // for(j = 0; j < dwNumDatagrams; j++) { if (DatagramRecvBC(&ncb[0], dwOneLana, dwNum[0], szMessageArray[0], MAX_DATAGRAM_SIZE, NULL) != NRC_GOODRET) return 1; FormatNetbiosName(ncb[0].ncb_callname, szSender); printf("%03d [LANA %d] Message: '%s' " "received from: %s\n", j, ncb[0].ncb_lana_num, szMessageArray[0], szSender); } } else { // Post asynchronous broadcast receives on each LANA // number available. For each command that succeeded, // print the message; otherwise, cancel the command. // for(j = 0; j < dwNumDatagrams; j++) { for(i = 0; i < lenum.length; i++) { dwBytesRead = MAX_DATAGRAM_SIZE; if (DatagramRecvBC(&ncb[i], lenum.lana[i], dwNum[i], szMessageArray[i], MAX_DATAGRAM_SIZE, hEvent[i]) != NRC_GOODRET) return 1; } dwRet = WaitForMultipleObjects(lenum.length, hEvent, FALSE, INFINITE); if (dwRet == WAIT_FAILED) { printf("WaitForMultipleObjects failed: %d\n", GetLastError()); return 1; } for(i = 0; i < lenum.length; i++) { if (ncb[i].ncb_cmd_cplt == NRC_PENDING) Cancel(&ncb[i]); else { ncb[i].ncb_buffer[ncb[i].ncb_length] = 0; FormatNetbiosName(ncb[i].ncb_callname, szSender); printf("%03d [LANA %d] Message: '%s' " "received from: %s\n", j, ncb[i].ncb_lana_num, szMessageArray[i], szSender); } ResetEvent(hEvent[i]); } } } } else { if (bOneLana) { // Make a blocking datagram receive on the specified // LANA number // for(j = 0; j < dwNumDatagrams; j++) { if (bRecvAny) { // Receive data destined for any NetBIOS name // in this process's name table // if (DatagramRecv(&ncb[0], dwOneLana, 0xFF, szMessageArray[0], MAX_DATAGRAM_SIZE, NULL) != NRC_GOODRET) return 1; } else { if (DatagramRecv(&ncb[0], dwOneLana, dwNum[0], szMessageArray[0], MAX_DATAGRAM_SIZE, NULL) != NRC_GOODRET) return 1; } FormatNetbiosName(ncb[0].ncb_callname, szSender); printf("%03d [LANA %d] Message: '%s' " "received from: %s\n", j, ncb[0].ncb_lana_num, szMessageArray[0], szSender); } } else { // Post asynchronous datagram receives on each LANA // available. For all those commands that succeeded, // print the data; otherwise, cancel the command. // for(j = 0; j < dwNumDatagrams; j++) { for(i = 0; i < lenum.length; i++) { if (bRecvAny) { // Receive data destined for any NetBIOS // name in this process's name table // if (DatagramRecv(&ncb[i], lenum.lana[i], 0xFF, szMessageArray[i], MAX_DATAGRAM_SIZE, hEvent[i]) != NRC_GOODRET) return 1; } else { if (DatagramRecv(&ncb[i], lenum.lana[i], dwNum[i], szMessageArray[i], MAX_DATAGRAM_SIZE, hEvent[i]) != NRC_GOODRET) return 1; } } dwRet = WaitForMultipleObjects(lenum.length, hEvent, FALSE, INFINITE); if (dwRet == WAIT_FAILED) { printf("WaitForMultipleObjects failed: %d\n", GetLastError()); return 1; } for(i = 0; i < lenum.length; i++) { if (ncb[i].ncb_cmd_cplt == NRC_PENDING) Cancel(&ncb[i]); else { ncb[i].ncb_buffer[ncb[i].ncb_length] = 0; FormatNetbiosName(ncb[i].ncb_callname, szSender); printf("%03d [LANA %d] Message: '%s' " "from: %s\n", j, ncb[i].ncb_lana_num, szMessageArray[i], szSender); } ResetEvent(hEvent[i]); } } } } // Clean up // for(i = 0; i < lenum.length; i++) { CloseHandle(hEvent[i]); GlobalFree(szMessageArray[i]); } GlobalFree(hEvent); GlobalFree(szMessageArray); } // Clean things up // if (bOneLana) DelName(dwOneLana, szLocalName); else { for(i = 0; i < lenum.length; i++) DelName(lenum.lana[i], szLocalName); } GlobalFree(dwNum); return 0; }
Once you've compiled the example, run the following tests to get an idea of how datagrams work. For learning purposes, you should run two instances of the applications, but on separate machines. If you run them on the same machine, they'll work, but this hides some important concepts. When run on the same machine, the LANA numbers for each side correspond to the same protocol. It's more interesting when they don't. Table 17-5 lists some commands to try, and Table 17-6 lists all the command-line options available for use with the sample program.
Client Command | Server Command |
Nbdgram /n:CLIENT01 | Nbdgram /s /n:SERVER01 /r:CLIENT01 |
Nbdgram /n:CLIENT01 /b | Nbdgram /s /n:SERVER01 /b |
Nbdgram /g:CLIENTGROUP | Nbdgram /s /r:CLIENTGROUP |
Flag | Meaning |
/n:my-name | Register the unique name my-name. |
/g:group-name | Register the group name group-name. |
/s | Send datagrams (by default, the sample receives datagrams). |
/c:n | Send or receive n number of datagrams. |
/r:receiver | Specify the NetBIOS name to send the datagrams to. |
/b | Use broadcast datagrams. |
/a | Post receives for any NetBIOS name (set ncb_num to 0xFF). |
/l:n | Perform all operations on LANA n only (by default, all sends and receives are posted on each LANA). |
/d:n | Wait n milliseconds between sends. |
For the third command in Table 17-5, run several clients on various machines. This illustrates one server sending one message to a group, and each member of the group waiting for data will receive the message. Also, try various combinations of the listed commands with the /l:x command-line option, where x is a valid LANA number. This command-line option switches the program's mode from performing the commands on all LANAs to performing the commands on the listed LANA only. For example, the command Nbdgram /n:CLIENT01 /l:0 makes the application listen only for incoming datagrams on LANA 0 and ignore any data arriving on any other LANA. Additionally, option /a is meaningful only to the clients. This flag causes the receive command to pick up incoming datagrams destined for any NetBIOS name registered by the process. In our example, this isn't very meaningful because the client registers only one name, but you can at least see how this would be coded. You might want to try modifying the code so that it registers a name for every /n:name option in the command line. Start up the server with the recipient flag set to only one of the names that the client registered. The client will receive the data, even though the NCBDGRECV command does not specifically refer to a particular name.