/* -*- C++ -*- */ // $Id$ /* * T T C P . C * * Test TCP connection. Makes a connection on port 5001 * and transfers fabricated buffers or data copied from stdin. * * Usable on 4.2, 4.3, and 4.1a systems by defining one of * BSD42 BSD43 (BSD41a) * Machines using System V with BSD sockets should define SYSV. * * Modified for operation under 4.2BSD, 18 Dec 84 * T.C. Slattery, USNA * Minor improvements, Mike Muuss and Terry Slattery, 16-Oct-85. * Modified in 1989 at Silicon Graphics, Inc. * catch SIGPIPE to be able to print stats when receiver has died * for tcp, don't look for sentinel during reads to allow small transfers * increased default buffer size to 8K, nbuf to 2K to transfer 16MB * moved default port to 5001, beyond IPPORT_USERRESERVED * make sinkmode default because it is more popular, * -s now means don't sink/source * count number of read/write system calls to see effects of * blocking from full socket buffers * for tcp, -D option turns off buffered writes (sets TCP_NODELAY sockopt) * buffer alignment options, -A and -O * print stats in a format that's a bit easier to use with grep & awk * for SYSV, mimic BSD routines to use most of the existing timing code * Modified by Steve Miller of the University of Maryland, College Park * -b sets the socket buffer size (SO_SNDBUF/SO_RCVBUF) * Modified Sept. 1989 at Silicon Graphics, Inc. * restored -s sense at request of tcs@brl * Modified Oct. 1991 at Silicon Graphics, Inc. * use getopt(3) for option processing, add -f and -T options. * SGI IRIX 3.3 and 4.0 releases don't need #define SYSV. * Modified Aug.1993 at University Paderborn, Germany * some SVR4 changes and time functions changed to itimer() calls * Modified by Douglas C. Schmidt September 28, 1994 * added support for testing UNIX domain socket performance * Modified by Tim Harrison May, 1995 * added support for ACE wrappers * Distribution Status - * Public Domain. Distribution Unlimited. */ #ifndef lint static char RCSid[] = "ttcp.c $Revision$"; #endif /* #define BSD43 */ /* #define BSD42 */ /* #define BSD41a */ #define SYSV /* required on SGI IRIX releases before 3.3 */ #include ACE_SOCK_Connector connector_factory; #include ACE_SOCK_Acceptor acceptor_factory; #include ACE_INET_Addr address; #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* struct itimerval */ #include #include #include #if defined(SYSV) #define bcopy(b1,b2,n) memcpy(b2,b1,n) #define bzero(b1,n) memset(b1,0,n) #include #include struct rusage { struct timeval ru_utime, ru_stime; }; #define RUSAGE_SELF 0 #else #include #endif struct sockaddr_in sinme; struct sockaddr_un sunme; struct sockaddr_in sinhim; struct sockaddr_un sunhim; struct sockaddr_in frominet; struct sockaddr_un fromunix; int send_n (const void *buf, int len); int recv_n (void *buf, int len); int connection_descriptor; struct Session_Control_Message { long nbuf_; // number of buffers that will be sent this round. long size_; // size of the buffers that will be sent } session_control_buf; struct Data_Control_Message { long size_; char data_; } *message_buf; int fromlen; int domain = PF_INET; /* Default is to use Internet domain sockets. */ char *domainname; /* Rendezvous address for UNIX domain sockets. */ int fd; /* fd of network socket */ int data_buf_len = 1024 * 1024 * 2; // length of data portion long total_msg_len; // length of entire message char *data_buf; // pointer to data portion int nbuf = 2 * 1024; /* number of buffers to send in sinkmode */ int bufoffset = 0; /* align buffer to this */ int bufalign = 16 * 1024; /* modulo this */ int udp = 0; /* 0 = tcp, !0 = udp */ int options = 0; /* socket options */ int one = 1; /* for 4.3 BSD style setsockopt() */ short port = 5001; /* TCP port number */ char *host; /* ptr to name of host */ int trans; /* 0=receive, !0=transmit mode */ int sinkmode = 0; /* 0=normal I/O, !0=sink/source mode */ int verbose = 0; /* 0=print basic info, 1=print cpu rate, proc * resource usage. */ int nodelay = 0; /* set TCP_NODELAY socket option */ int b_flag = 0; /* use mread() */ int sockbufsize = 0; /* socket buffer size to use */ char fmt = 'K'; /* output format: k = kilobits, K = kilobytes, * m = megabits, M = megabytes, * g = gigabits, G = gigabytes */ int touchdata = 0; /* access data after reading */ struct hostent *addr; extern int errno; extern int optind; extern char *optarg; char Usage[] = "\ Usage: ttcp -t [-options] host [ < in ]\n\ ttcp -r [-options > out]\n\ Common options:\n\ -l ## length of bufs read from or written to network (default 8192)\n\ -u use UDP instead of TCP\n\ -U use UNIX domain sockets instead of Internet domain sockets\n\ -p ## port number to send to or listen at (default 5001)\n\ -s -t: source a pattern to network\n\ -r: sink (discard) all data from network\n\ -A align the start of buffers to this modulus (default 16384)\n\ -O start buffers at this offset from the modulus (default 0)\n\ -v verbose: print more statistics\n\ -d set SO_DEBUG socket option\n\ -b ## set socket buffer size (if supported)\n\ -f X format for rate: k,K = kilo{bit,byte}; m,M = mega; g,G = giga\n\ Options specific to -t:\n\ -n## number of source bufs written to network (default 2048)\n\ -D don't buffer TCP writes (sets TCP_NODELAY socket option)\n\ Options specific to -r:\n\ -B for -s, only output full blocks as specified by -l (for TAR)\n\ -T \"touch\": access each byte as it's read\n\ "; char stats[128]; unsigned long nbytes; /* bytes on net */ unsigned long numCalls = 0; /* # of I/O system calls */ double cput, realt; /* user, real time (seconds) */ void err (char *s); void mes (char *s); void pattern (register char *cp, register int cnt); char *outfmt (double b); static void getrusage (int ignored, register struct rusage *ru); static void gettimeofday (struct timeval *tp, struct timezone *zp); void prep_timer (void); double read_timer (char *str, int len); static void prusage (register struct rusage *r0, struct rusage *r1, struct timeval *e, struct timeval *b, char *outp); static void tvadd (struct timeval *tsum, struct timeval *t0, struct timeval *t1); static void tvsub (struct timeval *tdiff, struct timeval *t1, struct timeval *t0); static void psecs (long l, register char *cp); void delay (int us); int mread (int fd, register char *bufp, unsigned n); int Nread (ACE_SOCK_Stream &s, void *buf, int count); int Nwrite (ACE_SOCK_Stream &s, void *buf, int count); #if !defined (__cplusplus) typedef void (*SIG_TYP)(); #endif #ifdef SVR4 void sigpipe (int foo) #else void sigpipe () #endif { } char *title = 0; int new_line = 0; int main (int argc, char *argv[]) { ACE_SOCK_Stream connection_stream; unsigned long addr_tmp; int c; if (argc < 2) goto usage; while ((c = getopt (argc, argv, "drstU:uvBDTb:f:l:n:p:A:O:L:xh:")) != -1) { switch (c) { case 'h': host = optarg; break; case 'x': new_line = 1; break; case 'L': title = optarg; break; case 'B': b_flag = 1; break; case 't': trans = 1; break; case 'r': trans = 0; break; case 'd': options |= SO_DEBUG; break; case 'D': #ifdef TCP_NODELAY nodelay = 1; #else fprintf (stderr, "ttcp: -D option ignored: TCP_NODELAY socket option not supported\n"); #endif break; case 'n': nbuf = atoi (optarg); break; case 'l': data_buf_len = atoi (optarg); break; case 's': sinkmode = !sinkmode; break; case 'p': port = atoi (optarg); break; case 'U': domain = PF_UNIX; domainname = optarg; break; case 'u': udp = 1; break; case 'v': verbose = 1; break; case 'A': bufalign = atoi (optarg); break; case 'O': bufoffset = atoi (optarg); break; case 'b': #if defined(SO_SNDBUF) || defined(SO_RCVBUF) sockbufsize = atoi (optarg); #else fprintf (stderr, "ttcp: -b option ignored: SO_SNDBUF/SO_RCVBUF socket options not supported\n"); #endif break; case 'f': fmt = *optarg; break; case 'T': touchdata = 1; break; default: goto usage; } } /* if transmitter, create remote address to transmit to. */ if (trans) { if (address.set (port, host) == -1) perror ("address.set"), exit (1); } /* else, receiver create address to listen on */ else { address.set (port); } total_msg_len = sizeof (long) + data_buf_len; // allocate the buffer message_buf = (Data_Control_Message *) malloc (total_msg_len); if (message_buf == 0) err ("malloc"); // if (bufalign != 0) // message_buf += (bufalign - ((int) message_buf % bufalign) + bufoffset) % bufalign; // let's go ahead and set the control message for every send right now message_buf->size_ = data_buf_len; session_control_buf.nbuf_ = nbuf; session_control_buf.size_ = data_buf_len; // // print out option values for trans and receiver // if (trans) { fprintf (stdout, "ttcp-t: data_buf_len=%d, nbuf=%d, align=%d/%d, port=%d", data_buf_len, nbuf, bufalign, bufoffset, port); if (sockbufsize) fprintf (stdout, ", sockbufsize=%d", sockbufsize); fprintf (stdout, " %s -> %s\n", domain == PF_INET ? (udp ? "udp" : "tcp") : "unix", host == 0 ? domainname : host); } else // receiver { fprintf (stdout, "ttcp-r: data_buf_len=%d, nbuf=%d, align=%d/%d, port=%d", data_buf_len, nbuf, bufalign, bufoffset, port); if (sockbufsize) fprintf (stdout, ", sockbufsize=%d", sockbufsize); fprintf (stdout, " %s\n", domain == PF_INET ? (udp ? "udp" : "tcp") : "unix"); } mes ("socket"); // // connect and accept // if (!udp) { signal (SIGPIPE, (SIG_TYP) sigpipe); /* the transmitter will set options and connect to receiver */ if (trans) { if (connector_factory.connect (connection_stream, address) == -1) perror ("connection failed"), exit (1); fprintf (stdout, "ttcp-t: data_buf_len=%d, nbuf=%d, align=%d/%d, port=%d", data_buf_len, nbuf, bufalign, bufoffset, port); // turn off weird ack things if (nodelay) { struct protoent *p = getprotobyname ("tcp"); if (p && connection_stream.set_option (p->p_proto, TCP_NODELAY, (char *)& one, sizeof (one))) err ("setsockopt: nodelay"); mes ("nodelay"); } if (sockbufsize) { if (connection_stream.set_option (SOL_SOCKET, SO_SNDBUF, (char *) &sockbufsize, sizeof sockbufsize) == -1) err ("acceptor_factory.set_option"); mes ("sndbuf"); } } /* receiver will listen for connections from the transmitter */ else { if (acceptor_factory.open (address, 1) == -1) perror ("acceptor open"), exit (1); ACE_INET_Addr remote_address; if (acceptor_factory.accept (connection_stream, (ACE_Addr *) &remote_address) == -1) perror ("acceptor accept"), exit (1); // set the window size if (sockbufsize) { if (connection_stream.set_option (SOL_SOCKET, SO_RCVBUF, (char *) &sockbufsize, sizeof sockbufsize) == -1) err ("acceptor_factory.set_option"); mes ("rcvbuf"); } fprintf (stderr, "ttcp-r: accept from %s\n", remote_address.get_host_name()); } } // // start timer // errno = 0; // used in send_n and recv_n connection_descriptor = connection_stream.get_handle (); if (trans) { pattern (& (message_buf->data_), data_buf_len); prep_timer (); ACE_DEBUG ((LM_DEBUG, "Sending session control message" " nbuf %d, size %d\n", session_control_buf.nbuf_, session_control_buf.size_)); if (send_n ((char *) &session_control_buf, sizeof (Session_Control_Message)) != sizeof (Session_Control_Message)) ACE_ERROR_RETURN ((LM_ERROR, "%p send session control failed\n", "ttcp"), -1); long ack; int send_result; while (nbuf--) { send_result = send_n ((char *) message_buf, total_msg_len); if (send_result != total_msg_len) ACE_ERROR_RETURN ((LM_ERROR, "%p only sent %d of %d bytes on call %d\n", "ttcp", send_result, total_msg_len, numCalls + 1), -1); numCalls++; nbytes += data_buf_len; if (recv_n ((char *) &ack, sizeof ack) != sizeof ack) ACE_ERROR_RETURN ((LM_ERROR, "%p recv of ack failed\n", "ttcp"), -1); if (ack != data_buf_len) ACE_DEBUG ((LM_DEBUG, "%received ack for only %d bytes\n", ack)); } } else { prep_timer (); if (recv_n ((char *) &session_control_buf, sizeof (Session_Control_Message)) != sizeof (Session_Control_Message)) ACE_ERROR_RETURN ((LM_ERROR, "%p recv session control failed\n", "ttcp"), -1); ACE_DEBUG ((LM_DEBUG, "received session control message" " nbuf %d, size %d\n", session_control_buf.nbuf_, session_control_buf.size_)); nbuf = session_control_buf.nbuf_; // ignore session_control_buf.size_ for now long cnt; while (nbuf--) { if (recv_n ((char *) message_buf, sizeof (long)) != sizeof (long)) ACE_ERROR_RETURN ((LM_ERROR, "%p recv data control failed\n", "ttcp"), -1); cnt = recv_n (& (message_buf->data_), message_buf->size_); if (cnt != message_buf->size_) ACE_ERROR_RETURN ((LM_ERROR, "recv data failed\n"), -1); numCalls++; nbytes += cnt; if (send_n ((char *) &cnt, sizeof cnt) != sizeof cnt) ACE_ERROR_RETURN ((LM_ERROR, "%p send ack failed\n", "ttcp"), -1); } } if (errno) err ("IO"); // // stop the timer // (void) read_timer (stats, sizeof (stats)); if (udp && trans) { (void) Nwrite (connection_stream, message_buf, 4); /* rcvr end */ (void) Nwrite (connection_stream, message_buf, 4); /* rcvr end */ (void) Nwrite (connection_stream, message_buf, 4); /* rcvr end */ (void) Nwrite (connection_stream, message_buf, 4); /* rcvr end */ } if (cput <= 0.0) cput = 0.001; if (realt <= 0.0) realt = 0.001; #if defined (LM_RESULTS) if (trans && (title != 0)) { double tmp; FILE *fd; char filename[BUFSIZ]; ACE_OS::sprintf (filename, "%s.results", title); fd = fopen(filename,"a+"); if (new_line) fprintf(fd,"\n -l %ldk \t", data_buf_len/1024); tmp = ((double) nbytes) / realt; fprintf(fd,"%.2f ", tmp * 8.0 / 1024.0 / 1024.0); fclose(fd); } #endif fprintf (stdout, "ttcp%s: %ld bytes in %.2f real seconds = %s/sec +++\n", trans ? "-t" : "-r", nbytes, realt, outfmt (((double) nbytes) / realt)); if (verbose) { fprintf (stdout, "ttcp%s: %ld bytes in %.2f CPU seconds = %s/cpu sec\n", trans ? "-t" : "-r", nbytes, cput, outfmt (((double) nbytes) / cput)); } fprintf (stdout, "ttcp%s: %d I/O calls, msec/call = %.2f, calls/sec = %.2f\n", trans ? "-t" : "-r", numCalls, 1024.0 * realt / ((double) numCalls), ((double) numCalls) / realt); fprintf (stdout, "ttcp%s: %s\n", trans ? "-t" : "-r", stats); if (verbose) { fprintf (stdout, "ttcp%s: buffer address %#x\n", trans ? "-t" : "-r", message_buf); } exit (0); usage: fprintf (stderr, Usage); return 1; } int send_n (const void *buf, int len) { size_t bytes_written; int n; for (bytes_written = 0; bytes_written < len; bytes_written += n) if ((n = write (connection_descriptor, (const char *) buf + bytes_written, len - bytes_written)) == -1) return -1; return bytes_written; } int recv_n (void *buf, int len) { size_t bytes_read; int n; for (bytes_read = 0; bytes_read < len; bytes_read += n) if ((n = read (connection_descriptor, (char *) buf + bytes_read, len - bytes_read)) == -1) return -1; else if (n == 0) break; return bytes_read; } void err (char *s) { fprintf (stderr, "ttcp%s: ", trans ? "-t" : "-r"); perror (s); fprintf (stderr, "errno=%d\n", errno); exit (1); } void mes (char *s) { fprintf (stderr, "ttcp%s: %s\n", trans ? "-t" : "-r", s); } void pattern (register char *cp, register int cnt) { register char c; c = 0; while (cnt-- > 0) { while (!isprint ((c & 0x7F))) c++; *cp++ = (c++ & 0x7F); } } char * outfmt (double b) { static char obuf[50]; switch (fmt) { case 'G': sprintf (obuf, "%.2f GB", b / 1024.0 / 1024.0 / 1024.0); break; default: case 'K': sprintf (obuf, "%.2f KB", b / 1024.0); break; case 'M': sprintf (obuf, "%.2f MB", b / 1024.0 / 1024.0); break; case 'g': sprintf (obuf, "%.2f Gbit", b * 8.0 / 1024.0 / 1024.0 / 1024.0); break; case 'k': sprintf (obuf, "%.2f Kbit", b * 8.0 / 1024.0); break; case 'm': sprintf (obuf, "%.2f Mbit", b * 8.0 / 1024.0 / 1024.0); break; } return obuf; } static struct itimerval itime0; /* Time at which timing started */ static struct rusage ru0; /* Resource utilization at the start */ #if defined(SYSV) /*ARGSUSED */ static void getrusage (int ignored, register struct rusage *ru) { struct tms buf; times (&buf); /* Assumption: HZ <= 2147 (LONG_MAX/1000000) */ ru->ru_stime.tv_sec = buf.tms_stime / HZ; ru->ru_stime.tv_usec = ((buf.tms_stime % HZ) * 1000000) / HZ; ru->ru_utime.tv_sec = buf.tms_utime / HZ; ru->ru_utime.tv_usec = ((buf.tms_utime % HZ) * 1000000) / HZ; } /*ARGSUSED */ static void gettimeofday (struct timeval *tp, struct timezone *zp) { tp->tv_sec = time (0); tp->tv_usec = 0; } #endif /* SYSV */ /* * P R E P _ T I M E R */ void prep_timer () { itime0.it_interval.tv_sec = 0; itime0.it_interval.tv_usec = 0; itime0.it_value.tv_sec = LONG_MAX / 22; /* greatest possible value , itimer() count backwards */ itime0.it_value.tv_usec = 0; getrusage (RUSAGE_SELF, &ru0); /* Init REAL Timer */ if (setitimer (ITIMER_REAL, &itime0, NULL)) { perror ("Setting 'itimer' REAL failed"); return; } } /* * R E A D _ T I M E R * */ double read_timer (char *str, int len) { struct itimerval itimedol; struct rusage ru1; struct timeval td; struct timeval tend, tstart; char line[132]; getrusage (RUSAGE_SELF, &ru1); if (getitimer (ITIMER_REAL, &itimedol)) { perror ("Getting 'itimer' REAL failed"); return (0.0); } prusage (&ru0, &ru1, &itime0.it_value, &itimedol.it_value, line); (void) strncpy (str, line, len); /* Get real time */ tvsub (&td, &itime0.it_value, &itimedol.it_value); realt = td.tv_sec + ((double) td.tv_usec) / 1000000; /* Get CPU time (user+sys) */ tvadd (&tend, &ru1.ru_utime, &ru1.ru_stime); tvadd (&tstart, &ru0.ru_utime, &ru0.ru_stime); tvsub (&td, &tend, &tstart); cput = td.tv_sec + ((double) td.tv_usec) / 1000000; if (cput < 0.00001) cput = 0.00001; return (cput); } static void prusage (register struct rusage *r0, struct rusage *r1, struct timeval *e, struct timeval *b, char *outp) { struct timeval tdiff; register time_t t; register char *cp; register int i; int ms; t = (r1->ru_utime.tv_sec - r0->ru_utime.tv_sec) * 100 + (r1->ru_utime.tv_usec - r0->ru_utime.tv_usec) / 10000 + (r1->ru_stime.tv_sec - r0->ru_stime.tv_sec) * 100 + (r1->ru_stime.tv_usec - r0->ru_stime.tv_usec) / 10000; ms = (e->tv_sec - b->tv_sec) * 100 + (e->tv_usec - b->tv_usec) / 10000; #define END(x) {while(*x) x++;} #if defined(SYSV) cp = "%Uuser %Ssys %Ereal %P"; #else #if defined(sgi) /* IRIX 3.3 will show 0 for %M,%F,%R,%C */ cp = "%Uuser %Ssys %Ereal %P %Mmaxrss %F+%Rpf %Ccsw"; #else cp = "%Uuser %Ssys %Ereal %P %Xi+%Dd %Mmaxrss %F+%Rpf %Ccsw"; #endif #endif for (; *cp; cp++) { if (*cp != '%') *outp++ = *cp; else if (cp[1]) switch (*++cp) { case 'U': tvsub (&tdiff, &r1->ru_utime, &r0->ru_utime); sprintf (outp, "%d.%01d", tdiff.tv_sec, tdiff.tv_usec / 100000); END (outp); break; case 'S': tvsub (&tdiff, &r1->ru_stime, &r0->ru_stime); sprintf (outp, "%d.%01d", tdiff.tv_sec, tdiff.tv_usec / 100000); END (outp); break; case 'E': psecs (ms / 100, outp); END (outp); break; case 'P': sprintf (outp, "%d%%", (int) (t * 100 / ((ms ? ms : 1)))); END (outp); break; #if !defined(SYSV) case 'W': i = r1->ru_nswap - r0->ru_nswap; sprintf (outp, "%d", i); END (outp); break; case 'X': sprintf (outp, "%d", t == 0 ? 0 : (r1->ru_ixrss - r0->ru_ixrss) / t); END (outp); break; case 'D': sprintf (outp, "%d", t == 0 ? 0 : (r1->ru_idrss + r1->ru_isrss - (r0->ru_idrss + r0->ru_isrss)) / t); END (outp); break; case 'K': sprintf (outp, "%d", t == 0 ? 0 : ((r1->ru_ixrss + r1->ru_isrss + r1->ru_idrss) - (r0->ru_ixrss + r0->ru_idrss + r0->ru_isrss)) / t); END (outp); break; case 'M': sprintf (outp, "%d", r1->ru_maxrss / 2); END (outp); break; case 'F': sprintf (outp, "%d", r1->ru_majflt - r0->ru_majflt); END (outp); break; case 'R': sprintf (outp, "%d", r1->ru_minflt - r0->ru_minflt); END (outp); break; case 'I': sprintf (outp, "%d", r1->ru_inblock - r0->ru_inblock); END (outp); break; case 'O': sprintf (outp, "%d", r1->ru_oublock - r0->ru_oublock); END (outp); break; case 'C': sprintf (outp, "%d+%d", r1->ru_nvcsw - r0->ru_nvcsw, r1->ru_nivcsw - r0->ru_nivcsw); END (outp); break; #endif /* !SYSV */ } } *outp = '\0'; } static void tvadd (struct timeval *tsum, struct timeval *t0, struct timeval *t1) { tsum->tv_sec = t0->tv_sec + t1->tv_sec; tsum->tv_usec = t0->tv_usec + t1->tv_usec; if (tsum->tv_usec > 1000000) tsum->tv_sec++, tsum->tv_usec -= 1000000; } static void tvsub (struct timeval *tdiff, struct timeval *t1, struct timeval *t0) { tdiff->tv_sec = t1->tv_sec - t0->tv_sec; tdiff->tv_usec = t1->tv_usec - t0->tv_usec; if (tdiff->tv_usec < 0) tdiff->tv_sec--, tdiff->tv_usec += 1000000; } static void psecs (long l, register char *cp) { register int i; i = l / 3600; if (i) { sprintf (cp, "%d:", i); END (cp); i = l % 3600; sprintf (cp, "%d%d", (i / 60) / 10, (i / 60) % 10); END (cp); } else { i = l; sprintf (cp, "%d", i / 60); END (cp); } i %= 60; *cp++ = ':'; sprintf (cp, "%d%d", i / 10, i % 10); } /* * N R E A D */ int Nread (ACE_SOCK_Stream &s, void *buf, int count) { numCalls++; return (s.recv (buf, count)); } /* * N W R I T E */ int Nwrite (ACE_SOCK_Stream &s, void *buf, int count) { numCalls++; return s.send (buf, count); } void delay (int us) { struct timeval tv; tv.tv_sec = 0; tv.tv_usec = us; (void) select (1, (fd_set *) 0, (fd_set *) 0, (fd_set *) 0, &tv); } /* * M R E A D * * This function performs the function of a read(II) but will * call read(II) multiple times in order to get the requested * number of characters. This can be necessary because * network connections don't deliver data with the same * grouping as it is written with. Written by Robert S. Miles, BRL. */ int mread (int fd, register char *bufp, unsigned n) { register unsigned count = 0; register int nread; do { nread = read (fd, bufp, n - count); numCalls++; if (nread < 0) { perror ("ttcp_mread"); return (-1); } if (nread == 0) return ((int) count); count += (unsigned) nread; bufp += nread; } while (count < n); return ((int) count); }