Socket programming practices in Linux (5) solutions for setting socket I/O timeout
(1) Use the alarm function to set timeout
#include
unsigned int alarm(unsigned int seconds);
Its main function is to set a signal transmission alarm. The signal SIGALRM is sent to the current process after the specified number of seconds in seconds. If the alarm function is called again within the scheduled uncompleted time, the subsequent timer setting will overwrite the previous setting, when seconds is set to 0, the timer is canceled. It returns the remaining time of the last timer, and returns 0 if it is set for the first time.
Void sigHandlerForSigAlrm (int signo) {return;} signal (SIGALRM, sigHandlerForSigAlrm); alarm (5); int ret = read (sockfd, buf, sizeof (buf )); if (ret =-1 & errno = EINTR) {// It is blocked and reaches 5 s. if it times out, the error code errno = ETIMEDOUT is returned ;} else if (ret> = 0) {// normal return (no timeout), turn off alarm (0 );}
If the returned message is interrupted by the SIGALRM signal when the read is blocked, the read operation times out. Otherwise, the read data has not timed out and the alarm is canceled. However, this method is not commonly used, because sometimes alarm may be used elsewhere, causing confusion.
(2) socket options: SO_SNDTIMEO and SO_RCVTIMEO. Call setsockopt to set the read/write timeout time.
/Example: read timeout int seconds = 5; if (setsockopt (sockfd, SOL_SOCKET, SO_RCVTIMEO, & seconds, sizeof (seconds) =-1) err_exit ("setsockopt error"); int ret = read (sockfd, buf, sizeof (buf); if (ret =-1 & errno = EWOULDBLOCK) {// timeout, interrupted by the clock signal errno = ETIMEDOUT ;}
SO_RCVTIMEO indicates the receiving timeout, and SO_SNDTIMEO indicates the sending timeout. This method is not often used, because this method cannot be transplanted, and some socket implementations do not support this method.
(3) Timeout using the select function
#include
int select(int maxfdp1, fd_set *readset, fd_set *writeset, fd_set *exceptset,struct timeval *timeout);
Return: number of file descriptors ready for use. The timeout value is 0 and the error value is-1.
Struct timeval {long TV _sec;/* seconds */long TV _usec;/* microseconds */}
The select function is a very important function in linux programming. It has many functions, including controlling the set of read, write, and exceptions, and setting timeout.
The following describes the usage of select in timeout settings by encapsulating four functions: read_timeout, write_timeout, accept_timeout, and connect_timeout.
1. read_timeout
/*** Read_timeout-read timeout detection function, which does not contain read Operations * @ fd: file descriptor * @ waitSec: Waiting For timeout seconds. 0 indicates no detection timeout * succeeded (not timed out) 0 is returned,-1 is returned for failure,-1 is returned for timeout, and errno = ETIMEDOUT **/int read_timeout (int fd, long waitSec) {int returnValue = 0; if (waitSec> 0) {fd_set readSet; FD_ZERO (& readSet); FD_SET (fd, & readSet); // Add struct timeval waitTime; waitTime. TV _sec = waitSec; waitTime. TV _usec = 0; // set the microsecond to 0 (not set). If set, the time will be more accurate to do {returnValue = select (Fd + 1, & readSet, NULL, NULL, & waitTime) ;}while (returnValue <0 & errno = EINTR); // wait (signal) in case of interruption, restart select if (returnValue = 0) // an event does not arrive during the waitTime period, timeout {returnValue =-1; // return-1 errno = ETIMEDOUT;} else if (returnValue = 1) // returnValue = 0 is generated for events in the waitTime period; // return 0, indicates success // If (returnValue =-1) and (errno! = EINTR),-1 (returnValue)} return returnValue ;}
The FD_ZERO macro sets all bits of an fd_set type variable to 0, and uses FD_SET to position a variable. When clearing a bit, you can use FD_CLR. We can use FD_ISSET to test whether a bit is set.
After a file descriptor set is declared, all locations must be zero with FD_ZERO. Then, the location of the descriptor we are interested in will be taken as follows:
fd_set rset; int fd; FD_ZERO(&rset); FD_SET(fd, &rset); FD_SET(stdin, &rset);
After the select statement is returned, use FD_ISSET to test whether the position is set:
if(FD_ISSET(fd, &rset) { ... }
2. write_timeout
The implementation method is basically the same as read_timeout.
/*** Write_timeout-write timeout detection function, which does not contain write operations * @ fd: file descriptor * @ waitSec: Waiting For timeout seconds. 0 indicates no detection timeout * succeeded (not timed out) 0 is returned,-1 is returned for failure,-1 is returned for timeout, and errno = ETIMEDOUT **/int write_timeout (int fd, long waitSec) {int returnValue = 0; if (waitSec> 0) {fd_set writeSet; FD_ZERO (& writeSet); // clear FD_SET (fd, & writeSet); // Add struct timeval waitTime; waitTime. TV _sec = waitSec; waitTime. TV _usec = 0; do {returnValue = select (fd + 1, NULL, & writeSet, NULL, & waitTime) ;}while (returnValue <0 & errno = EINTR ); // if (returnValue = 0) when the (signal) is interrupted // an event does not reach {returnValue =-1 during the waitTime period; // return-1 errno = ETIMEDOUT;} else if (returnValue = 1) // returnValue = 0 is generated for events in the waitTime period; // return 0, success} return returnValue ;}
3. accept_timeout
/*** Accept_timeout-accept with timeout * @ fd: file descriptor * @ addr: output parameter, returns the recipient's address * @ waitSec: Waiting For timeout seconds, 0 indicates that no timeout detection is used. If accept * in normal mode is used successfully (no timeout), 0 is returned, and-1 is returned for failure, timeout returns-1 and errno = ETIMEDOUT **/int accept_timeout (int fd, struct sockaddr_in * addr, long waitSec) {int returnValue = 0; if (waitSec> 0) {fd_set acceptSet; FD_ZERO (& acceptSet); FD_SET (fd, & acceptSet); // Add struct timeval waitTime; waitTime. TV _sec = waitSec; waitTime. TV _usec = 0; do {returnValue = select (fd + 1, & acceptSet, NULL, NULL, & waitTime) ;}while (returnValue <0 & errno = EINTR ); if (returnValue = 0) // no event is generated during the waitTime period {errno = ETIMEDOUT; return-1;} else if (returnValue =-1) // error return-1;}/** select returned correctly: indicates that a select wait event occurs: The peer completes three handshakes and a new link is established on the client, then, the accept call will not block */socklen_t socklen = sizeof (struct sockaddr_in); if (addr! = NULL) returnValue = accept (fd, (struct sockaddr *) addr, & socklen); else returnValue = accept (fd, NULL, NULL); return returnValue ;}
4. connect_timeout
(1) Why do we need this function?
If an exception occurs when the TCP/IP client connects to the server, the return time of connect (if it is blocked by default) is RTT (which is equivalent to the time when the client is blocked for such a long time, the customer needs to wait for such a long time. Obviously, the client user experience is not good (RTT is required to complete three handshakes). This will cause serious software quality degradation.
(Note:
RTT (Round-Trip Time) Introduction:
RTT round-trip latency: it is an important performance indicator in the computer network, indicating that data is sent from the sending end, the total latency experienced when the sender receives the confirmation from the receiver (the receiver sends the confirmation immediately after receiving the data.
RTT is determined by three parts: the propagation time of the link, the processing time of the end system, and the queuing and processing time in the cache of the router. Among them, the values of the first two parts are relatively fixed as a TCP connection, and the queuing and processing time in the cache of the router will change with the change of the overall network congestion. Therefore, the changes in RTT reflect the changes in the degree of network congestion to a certain extent. In short, it is the time that the sender experiences from sending data to receiving confirmation information from the recipient .)
(2) The client calls int connect (int sockfd, const struct sockaddr * addr, socklen_t len) to initiate a connection request to the socket on the server, if the socket descriptor of the client is in blocking mode, the connection will be blocked until the connection is established or the connection fails (note that the timeout time in blocking mode may be between 75 seconds and several minutes). If the socket descriptor is in non-blocking mode, if the connection cannot be established immediately after connect is called,-1 is returned (errno is set to EINPROGRESS. Note that the connection may be established immediately, for example, the server process connecting to the local machine). If no connection is established immediately, at this time, the three-way handshake of TCP continues behind the scenes, while the program can do other things, and then call select to check whether non-blocking connect is complete (in this case, you can specify the select timeout time, this time-out period can be set to a shorter time-out period than the connect time-out period). If the select statement times out, the socket is closed and a new socket can be created to reconnect, if the select statement returns a non-blocking socket descriptor that can be written, the connection is established successfully. If the select statement returns a non-blocking socket descriptor that is readable and writable, the connection fails. (Note: this must be distinguished from another normal connection, that is, after the connection is established, the server sends data to the client, in this case, select also returns the non-blocking socket descriptor which is both readable and writable. You can distinguish it by using the following methods:
1. Call getpeername to obtain the socket address of the Peer end. If getpeername returns ENOTCONN, the connection fails to be established. Then, use SO_ERROR to call getsockopt to obtain the pending error on the set interface descriptor;
2. call read to read data with a length of 0 bytes. if the read call fails, the connection establishment fails, and the errno returned by the read operation specifies the reason for the connection failure. if the connection is established successfully, read should return 0;
3. Call connect again. It should fail. If the error errno is EISCONN, it indicates that the set of interfaces has been established and the first connection is successful; otherwise, the connection fails;
/* Activate_nonblock-set IO to non-blocking mode * fd: file descriptor */void activate_nonblock (int fd) {int ret; int flags = fcntl (fd, F_GETFL ); if (flags =-1) ERR_EXIT ("fcntl error"); flags | = O_NONBLOCK; ret = fcntl (fd, F_SETFL, flags); if (ret =-1) ERR_EXIT ("fcntl error");}/* deactivate_nonblock-set IO to blocking mode * fd: file descriptor */void deactivate_nonblock (int fd) {int ret; int flags = fcntl (fd, F_GETFL); if (Flags =-1) ERR_EXIT ("fcntl error"); flags & = ~ O_NONBLOCK; ret = fcntl (fd, F_SETFL, flags); if (ret =-1) ERR_EXIT ("fcntl error ");} /* connect_timeout-connect * fd: Socket * addr: output parameter with timeout, return the recipient's address * wait_seconds: Waiting For timeout seconds. If it is 0, it indicates normal mode * succeeded (not timed out) 0 is returned,-1 is returned for failure,-1 is returned for timeout, and errno = ETIMEDOUT */int connect_timeout (int fd, struct sockaddr_in * addr, unsigned int wait_seconds) {int ret; socklen_t addrlen = sizeof (struct sockaddr_in); if (wait_seconds> 0) activate_nonblock (fd); ret = connect (fd, (struct sockaddr *) addr, addrlen ); if (ret <0 & errno = EINPROGRESS) {fd_set connect_fdset; struct timeval timeout; FD_ZERO (& connect_fdset); FD_SET (fd, & connect_fdset); timeout. TV _sec = wait_seconds; timeout. TV _usec = 0; do {/* once the connection is established, the socket can write */ret = select (fd + 1, NULL, & connect_fdset, NULL, & timeout );} while (ret <0 & errno = EINTR); if (ret = 0) {errno = ETIMEDOUT; return-1;} else if (ret <0) return-1; else if (ret = 1) {/* ret returns 1, either of which is successful, one is that the socket produces an error * at this time, the error message is not saved to the errno variable (select has no error). Therefore, you need to call * getsockopt to obtain */int err; socklen_t socklen = sizeof (err); int sockoptret = getsockopt (fd, SOL_SOCKET, SO_ERROR, & err, & socklen); if (sockoptret =-1) return-1; if (err = 0) ret = 0; else {errno = err; ret =-1 ;}} if (wait_seconds> 0) deactivate_nonblock (fd); return ret ;}
Test read_timeout
int ret; ret = read_timeout(fd, 5 ); if (ret == 0 ) read(fd, buf, sizeof (buf)); else if (ret == - 1 && errno == ETIMEOUT) printf( "timeout...\n" ); else ERR_EXIT( "read_timeout" );
Test connect_timeout
** Test: use the full client code of connect_timeout (the server side is like the front) **/int main () {int sockfd = socket (AF_INET, SOCK_STREAM, 0 ); if (sockfd =-1) err_exit ("socket error"); struct sockaddr_in serverAddr; serverAddr. sin_family = AF_INET; serverAddr. sin_port = htons (8001); serverAddr. sin_addr.s_addr = inet_addr ("127.0.0.1"); int ret = connect_timeout (sockfd, & serverAddr, 5); if (ret =-1 & errno = ETIMEDOUT) {cerr <"timeout... "<endl; err_exit (" connect_timeout error ");} else if (ret =-1) err_exit (" connect_timeout error "); // obtain and print peer information struct sockaddr_in peerAddr; socklen_t peerLen = sizeof (peerAddr); if (getpeername (sockfd, (struct sockaddr *) & peerAddr, & peerLen) =-1) err_exit ("getpeername"); cout <"Server information:" <inet_ntoa (peerAddr. sin_addr) <"," <ntohs (peerAddr. sin_port) <endl; close (sockfd );}