recvn.c
1 /******************************************************************** 2 * Description: recvn.c 3 * Provides a C file for the recvn function from the book Advanced 4 * Programming in the UNIX Environment by Richard Stevens. 5 * The recvn function is called repeatedly until n bytes have been 6 * received from the file descriptor. It uses select and FIONREAD 7 * checks ahead of time to guarantee that even if the socket is 8 * blocking the timeout will be enforced. To retry a socket to for 9 * the data missed during past timeouts the application should pass 10 * recvn the same buffer and address of a variable storing the number 11 * of bytes read on previous attempts. 12 * 13 * Derived from a work by Fred Proctor & Will Shackleford 14 * 15 * Author: 16 * License: LGPL Version 2 17 * System: Linux 18 * 19 * Copyright (c) 2004 All rights reserved. 20 * 21 * Last change: 22 ********************************************************************/ 23 24 #include "recvn.h" /* recvn(int, void *, int, double) */ 25 #include <stddef.h> /* size_t */ 26 #include <errno.h> /* errno */ 27 #include <sys/types.h> /* typedef fd_set, FD_ZERO, FD_SET */ 28 #include <sys/ioctl.h> /* FIONREAD */ 29 #include <sys/socket.h> /* recv() */ 30 #include <sys/time.h> /* struct timeval */ 31 #include <stdlib.h> /* malloc(), free() */ 32 #include <string.h> /* strerror() */ 33 #include <math.h> /* modf() */ 34 #include "rcs_print.hh" /* rcs_print_error() */ 35 #include "_timer.h" /* etime(), esleep() */ 36 37 int recvn_timedout = 0; 38 int print_recvn_timeout_errors = 1; 39 40 /* Read "n" bytes from a descriptor. */ 41 int recvn(int fd, void *vptr, int n, int flags, double _timeout, 42 int *bytes_read_ptr) 43 { 44 int nleft, nrecv; 45 char *ptr; 46 double start_time; 47 struct timeval timeout_tv; 48 fd_set recv_fd_set; 49 int bytes_ready; 50 int bytes_to_read; 51 if (etime_disabled) { 52 _timeout = -1.0; 53 } 54 55 bytes_ready = 0; 56 timeout_tv.tv_sec = (long) _timeout; 57 timeout_tv.tv_usec = (long) (_timeout * 1000000.0); 58 if (timeout_tv.tv_usec >= 1000000) { 59 timeout_tv.tv_usec = timeout_tv.tv_usec % 1000000; 60 } 61 FD_ZERO(&recv_fd_set); 62 FD_SET(fd, &recv_fd_set); 63 64 recvn_timedout = 0; 65 ptr = (char *) vptr; 66 nleft = n; 67 if (NULL != bytes_read_ptr) { 68 if (*bytes_read_ptr >= n) { 69 rcs_print_error 70 ("recvn: Invalid parameter -- (*bytes_read_ptr = %d) must be less than (n = %d).\n", 71 *bytes_read_ptr, n); 72 return -1; 73 } 74 if (*bytes_read_ptr < 0) { 75 rcs_print_error 76 ("recvn: Invalid parameter -- (*bytes_read_ptr = %d) must be greater than or equal to zero.\n", 77 *bytes_read_ptr); 78 return -1; 79 } 80 ptr += *bytes_read_ptr; 81 nleft -= *bytes_read_ptr; 82 } 83 84 start_time = etime(); 85 while (nleft > 0) { 86 if (_timeout > 0.0) { 87 double timeleft; 88 timeleft = start_time + _timeout - etime(); 89 if (timeleft <= 0.0) { 90 if (print_recvn_timeout_errors) { 91 rcs_print_error("Recv timed out.\n"); 92 if (NULL == bytes_read_ptr) { 93 rcs_print_error 94 ("recvn(fd=%d, vptr=%p, int n=%d, int flags=%d, double _timeout=%f) failed.\n", 95 fd, vptr, n, flags, _timeout); 96 } else { 97 rcs_print_error 98 ("recvn(fd=%d, vptr=%p, int n=%d, int flags=%d, double _timeout=%f,bytes_read=%d) failed.\n", 99 fd, vptr, n, flags, _timeout, *bytes_read_ptr); 100 } 101 } 102 recvn_timedout = 1; 103 if (NULL != bytes_read_ptr) { 104 *bytes_read_ptr = (n - nleft); 105 } 106 return -1; 107 } 108 timeout_tv.tv_sec = (long) timeleft; 109 timeout_tv.tv_usec = (long) (timeleft * 1000000.0); 110 if (timeout_tv.tv_usec >= 1000000) { 111 timeout_tv.tv_usec = timeout_tv.tv_usec % 1000000; 112 } 113 switch (select(fd + 1, &recv_fd_set, (fd_set *) NULL, 114 (fd_set *) NULL, &timeout_tv)) { 115 case -1: 116 rcs_print_error("Error in select: %d -> %s\n", errno, 117 strerror(errno)); 118 if (NULL == bytes_read_ptr) { 119 rcs_print_error 120 ("recvn(fd=%d, vptr=%p, int n=%d, int flags=%d, double _timeout=%f) failed.\n", 121 fd, vptr, n, flags, _timeout); 122 } else { 123 rcs_print_error 124 ("recvn(fd=%d, vptr=%p, int n=%d, int flags=%d, double _timeout=%f,bytes_read=%d) failed.\n", 125 fd, vptr, n, flags, _timeout, *bytes_read_ptr); 126 } 127 return -1; 128 129 case 0: 130 if (print_recvn_timeout_errors) { 131 rcs_print_error("Recv timed out.\n"); 132 if (NULL == bytes_read_ptr) { 133 rcs_print_error 134 ("recvn(fd=%d, vptr=%p, int n=%d, int flags=%d, double _timeout=%f) failed.\n", 135 fd, vptr, n, flags, _timeout); 136 } else { 137 rcs_print_error 138 ("recvn(fd=%d, vptr=%p, int n=%d, int flags=%d, double _timeout=%f,bytes_read=%d) failed.\n", 139 fd, vptr, n, flags, _timeout, *bytes_read_ptr); 140 } 141 } 142 recvn_timedout = 1; 143 if (NULL != bytes_read_ptr) { 144 *bytes_read_ptr = (n - nleft); 145 } 146 return -1; 147 148 default: 149 break; 150 } 151 bytes_ready = 0; 152 153 ioctl(fd, FIONREAD, (caddr_t) & bytes_ready); 154 155 bytes_to_read = (nleft <= bytes_ready) ? nleft : bytes_ready; 156 } else { 157 bytes_to_read = nleft; 158 } 159 nrecv = 0; 160 if (bytes_to_read > 0) { 161 if ((nrecv = recv(fd, ptr, bytes_to_read, flags)) == -1) { 162 if (errno == EWOULDBLOCK) { 163 if (fabs(_timeout) < 1e-6) { 164 recvn_timedout = 1; 165 if (NULL != bytes_read_ptr) { 166 *bytes_read_ptr = (n - nleft); 167 } 168 return -1; 169 } 170 } else { 171 rcs_print_error("Recv error: %d = %s\n", errno, 172 strerror(errno)); 173 if (NULL == bytes_read_ptr) { 174 rcs_print_error 175 ("recvn(fd=%d, vptr=%p, int n=%d, int flags=%d, double _timeout=%f) failed.\n", 176 fd, vptr, n, flags, _timeout); 177 } else { 178 rcs_print_error 179 ("recvn(fd=%d, vptr=%p, int n=%d, int flags=%d, double _timeout=%f,bytes_read=%d) failed.\n", 180 fd, vptr, n, flags, _timeout, *bytes_read_ptr); 181 } 182 if (NULL != bytes_read_ptr) { 183 *bytes_read_ptr = (n - nleft); 184 } 185 return (-1); /* error, return < 0 */ 186 } 187 nrecv = 0; 188 } else if (nrecv == 0) { 189 rcs_print_error("recvn: Premature EOF received.\n"); 190 return (-2); 191 } 192 } 193 nleft -= nrecv; 194 ptr += nrecv; 195 if (nleft > 0 && _timeout > 0.0) { 196 esleep(0.001); 197 if (etime() - start_time > _timeout) { 198 rcs_print_error("Recv timed out.\n"); 199 recvn_timedout = 1; 200 if (NULL != bytes_read_ptr) { 201 *bytes_read_ptr = (n - nleft); 202 } 203 return (-1); 204 } 205 } 206 } 207 rcs_print_debug(PRINT_SOCKET_READ_SIZE, "read %d bytes from %d\n", n, fd); 208 if (NULL != bytes_read_ptr) { 209 *bytes_read_ptr = (n - nleft); 210 } 211 return (n - nleft); /* return >= 0 */ 212 }