Monday, December 10, 2007

Socket Options

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

Table 1.3. Socket Options 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.

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

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

Blocking and Nonblocking Sockets
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.

[ Team LiB ]

Support Functions
Several extremely useful Winsock support functions can help you get information about peers, make DNS queries, and convert between various data formats that are supported with Winsock.

Connected Peer Functions
To get address information for a connection that a socket is currently connected with, you can use the getpeername() function:

int getpeername (SOCKET s, struct sockaddr *name, int *namelen);

The first parameter is the socket for which you want information, the name parameter is a pointer to a SOCKADDR structure (which will be a SOCKADDR_IN for TCP/IP), and namelen is a pointer to its length.

To retrieve address information for the local interface of a connected socket, the following function is defined:

int getsockname (SOCKET s, struct sockaddr *name, int *namelen);

The getsockname() function takes the same parameters as the getpeername() function, except that the name parameter will return local address information, rather than the remote connection.

Host Names
To get your device's host name, you can call the aptly named gethostname() function:

int gethostname (char *name, int namelen);

The name parameter is a pointer to a character buffer that will receive the name, and namelen is the length of the name buffer specified.

To set the device's host name, use the sethostname() function, which is defined as follows:

int sethostname(char *pName, int cName);

This function takes two parameters. The first is a pointer to a buffer that contains the new host name, and the second is the cName parameter, which specifies the buffer's length.

Name Resolution
The inet_addr function is used to convert an IP "dot" address (e.g., 192.158.0.0) into an unsigned long value:

unsigned long inet_addr (const char *cp);

It takes a single parameter, a pointer to a character buffer cp containing the IP "dot" address string.

If you want to perform the inverse function, use inet_ntoa(), which will convert an address to a string:

char * inet_ntoa (struct in_addr in);

Here, you pass in an address structure; typically, a SOCKADDR_IN that defines the address you want to convert.

The next set of support functions we will look at deals with host name resolution. All Winsock functions dealing with host addresses and names use a HOSTENT data structure. This structure contains all the available information about an individual host. It is defined as follows:

struct hostent {
char *h_name;
char **h_aliases;
short h_addrtype;
short h_length;
char **h_addr_list;
};

The h_name field contains the official name of the host. The h_aliases field points to an array of alternative host name string pointers that are terminated by a NULL pointer value. The h_addrtype field is the type of address being returned, and will be either AF_INET or AF_IRDA. The h_length field is the length in bytes of each address in the h_addr_list array. The h_addr_list array is a null-terminated array of network addresses in network byte order.

To get host information for a network device by name, use the following:

struct hostent *gethostbyname(const char *name);

The only parameter needed is the name of the host for which you want information.

Use the following to get information about a host by address:

struct hostent *gethostbyaddr(const char *addr, int len, int type);

Here, you need to pass in a bit more information about the host. The first parameter is the address structure, with information about the host you want to query. This will typically be the value that is returned from calling the inet_addr() function. The len parameter is the length in bytes of the address, and the type parameter is the type of address—either AF_INET or AF_IDRA.

Byte Order
The final set of support functions deals with converting values from host byte order to network byte order.

To convert a long value to/from host byte order to network byte order, use the following functions:

u_long htonl (u_long hostlong);
u_long ntohl (u_long netlong);

Convert a short value to/from host byte order to network byte order as follows:

u_short htons (u_short hostshort);
u_short ntohs (u_short netshort);


[ Team LiB ]

No comments: