Basic Sockets
Winsock is the name for the Windows Sockets API. Winsock is the API for the Windows CE TCP/IP networking stack and is used to access the IrDA and Bluetooth communication stacks. What's left out of the Windows CE implementation of Winsock is the ever-so-handy WSAAsyncSelect function, which enables (under other Windows systems) an application to be informed when a Winsock event has occurred. Actually, in the Winsock 1.1 implementation, many of the WSAxxx calls that provide asynchronous actions are missing from Windows CE. Instead, the Windows CE implementation is more like the original Berkeley socket API. Windows CE's developers decided not to support these functions to reduce the size of the Winsock implementation. These functions were handy but not required because Windows CE is multithreaded.The lack of asynchronous functions doesn't mean that you're left with calling socket functions that block on every call. You can put a socket in nonblocking mode so that any function that can't accomplish its task without waiting on an event will return with a return code indicating that the task isn't yet completed.
Initializing the Winsock DLL
Like other versions of Winsock, the Windows CE version should be initialized before you use it. You accomplish this by calling WSAStartup, which initializes the Winsock DLL. It's prototyped as
int WSAStartup (WORD wVersionRequested, LPWSADATA lpWSAData);
The first parameter is the version of Winsock you're requesting to open. For all current versions of Windows CE, you should indicate version 2.0. An easy way to do this is to use the MAKEWORD macro, as in MAKEWORD (2,0). The second parameter must point to a WSAData structure.
struct WSAData {
WORD wVersion;
WORD wHighVersion;
char szDescription[WSADESCRIPTION_LEN+1];
char szSystemStatus[WSASYSSTATUS_LEN+1];
unsigned short iMaxSockets;
unsigned short iMaxUdpDg;
char FAR * lpVendorInfo;
};
This structure is filled in by WSAStartup, providing information about the specific implementation of this version of Winsock. Currently the first two fields return either 0x0101, indicating support for version 1.1, or 0x0202, indicating that the system supports the Winsock 2.0 stack. The szDescription and szSystemStatus fields can be used by Winsock to return information about itself. In the current Windows CE version of Winsock, these fields aren't used. The iMaxSockets parameter suggests a maximum number of sockets that an application should be able to open. This number isn't a hard maximum but rather a suggested maximum. The iMaxUdpDg field indicates the maximum size of a datagram packet. A 0 indicates no maximum size for this version of Winsock. Finally, lpVendorInfo points to optional vendor-specific information.WSAStartup returns 0 if successful; otherwise, the return value is the error code for the function. Don't call WSAGetLastError in this situation because the failure of this function indicates that Winsock, which provides WSAGetLastError, wasn't initialized correctly.Windows CE also supports WSACleanup, which is traditionally called when an application has finished using the Winsock DLL. For Windows CE, this function performs no action but is provided for compatibility. Its prototype is
int WSACleanup ();
ASCII vs. Unicode
One issue that you'll have to be careful of is that almost all the string fields used in the socket structures are char fields, not Unicode. Because of this, you'll find yourself using the functions
int WideCharToMultiByte(UINT CodePage, DWORD dwFlags,
LPCWSTR lpWideCharStr, int cchWideChar,
LPSTR lpMultiByteStr, int cchMultiByte,
LPCSTR lpDefaultChar, LPBOOL lpUsedDefaultChar);
to convert Unicode strings to multibyte strings and
int MultiByteToWideChar (UINT CodePage, DWORD dwFlags,
LPCSTR lpMultiByteStr, int cchMultiByte,
LPWSTR lpWideCharStr, int cchWideChar);
to convert multibyte characters to Unicode. The functions refer to multibyte characters instead of ASCII because on double-byte coded systems, they convert double-byte characters to Unicode.
Stream Sockets
Like all socket implementations, Winsock under Windows CE supports both stream and datagram connections. In a stream connection, a socket is basically a data pipe. Once two points are connected, data is sent back and forth without the need for additional addressing. In a datagram connection, the socket is more like a mailslot, with discrete packets of data being sent to specific addresses. In describing the Winsock functions, I'm going to cover the process of creating a stream connection (sometimes called a connection-oriented connection) between a client application and a server application. I'll leave the explanation of the datagram connection to other, more network-specific, books.The life of a stream socket is fairly straightforward: it's created, bound, or connected to an address; read from or written to; and finally closed. A few extra steps along the way, however, complicate the story slightly. Sockets work in a client/server model. A client initiates a conversation with a known server. The server, on the other hand, waits around until a client requests data. When setting up a socket, you have to approach the process from either the client side or the server side. This decision determines which functions you call to configure a socket. Table 14-1 illustrates the process from both the client and the server side. For each step in the process, the corresponding Winsock function is shown.
Both the client and the server must first create a socket. After that, the process diverges. The server must attach or, to use the function name, bind, the socket to an address so that another computer or even a local process can connect to the socket. Once an address has been bound, the server configures the socket to listen for a connection from a client. The server then waits to accept a connection from a client. Finally, after all this, the server is ready to converse.The client's job is simpler: the client creates the socket, connects the socket to a remote address, and then sends and receives data. This procedure, of course, ignores the sometimes not-so-simple process of determining the address to connect to. I'll leave that problem for a few moments while I talk about the functions behind this process.
Creating a Socket
You create a socket with the function
SOCKET socket (int af, int type, int protocol);
The first parameter, af, specifies the addressing family for the socket. Windows CE supports three addressing formats: AF_INET, AF_IRDA, and AF_BT. You use the AF_BT constant when you're creating a socket for Bluetooth use, AF_IRDA for an IrDA socket, and AF_INET for TCP/IP communication. The type parameter specifies the type of socket being created. For a TCP/IP socket, this can be either SOCK_STREAM for a stream socket or SOCK_DGRAM for a datagram socket. For Bluetooth and IrDA sockets, the type parameter must be SOCK_STREAM. Windows CE doesn't currently expose a method to create a raw socket, which is a socket that allows you to interact with the IP layer of the TCP/IP protocol. Among other uses, raw sockets are used to send an echo request to other servers, in the process known as pinging. However, Windows CE does provide a method of sending an Internet Control Message Protocol (ICMP) echo request. The protocol parameter specifies the protocol used by the address family specified by the af parameter. For Bluetooth, this parameter should be set to BTHPROTO_RFCOMM. The function returns a handle to the newly created socket. If an error occurs, the socket returns INVALID_SOCKET. You can call WSAGetLastError to query the extended error code.
Server Side: Binding a Socket to an Address
For the server, the next step is to bind the socket to an address. You accomplish this with the function
int bind (SOCKET s, const struct sockaddr FAR *addr, int namelen);
The first parameter is the handle to the newly created socket. The second parameter is dependent on whether you're dealing with a TCP/IP socket, an IrDA socket, or a Bluetooth socket. For a standard TCP/IP socket, the structure pointed to by addr should be SOCKADDR_IN, which is defined as
struct sockaddr_in {
short sin_family;
unsigned short sin_port;
IN_ADDR sin_addr;
char sin_zero[8];
};
The first field, sin_family, must be set to AF_INET. The second field is the IP port, while the third field specifies the IP address. The last field is simply padding to fit the standard SOCKADDR structure. The last parameter of bind, namelen, should be set to the size of the SOCKADDR_IN structure.
When you're using IrSock, the address structure pointed to by sockaddr is SOCKADDR_IRDA, which is defined as
struct sockaddr_irda {
u_short irdaAddressFamily;
u_char irdaDeviceID[4];
char irdaServiceName[25];
};
The first field, irdaAddressFamily, should be set to AF_IRDA to identify the structure. The second field, irdaDeviceID, is a 4-byte array that defines the address for this IR socket. This can be set to 0 for an IrSock server. The last field should be set to a string to identify the server.You can also use a special predefined name in the irdaServiceName field to bypass the IrDA address resolution features. If you specify the name LSAP-SELxxx, where xxx is a value from 001 through 127, the socket will be bound directly to the LSAP (Logical Service Access Point) selector defined by the value. Applications should not, unless absolutely required, bind directly to a specific LSAP selector. Instead, by specifying a generic string, the IrDA address resolution code determines a free LSAP selector and uses it.For a Bluetooth socket, the address structure pointed to by sockaddr is SOCKADDR_BTH, which is defined as
typedef struct _SOCKADDR_BTH {
USHORT addressFamily;
bt_addr btAddr;
GUID serviceClassId;
ULONG port;
} SOCKADDR_BTH, *PSOCKADDR_BTH;
The addressFamily field should be set to AF_BT. The bt_addr structure is a 64-bit field that contains the device's 48-bit Bluetooth address. This field isn't used in the bind call. The serviceClassId field is used in the connect function to tell the client which server service to connect to. The port field can be set to RFCOMM channel 1 through 31 or set to 0 to have the system choose a free channel.
Listening for a Connection
Once a socket has been bound to an address, the server places the socket in listen mode so that it will accept incoming communication attempts. You place the socket in listen mode by using the aptly named function
int listen (SOCKET s, int backlog);
The two parameters are the handle to the socket and the size of the queue that you're creating to hold the pending connection attempts. This size value can be set to SOMAXCONN to set the queue to the maximum supported by the socket implementation.
Accepting a Connection
When a server is ready to accept a connection to a socket in listen mode, it calls this function:
SOCKET accept (SOCKET s, struct sockaddr FAR *addr,
int FAR *addrlen);
The first parameter is the socket that has already been placed in listen mode. The next parameter should point to a buffer that receives the address of the client socket that has initiated a connection. The format of this address is dependent on the protocol used by the socket. For Windows CE, this is a SOCKADDR_IN, a SOCKADDR_IRDA, or a SOCKADDR_BTH structure. The final parameter is a pointer to a variable that contains the size of the buffer. This variable is updated with the size of the structure returned in the address buffer when the function returns.The accept function returns the handle to a new socket that's used to communicate with the client. The socket that was originally created by the call to socket will remain in listen mode and can potentially accept other connections. If accept detects an error, it returns INVALID_SOCKET. In this case, you can call WSAGetLastError to get the error code.The accept function is the first function I've talked about so far that blocks. That is, it won't return until a remote client requests a connection. You can set the socket in nonblocking mode so that, if no request for connection is queued, accept will return INVALID_SOCKET with the extended error code WSAEWOULDBLOCK. I'll talk about blocking vs. nonblocking sockets shortly.
Client Side: Connecting a Socket to a Server
On the client side, things are different. Instead of calling the bind and accept functions, the client simply connects to a known server. I said simply, but as with most things, we must note a few complications. The primary one is addressing—knowing the address of the server you want to connect to. I'll put that topic aside for a moment and assume the client knows the address of the server.To connect a newly created socket to a server, the client uses the function
int connect (SOCKET s, const struct sockaddr FAR *name,
int namelen);
The first parameter is the socket handle that the client created with a call to socket. The other two parameters are the address and address length values we've seen in the bind and accept functions.
If connect is successful, it returns 0. Otherwise, it returns SOCKET_ERROR, and you should call WSAGetLastError to get the reason for the failure.
Sending and Receiving Data
At this point, both the server and the client have socket handles they can use to communicate with one another. The client uses the socket originally created with the call to socket, while the server uses the socket handle returned by the accept function.All that remains is data transfer. You write data to a socket this way:
int send (SOCKET s, const char FAR *buf, int len, int flags);
The first parameter is the socket handle to send the data. You specify the data you want to send in the buffer pointed to by the buf parameter, while the length of that data is specified in len. The flags parameter must be 0.You receive data by using the function
int recv (SOCKET s, char FAR *buf, int len, int flags);
The first parameter is the socket handle. The second parameter points to the buffer that receives the data, while the third parameter should be set to the size of the buffer. The flags parameter can be 0, or it can be MSG_PEEK if you want to have the current data copied into the receive buffer but not removed from the input queue or if this is a TCP/IP socket (MSG_OOB) for receiving any out-of-band data that has been sent.Two other functions can send and receive data; they are the following:
int sendto (SOCKET s, const char FAR *buf, int len, int flags,
const struct sockaddr FAR *to, int token);
and
int recvfrom (SOCKET s, char FAR *buf, int len, int flags,
struct sockaddr FAR *from, int FAR *fromlen);
These functions enable you to direct individual packets of data using the address parameters provided in the functions. They're used for connectionless sockets, but I mention them now for completeness. When used with connection-oriented sockets such as those I've just described, the addresses in sendto and recvfrom are ignored and the functions act like their simpler counterparts, send and recv.
Closing a Socket
When you have finished using the sockets, call this function:
int shutdown (SOCKET s, int how);
The shutdown function takes the handle to the socket and a flag indicating the part of the connection you want to shut down. The how parameter can be SD_RECEIVE to prevent any further recv calls from being processed, SD_SEND to prevent any further send calls from being processed, or SD_BOTH to prevent either send or recv calls from being processed. The shutdown function affects the higher-level functions send and recv but doesn't prevent data previously queued from being processed. Once you have shut down a socket, it can't be used again. It should be closed and a new socket created to restart a session.Once a connection has been shut down, you should close the socket with a call to this function:
int closesocket (SOCKET s);
The action of closesocket depends on how the socket is configured. If you've properly shut down the socket with a call to shutdown, no more events will be pending and closesocket should return without blocking. If the socket has been configured into linger mode and configured with a timeout value, closesocket will block until any data in the send queue has been sent or the timeout expires.