Larry McVoy wrote:
A short summary is "can someone please post a test program that sources and sinks data at the wire speed?" because apparently I'm too old and clueless to write such a thing.
Here's a simple reference tcp source/sink that's I've used for years. For example, on a couple gigabit machines:
$ ./tcpsend -t10 dew Sent 1240415312 bytes in 10.033101 seconds Throughput: 123632294 B/s -John
/* * discard.c * A simple discard server. * * Copyright 2003 John Heffner. */ #include <stdio.h> #include <signal.h> #include <unistd.h> #include <string.h> #include <stdlib.h> #include <errno.h> #include <sys/types.h> #include <sys/socket.h> #include <sys/poll.h> #include <sys/wait.h> #include <sys/time.h> #include <sys/param.h> #include <netinet/in.h> #if 0 #define RATELIMIT #define RATE 100000 /* bytes/sec */ #define WAIT_TIME (1000000/HZ-1) #define READ_SIZE (RATE/HZ) #else #define READ_SIZE (1024*1024) #endif void child_handler(int sig) { int status; wait(&status); } int main(int argc, char *argv[]) { int port = 9000; int lfd; struct sockaddr_in laddr; int newfd; struct sockaddr_in newaddr; int pid; socklen_t len; if (argc > 2) { fprintf(stderr, "usage: discard [port]\n"); exit(1); } if (argc == 2) { if (sscanf(argv[1], "%d", &port) != 1 || port < 0 || port > 65535) { fprintf(stderr, "discard: error: not a port number\n"); exit(1); } } if (signal(SIGCHLD, child_handler) == SIG_ERR) { perror("signal"); exit(1); } memset(&laddr, 0, sizeof (laddr)); laddr.sin_family = AF_INET; laddr.sin_port = htons(port); laddr.sin_addr.s_addr = INADDR_ANY; if ((lfd = socket(PF_INET, SOCK_STREAM, 0)) < 0) { perror("socket"); exit(1); } if (bind(lfd, (struct sockaddr *)&laddr, sizeof (laddr)) != 0) { perror("bind"); exit(1); } if (listen(lfd, 5) != 0) { perror("listen"); exit(1); } for (;;) { if ((newfd = accept(lfd, (struct sockaddr *)&newaddr, &len)) < 0) { if (errno == EINTR) continue; perror("accept"); exit(1); } if ((pid = fork()) < 0) { perror("fork"); exit(1); } else if (pid == 0) { int n; char buf[READ_SIZE]; int64_t data_rcvd = 0; struct timeval stime, etime; float time; gettimeofday(&stime, NULL); while ((n = read(newfd, buf, READ_SIZE)) > 0) { data_rcvd += n; #ifdef RATELIMIT usleep(WAIT_TIME); #endif } gettimeofday(&etime, NULL); close(newfd); time = (float)(1000000*(etime.tv_sec - stime.tv_sec) + etime.tv_usec - stime.tv_usec) / 1000000.0; printf("Received %lld bytes in %f seconds\n", (long long)data_rcvd, time); printf("Throughput: %d B/s\n", (int)((float)data_rcvd / time)); exit(0); } close(newfd); } return 1; }
/* * tcpsend.c * Send pseudo-random data through a TCP connection. * * Copyright 2003 John Heffner. */ #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <netdb.h> #include <signal.h> #include <errno.h> #include <fcntl.h> #include <netinet/in.h> #include <sys/types.h> #include <sys/socket.h> #include <sys/time.h> #include <sys/stat.h> #ifdef __linux__ #include <sys/sendfile.h> #endif #define SNDSIZE (1024 * 10) #define BUFSIZE (1024 * 1024) #define max(a,b) (a > b ? a : b) #define min(a,b) (a < b ? a : b) int time_done = 0; int interrupt_done = 0; struct timeval starttime; void int_handler(int sig) { interrupt_done = 1; } void alarm_handler(int sig) { time_done = 1; } static void usage_error(int err) { fprintf(stderr, "usage: tcpsend [-z] [-b max_bytes] [-t max_time] hostname [port]\n"); exit(err); } static void cleanup_exit(int fd, char *filename, int status) { if (fd > 0) close(fd); if (filename) unlink(filename); exit(status); } int main(int argc, char *argv[]) { char *hostname = "localhost"; int port = 9000; int max_time = -1; int max_bytes = -1; int zerocopy = 0; int sockfd; struct sockaddr_in addr; struct hostent *hent; struct sigaction act; int i; int arg_state; char *tmp; int add; char *buf; int64_t data_sent; int n; off_t start; int amt; struct timeval etime; float time; int err; char *namebuf = NULL; int fd = -1; /* Read in args */ if (argc == 2 && strcmp(argv[1], "-h") == 0) usage_error(0); for (arg_state = 0, i = 1; i < argc; i++) { if (argv[i][0] == '-') { if (arg_state != 0) usage_error(1); if (strlen(argv[i]) < 2) usage_error(1); add = 0; if (argv[i][1] == 'z') { zerocopy = 1; } else if (argv[i][1] == 'b' || argv[i][1] == 't') { if (strlen(argv[i]) > 2) { tmp = &(argv[i][2]); } else { add = 1; if (i + 1 >= argc) usage_error(1); tmp = argv[i + 1]; } if (argv[i][1] == 'b') { if (sscanf(tmp, "%d", &max_bytes) != 1 || max_bytes < 0) usage_error(1); } else { if (sscanf(tmp, "%d", &max_time) != 1 || max_time < 0) usage_error(1); } } else { usage_error(1); } i += add; } else { switch (arg_state) { case 0: arg_state = 1; hostname = argv[i]; break; case 1: arg_state = 2; if (sscanf(argv[i], "%d", &port) != 1 || port < 0 || port > 65535) usage_error(1); break; default: usage_error(1); } } } if (arg_state < 1) usage_error(1); #ifndef __linux__ if (zerocopy) { fprintf(stderr, "Zero-copy is only supported under Linux.\n"); exit(1); } #endif /* Set up addr struct from hostname and port */ if ((hent = gethostbyname(hostname)) == NULL) { fprintf(stderr, "tcpsend: gethostbyname error\n"); exit(1); } memset(&addr, 0, sizeof (addr)); addr.sin_family = AF_INET; memcpy(&addr.sin_addr, hent->h_addr_list[0], 4); addr.sin_port = htons(port); /* Create buffer and fill with random data */ if (gettimeofday(&starttime, NULL) < 0) { perror("gettimeofday"); exit(1); } srand((unsigned int)(starttime.tv_usec + 1000000 * starttime.tv_sec)); if ((buf = (char *)malloc(BUFSIZE)) == NULL) { fprintf(stderr, "malloc failed\n"); exit(1); } for (i = 0; i < BUFSIZE; i += sizeof (int)) { *(int *)&buf[i] = rand(); } if (zerocopy) { if ((namebuf = malloc(64)) == NULL) { fprintf(stderr, "malloc failed\n"); exit(1); } sprintf(namebuf, "/tmp/tcpsend%d", getpid()); if ((fd = open(namebuf, O_RDWR | O_CREAT, 0600)) < 0) { perror("open"); exit(1); } for (amt = BUFSIZE; amt > 0; ) { if ((n = write(fd, buf, amt)) < 0) { perror("write"); cleanup_exit(fd, namebuf, 1); } amt -= n; } } /* Open connection */ if ((sockfd = socket(PF_INET, SOCK_STREAM, 0)) < 0) { perror("socket"); cleanup_exit(fd, namebuf, 1); } if (connect(sockfd, (struct sockaddr *)&addr, sizeof (addr)) != 0) { perror("connect"); cleanup_exit(fd, namebuf, 1); } /* Set up signal handlers */ if (max_time >= 0) { if (sigaction(SIGALRM, NULL, &act) != 0) { perror("sigaction: SIGALRM"); cleanup_exit(fd, namebuf, 1); } act.sa_handler = alarm_handler; act.sa_flags = 0; if (sigaction(SIGALRM, &act, NULL) != 0) { perror("sigaction: SIGALRM"); cleanup_exit(fd, namebuf, 1); } alarm(max_time); } if (sigaction(SIGINT, NULL, &act) != 0) { perror("sigaction: SIGINT"); cleanup_exit(fd, namebuf, 1); } act.sa_handler = int_handler; act.sa_flags = 0; if (sigaction(SIGINT, &act, NULL) != 0) { perror("sigaction: SIGINT"); cleanup_exit(fd, namebuf, 1); } /* Send random data until we hit a max */ data_sent = 0; while ((max_bytes < 0 ? 1 : data_sent < max_bytes) && !time_done && !interrupt_done) { start = rand() / (RAND_MAX / (BUFSIZE - SNDSIZE) + 1); if (max_bytes < 0) amt = SNDSIZE; else amt = min(SNDSIZE, max_bytes - data_sent); if (zerocopy) { #ifdef __linux__ if ((n = sendfile(sockfd, fd, &start, amt)) < 0 && errno != EINTR) { perror("sendfile"); cleanup_exit(fd, namebuf, 1); } else if (n == 0) { fprintf(stderr, "tcpsend: socket unexpectedly closed\n"); cleanup_exit(fd, namebuf, 1); } #endif } else { if ((n = write(sockfd, &buf[start], amt)) < 0 && errno != EINTR) { perror("write"); cleanup_exit(fd, namebuf, 1); } else if (n == 0) { fprintf(stderr, "tcpsend: socket unexpectedly closed\n"); cleanup_exit(fd, namebuf, 1); } } data_sent += n; } /* Close the socket and wait for the remote host to close */ if (shutdown(sockfd, SHUT_WR) != 0) { perror("shutdown"); cleanup_exit(fd, namebuf, 1); } err = read(sockfd, buf, 1); if (err < 0) { perror("read"); cleanup_exit(fd, namebuf, 1); } else if (err > 0) { fprintf(stderr, "warning: data read on socket\n"); } gettimeofday(&etime, NULL); time = (float)(1000000*(etime.tv_sec - starttime.tv_sec) + etime.tv_usec - starttime.tv_usec) / 1000000.0; printf("Sent %lld bytes in %f seconds\n", (long long)data_sent, time); printf("Throughput: %d B/s\n", (int)((float)data_sent / time)); cleanup_exit(fd, namebuf, 0); return 0; }
CFLAGS = -g -O2 -Wall all: tcpsend discard clean: rm -f tcpsend discard