Examples

In this section, we'll take a look at three programming examples of how to use QOS. The first example, which uses TCP, is the most straightforward because it is connection-oriented. The second example uses UDP and does not use any connect calls. The last example uses multicast UCP. In all three examples, we will use WSAEventSelect because it's a bit simpler than WSAAsyncSelect. While we include the entire TCP unicast example, we include only important code snippets for the UDP and multicasting examples because many of the concepts are the same, no matter what type of socket is used. See the accompanying CD for the entire example. All three examples rely on a couple of support routines, PrintQos and FindProtocolInfo, which are defined in the files Printqos.c and Provider.c, respectively. The former routine simply prints out the contents of a QOS structure, while the latter finds a protocol from the provider catalog with the required attributes, such as QOS.

Unicast TCP

The example for unicast TCP QOS is given in Figure 12-3. The code for this example can be found in the Chapter 12 folder as Qostcp.c. The example is a bit long, but not particularly complicated. Most of the code is nothing more than the usual WSAEventSelect code that we introduced in Chapter 8. The only exception is what we do in the case of an FD_QOS event. The main function doesn't do anything out of the ordinary. The arguments are parsed, a socket is created, and either the Server or the Client function is called—depending on whether the application is called as the server or the client. Let's examine the client connection first.

In all three examples, a command line parameter tells the example when to set QOS: before connection, during connection, after connection, or after the peer requests QOS to set QOS locally. (The command line parameters for Qostp.c are listed in Table 12-8.) If QOS is selected to be set before connection (for the client), bind the socket to a random port and then call SIO_SET_QOS with a sending QOS FLOWSPEC. Note that it isn't really necessary to bind before calling SIO_SET_QOS because the peer's address is not known until a connect call is made, and an RSVP session cannot be initiated until the peer's address is known.

If the user elects to set QOS during connection, the example code passes the QOS structure into the WSAConnect call. This call initiates an RSVP session and connects the client to the specified server. Otherwise, the user specifies that the example should wait for the peer to set QOS, and no QOS structure is passed to WSAConnect. Instead the code takes the sending QOS structure, ORs in the SERVICE_NO_QOS_SIGNALING flag to the ServiceType field in the FLOWSPEC structures, and calls WSAIoctl with the SIO_SET_FLAG ioctl command. This tells the QOS service provider not to invoke Traffic Control but to still look for RSVP messages.

After QOS is set, the events that the client wants to be notified of are registered, including FD_QOS. Notice that QOS must be set on the socket beforehand in order for the application to request receiving FD_QOS. Once this occurs, the client waits in a loop on WSAWaitForMultipleEvents, which unblocks when one of the selected events is signaled. Once an event occurs, the events are enumerated along with any errors in WSAEnumNetworkEvents.

For the most part, Qostcp.c handles the other events, such as FD_READ, FD_WRITE, and FD_CLOSE, in the same way as the WSAEventSelect example code in Chapter 8. The only item of note is in the FD_WRITE event. One of the command line options is to wait until an RSVP PATH message has been received before sending the data. This is especially relevant if the data being transmitted is likely to exceed the best-effort bandwidth available on the network. The AbleToSend function calls SIO_CHK_QOS to determine whether the QOS parameters requested are within the available best-effort limits. If so, it should be OK to start sending data; otherwise, wait for a confirmation to send data.

In our client's case, we want to receive the WSA_QOS_RECEIVERS message to indicate the receipt of a RESV message. This can be indicated upon receipt of an FD_QOS event. At this point, we call the SIO_CHK_QOS command to obtain status information. This WSA_QOS_RECEIVERS flag can be returned in two ways. First the flag can be returned in the iErrorCode field of the WSANETWORKEVENTS structure as the element indexed by FD_QOS_BIT. Second an RSVP_STATUS_INFO structure can be returned in the buffer passed to WSAIoctl using the SIO_GET_QOS ioctl command. This structure too might contain the WSA_QOS_RECEIVERS flag in its StatusCode field. If the wait to send flag has been specified, we check the error field from WSANETWORKEVENTS to see whether an RSVP_STATUS_INFO structure has been returned. If the flag is present, we send data. That's all! The code necessary to support QOS for the client is straightforward.

The server side of the example is a bit more complicated, but only because it needs to manage zero or more client connections. The listening socket and the client sockets are handled in a single array, sc. Array element 0 is the listening socket, while the rest are possible client connections. The global variable nConns contains the number of current clients. Whenever a client connection finishes, all active sockets are compacted toward the beginning of the socket array. There is also a corresponding array of event handles.

The server first binds the listening socket and sets receiving QOS if the user chooses to set QOS before accepting client connections. Any QOS parameters set on the listening socket are copied to the client connection (unless the server is using AcceptEx). The listening socket registers to receive only FD_ACCEPT events. The rest of the server routine is a loop that waits for events on the array of socket handles. At first the only socket in the array is the listening socket, but as more client connections are established there will be more sockets and their corresponding events. If WSAWaitForMultipleEvents unblocks as a result of an event and indicates the event handle in array element 0, the event is occurring on the listening socket. If so, the code calls WSAEnumNetworkEvents to find out which event is occurring. If the event is occurring on a client socket, the code calls the handler routine HandleClientEvents.

On the listening socket, the event of interest is FD_ACCEPT. When this event happens, WSAAccept is called with a conditional function. Remember that the QOS parameters passed into the conditional function can't be trusted, and if the QOS parameter is non-null on Windows 98, some sort of QOS must be set. Windows 2000 does not have that limitation; QOS can be set at any time. If the user specifies that QOS be set during the accept call, this occurs within the conditional function. Once the client socket is accepted, a corresponding event handle is created and the appropriate events are registered for the socket.

The function HandleClientEvents handles any events occurring on the client sockets. The read and write events are straightforward—the only exception is whether to wait for the reservation confirmation before sending. If the user specifies to wait for the reservation confirmation to arrive before sending data, the client waits for the WSA_QOS_RECEIVERS message to be returned in an FD_QOS event. If the message returns, the sending of the data doesn't occur until FD_QOS is received. Usually the most significant aspect of this example is the setting of QOS on the socket and the FD_QOS handler.

Figure 12-3. Unicast TCP example (Qostcp.c)

 // Module: Qostcp.c // #include <winsock2.h> #include <windows.h> #include <qos.h> #include <qossp.h> #include "provider.h" #include "printqos.h" #include <stdio.h> #include <stdlib.h> #define QOS_BUFFER_SZ 16000 // Default buffer size for // SIO_GET_QOS #define DATA_BUFFER_SZ 2048 // Send/Recv buffer size #define SET_QOS_NONE 0 // No QOS #define SET_QOS_BEFORE 1 // Set QOS on listening socket #define SET_QOS_DURING 2 // Set QOS in conditional accept #define SET_QOS_AFTER 3 // Set QOS after accept #define SET_QOS_EVENT 4 // Wait for FD_QOS and then set #define MAX_CONN 10 int iSetQos, // When to set QOS? nConns; BOOL bServer, // Client or server? bWaitToSend, // Wait to send data until RESV bConfirmResv; char szServerAddr[64]; // Server's address QOS clientQos, // QOS client structure serverQos; // QOS server structure RSVP_RESERVE_INFO qosreserve; // // Set up some common FLOWSPEC structures // const FLOWSPEC flowspec_notraffic = {QOS_NOT_SPECIFIED, QOS_NOT_SPECIFIED, QOS_NOT_SPECIFIED, QOS_NOT_SPECIFIED, QOS_NOT_SPECIFIED, SERVICETYPE_NOTRAFFIC, QOS_NOT_SPECIFIED, QOS_NOT_SPECIFIED}; const FLOWSPEC flowspec_g711 = {8500, 680, 17000, QOS_NOT_SPECIFIED, QOS_NOT_SPECIFIED, SERVICETYPE_CONTROLLEDLOAD, 340, 340}; const FLOWSPEC flowspec_guaranteed = {17000, 1260, 34000, QOS_NOT_SPECIFIED, QOS_NOT_SPECIFIED, SERVICETYPE_GUARANTEED, 340, 340}; // // Function: SetReserveInfo // // Description: // For receivers, if a confirmation is requested this must be // done with an RSVP_RESERVE_INFO structure // void SetQosReserveInfo(QOS *lpqos) { qosreserve.ObjectHdr.ObjectType = RSVP_OBJECT_RESERVE_INFO; qosreserve.ObjectHdr.ObjectLength = sizeof(RSVP_RESERVE_INFO); qosreserve.Style = RSVP_DEFAULT_STYLE; qosreserve.ConfirmRequest = bConfirmResv; qosreserve.NumPolicyElement = 0; qosreserve.PolicyElementList = NULL; qosreserve.FlowDescList = NULL; lpqos->ProviderSpecific.buf = (char *)&qosreserve; lpqos->ProviderSpecific.len = sizeof(qosreserve); return; } // // Function: InitQos // // Description: // Set up the client and server QOS structures. This is // broken out into a separate function so that you can change // the requested QOS parameters to see how that affects // the application. // void InitQos() { clientQos.SendingFlowspec = flowspec_g711; clientQos.ReceivingFlowspec = flowspec_notraffic; clientQos.ProviderSpecific.buf = NULL; clientQos.ProviderSpecific.len = 0; serverQos.SendingFlowspec = flowspec_notraffic; serverQos.ReceivingFlowspec = flowspec_g711; serverQos.ProviderSpecific.buf = NULL; serverQos.ProviderSpecific.len = 0; if (bConfirmResv) SetQosReserveInfo(&serverQos); } // // Function: usage // // Description: // Print out usage information // void usage(char *progname) { printf("usage: %s -q:x -s -c:IP\n", progname); printf(" -q:[b,d,a,e] When to request QOS\n"); printf(" b Set QOS before bind or connect\n"); printf(" d Set QOS during accept cond func\n"); printf(" a Set QOS after session setup\n"); printf(" e Set QOS only upon receipt of FD_QOS\n"); printf(" -s Act as server\n"); printf(" -c:Server-IP Act as client\n"); printf(" -w Wait to send until RESV has arrived\n"); printf(" -r Confirm reservation request\n"); ExitProcess(-1); } // // Function: ValidateArgs // // Description: // Parse command line arguments and set global variables to // indicate how the application should act // void ValidateArgs(int argc, char **argv) { int i; // Initialize globals to a default value // iSetQos = SET_QOS_NONE; bServer = TRUE; bWaitToSend = FALSE; bConfirmResv = FALSE; for(i = 1; i < argc; i++) { if ((argv[i][0] == '-') || (argv[i][0] == '/')) { switch (tolower(argv[i][1])) { case 'q': // When to set QOS if (tolower(argv[i][3]) == 'b') iSetQos = SET_QOS_BEFORE; else if (tolower(argv[i][3]) == 'd') iSetQos = SET_QOS_DURING; else if (tolower(argv[i][3]) == 'a') iSetQos = SET_QOS_AFTER; else if (tolower(argv[i][3]) == 'e') iSetQos = SET_QOS_EVENT; else usage(argv[0]); break; case 's': // Server printf("Server flag set!\n"); bServer = TRUE; break; case 'c': // Client printf("Client flag set!\n"); bServer = FALSE; if (strlen(argv[i]) > 3) strcpy(szServerAddr, &argv[i][3]); else usage(argv[0]); break; case 'w': // Wait to send data until // RESV has arrived bWaitToSend = TRUE; break; case 'r': bConfirmResv = TRUE; break; default: usage(argv[0]); break; } } } return; } // // Function: AbleToSend // // Description: // Checks to see whether data can be sent on the socket before // any RESV messages have arrived. This function checks to see whether // the best-effort level currently available on the network is // sufficient for the QOS levels that were set on the socket. // BOOL AbleToSend(SOCKET s) { int ret; DWORD dwCode = ALLOWED_TO_SEND_DATA, dwValue, dwBytes; ret = WSAIoctl(s, SIO_CHK_QOS, &dwCode, sizeof(dwCode), &dwValue, sizeof(dwValue), &dwBytes, NULL, NULL); if (ret == SOCKET_ERROR) { printf("WSAIoctl() failed: %d\n", WSAGetLastError()); return FALSE; } return (BOOL)dwValue; } // // Function: ChkForQosStatus // // Description: // Check for the presence of an RSVP_STATUS_INFO object and // determine whether the supplied flags are present in that // object // DWORD ChkForQosStatus(QOS *lpqos, DWORD dwFlags) { QOS_OBJECT_HDR *objhdr = NULL; RSVP_STATUS_INFO *status = NULL; char *bufptr = NULL; BOOL bDone = FALSE; DWORD objcount = 0; if (lpqos->ProviderSpecific.len == 0) return 0; bufptr = lpqos->ProviderSpecific.buf; objhdr = (QOS_OBJECT_HDR *)bufptr; while (!bDone) { if (objhdr->ObjectType == RSVP_OBJECT_STATUS_INFO) { status = (RSVP_STATUS_INFO *)objhdr; if (status->StatusCode & dwFlags) return 1; } else if (objhdr->ObjectType == QOS_OBJECT_END_OF_LIST) bDone = TRUE; bufptr += objhdr->ObjectLength; objcount += objhdr->ObjectLength; objhdr = (QOS_OBJECT_HDR *)bufptr; if (objcount >= lpqos->ProviderSpecific.len) bDone = TRUE; } return 0; } // // Function: HandleClientEvents // // Description: // This function is called by the Server function to handle // events that occurred on client SOCKET handles. The socket // array is passed in along with the event array and the index // of the client who received the signal. Within the function, // the event is decoded and the appropriate action occurs. // void HandleClientEvents(SOCKET socks[], HANDLE events[], int index) { WSANETWORKEVENTS ne; char databuf[4096]; WSABUF wbuf; DWORD dwBytesRecv, dwFlags; int ret, i; // Enumerate the network events that occurred // ret = WSAEnumNetworkEvents(socks[index], events[index], &ne); if (ret == SOCKET_ERROR) { printf("WSAEnumNetworkEvents() failed: %d\n", WSAGetLastError()); return; } // Data to be read // if ((ne.lNetworkEvents & FD_READ) == FD_READ) { wbuf.buf = databuf; wbuf.len = 4096; if (ne.iErrorCode[FD_READ_BIT]) printf("FD_READ error: %d\n", ne.iErrorCode[FD_READ_BIT]); else printf("FD_READ\n"); dwFlags = 0; ret = WSARecv(socks[index], &wbuf, 1, &dwBytesRecv, &dwFlags, NULL, NULL); if (ret == SOCKET_ERROR) { printf("WSARecv() failed: %d\n", WSAGetLastError()); return; } wbuf.len = dwBytesRecv; printf("Read: %d bytes\n", dwBytesRecv); } // Able to write data; nothing to do here // if ((ne.lNetworkEvents & FD_WRITE) == FD_WRITE) { if (ne.iErrorCode[FD_WRITE_BIT]) printf("FD_WRITE error: %d\n", ne.iErrorCode[FD_WRITE_BIT]); else printf("FD_WRITE\n"); } // The client closed the connection. Close the socket on our // end and clean up the data structures. // if ((ne.lNetworkEvents & FD_CLOSE) == FD_CLOSE) { if (ne.iErrorCode[FD_CLOSE_BIT]) printf("FD_CLOSE error: %d\n", ne.iErrorCode[FD_CLOSE_BIT]); else printf("FD_CLOSE ...\n"); closesocket(socks[index]); WSACloseEvent(events[index]); socks[index] = INVALID_SOCKET; // // Remote the client socket entry from the array and // compact the remaining clients to the beginning of the // array // for(i = index; i < MAX_CONN - 1; i++) socks[i] = socks[i + 1]; nConns--; } // Received an FD_QOS event. This could mean several things. // if ((ne.lNetworkEvents & FD_QOS) == FD_QOS) { char buf[QOS_BUFFER_SZ]; QOS *lpqos = NULL; DWORD dwBytes; if (ne.iErrorCode[FD_QOS_BIT]) printf("FD_QOS error: %d\n", ne.iErrorCode[FD_QOS_BIT]); else printf("FD_QOS\n"); lpqos = (QOS *)buf; lpqos->ProviderSpecific.buf = &buf[sizeof(QOS)]; lpqos->ProviderSpecific.len = sizeof(buf) - sizeof(QOS); ret = WSAIoctl(socks[index], SIO_GET_QOS, NULL, 0, buf, QOS_BUFFER_SZ, &dwBytes, NULL, NULL); if (ret == SOCKET_ERROR) { printf("WSAIoctl(SIO_GET_QOS) failed: %d\n", WSAGetLastError()); return; } PrintQos(lpqos); // // See whether we're set for receiving FD_QOS events only. // If so, we need to invoke QOS on the connection // now; otherwise, client will never receive a RESV message. // if (iSetQos == SET_QOS_EVENT) { lpqos->ReceivingFlowspec.ServiceType = serverQos.ReceivingFlowspec.ServiceType; ret = WSAIoctl(socks[index], SIO_SET_QOS, lpqos, dwBytes, NULL, 0, &dwBytes, NULL, NULL); if (ret == SOCKET_ERROR) { printf("WSAIoctl(SIO_SET_QOS) failed: %d\n", WSAGetLastError()); return; } // // Change iSetQos so we don't set QOS again if we // receive another FD_QOS event // iSetQos = SET_QOS_BEFORE; } } return; } // // Function: SrvCondAccept // // Description: // This is the conditional function for WSAAccept. The QOS service // provider is limited in that the QOS values passed into here are // unreliable, so the option SET_QOS_DURING is useless unless we call // SIO_SET_QOS with our own values (as opposed to the values the client // is requesting since those values are supposed to be returned in // lpSQOS). Note that on Windows 98, if lpSQOS is not NULL you have to // set some QOS values (with SIO_SET_QOS) in the conditional // function; otherwise, WSAAccept will fail. // int CALLBACK SrvCondAccept(LPWSABUF lpCallerId, LPWSABUF lpCallerdata, LPQOS lpSQOS, LPQOS lpGQOS, LPWSABUF lpCalleeId, LPWSABUF lpCalleeData, GROUP *g, DWORD dwCallbackData) { DWORD dwBytes = 0; SOCKET s = (SOCKET)dwCallbackData; SOCKADDR_IN client; int ret; if (nConns == MAX_CONN) return CF_REJECT; memcpy(&client, lpCallerId->buf, lpCallerId->len); printf("Client request: %s\n", inet_ntoa(client.sin_addr)); if (iSetQos == SET_QOS_EVENT) { printf("Setting for event!\n"); serverQos.SendingFlowspec.ServiceType |= SERVICE_NO_QOS_SIGNALING; serverQos.ReceivingFlowspec.ServiceType |= SERVICE_NO_QOS_SIGNALING; ret = WSAIoctl(s, SIO_SET_QOS, &serverQos, sizeof(serverQos), NULL, 0, &dwBytes, NULL, NULL); if (ret == SOCKET_ERROR) { printf("WSAIoctl() failed: %d\n", WSAGetLastError()); return CF_REJECT; } } return CF_ACCEPT; } // // Function: Server // // Description: // This server routine handles incoming client connections. // First it sets up the listening socket, sets QOS when // appropriate, and waits for incoming clients and events. // void Server(SOCKET s) { SOCKET sc[MAX_CONN + 1]; WSAEVENT hAllEvents[MAX_CONN+1]; SOCKADDR_IN local, client; int clientsz, ret, i; DWORD dwBytesRet; WSANETWORKEVENTS ne; // Initialize the arrays to invalid values // for(i = 0; i < MAX_CONN+1; i++) { hAllEvents[i] = WSA_INVALID_EVENT; sc[i] = INVALID_SOCKET; } // Array index 0 will be our listening socket // hAllEvents[0] = WSACreateEvent(); sc[0] = s; nConns = 0; local.sin_family = AF_INET; local.sin_port = htons(5150); local.sin_addr.s_addr = htonl(INADDR_ANY); if (bind(s, (SOCKADDR *)&local, sizeof(local)) == SOCKET_ERROR) { printf("bind() failed: %d\n", WSAGetLastError()); return; } listen(s, 7); if (iSetQos == SET_QOS_BEFORE) { ret = WSAIoctl(sc[0], SIO_SET_QOS, &serverQos, sizeof(serverQos), NULL, 0, &dwBytesRet, NULL, NULL); if (ret == SOCKET_ERROR) { printf("WSAIoctl(SIO_SET_QOS) failed: %d\n", WSAGetLastError()); return; } printf("Set QOS on listening socket:\n"); PrintQos(&serverQos); } if (WSAEventSelect(sc[0], hAllEvents[0], FD_ACCEPT) == SOCKET_ERROR) { printf("WSAEventSelect() failed: %d\n", WSAGetLastError()); return; } while (1) { ret = WSAWaitForMultipleEvents(nConns+1, hAllEvents, FALSE, WSA_INFINITE, FALSE); if (ret == WSA_WAIT_FAILED) { printf("WSAWaitForMultipleObject() failed: %d\n", WSAGetLastError()); return; } if ((i = ret - WSA_WAIT_EVENT_0) > 0) // Client network event HandleClientEvents(sc, hAllEvents, i); else { ret = WSAEnumNetworkEvents(sc[0], hAllEvents[0], &ne); if (ret == SOCKET_ERROR) { printf("WSAEnumNetworkevents() failed: %d\n", WSAGetLastError()); return; } if ((ne.lNetworkEvents & FD_ACCEPT) == FD_ACCEPT) { if (ne.iErrorCode[FD_ACCEPT_BIT]) printf("FD_ACCEPT error: %d\n", ne.iErrorCode[FD_ACCEPT_BIT]); else printf("FD_ACCEPT\n"); clientsz = sizeof(client); sc[++nConns] = WSAAccept(s, (SOCKADDR *)&client, &clientsz, SrvCondAccept, sc[nConns]); if (sc[nConns] == SOCKET_ERROR) { printf("WSAAccept() failed: %d\n", WSAGetLastError()); nConns--; return; } hAllEvents[nConns] = WSACreateEvent(); Sleep(10000); if (iSetQos == SET_QOS_AFTER) { ret = WSAIoctl(sc[nConns], SIO_SET_QOS, &serverQos, sizeof(serverQos), NULL, 0, &dwBytesRet, NULL, NULL); if (ret == SOCKET_ERROR) { printf("WSAIoctl() failed: %d\n", WSAGetLastError()); return; } } ret = WSAEventSelect(sc[nConns], hAllEvents[nConns], FD_READ | FD_WRITE | FD_CLOSE | FD_QOS); if (ret == SOCKET_ERROR) { printf("WSAEventSelect() failed: %d\n", WSAGetLastError()); return; } } if (ne.lNetworkEvents & FD_CLOSE) printf("FD_CLOSEn"); if (ne.lNetworkEvents & FD_READ) printf("FD_READn"); if (ne.lNetworkEvents & FD_WRITE) printf("FD_WRITEn"); if (ne.lNetworkEvents & FD_QOS) printf("FD_QOS\n"); } } return; } // // Function: Client // // Description: // The client routine initiates the connection, sets QOS when // appropriate, and handles incoming events. // void Client(SOCKET s) { SOCKADDR_IN server, local; WSABUF wbuf; DWORD dwBytes, dwBytesSent, dwBytesRecv, dwFlags; HANDLE hEvent; int ret, i; char databuf[DATA_BUFFER_SZ]; QOS *lpqos; WSANETWORKEVENTS ne; hEvent = WSACreateEvent(); if (hEvent == NULL) { printf("WSACreateEvent() failed: %d\n", WSAGetLastError()); return; } lpqos = NULL; if (iSetQos == SET_QOS_BEFORE) { local.sin_family = AF_INET; local.sin_port = htons(0); local.sin_addr.s_addr = htonl(INADDR_ANY); if (bind(s, (SOCKADDR *)&local, sizeof(local)) == SOCKET_ERROR) { printf("bind() failed: %d\n", WSAGetLastError()); return; } ret = WSAIoctl(s, SIO_SET_QOS, &clientQos, sizeof(clientQos), NULL, 0, &dwBytes, NULL, NULL); if (ret == SOCKET_ERROR) { printf("WSAIoctl(SIO_SET_QOS) failed: %d\n", WSAGetLastError()); return; } } else if (iSetQos == SET_QOS_DURING) lpqos = &clientQos; else if (iSetQos == SET_QOS_EVENT) { clientQos.SendingFlowspec.ServiceType |= SERVICE_NO_QOS_SIGNALING; clientQos.ReceivingFlowspec.ServiceType |= SERVICE_NO_QOS_SIGNALING; ret = WSAIoctl(s, SIO_SET_QOS, &clientQos, sizeof(clientQos), NULL, 0, &dwBytes, NULL, NULL); if (ret == SOCKET_ERROR) { printf("WSAIoctl() failed: %d\n", WSAGetLastError()); return; } } server.sin_family = AF_INET; server.sin_port = htons(5150); server.sin_addr.s_addr = inet_addr(szServerAddr); printf("Connecting to: %s\n", inet_ntoa(server.sin_addr)); ret = WSAConnect(s, (SOCKADDR *)&server, sizeof(server), NULL, NULL, lpqos, NULL); if (ret == SOCKET_ERROR) { printf("WSAConnect() failed: %d\n", WSAGetLastError()); return; } ret = WSAEventSelect(s, hEvent, FD_READ | FD_WRITE | FD_CLOSE | FD_QOS); if (ret == SOCKET_ERROR) { printf("WSAEventSelect() failed: %d\n", WSAGetLastError()); return; } wbuf.buf = databuf; wbuf.len = DATA_BUFFER_SZ; memset(databuf, '#', DATA_BUFFER_SZ); databuf[DATA_BUFFER_SZ-1] = 0; while (1) { ret = WSAWaitForMultipleEvents(1, &hEvent, FALSE, WSA_INFINITE, FALSE); if (ret == WSA_WAIT_FAILED) { printf("WSAWaitForMultipleEvents() failed: %d\n", WSAGetLastError()); return; } ret = WSAEnumNetworkEvents(s, hEvent, &ne); if (ret == SOCKET_ERROR) { printf("WSAEnumNetworkEvents() failed: %d\n", WSAGetLastError()); return; } if (ne.lNetworkEvents & FD_READ) { if (ne.iErrorCode[FD_READ_BIT]) printf("FD_READ error: %d\n", ne.iErrorCode[FD_READ_BIT]); else printf("FD_READ\n"); wbuf.len = 4096; dwFlags = 0; ret = WSARecv(s, &wbuf, 1, &dwBytesRecv, &dwFlags, NULL, NULL); if (ret == SOCKET_ERROR) { printf("WSARecv() failed: %d\n", WSAGetLastError()); return; } printf("Read: %d bytes\n", dwBytesRecv); wbuf.len = dwBytesRecv; ret = WSASend(s, &wbuf, 1, &dwBytesSent, 0, NULL, NULL); if (ret == SOCKET_ERROR) { printf("WSASend() failed: %d\n", WSAGetLastError()); return; } printf("Sent: %d bytes\n", dwBytesSent); } if (ne.lNetworkEvents & FD_WRITE) { if (ne.iErrorCode[FD_WRITE_BIT]) printf("FD_WRITE error: %d\n", ne.iErrorCode[FD_WRITE_BIT]); else printf("FD_WRITE\n"); if (!bWaitToSend) { wbuf.buf = databuf; wbuf.len = DATA_BUFFER_SZ; // // If the network can't support the bandwidth, // don't send // if (!AbleToSend(s)) { printf("Network is unable to provide " "sufficient best-effort bandwidth\n"); printf("before the reservation " "request is approved\n"); } for(i = 0; i < 1; i++) { ret = WSASend(s, &wbuf, 1, &dwBytesSent, 0, NULL, NULL); if (ret == SOCKET_ERROR) { printf("WSASend() failed: %d\n", WSAGetLastError()); return; } printf("Sent: %d bytes\n", dwBytesSent); } } } if (ne.lNetworkEvents & FD_CLOSE) { if (ne.iErrorCode[FD_CLOSE_BIT]) printf("FD_CLOSE error: %d\n", ne.iErrorCode[FD_CLOSE_BIT]); else printf("FD_CLOSE ...\n"); closesocket(s); WSACloseEvent(hEvent); return; } if (ne.lNetworkEvents & FD_QOS) { char buf[QOS_BUFFER_SZ]; QOS *lpqos = NULL; DWORD dwBytes; BOOL bRecvRESV = FALSE; if (ne.iErrorCode[FD_QOS_BIT]) { printf("FD_QOS error: %d\n", ne.iErrorCode[FD_QOS_BIT]); if (ne.iErrorCode[FD_QOS_BIT] == WSA_QOS_RECEIVERS) bRecvRESV = TRUE; } else printf("FD_QOS\n"); lpqos = (QOS *)buf; ret = WSAIoctl(s, SIO_GET_QOS, NULL, 0, buf, QOS_BUFFER_SZ, &dwBytes, NULL, NULL); if (ret == SOCKET_ERROR) { printf("WSAIoctl(SIO_GET_QOS) failed: %d\n", WSAGetLastError()); return; } PrintQos(lpqos); // // Check to see whether a status object is returned // in the QOS structure that might also contain the // WSA_QOS_RECEIVERS flag // if (ChkForQosStatus(lpqos, WSA_QOS_RECEIVERS)) bRecvRESV = TRUE; if (iSetQos == SET_QOS_EVENT) { lpqos->SendingFlowspec.ServiceType = clientQos.SendingFlowspec.ServiceType; ret = WSAIoctl(s, SIO_SET_QOS, lpqos, dwBytes, NULL, 0, &dwBytes, NULL, NULL); if (ret == SOCKET_ERROR) { printf("WSAIoctl(SIO_SET_QOS) failed: %d\n", WSAGetLastError()); return; } // // Change iSetQos so that we don't set QOS again if we // receive another FD_QOS event // iSetQos = SET_QOS_BEFORE; } if (bWaitToSend && bRecvRESV) { wbuf.buf = databuf; wbuf.len = DATA_BUFFER_SZ; for(i = 0; i < 1; i++) { ret = WSASend(s, &wbuf, 1, &dwBytesSent, 0, NULL, NULL); if (ret == SOCKET_ERROR) { printf("WSASend() failed: %d\n", WSAGetLastError()); return; } printf("Sent: %d bytes\n", dwBytesSent); } } } } return; } // // Function: main // // Description: // Initialize Winsock, parse command line arguments, create // a QOS TCP socket, and call the appropriate handler // routine depending on the arguments supplied // int main(int argc, char **argv) { WSADATA wsd; WSAPROTOCOL_INFO *pinfo = NULL; SOCKET s; // Parse the command line ValidateArgs(argc, argv); if (WSAStartup(MAKEWORD(2,2), &wsd) != 0) { printf("Unable to load Winsock: %d\n", GetLastError()); return -1; } pinfo = FindProtocolInfo(AF_INET, SOCK_STREAM, IPPROTO_TCP, XP1_QOS_SUPPORTED); if (!pinfo) { printf("unable to find suitable provider!\n"); return -1; } printf("Provider returned: %s\n", pinfo->szProtocol); s = WSASocket(FROM_PROTOCOL_INFO, FROM_PROTOCOL_INFO, FROM_PROTOCOL_INFO, pinfo, 0, WSA_FLAG_OVERLAPPED); if (s == INVALID_SOCKET) { printf("WSASocket() failed: %d\n", WSAGetLastError()); return -1; } InitQos(); if (bServer) Server(s); else Client(s); closesocket(s); WSACleanup(); return 0; } 

Table 12-8. Qostcp.c command line parameters

Parameter Meaning
-q:[b,d,a,e] Set QOS either before (b), during (d), or after (a), or wait for an FD_QOS event (e) before setting
-s Act as the server
-c:Server-IP Act as the client, and connect to the server at the given IP address
-w Wait to send data until a RESV message has been received
-r Set the option to receive notification once the reservation has been confirmed

Unicast UDP

Using unicast UDP offers a few more possibilities than TCP does. The UDP example on the accompanying CD is named Qosudp.c. It incorporates both the sender and the receiver. For the sender, there are two methods for telling the QOS service provider where data is to be sent. Remember that the peer's address is required to initiate an RSVP session. This can be accomplished by calling either WSAConnect or SIO_SET_QOS with the QOS_DESTADDR object. The unicast UDP example also takes a parameter on when QOS is to be set. If the user specifies that QOS is to be set before binding or connecting, the SIO_SET_QOS ioctl command with a QOS_DESTADDR object is used. If the user specifies that QOS is to be set during WSAAccept's conditional function, QOS parameters are specified in the WSAConnect call. If the user specifies that QOS is to be set after session setup, WSAConnect is called without QOS and SIO_SET_QOS is invoked later without the QOS_DESTADDR object. Finally, if the user specifies that QOS is to be set only upon receipt of a FD_QOS event notification, SIO_SET_QOS is called with a QOS_DESTADDR object, but the flag SERVICE_QOS_NO_SIGNALING is ORed in with the ServiceType FLOWSPEC field.

The receiving side has considerably fewer options. The various flags for when to set QOS don't really apply here. QOS can be set before data is to be received, or the receiver can wait for an FD_QOS event to occur. This is because UDP doesn't receive any connection request—there is no setting QOS during the accept function or after session setup. The receiver also has the option of specifying a different filter style, such as fixed filter or shared explicit. If a different filter is specified, an IP address must be given with the -r:IP option. The function SetQosReceivers fills in an RSVP_RESERVE_INFO structure with an RSVP_FILTERSPEC structure, which defines the sender's IP address. One particularly important aspect of setting the filter is that the port number of the sender must be specified. This means that the receiver must know each sender's IP address and the port number it is bound to on the sender's side.

Note that the receiver can also use WSAConnect to associate the sender's IP address with the socket. However, because the UDP receiver can specify various filter styles as well as a number of senders, WSAConnect cannot be used. Remember that if WSAConnect is used to associate the endpoint's IP address, send and receive operations can occur only with that peer and have QOS associated with it.

If you compare the unicast UDP example to the TCP example, you'll find they're quite similar. The only exception is the different way QOS can be set on the socket. UDP applications require that the sender specify the recipient's IP address to invoke RSVP, and QOS provides two methods for this. TCP applications by their nature do this by default in the connect call. The event loop for both applications is almost the same. Don't forget the one major "gotcha" for UDP applications: the socket must be bound locally before any QOS (sending or receiving) can be set on it with SIO_SET_QOS when WSAConnect is not used. Binding to INADDR_ANY and port 0 is perfectly legal, as well as using a specific IP and port. Using WSAConnect performs an implicit bind, so if QOS is set at that point, you do not have to explicitly bind beforehand.

Multicast UDP

The last example provided is multicast QOS, which can be found on the CD as Qosmcast.c. The central function here is WSAJoinLeaf, which an application must call to join a multicast group, as we saw in the previous chapter. When an application joins the group, it can also pass QOS parameters. The multicast example takes the same parameters as the unicast UDP example. You can select the point at which QOS can be set on the socket. If you choose to set QOS during the accept conditional function, QOS is passed into the WSAJoinLeaf call. Otherwise, set QOS by calling WSAIoctl with SIO_SET_QOS.

One of the parameters for receivers allows users to set the filter style to either fixed filter or shared explicit. Remember that multicast UDP uses wildcard style by default. Specifying a different filter type applies only to receivers, and if either of these two filter types is desired, each sender's address is specified in an -r:SenderIP command line option. Users can specify the filters via the -f option with se for shared explicit and with ff for fixed filter.

For both sender and receiver, the -m option is used to specify the multicast group to join. Note that this option can be specified multiple times to join as many groups as you want. The -s option indicates that the program should act as a sender. The w option tells the sender to wait until there is a WSA_QOS_RECEIVERS notification before sending any data. Finally, the -q option identifies when to set QOS. No matter when QOS is specified to be set, the socket is bound locally to port 5150. In actuality, any port can be chosen or 0 can be specified so that the port is chosen for you; however, if the receiver is to set either the fixed or the shared explicit filter, it must also provide the IP and port of the sender. We use a fixed port for the sake of simplicity. Unlike in unicast UDP, the receiver is not required to bind the port locally to set QOS. The reason for this is that WSAJoinLeaf implicitly binds the socket if it is not already bound. The other question you might be asking is whether the socket option commands IP_ ADD_MEMBERSHIP and IP_ DROP_MEMBERSHIP can be used instead of WSAJoinLeaf. The answer is no. If these commands are used, the QOS parameters requested will not be applied to the socket.

We won't go into the exact details of what happens when QOS is chosen to be set because it is quite similar to the unicast UDP example. By now, you should be familiar with how the RSVP session is initiated and what is needed to generate the PATH and RESV messages.



Network Programming for Microsoft Windows
Linux Server Hacks, Volume Two: Tips & Tools for Connecting, Monitoring, and Troubleshooting
ISBN: 735615799
EAN: 2147483647
Year: 1998
Pages: 159

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net