Socket Programming

< BACK  NEXT >
[oR]

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:

  • When communicating using infrared ports to other Windows CE devices, desktop operating systems such as Windows 98 and 2000, and digital cameras.

  • When communicating with a server application where HTTP does not suffice. Note that you may need to write a multithreaded server application to handle requests from multiple Windows CE devices.

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 Servers

Sockets 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.

  • With binary data, the byte order of integer values is reversed on UNIX but is the same on Windows CE, 98, and 2000.

  • Decide whether text will be transmitted as ANSI or Unicode. The application your Windows CE application is communicating with will need to handle data using the same encoding.

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 applications
graphics/08fig03.gif

A 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 Library

The 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.

Table 8.14. WSAStartup Initializes the Winsock library
WSAStartup
WORD wVersionRequested Word containing the major version number in the high byte and minor version in the low byte.
LPWSADATA lpWSAData Pointer to a WSADATA that contains information about the socket library on return.
int Return Value 0 for success, non-zero for an error.

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 Addresses

There 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.

Table 8.15. inet_addr Converts IP address from "dot" to binary form
inet_addr
const char *cp Null-terminated string containing IP address in "dot" notation
unsigned long Return Value IP address as a four-byte integer value, or INADDR_NONE if the IP address is illegal

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.

Table 8.16. inet_ntoa Converts an IP address in binary form to string form
inet_ntoa
struct in_addr in An in_addr structure passed by value
char * Return Value Pointer to a string containing the IP address in dot notation

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 Name

There 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 address
 void 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 Function

Before 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:

  • IcmpCreateFile, which returns a handle through which other ICMP functions can be called

  • IcmpSendEcho, which sends the ping packet

  • IcmpCloseHandle, which closes the ICMP handle

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".

Table 8.17. IcmpSendEcho Sends a "ping" request
IcmpSendEcho
HANDLE IcmpHandle Handle returned from IcmpCreateFile.
IPAddr DestinationAddress IP destination address as an unsigned long.
LPVOID RequestData Pointer to data to be sent. The content of the data sent in a ping is irrelevant.
WORD RequestSize Number of bytes of data pointed to by RequestData.
PIP_OPTION_INFORMATION Pointer to an IP_OPTION_INFORMATION structure, or NULL if no RequestOptions extra options. This structure is documented in ipexport.h. It allows options like "time to live" to be set.
LPVOID ReplyBuffer Pointer to a buffer to receive a reply. The minimum size is 36, which is the size of one ICMP_ECHO_REPLY structure plus 8 bytes for an ICMP error. However, multiple ICMP_ECHO_REPLY structures could be returned, so this buffer should be larger (say, 1 KB).
DWORD ReplySize Size of the ReplyBuffer in bytes.
DWORD Timeout Number of milliseconds to wait before a timeout.
DWORD Return Value Zero on failure, or number of ICMP_ECHO_REPLY structures returned on success. GetLastError should be called for further error information. GetLastError returns "5" if the server with the given IP address could not be found.

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 Application

The 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:

Step 1.

Ensure that you have a network or dialup connection operating between the Windows CE device and desktop computer.

Step 2.

Run the SockServer.exe application on the Windows CE device.

Step 3.

Run the SockClient.exe application on the Windows NT/98/2000 desktop PC.

Step 4.

SockClient.exe will prompt for the IP address of the Windows CE machine. This can be obtained through the code shown in Listing 8.7.

Step 5.

SockClient will then prompt for you to enter lines of text. Each line will be sent to the socket server running on the Windows CE device and will be displayed in the application's client area (which is a disabled edit box).

Step 6.

SockClient (on the desktop PC) will terminate the connection when the text "<END>" is entered. The connection is also terminated when SockServer on the Windows CE device is closed.

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 Application

The client application, which runs on the desktop PC, does the following:

  1. Initializes the Winsock library.

  2. Creates a socket using the socket function.

  3. Connects to the server (listening) socket on the Windows CE device using the connect function.

  4. Accepts lines of input from the user and sends them to the socket server application using the send function.

  5. Receives back from the server a character count using the recv function. This is used to confirm that the correct number of characters were received.

  6. Terminates the connection when the user types "<END>" using the closesocket function.

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.

Table 8.18. socket Creates a socket
socket
int af Address family, must be AF_INET
int type Type of socket, either SOCK_STREAM for a stream (connection-based) socket, or SOCK_DGRAM for a datagram
int protocol Protocol to use, 0 for IP
SOCKET Return Value Valid SOCKET descriptor, or INVALID_SOCKET on error

A SOCKADDR_IN structure must be initialized with the following values for passing to the connect function:

  • The IP address to connect to in binary form. In Listing 8.9 the IP address in "dot" notation is converted to binary form using the inet_addr function.

  • The port number. The function htons is used to convert the byte order of the port number from host to network form. Byte ordering is described in the next section "Integer Byte Ordering."

  • The address family, which can only ever be AF_INET.

The function connect (Table 8.19) will attempt to make a connection for a socket with the server specified in the SOCKADDR_IN structure.

Table 8.19. connect Connects a socket to a server
connect
SOCKET s Socket descriptor returned from the socket function
const struct sockaddr FAR* name Pointer to a SOCKADDR_IN structure containing details about the server to connect to
int namelen Length of the structure pointed to by "name"
int Return Value Zero on success, otherwise SOCKET_ERROR

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 data
 int 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.

Table 8.20. send Sends data to a connected socket
send
SOCKET s Socket descriptor returned from the socket function
const char FAR * buf Pointer to data to be sent
int len Number of bytes to be sent
int flags Flags, 0 for default values
int Return Value Returns number of bytes sent, or SOCKET_ERROR if an error is detected

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).

Table 8.21. recv Receives data from a connected socket
recv
SOCKET Socket descriptor returned from the socket function
char FAR* buf Pointer to a buffer into which received data will be placed
int len The size of "buf" in bytes
int flags Flags, 0 for default values
int Return Value Number of bytes read, or SOCKET_ERROR for an error, or 0 if the socket has been closed

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 Ordering

Socket 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.

Table 8.22. Functions used for converting the byte order in integers
Function Data Type Conversion
htons short integer From host (Intel) to network order
nstoh short integer From network to host order
htonl long integer From host to network order
ntohl long integer From network to host order

The Socket Server Application

The 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 socket
 SOCKET 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.

Table 8.23. listen Sets socket as a listening socket
listen
SOCKET s Socket to set as a listener.
int backlog Number of requests to connect to the server to queue. This is limited to two in Windows CE.
int Return Value Zero for success, SOCKET_ERROR for an error.

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).

Table 8.24. accept Waits for a client socket to connect
accept
SOCKET s Descriptor of the listening socket
struct sockaddr *addr Pointer to SOCKADDR_IN structure in which information such as the IP address of the client socket is placed
int *addrlen Length of the structure pointed to by addr
SOCKET Return Address Socket descriptor which can be used to communicate with the client

Table 8.25. shutdown Closes socket communications
shutdown
SOCKET s Socket to shutdown
int how

How to shutdown:

SD_RECEIVE Subsequent calls to receive from a socket will fail

SD_SEND Subsequent calls to send to a socket will fail

SD_BOTH Combines previous two flags

int Return Value Zero for no error, otherwise SOCKET_ERROR

Lingering and Timeouts

In 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 Communications

Nearly 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 Devices

The 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 Port

The 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)); 

< BACK  NEXT >


Windows CE 3. 0 Application Programming
Windows CE 3.0: Application Programming (Prentice Hall Series on Microsoft Technologies)
ISBN: 0130255920
EAN: 2147483647
Year: 2002
Pages: 181

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