I/O Multiplexing


The following is cited from APUE
/******************************************
One way to handle I/O multiplexing is to divide the process in two pieces(using fork), with each half handling one direction of data. If we use two processes, we can let each process do a blocking read. 
Second way is to use polling. We could use nonblocking I/O in a single process by setting both descriptors nonblocking and issuing a read on the first descriptor. If data is present, we read it and process it. If there is no data to read, the call returns immediately. We then do the same thing with the second descriptor. After this, we wait for some amount of time and then try to read from the first descriptor again. The problem is that it wastes CPU time. Most of the time, there won't be data to read, so we waste time performing the read system calls. Although is works on any system that supports nonblocking I/O, polling should be avoided on a multitasking system.
A better technique is to use I/O multiplexing. To do this, we build a list of the descriptors that doesn't return until one of descriptors is ready for I/O. On return from the function, we are told which descriptors are ready for I/O.
Three functions  - poll, pselect, select - allow us to perform I/O multiplexing.
The arguments we pass to select tell the kernel:
1, which descriptors we're interested in.
, what conditions we're interested in for each descriptor.
3. how long we want to wait.
On the return from select, the kernel tells us:
1. the total count of the number of descriptors that are ready.
2, which descriptors are ready for each of the three conditions.
int select(int maxfdp1, fd_set *restrict readfds, fd-set *restrict writefds, fd_set *restrict exceptfds, struct timeval *restrict tvptr)
On return from select, we can test whether a given bit int the set is still on using FD_ISSET:
if (FD_ISSET(fd, &rset)
{
...
}
If all three pointers are NULL, then we have a higher precision timer than provided by sleep.
The first argument to select, maxfdp1, stands for "maximum file descriptor plus 1".
It is important to realize that whether a descriptor is blocking or not doesn't affect whether select blocks. That is, if we have a nonblocking descriptor that we want tot read from and we call select with a timeout value of 5 seconds, select will block for up to 5 seconds. Similarly, if we specify an infinite timeout, select blocks until data is ready fro the descriptor or until a signal is caught.
/*************************************************************/
The following code is implemented by loop and I/O multiplexing.
The loop version:
str_len = recv(sockconn, recv_buffer, 1024, 0);

	int error;

	error = WSAGetLastError();

	if((error != 0 && error != WSAEWOULDBLOCK) || len == 0)
	{
		printf("
Lost connection!
"); for (i = 1; i <= MAX_FLOOR; i++) { if (floors_count[i] != 0) { if(TerminateProcess(pi[i].hProcess, 0)) printf(" ID :%d
", pi[i].dwProcessId); else printf(" !
"); } } goto restart; }

The I/O multiplexing version:
while (fd_count)
	{
		FD_ZERO(&fdread);
		for (i = 1; i <= MAX_FLOOR; i++)
		{
			if (floors_count[i] != 0)
			{
				FD_SET(s[i], &fdread);
			}
		}
		//printf("I'm here!
"); ret = select(MAX_FLOOR + 1, &fdread, NULL, NULL, NULL); if (ret == 0) { continue; } for (i = 1; i <= MAX_FLOOR; i++) { if (FD_ISSET(s[i], &fdread)) { // A read event happened on s[i] ret = recv(s[i], client_buffer[i], 1024, 0); if (ret == 0 || (ret == SOCKET_ERROR && WSAGetLastError() == WSAECONNRESET)) { // Client socket closed printf("Client socket %d closed.
", 10000 + i); closesocket(s[i]); floors_count[i] = 0; fd_count--; if (TerminateProcess(pi[i].hProcess, 0)) printf(" ID :%d
", pi[i].dwProcessId); } else { printf("%s
", client_buffer[i]); send(sockconn, client_buffer[i], ret, 0); } } } }

we calculate the CPU usage by the two implementation, the loop version is 54% while the I/O multiplexing version is only 5%. So the I/O multiplexing is highly efficient.