You can set several socket options to further control the behavior of an individual socket. Some of these attributes are used to query information about a socket, such as its current state, while other options are used to change how a socket interacts with the network. For example, if you wanted to send a UDP datagram that is broadcast to the entire network, you would enable the SO_BROADCAST socket option on that particular socket.
Socket options can affect either the socket itself or the protocol that it is using. For example, the option TCP_NODELAY is designed to toggle the Nagle algorithm when using the TCP protocol, and will produce an error on a socket that is currently UDP-based. Note that options that begin with SO_ are generic items for the socket layer, whereas those that begin with TCP_ or IP_ are for the underlying protocol. In addition, you need to verify that some of the generic socket options, such as SO_BROADCAST, are supported by the underlying socket protocol for your socket. In other words, if you try to create a broadcast socket on one that was created to use the TCP protocol, it will also fail.
The function that you use to get socket options is getsockopt() and is defined as follows:
int getsockopt (SOCKET s, int level, int optname, char *optval, int *optlen);
To set socket options, you use the setsockopt() function:
int setsockopt (SOCKET s, int level, int optname, const char *optval, int optlen);
Both take almost the exact same parameters. The s parameter is the socket for which you want to get or set an option. The next parameter, level, defines what level in the OSI model the option will affect. On Pocket PC, this can be SOL_SOCKET, IPPROTO_TCP, or IPPROTO_IP. The available socket options described in Table 1.3 will help you determine the proper level for the option you want to manipulate. The optname field identifies the option you want to get or set. The last two parameters work a bit differently depending on whether you are getting or setting a socket option value. If you are using getsockopt(), the optval parameter points to the value of the option, and optlen is a pointer to the size of the buffer to which optval points. When setting values using setsockopt(), optval points to the new value you want to set, and optlen is the size of the optval buffer.
Level | Option Name | Type | Get/Set | Description |
---|---|---|---|---|
SOL_SOCKET | SO_ACCEPTCONN | BOOL | Get | Is the socket listening? |
SO_BROADCAST | BOOL | Both | Allows broadcast messages on the socket | |
SO_DONTLINGER | BOOL | Both | Enables or disables immediate return from closesocket() | |
SO_KEEPALIVE | BOOL | Both | Sends keep-alive messages | |
SO_LINGER | struct linger | Both | Enables or disables immediate return from closesocket() | |
SO_OOBINLINE | BOOL | Get | Out-of-band data is in the normal data stream | |
SO_REUSEADDR | BOOL | Both | Enables or disables the reuse of a bound socket | |
SO_SECURE | DWORD | Both | Enables or disables SSL encryption on the socket | |
SO_SNDBUF | int | Both | Size of the buffer allocated for sending data | |
SO_TYPE | int | Get | Socket type | |
IPPROTO_TCP | TCP_NODELAY | BOOL | Both | Turns on/off the Nagle algorithm |
IPPROTO_IP | IP_MULTICAST_TTL | int | Both | Time to live for a multicast packet |
IP_MULTICAST_IF | unsigned long | Both | Address of the outgoing multicast interface | |
IP_ADD_MEMBERSHIP | struct ip_mreg | Set | Adds socket to a multicast group | |
IP_DROP_MEMBERSHIP | struct | Set | Removes socket from a multicast group |
The SO_LINGER option is somewhat related to the SO_DONTLINGER option in that when it is disabled, SO_DONTLINGER is enabled. Both of the "linger" options determine how the socket should react when the closesocket() function call is made and there is additional data in the TCP send buffer. SO_LINGER uses a LINGER structure to set its state, which is defined as follows:
struct linger { u_short l_onoff; u_short l_linger; }
The l_onoff parameter determines if linger is currently on or off. When SO_DONTLINGER is set to TRUE, l_onoff is 0 (i.e., don't linger). When l_onoff is enabled and set to 1, the l_linger field specifies the time to wait, in seconds, before the socket is closed.
Use the SO_BROADCAST socket option with care. It is generally considered bad practice to flood a network with data, especially when using a device such as a Pocket PC for which bandwidth and network resources are crucial. It does have some practical uses, such as discovering devices on a subnet, or sending information to a wide group of devices at once, so there can be some benefit to using broadcast sockets.
Broadcasting is only available for sockets that are on UDP, as TCP sockets are not capable of transmitting broadcast messages. It is important to ensure that your broadcast packets are small enough that datagram fragmentation doesn't occur, which means you should not exceed 512 bytes for your message. When setting up your SOCKADDR_IN structure, both the client using recvfrom() and the sender using sendto() should configure their functions to send/receive from the same port, and they can use the address INADDR_BROADCAST to designate the target address as a broadcast message.
Pocket PC devices also support the capability to use the device's built-in security to create secure sockets using SSL 2.0 and 3.0. When using the SO_SOSECURE socket option, set the DWORD value you are passing in for the optval parameter to SO_SEC_SLL.
When a socket is initially created, it is in blocking mode, which means that your program will not return from a blocking function until it has completed its operation. For example, when using a TCP socket, if you call the accept() function, your program will appear to "hang" until an incoming connection has arrived. Another example would be the connect() function, which will not return until either it has connected to its destination or an error has occurred. This can be rather unnerving on a device such as Pocket PC, as it will appear as if the device has "locked up" until it returns from the blocking function.
On a Windows-based system, this is solved by using the asynchronous Winsock functions, which provide Windows notification messages when a socket event has occurred. Unfortunately, these are not available on Pocket PC devices; instead, you need to put the sockets into nonblocking mode if you want them to return immediately. When a socket is set to nonblocking mode, any call to a blocking function will immediately return to you with a SOCKET_ERROR result. Calling WSAGetLastError() will return the error code WSAWOULDBLOCK, which means that you will need to check the socket again at some point to see whether the operation has completed. This can be done by using the select() function, rather than repeatedly calling into the nonblocking function to see whether it has completed.
To change the blocking mode of a socket, you can call the following function:
int ioctlsocket (SOCKET s, long cmd, u_long *argp);
The first parameter is the socket for which you want to change the mode. The cmd parameter is used to specify what operation you want to perform on the socket, and can be either FIONBIO or FIONREAD. Setting cmd to FIONBIO will set the socket's blocking mode to nonblocking if argp is set to 0; otherwise, setting argp to a positive value will put the socket into blocking mode. The FIONREAD command will return the number of bytes that are currently in the receive queue for the socket to the pointer passed in for the argp parameter.
For example, you could use the following if you wanted to change a socket from blocking to nonblocking:
// Create a connection-oriented socket SOCKET s = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); // Check to see if we have a valid socket if(s == INVALID_SOCKET) { int iSocketError = WSAGetLastError(); return FALSE; } // Make it non-blocking DWORD dwNonBlocking = 0; if(ioctlsocket(s, FIONBIO, &dwNonBlocking) != 0) { int iSocketError = WSAGetLastError(); return FALSE; }
Once you have placed the socket into nonblocking mode, you can use the select() function to see if any data is available on the socket. It is extremely important that you use this method of notification on your nonblocking sockets to determine their completion, as continuously polling the socket is a major drain of device resources.
The select() function is defined as follows:
int select (int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, const struct timeval *timeout);
The first parameter, nfds, is ignored. The next three parameters, readfds, writefds, and exceptfds, are pointers to a collection of socket sets (FD_SET), which are used for determining whether it can be read or written to, and whether there is out-of-band data. An FD_SET collection is an internal structure that is used to maintain a collection of sockets that you want select to monitor. Winsock provides several macros for adding and removing sockets from an FD_SET collection:
FD_ZERO(*set)? Initializes the set to NULL
FD_CLR(s, *set)? Removes the socket s from the set
FD_ISSET(s, *set)? Determines whether socket s is a part of the set, and returns TRUE if so
FD_SET(s, *set)? Adds socket s to the set
For example, if you want to monitor a socket to determine when it is safe to write to it, you can simply add your socket to the writefds set by calling the FD_SET macro.
Finally, the timeout parameter of the select() function is a pointer to a timeval structure (defined in winsock.h):
struct timeval { long tv_sec; long tv_usec; };
The tv_sec field specifies how long the select() function will wait in seconds, and the tv_usec field indicates the number of milliseconds. If these two fields are set to 0, select() will return immediately. If the pointer timeout is NULL, it will wait indefinitely; otherwise, select() will wait the specified number of seconds and milliseconds defined in this structure.
Select will return 0 if a timeout has occurred, or a SOCKET_ERROR if there is an error. Otherwise, the select function won't return until a specific socket event has happened, and the return value will be the number of sockets on which the event has occurred.
Sockets in the readfds set will be identified under the following conditions:
There is data in the receive queue.
The connection has been lost, closed, or reset.
The socket is in listen() mode and a client is attempting to connect. This will allow accept() to succeed.
Sockets in the writefds set will be identified under these circumstances:
There is data to be sent on the socket.
A connect() has been accepted by a server.
Finally, sockets that are included in the exceptfds set are identified under the following conditions:
A connect() has failed from the server.
Out-of-band data is available for reading.