Using select() to Avoid Multithreading

[ LiB ]

I have one more topic to cover in this chapter: using the select() function to avoid using multithreading in your programs. As you will see in Chapter 3, multithreading can sometimes be a pain in the butt; and you should consider yourself lucky that there is a way around it.

The select() function essentially checks a set of sockets, to see if any of them has activity. This helps greatly, because you do not need to waste time checking every socket to see if it has activity (like the non-blocking method), or creating a thread for every socket, which consumes memory.

The select() can handle a number of functions in the Sockets API block including the accept , send , and recv functions as well as the lesser-used DNS functions gethostbyname and gethostbyaddr . Unfortunately, you can't prevent the DNS functions from blocking, unless you specifically use multithreading, since DNS functions don't actually have anything to do with sockets. The connect function also blocks, but there is no way to prevent it from lagging up your program unless you use multithreading.

Here is the select() function definition:

 int select( int numfds,             fd_set *readfds,               fd_set *writefds,             fd_set *exceptfds,             struct timeval *timeout); 

The first parameter is the number of the highest possible socket descriptor. Winsock just ignores this parameter, but UNIX doesn't. You can handle this in two ways. The first method is to keep track of your sockets and which one has the highest value. As you can imagine, this method is cumbersome; it's easier to just pass in the maximum value of an int , which is logically the highest value a socket descriptor can have. For a 32-bit system, you can pretty much assume that this is below the hexadecimal value 0x7FFFFFFF , which is the highest value for an int . Whether this changes on 64-bit systems remains to be seen.


You shouldn't really pass in 0x7FFFFFFF to the function, because that wastes a lot of time on UNIX-based systems. Since this is just a simple demo, I'm not terribly concerned about it, but when I get to the SocketLib in Chapters 4 and 5 , you'll see me implement a better method.

The next three parameters of the select() function are all fd_set s, which are structures that keep track of a set of sockets. ( fd means file descriptor , because in UNIX, sockets are files.)

You can use four helpful functions that relate to fd_set s: FD_ZERO , FD_SET , FD_CLR , and FD_ISSET . They clear an entire set, put a descriptor in a set, remove a descriptor from a set, and check to see if a descriptor is in a set, respectively. This example uses the four functions. The example assumes that sock is already defined elsewhere:

 struct fd_set set;     // declare the set FD_ZERO( &set );       // clear everything from the set FD_SET( sock, &set );  // add sock to the set FD_CLR( sock, &set );  // remove the socket from the set bool b = FD_ISSET( sock, &set );     // this will return false 

Pretty simple, isn't it?

Now, whenever you want to use the select() function, you need to fill out a set containing all of the sockets you wish to test using those functions. However, the function accepts three sets of sockets, so which set does what?

Check the first set of sockets, readfds, to see if you can read from them. For listening sockets, you can use this set of sockets to check if there are any incoming connections. For data sockets, this set of sockets tells you if any data has been received.

Check the second set of sockets, writefds , to be sure you can write to them. You can use this set of sockets to check if you can send data to the socket. (Sending data can block the system if you are trying to send too much at once.)

Check the final set of sockets, exceptfds , for errors. This set of sockets isn't used as often as the other two sets, because if an error occurs during the other operations, they return an error immediately anyway.

The final parameter to the function is a pointer to a timeval structure:

 struct timeval {     int tv_sec;     // seconds     int tv_usec;    // microseconds }; 

This structure essentially holds a number of seconds and microseconds, which the select function is supposed to wait for. If you set both values to 0, the function essentially returns immediately. If you pass NULL , the function waits forever, essentially blocking until something happens.


Even though the timeval structure has a microsecond value, it's no where near as accurate as that. You may find yourself waiting several thousand microseconds for the function to return, even if you specified 1!

Instead of always creating a new structure whenever I call select , I like to create a global 0-time timeval structure and use that to pass into the function.

So, finally, take a look at the function in action ( assuming lsock and dsock are listening/data sockets that have been created previously):

 struct fd_set set; FD_SET( lsock, &set ); FD_SET( dsock, &set ); struct timeval zerotime; zerotime.tv_usec = 0; zerotime.tv_sec = 0; int err = select( 0x7FFFFFFF, &set, NULL, NULL, &zerotime ); 

The function has a few different return values. If the function times out without finding sockets with activity, it returns 0. If there is an error, the function returns -1. If there are sockets with activity, the function returns the number of sockets that have activity.

So how do you know what sockets had activity? The function physically changes the three sets that were passed into it and removes every socket from the set that had no activity. Therefore, you need to go through each set, find out which sockets are still in them, and take care of them.

In the previous code example, the set variable contains lsock if there are any incoming connections, and dsock if the socket receives any data.

Easy, isn't it?

[ LiB ]

MUD Game Programming
MUD Game Programming (Premier Press Game Development)
ISBN: 1592000908
EAN: 2147483647
Year: 2003
Pages: 147
Authors: Ron Penton

Similar book on Amazon © 2008-2017.
If you may any questions please contact us: