HTTP represents a high-level protocol for communicating over TCP/IP networks, whereas socket programming is at a much lower level. In fact, HTTP itself uses sockets to communicate. Wherever feasible, you should use a high-level protocol such as HTTP or File Transfer Protocol (FTP), but there are times when socket programming is necessary. Examples include the following:
ActiveSync versions 3.0 and later provide Windows CE device to desktop PC connectivity without using Remote Access Services (RAS) on Windows NT/2000 or Dialup Network (DUN) on Windows 98 in the default setup. This means that sockets cannot be used to communicate between a Windows CE device connected to a desktop PC. Instead, you should use the Remote API (RAPI, see Chapter 10) to provide communications. Windows CE implements a subset of the Winsock 1.1 library found on desktop Windows, and Winsock itself is based around the Berkeley socket library. The major feature not supported by Windows CE is asynchronous mode. In Windows CE calls to Winsock functions will block (that is, not return), until the operation is complete. Therefore, it is usual to create a thread (see Chapters 5 and 6) and call Winsock functions on the thread. Then, if the call blocks, your primary thread will not be blocked and the user interface will still be responsive to the user. MFC supports class libraries that provide wrappers around Winsock functions such as CSocket and CSocketFile. These classes implement asynchronous calls to Winsock functions by using Windows messages. However, this means that the calls to the socket functions do not block if they are called on a worker thread (that is, a thread that does not have a message queue). The solution is to call the functions on the primary thread in asynchronous mode, or have the primary thread block (which will make the user interface unresponsive). Alternatively, you should create a secondary user interface thread that has a message queue. For these reasons it is often easier to call Winsock functions directly rather than use the MFC classes. Sockets can be used for UDP Datagram or Stream communications. UDP is an unreliable, connectionless protocol used for broadcasting messages. Stream sockets use two-way, reliable communications, and are the focus of the remainder of this chapter. Socket Clients and ServersSockets are programmed so that one application acts as a client (and therefore initiates the communication) and another application acts as a server (and therefore waits for a client to connect). Once connected, communication between client and server is two-way. Data sent between sockets can be binary or text. You need to take care when sending data to applications running on different operating systems such as Windows 98 or 2000, or UNIX.
The Winsock functions used for socket stream communications are shown in Figure 8.3. The server application creates a listening socket and waits for a client to connect. The client creates a socket and connects to the server listening socket. At this point, the listening socket creates another socket to which the client's socket connects, and the server's listening socket goes back to waiting for another connection. Either the client or the server can terminate the connection. Figure 8.3. Socket function-calling sequence for client and server applicationsA listening socket is associated with an IP address and a port number. The client application will supply the IP address and the port number when connecting to the server socket. Only one listening socket can be associated with a particular IP address and port number. Initializing the Winsock LibraryThe Winsock library must be initialized before any Winsock function can be called. This is done by calling WSAStartup. WSADATA wsaData; if(WSAStartup(MAKEWORD(1,1), &wsaData) != 0) { cout _T("Could not initialize sockets"); return; } The function is passed the required Winsock library version number (1.1) and a pointer to a WSADATA structure, into which information about Winsock is placed.
The function WSACleanup should be called when you have finished with the Winsock library. if(WSACleanup() == SOCKET_ERROR) { cout _T("Could not cleanup sockets:") WSAGetLastError() endl; return; } This function returns the value SOCKET_ERROR if an error is detected. Further information about the error can be obtained by calling WSAGetLastError. It is important not to call GetLastError when handling Winsock errors. WSAGetLastError can be called for determining errors from all Winsock functions except WSAStartup. Manipulating IP AddressesThere are several different ways to store IP address in Winsock. First, the IP address may be stored as a string using the 'dot' notation, such as "192.168.0.1". Each of the four elements of the IP address is known as an octet. The Winsock functions in Windows CE expect this to be an ANSI rather than a Unicode string. The Winsock function inet_addr can be used to convert the 'dot' string notation into a binary form. An IP address is typically stored using an unsigned long or DWORD. Each byte in this long represents one of the octets.
The structure in_addr can be used to represent an IP address in its binary form. This structure has union/structure members and defines that allow the four bytes, or two words, of the address to be conveniently referenced. For example, with the IP address "192.168.0.1" stored in an in_addr structure called Myaddr, the following members can be used: Myaddr.S_un.S_un_b.s_b1; // 192 Myaddr.S_un.S_un_b.s_b2; // 168 Myaddr.S_un.S_un_b.s_b3; // 0 Myaddr.S_un.S_un_b.s_b4; // 1 Myaddr.S_un.S_addr; // Return all 4 bytes Defines are also provided in the in_addr structure to provide easier member access using names often associated with the octets: Myaddr.s_net; // 192, network number Myaddr.s_host; // 168, host number Myaddr.s_lh; // 0, logical host number Myaddr.s_impno; // 1, imp number Myaddr.s_addr; // Return all 4 bytes The Winsock function inet_ntoa is used to convert an IP address from its binary form (either an in_addr structure or from a DWORD by casting) to a string using the dot notation. The string pointer returned by inet_ntoa should not be deleted it is owned by the Winsock library. You should copy the contents immediately since the buffer may be reused at a later time.
You should note that the bytes are stored in reverse order. So, for the IP address "192.168.0.1", the following code will display "192" and not the expected "1": LONG l; memcpy(&l, &address, sizeof(address)); cout LOBYTE(LOWORD(l)) endl; Determining a Device's IP Address and Host NameThere are times when you need to determine the IP addresses in use on a device and the device's host name. The first stage is to determine the Windows CE device's host name. This is typically the name "Device Name" configured through the Control Panel's "Communication" icon. This function is passed a character buffer into which the name is placed. Note that a Unicode string is not returned. In Listing 8.7 the contents of the returned string in szHostName is displayed the cout object has an operator overload on which converts ANSI character strings to Unicode for display. The host name is also available through the HKEY_LOCAL_MACHINE\Ident\Name registry key. Next, the host name in szHostName is passed to gethostbyname, which returns a pointer to a HOSTENT structure. It is this structure that contains, among other information, the IP address. Listing 8.7 Determining the host name and IP addressvoid Listing8_7() { WSADATA wsaData; char szHostName[1024]; HOSTENT* lphostent; in_addr address; if(WSAStartup(MAKEWORD(1,1), &wsaData) != 0) { cout _T("Could not initialize sockets") endl; return; } if(gethostname (szHostName, 1024) == SOCKET_ERROR) { cout _T("Could not get host name") WSAGetLastError() endl; return; } cout _T("Host Name:") szHostName endl; lphostent = gethostbyname(szHostName); if(lphostent == NULL) { cout _T("Could not get host information:") WSAGetLastError() endl; return; } for(int i = 0; lphostent->h_addr_list[i] != NULL; i++) { memcpy(&address, lphostent->h_addr_list[i], sizeof(address)); cout _T("IP Address: ") cout _T("IP Address: ") address.S_un.S_un_b.s_b1 _T(".") address.S_un.S_un_b.s_b2 _T(".") address.S_un.S_un_b.s_b3 _T(".") address.S_un.S_un_b.s_b4 endl; } if(WSACleanup() == SOCKET_ERROR) { cout _T("Could not cleanup sockets:") WSAGetLastError() endl; } return; } The HOSTENT pointer returned from gethostbyname points at a structure owned by the Winsock library. You should not modify the contents of this structure or delete it. The h_addr_list member contains an array of IP addresses. You can, for example, have one IP address for a PPP (Point to Point Protocol) connection through a serial or dialup connection, and another IP address for a network connection through a network adapter card. The h_addr_list member is a char* pointer, although it actually points at an unsigned long integer value containing the IP address in this case. In Listing 8.7 this unsigned long integer is copied into an in_addr structure that is used to store IP addresses. The contents of this address are displayed. The IP address "127.0.0.1" indicates that no connections exist, and this special IP address refers to the device itself. The function gethostbyname can be used to resolve any host name on the network. If you want to find the IP address or addresses associated with a host, simply pass the host name to gethostbyname and use code like that in Listing 8.7 to obtain the IP addresses. Implementing a Ping FunctionBefore attempting to communicate between two computers using sockets, you should check that you can perform the simplest of communications this is a ping. A ping is simply sending a specific type of IP packet to the other computer, and then waiting for a response. ICMP, or the Internet Control Message Protocol, defines the format of a ping packet. On most socket implementations a ping ICMP packet is sent by first opening a raw socket. A raw socket allows IP packets to be sent without using TCP to order and control the sending and arrival of the packets. However, raw sockets are not supported in Windows CE. Instead, three ICMP API functions are used for pinging:
You will need to include ipexport.h and icmpapi.h when using these functions, and include icmplib.lib in the project. Some versions of ipexport.h and icmpapi.h are not written correctly for inclusion in a C++ project, as they generate decorated (mangled) C++ names rather than C function names. Therefore, it might be necessary to wrap the #include statements with an extern "C" block, as shown in Listing 8.8. These functions cannot currently be called from emulation. Calling IcmpCreateFile is straightforward the function takes no parameters and returns a handle on success, or INVALID_HANDLE_VALUE on return. Calling IcmpCloseHandle is equally simple just pass the handle returned from IcmpCreateFile. Listing 8.8 shows a complete ping function. The user is prompted for an IP address to ping. The function could be extended to take an IP address or host name, and if a host name was supplied, convert it to an IP address using gethostbyname. The string containing the IP address is converted from Unicode to ANSI, and passed to inet_addr to convert the IP address in dot notation to a DWORD value. Listing 8.8 A ping function// *** Listing 8.8 // // Pings an IP address // extern "C" { #include <ipexport.h> #include <icmpapi.h> } // NOTE: include icmplib.lib into the project. void Listing8_8() { TCHAR szIPAddr[30]; char szchrIPAddr[30]; HANDLE hIcmp; char* lpToSend = "Ping information"; BYTE bIn[1024]; int rc; in_addr ipFromAddress; PICMP_ECHO_REPLY lpEchoReply; DWORD dwToPing; if(!GetTextResponse( _T("IP Address (e.g. 192.168.0.2) to Ping: "), szIPAddr, 30)) return; // Convert to ANSI char string wcstombs(szchrIPAddr, szIPAddr, wcslen(szIPAddr) + 1); dwToPing = inet_addr(szchrIPAddr); if(dwToPing == -1) { cout _T("Invalid IP address") endl; return; } hIcmp = IcmpCreateFile(); if(hIcmp == INVALID_HANDLE_VALUE) { cout _T("Cannot open Icmp") endl; return; } rc = IcmpSendEcho(hIcmp, dwToPing, lpToSend, strlen(lpToSend), NULL, bIn, sizeof(bIn), 2000); if(rc == 0) cout _T("Ping failed:") GetLastError() endl; else { lpEchoReply = (PICMP_ECHO_REPLY)bIn; for(int i = 0; i rc; i++) { memcpy(&ipFromAddress, &lpEchoReply->Address, sizeof(in_addr)); cout _T("Reply from:") inet_ntoa(ipFromAddress) _T(" Number bytes:") lpEchoReply->DataSize _T(" Round Trip:") lpEchoReply->RoundTripTime _T(" Milliseconds.") endl; lpEchoReply++; // move to next reply } } IcmpCloseHandle(hIcmp); } In Listing 8.8 IcmpSendEcho is called with a timeout of 2000 milliseconds and will return in bIn one or more ICMP_ECHO_REPLY structures. A return value of 0 indicates failure, the most likely reason for which is that the server with the given IP address in dwToPing could not be found. In this case, GetLastError will return "5".
The remainder of the code in Listing 8.8 walks through each of the ICMP_ECHO_REPLY structures and displays the IP address the reply was received from, the number of bytes in the ping request, and the round-trip time in milliseconds. The structure ICMP_ECHO_REPLY is documented in ipexport.h. You will typically only receive back one ICMP_ECHO_REPLY (from the target server itself) unless your request goes through a proxy server or router that itself generates replies. Simple Socket Sample ApplicationThe next sections describe a simple socket client/server application. The socket server is implemented on a Windows CE device as an API C++ application. The source code can be found in the directory \SockServer on the CDROM accompanying this book. The socket client is implemented on Windows NT/98/2000 as a command line application, and the source code is located in the directory \SockClient on the CDROM. Use the following set of steps to run the sample application:
As mentioned earlier in the chapter, socket communication will not operate between a desktop PC and a Windows CE device that are only connected via ActiveSync 3.0 or later. This is because ActiveSync does not, by default, use Remote Access Service (RAS), and therefore does not expose TCP/IP functionality to applications running on the Windows CE device or desktop PC. You can configure ActiveSync to use RAS and hence provide TCP/IP support. This is described in the document "readras.doc" on the ActiveSync CD. The Socket Client ApplicationThe client application, which runs on the desktop PC, does the following:
SockClient implements a function called ConnectSocket that creates a socket and connects to a server (Listing 8.9). This function is passed the IP address to connect to in "dot" notation. The port number used for the server and client socket is defined. This value should be the same for the server and client and should be greater than 1024. Listing 8.9 Creates a socket and connects to server (SockClient. cpp)#define SERVER_PORT 50000 SOCKET ConnectSocket(char* szIPAddress) { DWORD dwDestAddr; SOCKADDR_IN sockAddrDest; SOCKET sockDest; // create socket sockDest = socket(AF_INET, SOCK_STREAM, 0); if(sockDest == SOCKET_ERROR) { cout "Could not create socket:" WSAGetLastError() endl; return INVALID_SOCKET; } // convert address to in_addr (binary) form dwDestAddr = inet_addr(szIPAddress); // Initialize SOCKADDR_IN with IP address, // port number and address family memcpy(&sockAddrDest.sin_addr, &dwDestAddr, sizeof(DWORD)); sockAddrDest.sin_port = htons(SERVER_PORT); sockAddrDest.sin_family = AF_INET; // attempt to connect to server if(connect(sockDest, (LPSOCKADDR)&sockAddrDest, sizeof(sockAddrDest)) == SOCKET_ERROR) { cout "Could not connect to server socket:" WSAGetLastError() endl; closesocket(sockDest); return INVALID_SOCKET; } return sockDest; } Calling the function socket to create a socket is straightforward Table 8.18 describes the available parameters and their meanings. WSAGetLastError should be called to obtain the error number if socket returns INVALID_SOCKET.
A SOCKADDR_IN structure must be initialized with the following values for passing to the connect function:
The function connect (Table 8.19) will attempt to make a connection for a socket with the server specified in the SOCKADDR_IN structure.
Listing 8.10 shows the main function in the socket client application. The function prompts the user for the IP address to connect to, and initializes the Winsock library by calling WSAStartup. Next, ConnectSocket is called to make the connection (described above). Listing 8.10 Initiates connection and sends/receives dataint main(int argc, char* argv[]) { WSADATA wsaData; char szIPAddress[100]; char szBuffer[100]; int nSent, nToSend, nRecv, nReceived; SOCKET sock; // Get Server IP address cout "Enter IP address of CE Device: "; cin.getline(szIPAddress, 1024); cout "Connecting to:" szIPAddress endl; // Initialize WinSock if(WSAStartup(MAKEWORD(1,1), &wsaData) != 0) { cout "Could not initialize sockets" endl; return 1; } // Create socket and connect to server sock = ConnectSocket(szIPAddress); if(sock == INVALID_SOCKET) return 1; // Now send information to server while(TRUE) { // read line of input from user cout "Line to send or <END> to finish:"; cin.getline(szBuffer, 1024); // marks end of text from user if(strcmp(szBuffer, "<END>") == 0) break; strcat(szBuffer, "\r\n"); nToSend = strlen(szBuffer) + 1; // send this line to the server nSent = send(sock, szBuffer, nToSend, 0); if(nSent == SOCKET_ERROR) { cout "Connection Broken:" WSAGetLastError() endl; break; } // now read back the number of chars received. nRecv = recv(sock, (char*)&nReceived, sizeof(nReceived), 0); if(nRecv != sizeof(nReceived)) { cout "Error reading acknowledgement:" WSAGetLastError() endl; break; } if(nReceived != nToSend) { cout "Error in number of bytes sent:" WSAGetLastError() endl; break; } } // close socket closesocket(sock); // Clean up Winsock if(WSACleanup() == SOCKET_ERROR) { cout "Could not cleanup sockets:" WSAGetLastError() endl; return 1; } return 0; } The send function (Table 8.20) is used to send each line of text to the server. This is read from the console using the cin console I/O object. Any type of data can be sent, including ANSI or Unicode strings and binary data. However, you need to ensure that the server is expecting the same data format. For example, a common mistake is to send Unicode text from a Windows CE device to a Windows NT/98/2000 server that is programmed to expect ANSI characters. Sending binary data to non-Windows CE or NT/98/2000 PCs can be problematic, since these computers may use different binary representations or have reversed byte ordering.
The server will receive the data from the send function, and returns the number of bytes of data received. This implements a simple protocol of send and acknowledge, which is essential for any form of communications through sockets (or any other communications medium, for that matter). This data is sent as a 2-byte binary value and is read using the recv function (Table 8.21).
The function recv will block until the requested number of bytes of data has been read. Therefore, it is often necessary to create a separate thread to call this function to avoid blocking your primary user-interface thread. This, of course, assumes that you know how many bytes of data to read, and this is not always the case. Usually a socket is sent length information, which it reads using recv, and then uses this information to call recv again to read the indicated number of bytes. The main function continues sending the text entered by the user until the server application breaks the connection or the user types "<END>". In either case, the function closesocket is used to close the socket, and then WSACleanup is used to un-initialize the Winsock library. The code described in this section is designed to work on Windows NT/98/2000 PCs. However, the coding for creating a socket client application for Windows CE is essentially identical. Integer Byte OrderingSocket programming evolved on Unix computers that are typically big-endian (meaning that the most significant byte is byte 0 and the least significant byte is byte 1 in a short integer value). Intel PCs and Windows CE devices are little-endian and store integer values in reverse byte order. This means that short integers being passed to Winsock functions may need to be converted using the htons function. Table 8.22 shows the available conversion functions. Care must be taken when deciding whether to convert values or not. Port numbers must always be converted. As it happens, if you forget to convert the port number on both the client and server, and both are running on little-endian (Intel) type computers, the error is transparent. However, the application will be using a different port number than the one you specified, and this may conflict with existing port numbers. An IP address returned from inet_addr will already be in network byte order and does not have to be converted.
The Socket Server ApplicationThe Windows CE application acts as the server. It therefore must create a listening socket and call the accept function to wait for a client application to connect. The call to the accept function will block. A thread therefore should be created to call accept to avoid blocking the primary thread. The entire code for this application is located in the \SockServer directory on the CDROM. In the sample application, the thread is created in response to the WM_CREATE message being received. hThread = CreateThread(NULL, 0, SockThread, 0, 0, &dwThreadID); if(hThread == NULL) MessageBox(hWnd, _T("Could not create thread"), NULL, MB_OK); else CloseHandle(hThread); break; The function CreateListener is called from the SockThread thread function, and this function creates a socket, binds it to an IP address, and then calls the listen function to make it a listening socket (Listing 8.11). The call to socket takes the same parameters as used in the client application. The ancillary function DisplaySocketError is implemented in SockServer.cpp, and displays the error message together with the error returned from calling WSAGetLastError. Listing 8.11 Creates a socket and binds and makes it a listening socketSOCKET CreateListener() { SOCKADDR_IN sockAddrListen; SOCKET sockListen; DWORD address; char szHostName[1024]; HOSTENT* lphostent; // create a socket sockListen = socket(AF_INET, SOCK_STREAM, 0); if(sockListen == INVALID_SOCKET) { DisplaySocketError( _T("Could not create socket: %d")); return INVALID_SOCKET; } if(gethostname (szHostName, 1024) == SOCKET_ERROR) { DisplaySocketError( _T("Could not get host name: %d")); return INVALID_SOCKET; } lphostent = gethostbyname(szHostName); if(lphostent == NULL) { DisplaySocketError( _T("Could not get host information: %d")); return INVALID_SOCKET; } memcpy(&address, lphostent->h_addr_list[0], sizeof(address)); memset(&sockAddrListen, 0, sizeof(SOCKADDR_IN)); // specify the port number sockAddrListen.sin_port = htons(SERVER_PORT); // specify address family as Internet sockAddrListen.sin_family = AF_INET; // specify address to bind to sockAddrListen.sin_addr.s_addr = address; // bind socket with the SOCKADDR_IN structure if(bind(sockListen, (LPSOCKADDR)&sockAddrListen, sizeof(SOCKADDR)) == SOCKET_ERROR) { DisplaySocketError( _T("Could not bind socket: %d")); return INVALID_SOCKET; } // listen for a connection if(listen(sockListen, 1) == SOCKET_ERROR) { DisplaySocketError( _T("Could not listen on socket: %d")); return INVALID_SOCKET; } return sockListen; } The listening socket needs to be bound to a particular IP address. It is quite possible that a Windows CE device has more than one IP address, for example one for a network connection through a network adapter card and another for a RAS connection through a modem. Since the two connections may be connected to a different network, it is important to specify which IP address the listening socket will accept connections from. Say, for example, the network adapter card may be assigned IP address "192.168.40.100" and the RAS connection "192.168.100.210". Further, assume both have a sub-net mask of "255.255.255.0". If the listening socket is bound to "192.168.40.100", clients on the network connected to by the RAS connection will not be able to connect to the listening socket. In Listing 8.11, a call is made to gethostname to get the host name for the Windows CE device, and gethostbyname to get the IP addresses. These calls are described in the section "Determining a Device's IP Address and Host Name" earlier in this chapter. Next, a SOCKADDR_IN structure is initialized, using the first IP address returned from gethostbyname. In the case where multiple IP addresses exist, you will need to work out which IP address to use. The port and address family are initialized as with the client server, and a call to the function bind is made. Finally, a call is made to the function listen to make this a listening socket. The function CreateListener returns the listener socket descriptor.
The thread function, shown in Listing 8.12, calls CreateListener to obtain a listening socket, then calls the function accept and waits for a connection. Once a connection is made, text is received from the client and displayed in the edit box through a call to the ancillary function AppendToEditBox. When the connection is broken, the SockThread function calls accept again to wait for another connection. Listing 8.12 SockThread Accepts connections and communicates with client.DWORD WINAPI SockThread(LPVOID) { WSADATA wsaData; SOCKET sockConnected, sockListen; int nReceive, nSent; char szmbsBuffer[1024]; TCHAR szBuffer[1024]; int nIndex; SOCKADDR_IN sockAddrClient; int nAddrLen = sizeof(SOCKADDR_IN); AppendToEditBox(_T("Thread started...\r\n")); // Initialize WinSock if(WSAStartup(MAKEWORD(1,1), &wsaData) != 0) { MessageBox(GetFocus(), _T("Could not initialize sockets"), NULL, MB_OK); return 1; } sockListen = CreateListener(); while(TRUE) { AppendToEditBox( _T("Waiting for connection\r\n")); // block until a socket attempts to connect sockConnected = accept(sockListen, (LPSOCKADDR)&sockAddrClient, &nAddrLen); if(sockConnected == INVALID_SOCKET) { DisplaySocketError( _T("Could not accept a connection: %d")); break; } if(sockConnected != INVALID_SOCKET) { // accept strings from client while(TRUE) { nReceive = recv(sockConnected, szmbsBuffer, 1024, 0); if(nReceive == 0) { AppendToEditBox( _T("Connection broken\r\n")); break; } else if(nReceive == SOCKET_ERROR) { DisplaySocketError( _T("Error receiving: %d")); break; } // convert to Unicode szmbsBuffer[nReceive] = '\0'; mbstowcs(szBuffer, szmbsBuffer, nReceive + 1); // append to edit box AppendToEditBox(szBuffer); // send acknowledgement nSent = send(sockConnected, (char*)&nReceive, sizeof(nReceive), 0); if(nSent == SOCKET_ERROR) { DisplaySocketError( _T("Cannot send ack: %d")); break; } } // connection broken, clean up. shutdown(sockConnected, SD_BOTH); closesocket(sockConnected); } } // Clean up Winsock if(WSACleanup() == SOCKET_ERROR) { MessageBox(GetFocus(), _T("Could not cleanup sockets"), NULL, MB_OK); return 1; } return 0; } The call to accept (Table 8.24) will block until a client connects. Notice that in this code, once a client connects, another call is not made to accept until the current connection is broken. This means that multiple simultaneous client connections are not supported by this implementation. If you need to support multiple simultaneous connections, you will need to create a new thread immediately after accept returns. This new thread will be responsible for communicating with the client, and another call can be made to accept immediately. This code uses calls to send and recv in a way similar to the client application. Once the connection is broken, a call is made to shutdown followed by closesocket. The function shutdown can be used to ensure an orderly termination of socket communications and ensures that all pending sends and receives have been completed (Table 8.25).
Lingering and TimeoutsIn some situations, you will need to send a final bit of information from a socket and then close down the socket. However, closing a socket can result in the sent information never being sent. To avoid this problem, a socket can be configured to "linger," so that pending data transfers will be completed. Lingering is set on a socket using the setsockopt function through a LINGER structure. LINGER linger; linger.l_linger = 600; // timeout in seconds linger.l_onoff = 1; if(setsockopt(sockConnected, SOL_SOCKET, SO_LINGER, (const char*) &linger, sizeof(linger)) == SOCKET_ERROR) // Report error In this case, lingering is turned on through setting l_onoff to a non-zero value, and the lingering timeout is set to 600 seconds. The SOL_SOCKET constant used when calling setsockopt specifies that a socket options is being set at the socket level (as opposed to, for example, the TCP level), and SO_LINGER specifies that a pointer to a LINGER structure is being passed. You may need to refine the default timeouts used when sending and receiving data. For example, timeouts can be shorter when communicating across a local area network (LAN), but may need to be longer on a dialup connection to the Internet. Once again, the setsockopt function is used. int timeout = 4000; s = socket( ... ); setsockopt(sockConnected, SOL_SOCKET, SO_SNDTIMEO, (char *)&timeout, sizeof(timeout)); The SO_SNDTIMEO constant sets the timeout for subsequent calls to the send function for the specified socket you can use the constant SO_RCVTIMEO to specify a timeout for receiving data. The timeout period is specified in milliseconds. Infrared Data Association (IrDA) Socket CommunicationsNearly all Windows CE devices and many laptops and desktop PCs have infrared (IR) ports. Both Windows 98 and 2000 support IR ports. You can use sockets or serial communications to send and receive data between these devices. Further, devices such as digital cameras and portable telephones also have IR ports, and, depending on the level of support they provide, similar programming techniques can be used to send images from the camera or telephone to the Windows CE device. Winsock can be used to communicate through IR ports using the Infrared Data Association (IrDA standard). Winsock does not use TCP/IP for communications when communicating using IrDA. Therefore, the setting of an address when creating sockets is different, but once the connection is made, functions like send and recv work in the same way as described in the previous sections of this chapter. IR devices come into and go out of range in a much more unpredictable way than TCP/IP network devices. There is no single arbitrator within a group of devices capable of communicating using IR, so addressing is more difficult. Each device has a unique device identifier that consists of four one-byte values, just like an IP address. Enumerating IrDA DevicesThe first task is to identify the IrDA devices that are in range, and to determine their device identifiers. The getsockopt function with the IRLMP_ENUMDEVICES constant is used to obtain a list of devices. The function returns a DEVICELIST structure containing the number of devices in the numDevice member, and an IRDA_DEVICE_LIST structure for each device. The IRDA_DEVICE_LIST contains the device, and a device name (which is up to 22 bytes long). Listing 8.13 shows code for enumerating the available IrDA devices. Note you should include the file af_irda.h for IR declarations. Listing 8.13 SockThread Accepts connections and communicates with client.// *** Listing 8.13 // // Display list of IR devices in range // #include <af_irda.h> void Listing8_13() { SOCKET irSocket; char chBuffer[1024]; int nSize; ULONG ul; TCHAR szDeviceName[23]; DEVICELIST *pDevList; irSocket = socket(AF_IRDA, SOCK_STREAM, 0); if(irSocket == INVALID_SOCKET) { cout _T("Could not open IR socket") endl; return; } nSize = sizeof(chBuffer); if(getsockopt(irSocket, SOL_IRLMP, IRLMP_ENUMDEVICES, chBuffer, &nSize) == SOCKET_ERROR) { cout _T("Could not get device list:") WSAGetLastError() endl; closesocket(irSocket); return; } pDevList = (DEVICELIST*) chBuffer; if(pDevList->numDevice == 0) cout _T("No devices found") endl; else { for(ul = 0; ul pDevList->numDevice; ul++) { cout _T("Device ID:") pDevList->Device[ul].irdaDeviceID[0] pDevList->Device[ul].irdaDeviceID[1] pDevList->Device[ul].irdaDeviceID[2] pDevList->Device[ul].irdaDeviceID[3]; mbstowcs(szDeviceName, pDevList->Device[ul].irdaDeviceName, sizeof(pDevList-> Device[ul].irdaDeviceName)); cout _T(" Name: ") szDeviceName endl; } } closesocket(irSocket); } A socket must first be opened using the socket function. The constant AF_IRDA is used to specify the IrDA address family rather than IP. Next getsockopt is called with the IRLMP_ENUMDEVICES option and a pointer to a character buffer. On return the character buffer will contain the DEVICELIST structure, which itself contains zero, one, or more IRDA_DEVICE_LIST structures. The code in Listing 8.13 displays the contents of each IRDA_DEVICE_LIST structure. Note that the device name is an ANSI string, and this is converted to Unicode before it is displayed. Note that the list may contain some IrDA devices that are no longer in range it takes a minute or so for the device to be removed from the list. Opening an IrDA Socket PortThe only important difference between TCP/IP Winsock communications described earlier in this chapter and IrDA Winsock communications is how the socket is opened in the first place, and how the address is bound using the bind function. IrDA still uses the socket function, but the options are a little different. SOCKADDR_IRDA irAddr; SOCKET irSocket; irSocket = socket(AF_IRDA, SOCK_STREAM, 0); memset(&irAddr, 0, sizeof(irAddr)); irAddr.irdaAddressFamily = AF_IRDA; memcpy(irAddr.irdaDeviceID, pDevList->Device[0].irdaDeviceID, 4); connect(irSocket, (sockaddr*) &irAddr, sizeof(irAddr)); AF_IRDA is used to specify the IrDA addressing family rather than AF_INET. A SOCKADDR_IRDA structure is used to specify the IrDA address rather than SOCKADDR_IN for IP addressing. The address family irdaAddressFamily member is assigned AF_IRDA, and the device identifier is copied into irdaDeviceID. This device identifier is obtained through calling getsockopt, as shown in Listing 8.13. A call to connect is made in the usual way. A listening socket is created in much the same way a SOCKADDR_IRDA structure is initialized. The irdaDeviceID in this case is set to 0. The irdaServiceName member contains a text string describing the service. SOCKADDR_IRDA irAddr; SOCKET irSocket; irSocket = socket(AF_IRDA, SOCK_STREAM, 0); memset(&irAddr, 0, sizeof(irAddr)); irAddr.irdaAddressFamily = AF_IRDA; memcpy(irAddr.irdaServiceName, "MyService", 10); bind(irSocket, (sockaddr*) &irAddr, sizeof(irAddr));
|