I. Tutorial Purpose
Understand the principles of I/O multiplexing technology.
Learn to write basic Single-thread concurrent server programs and customer programs.
Ii. Experimental Platform
Ubuntu-8.04 Operating System
Iii. experiment content
I/O multiplexing technology is used to implement a single-thread concurrent server and complete the function of using one thread to process concurrent client requests.
Iv. Experiment Principles
In addition to using multi-process and multi-thread methods to implement concurrent servers, I/O multiplexing can also be used. With this technology, the system kernel caches I/O data. When an I/O is ready, the system notifies the application that the I/O is readable or writable, in this way, the application can immediately complete the corresponding I/O operations without waiting for the system to complete the corresponding I/O operations, so that the application does not have to wait for the I/O operation.
Compared with multi-process and multi-thread technologies, the biggest advantage of I/O multiplexing technology is that the system overhead is small, and the system does not need to create processes/threads or maintain these processes/threads, this greatly reduces the system overhead.
Typical application of I/O multiplexing is as follows:
(1) When the customer processes multiple descriptive words (generally interactive input and Network Interface), I/O must be reused.
(2) When a customer processes multiple sets of interfaces at the same time, this situation is possible, but rarely occurs.
(3) If a TCP server needs to process both the listener interface and the connected interface, I/O multiplexing is also required.
(4) If a server needs to process TCP and UDP, I/O multiplexing is generally required.
(5) If a server needs to process multiple services or protocols, I/O reuse is generally required.
I/O reuse call the select () or poll () function, and block the function, waiting for the datagram set interface to be readable; When select () returns the readable condition, call recvfrom () copy the datagram to the application buffer, as shown in Figure 8.1.
Figure 8.1 I/O multiplexing process
Select () function:
The select () function allows the process to instruct the kernel to wait for any one of multiple events and wake up the process only when one or more events occur or after a specified time. The form of this function is as follows:
-------------------------------------------------------------------
# Include <sys/select. h>
# Include <sys/time. h>
Intselect (intmaxfdp1, fd_set * readset, fd_set * writeset, fd_set * execepset, conststructtimeval * timeout );
Return Value: the number of prepared descriptive words in all the descriptive sets. If the time is reached, 0 is returned. If an error occurs,-1 is returned.
-------------------------------------------------------------------
In the preceding parameter, we can see a timeval structure, which provides the number of seconds and milliseconds, as follows:
Structtimeval
{
Long TV _sec;/second */
Long TV _usec;/* microsecond */
}
The timeval structure has the following three possibilities:
(1) always wait: returns only when a descriptive word is ready for I/O. Therefore, you can set the timeout parameter to a null pointer.
(2) Wait for a fixed time: returns the result when I/O is prepared for a descriptive word, but does not exceed the number of seconds and microseconds IN THE timeval structure specified by the timeout parameter.
(3) Don't wait at all: Check the description and return immediately. This is called polling ).
In the first two cases, if a process captures a signal and returns it from the signal processing program, the wait is generally interrupted.
The readset, writeset, and execeptset Parameters specify the description words that allow the kernel to test read, write, and exception conditions. If we are not interested in them, we can set them as null pointers.
The select () function uses a describe set to specify multiple describe words for the parameter readset (writeset or effectset). The describe word set is an array of integers, each of which corresponds to one describe word, for example, if it is a 32-bit integer, the first element of the array corresponds to 0 ~ 31. The second element corresponds to 32 ~ 63 description characters.
The readset, writeset, and effectset parameters are value-result parameters. When we call select, we specify the descriptive words we care about, and the returned results indicate that the descriptive words are ready.
The maxfdp1 parameter specifies the number of descriptive words to be tested. It is the maximum descriptive word to be tested plus 1. To test the 1, 2, and 4 Descriptors, you must test the 5 descriptors, 0, 1, 2, 3, and 4.
The basic steps for implementing I/O multiplexing using the select () function are as follows:
(1) Clear the descriptor set;
(2) establish a connection between the descriptor to be monitored and the descriptor set;
(3) Call the select () function;
(4) Check all descriptors to be monitored and use the FD_ISSET macro to determine whether the descriptors are ready;
(5) perform I/O operations on the prepared descriptors.
5. Experiment steps
1. log on to the ubuntu operating system and create a new file named io. c.
2. Write and save the code in io. c as a server program. The client code is the same as mproc_client.c, blog address: http://blog.csdn.net/yueguanghaidao/article/details/7060350
3. Open a terminal and run the command to enter the directory where io. c and mproc_client.c are located.
4. Execute Command g ++-oioio. c to generate executable file io.
5. Run Command./io to run the server.
6. Open 2nd "terminals" and execute the command to enter the directory where io. c and mproc_client.c are located.
7. Run the command./mproc_client127.0.0.1 to simulate CUSTOMER 1.
8. Open 3rd "terminals" and execute the command to enter the directory where io. c and mproc_client.c are located.
9. Run the command./mproc_client127.0.0.1 to simulate Customer 2.
10. The program running result is as follows:
Server:
Customer 1:
Customer 2:
11. Press Ctrl + D on the client to close the client connection.
12. carefully analyze the source code and write the single-thread concurrent server program and client program.
6. Reference Program (io. c)
# Include <stdio. h>
# Include <stdlib. h>
# Include <string. h>
# Include <unistd. h>
# Include <sys/types. h>
# Include <sys/socket. h>
# Include <netinet/in. h>
# Include <arpa/inet. h>
# Include <sys/time. h>
# Define PORT 1234
# Define BACKLOG 5
# Define maxdatasalize 1000
Typedef struct {
Int fd;
Char * name;
Struct sockaddr_in addr;
Char * data;
} CLIENT;
Void process_cli (CLIENT * client, char * recvbuf, int len );
Void savedata (char * recvbuf, int len, char * data );
Main ()
{
Int I, maxi, maxfd, sockfd;
Int nready;
Ssize_t n;
Fd_set rset, allset;
Int listenfd, connectfd;
Struct sockaddr_in server;
CLIENT client [FD_SETSIZE];
Char recvbuf [MAXDATASIZE];
Socklen_t sin_size;
If (listenfd = socket (AF_INET, SOCK_STREAM, 0) =-1 ){
Perror ("Creatingsocket failed .");
Exit (1 );
}
Int opt = SO_REUSEADDR;
Setsockopt (listenfd, SOL_SOCKET, SO_REUSEADDR, & opt, sizeof (opt ));
Bzero (& server, sizeof (server ));
Server. sin_family = AF_INET;
Server. sin_port = htons (PORT );
Server. sin_addr.s_addr = htonl (INADDR_ANY );
If (bind (listenfd, (struct sockaddr *) & server, sizeof (struct sockaddr) =-1 ){
Perror ("Bind () error .");
Exit (1 );
}
If (listen (listenfd, BACKLOG) =-1 ){
Perror ("listen () error \ n ");
Exit (1 );
}
Sin_size = sizeof (struct sockaddr_in );
Maxfd = listenfd;
Maxi =-1;
For (I = 0; I <FD_SETSIZE; I ++ ){
Client [I]. fd =-1;
}
FD_ZERO (& allset );
FD_SET (listenfd, & allset );
While (1)
{
Struct sockaddr_in addr;
Rset = allset;
Nready = select (maxfd + 1, & rset, NULL );
If (FD_ISSET (listenfd, & rset )){
If (connectfd = accept (listenfd, (struct sockaddr *) & addr, & sin_size) =-1 ){
Perror ("accept () error \ n ");
Continue;
}
For (I = 0; I <FD_SETSIZE; I ++)
If (client [I]. fd <0 ){
Client [I]. fd = connectfd;
Client [I]. name = new char [MAXDATASIZE];
Client [I]. addr = addr;
Client [I]. data = new char [MAXDATASIZE];
Client [I]. name [0] = '\ 0 ';
Client [I]. data [0] = '\ 0 ';
Printf ("You got a connection from % s.", inet_ntoa (client [I]. addr. sin_addr ));
Break;
}
If (I = FD_SETSIZE) printf ("too route clients \ n ");
FD_SET (connectfd, & allset );
If (connectfd> maxfd) maxfd = connectfd;
If (I> maxi) maxi = I;
If (-- nready <= 0) continue;
}
For (I = 0; I <= maxi; I ++ ){
If (sockfd = client [I]. fd) <0) continue;
If (FD_ISSET (sockfd, & rset )){
If (n = recv (sockfd, recvbuf, MAXDATASIZE, 0) = 0 ){
Close (sockfd );
Printf ("Client (% s) closed connection. User's data: % s \ n", client [I]. name, client [I]. data );
FD_CLR (sockfd, & allset );
Client [I]. fd =-1;
Delete client [I]. name;
Delete client [I]. data;
}
Else
Process_cli (& client [I], recvbuf, n );
If (-- nready <= 0) break;
}
}
}
Close (listenfd );
}
Void process_cli (CLIENT * client, char * recvbuf, int len)
{
Char sendbuf [MAXDATASIZE];
Recvbuf [len-1] = '\ 0 ';
If (strlen (client-> name) = 0 ){
Memcpy (client-> name, recvbuf, len );
Printf ("Client 'sname is % s. \ n", client-> name );
Return;
}
Printf ("Receivedclient (% s) message: % s \ n", client-> name, recvbuf );
Savedata (recvbuf, len, client-> data );
For (int i1 = 0; i1 <len-1; i1 ++ ){
Sendbuf [i1] = recvbuf [len-i1-2];
}
Sendbuf [len-1] = '\ 0 ';
Send (client-> fd, sendbuf, strlen (sendbuf), 0 );
}
Void savedata (char * recvbuf, int len, char * data)
{
Int start = strlen (data );
For (int I = 0; I <len; I ++ ){
Data [start + I] = recvbuf [I];
}
}
From yihaibobb's column