/*
* Copyright (c) 2001-2020 Mellanox Technologies, Ltd. All rights reserved.
*
* This software is available to you under a choice of one of two
* licenses. You may choose to be licensed under the terms of the GNU
* General Public License (GPL) Version 2, available from the file
* COPYING in the main directory of this source tree, or the
* BSD license below:
*
* Redistribution and use in source and binary forms, with or
* without modification, are permitted provided that the following
* conditions are met:
*
* - Redistributions of source code must retain the above
* copyright notice, this list of conditions and the following
* disclaimer.
*
* - Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following
* disclaimer in the documentation and/or other materials
* provided with the distribution.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
/*
* How to Build: 'gcc -lrt -o tcp_lat tcp_lat.c'
*/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <getopt.h>
#include <string.h>
#include <sys/time.h>
#include <netinet/tcp.h>
#include <sys/socket.h>
#include <signal.h>
#include <netdb.h>
#include <errno.h>
#include <unistd.h>
#include <fcntl.h>
#include <pthread.h>
#include <signal.h>
#include <stdbool.h>
#include "src/utils/rdtsc.h"
#define log_dbg(fmt, args...) \
do { \
if (debug) \
printf(fmt, ##args); \
} while (0)
#define TCP_LAT_PORT 1212
#undef log_dbg
#define log_dbg(fmt, args...)
enum {
OPT_REQS_PER_RESP = 1,
OPT_USE_PERFECT_BATCH,
OPT_TIME_RR,
OPT_DELAY_TIME,
OPT_NOBLOCK,
OPT_SELECT_ON_ACCEPT,
OPT_TCP_PORT
};
static struct option long_options[] = {
{"server", 0, 0, 's'},
{"client", 1, 0, 'c'},
{"test", 1, 0, 't'},
{"msglen", 1, 0, 'l'},
{"msgnum", 1, 0, 'n'},
{"reqs-per-resp", 1, 0, OPT_REQS_PER_RESP},
{"use-perfect-batch", 0, 0, OPT_USE_PERFECT_BATCH},
{"time-rr", 0, 0, OPT_TIME_RR},
{"use-alert-poll", 0, 0, 'p'},
{"delay", 1, 0, OPT_DELAY_TIME},
{"port", 1, 0, OPT_TCP_PORT},
{"noblock", 0, 0, OPT_NOBLOCK},
{"select-on-accept", 0, 0, OPT_SELECT_ON_ACCEPT},
{"debug", 0, 0, 'd'},
{"help", 0, 0, 'h'},
};
static int debug = 0;
static int tcp_lat_pkt_size = 200;
static int max_n_msgs = 1000000;
//static int max_n_msgs = 1000;
static int reqs_per_resp = 1;
static int use_perfect_batch = 0;
static int time_rr = 0;
static int delay_time = 0;
static int noblock = 0;
static int select_on_accept = 0;
static int tcp_lat_port = TCP_LAT_PORT;
bool g_b_exit = false;
struct sigaction sigact;
//#define N_MSGS 1000000 //10000000
struct timestamp {
uint32_t secs;
uint32_t nsecs;
};
enum tcp_lat_msg_types {
TCP_LAT_MSG_TS = 0xAC
};
struct tcp_lat_msg {
uint8_t msg_type;
union {
struct timestamp ts;
};
} __attribute__((packed));
enum test_modes {
TST_BLOCKING_PING_PONG = 1,
TST_SELECT_PING_PONG,
TST_CL_THREADED_PING_PONG,
TST_MAX_TEST
};
void sig_handler(int signum)
{
if (g_b_exit) {
printf("Test end (interrupted by signal %d)", signum);
return;
}
switch (signum) {
case SIGINT:
printf("Test end (interrupted by user)");
break;
default:
printf("Test end (interrupted by signal %d)", signum);
break;
}
g_b_exit = true;
}
/* set the action taken when signal received */
void set_signal_action()
{
sigact.sa_handler = sig_handler;
sigemptyset(&sigact.sa_mask);
sigact.sa_flags = 0;
sigaction(SIGINT, &sigact, NULL);
}
static void usage()
{
printf("Usage: tcp_lat [options]\n"
"\t--test,-t <num> Test to run. Default is 1\n"
"\t--server,-s Server mode\n"
"\t--client,-c <ip> Client mode. Connect to server at <ip>\n"
"\t--msglen,-l <bytes> Message size in bytes. Default %d\n"
"\t--msgnum,-n <count> Total number of messages to send. Default %d\n"
"\t--reqs-per-resp <n> Send a responce on every nth request. Default %d\n"
"\t--use-perfect-batch Send one transaction as a one HUGE message (TCP only)\n"
"\t--time-rr Time every req/responce cycle with gettimeofday()\n"
"\t--delay <sec> Sleep <sec> between transactions\n"
"\t--noblock Use non blocking sockets\n"
"\t--select-on-accept Use select to check if socket is ready to accept()\n"
"\t--port <num> Listen/connect to port <num>. Default %d\n"
"\t--debug,-d Print extra debug info\n"
"\t--help,-h Print help and exit\n",
tcp_lat_pkt_size,
max_n_msgs,
reqs_per_resp,
tcp_lat_port
);
printf("Test types:\n"
" 1 - blocking ping pong\n"
" 2 - select() with non blocking ping pong\n"
"\n"
);
exit(1);
}
static void set_noblock(int ns)
{
int ret;
int flag;
// set it to non blocking mode
if (noblock) {
flag = fcntl(ns, F_GETFL);
if (flag < 0) {
printf("failed to get socket flags %m\n");
}
flag |= O_NONBLOCK;
ret = fcntl(ns, F_SETFL, flag);
if (ret < 0) {
printf("failed to set socket flags %m\n");
}
printf("set socket to nb mode\n");
}
}
static int do_select_on_accept(int s)
{
fd_set rfds;
int ret;
while(!g_b_exit) {
FD_ZERO(&rfds);
FD_SET(s, &rfds);
ret = select(s+1, &rfds, 0, 0, 0);
if (ret < 0 && errno == EINTR) {
printf("select interrupted\n");
continue;
}
if (ret < 0)
return -1;
if (FD_ISSET(s, &rfds))
return s;
}
return -1;
}
static int get_addr(char *dst, struct sockaddr_in *addr)
{
struct addrinfo *res;
int ret;
ret = getaddrinfo(dst, NULL, NULL, &res);
if (ret) {
printf
("getaddrinfo failed - invalid hostname or IP address\n");
return ret;
}
if (res->ai_family != PF_INET) {
ret = -1;
goto out;
}
*addr = *(struct sockaddr_in *)res->ai_addr;
out:
freeaddrinfo(res);
return ret;
}
static int tcp_read(int s, char *b, int count)
{
int n;
int nb;
nb = 0;
do {
n = read(s, b, count);
if (n == 0) {
printf("EOF?\n");
return nb;
}
if (n < 0) {
if (errno == EAGAIN) {
log_dbg("blocking read ret=%d read %d of %d = %m\n", n, nb, count);
continue;
}
printf("bad read ret=%d read %d of %d = %m(%d)\n", n, nb, count, errno);
return nb;
}
count -= n;
b += n;
nb += n;
} while (count > 0);
return nb;
}
static int tcp_write(int s, char *b, int count)
{
int n, nb;
nb = 0;
do {
n = write(s, b, count);
if (n <= 0) {
if (errno == EAGAIN) {
log_dbg("blocking write ret=%d written %d of %d = %m\n", n, nb, count);
continue;
}
printf("bad write ret=%d written %d of %d = %m(%d)\n", n, nb, count, errno);
return nb;
}
count -= n;
b += n;
nb += n;
} while (count > 0);
return nb;
}
void run_select_server()
{
int s, ns;
struct sockaddr_in addr;
int ret;
unsigned len;
char buf[tcp_lat_pkt_size];
//char batch_buf[tcp_lat_pkt_size*reqs_per_resp];
int flag;
fd_set rfds, wfds;
signal(SIGPIPE, SIG_IGN);
printf("starting TCP select() server\n");
s = socket(PF_INET, SOCK_STREAM, IPPROTO_IP);
if (s < 0) {
printf("Failed to create socket\n");
exit(1);
}
/* listen on any port */
memset(&addr, sizeof(addr), 0);
addr.sin_family = PF_INET;
addr.sin_addr.s_addr = INADDR_ANY;
addr.sin_port = htons(tcp_lat_port);
flag = 1;
setsockopt(s, SOL_SOCKET, SO_REUSEADDR, (char *) &flag, sizeof(int));
ret = bind(s, (struct sockaddr *)&addr, sizeof(addr));
if (ret < 0) {
printf("failed to bind = %m\n");
exit(1);
}
listen(s, 5);
while(!g_b_exit) {
//int flag;
printf("Waiting for connection\n");
len = sizeof(addr);
ns = accept(s, (struct sockaddr *)&addr, &len);
if (ns < 0) {
printf("accept failed = %m\n");
exit(1);
}
#if 1
flag = 1;
ret = setsockopt(ns, IPPROTO_TCP, TCP_NODELAY, (char *) &flag, sizeof(int));
if (ret < 0) {
printf("Failed to disable NAGLE\n");
}
#endif
//set_noblock(ns);
if (noblock) set_noblock(ns);
printf("connected\n");
while(!g_b_exit) {
FD_ZERO(&rfds);
FD_ZERO(&wfds);
// select()
FD_SET(ns, &rfds);
ret = select(ns+1, &rfds, 0, 0, 0);
if (ret <= 0) {
if (errno != EINTR) {
printf("select erroro %m\n");
break;
}
else {
printf("interrupted select!\n");
continue;
}
}
ret = tcp_read(ns, buf, tcp_lat_pkt_size);
if (ret < 0) {
printf("bad read? = %m (%d/%d)\n", ret, tcp_lat_pkt_size);
exit(1);
}
if (ret == 0) {
printf("EOF detected - going back to accept\n");
break;
}
// get requests till we block...
// send reply
log_dbg("Read request, sending responce\n");
ret = tcp_write(ns, buf, tcp_lat_pkt_size);
if (ret != tcp_lat_pkt_size) {
printf("partial packet write (%d != %d)\n", ret, tcp_lat_pkt_size);
exit(1);
}
log_dbg("==ack sent\n");
}
close(ns);
printf("all done\n");
}
}
static void run_tcp_server()
{
int s, ns;
struct sockaddr_in addr;
int ret, i;
unsigned len;
char buf[tcp_lat_pkt_size];
char batch_buf[tcp_lat_pkt_size*reqs_per_resp];
int flag;
signal(SIGPIPE, SIG_IGN);
printf("starting TCP server\n");
s = socket(PF_INET, SOCK_STREAM, IPPROTO_IP);
if (s < 0) {
printf("Failed to create socket\n");
exit(1);
}
/* listen on any port */
memset(&addr, sizeof(addr), 0);
addr.sin_family = PF_INET;
addr.sin_addr.s_addr = INADDR_ANY;
addr.sin_port = htons(tcp_lat_port);
flag = 1;
setsockopt(s, SOL_SOCKET, SO_REUSEADDR, (char *) &flag, sizeof(int));
ret = bind(s, (struct sockaddr *)&addr, sizeof(addr));
if (ret < 0) {
printf("failed to bind = %m\n");
exit(1);
}
listen(s, 5);
while(!g_b_exit) {
//int flag;
printf("Waiting for connection\n");
len = sizeof(addr);
if (select_on_accept) {
log_dbg("select() to check for new connection\n");
if (do_select_on_accept(s) < 0) {
printf("can not select on accept\n");
exit(1);
}
}
ns = accept(s, (struct sockaddr *)&addr, &len);
if (ns < 0) {
printf("accept failed = %m\n");
exit(1);
}
#if 1
flag = 1;
ret = setsockopt(ns, IPPROTO_TCP, TCP_NODELAY, (char *) &flag, sizeof(int));
if (ret < 0) {
printf("Failed to disable NAGLE\n");
}
#endif
if (noblock) set_noblock(ns);
printf("connected\n");
for (i = 0; i < max_n_msgs; i+=reqs_per_resp) {
int k;
uint64_t sum = 0;
if (use_perfect_batch) {
ret = tcp_read(ns, batch_buf, tcp_lat_pkt_size*reqs_per_resp);
if (ret < 0) {
printf("bad read? = %m (%d/%d)\n", ret, tcp_lat_pkt_size);
exit(1);
}
if (ret == 0) {
printf("EOF detected - going back to accept\n");
break;
}
}
for (k = 0; k < reqs_per_resp; k++) {
if (use_perfect_batch) {
memcpy(buf, batch_buf + k*tcp_lat_pkt_size, tcp_lat_pkt_size);
sum += buf[11];
}
else {
ret = tcp_read(ns, buf, tcp_lat_pkt_size);
if (ret < 0) {
printf("bad read? = %m (%d/%d)\n", ret, tcp_lat_pkt_size);
exit(1);
}
if (ret == 0) {
printf("EOF detected - going back to accept\n");
goto done;
}
log_dbg("==> trans req: %d\n", i);
}
}
//printf("Read request, sending responce\n");
ret = tcp_write(ns, buf, tcp_lat_pkt_size);
if (ret != tcp_lat_pkt_size) {
printf("partial packet write (%d != %d)\n", ret, tcp_lat_pkt_size);
exit(1);
}
log_dbg("==ack %d sent\n", i);
}
done:
close(ns);
printf("all done\n");
}
}
static int tcp_client_init(struct sockaddr_in *addr)
{
int s;
int ret;
int flag;
s = socket(PF_INET, SOCK_STREAM, IPPROTO_IP);
if (!s) {
printf("Failed to create socket\n");
exit(1);
}
addr->sin_port = htons(tcp_lat_port);
#if 1
flag = 1;
ret = setsockopt(s, IPPROTO_TCP, TCP_NODELAY, (char *) &flag, sizeof(int));
if (ret < 0) {
printf("Failed to disable NAGLE\n");
}
#endif
ret = connect(s, (struct sockaddr *)addr, sizeof(*addr));
if (ret < 0) {
printf("connect failed\n");
exit(1);
}
if (noblock) set_noblock(s);
return s;
}
static struct timeval _st, _et;
pthread_spinlock_t lck;
static unsigned tx_cnt;
static uint64_t _total_usec, _n_rr;
static void take_ts(struct tcp_lat_msg *m)
{
struct timeval dt;
m->msg_type = TCP_LAT_MSG_TS;
gettimeofday(&dt, 0);
m->ts.secs = dt.tv_sec;
m->ts.nsecs = dt.tv_usec * 1000;
// printf("tx start: sec: %u usec: %u\n", m->ts.secs, m->ts.nsecs/1000);
}
void *tcp_rep_handler(void *arg)
{
unsigned long s = (unsigned long)arg;
int ret, i;
char buf[tcp_lat_pkt_size];
unsigned rx_cnt = 0;
for (i = 0; i < max_n_msgs; i++) {
log_dbg("==> waiting for resp: %d\n", i);
//pthread_spin_lock(&lck);
// log_dbg("==> waiting for resp1: %d\n", i);
// while(tx_cnt <= rx_cnt);
// log_dbg("==> waiting for resp2: %d\n", i);
ret = tcp_read(s, buf, tcp_lat_pkt_size);
log_dbg("==> resp1: %d\n", i);
//pthread_spin_unlock(&lck);
if (ret != tcp_lat_pkt_size) {
printf("resp: %d partial packet read (%d != %d)\n", i, ret, tcp_lat_pkt_size);
//exit(1);
break;
}
if (time_rr) {
struct timeval st_rr, et_rr, dt_rr;
struct tcp_lat_msg *m = (struct tcp_lat_msg *)buf;
if (m->msg_type != TCP_LAT_MSG_TS) {
printf("expect timestamped packet\n");
exit(1);
}
gettimeofday(&et_rr, 0);
st_rr.tv_sec = m->ts.secs;
st_rr.tv_usec = m->ts.nsecs/1000;
//printf("rx start: sec: %u usec: %u\n", m->ts.secs, m->ts.nsecs/1000);
//printf("RX NOW: sec: %ld usec: %ld\n", et_rr.tv_sec, et_rr.tv_usec);
timersub(&et_rr, &st_rr, &dt_rr);
_total_usec += dt_rr.tv_sec * 1000000 + dt_rr.tv_usec;
//printf("DELTA: %ld\n", dt_rr.tv_sec * 1000000 + dt_rr.tv_usec);
_n_rr++;
}
log_dbg("==> resp: %d\n", i);
rx_cnt++;
}
gettimeofday(&_et, 0);
return 0;
}
static void run_tcp_threaded_client(struct sockaddr_in *addr)
{
int s;
char buf[tcp_lat_pkt_size];
pthread_t tid;
struct timeval dt;
int i, ret;
struct tcp_lat_msg *msg;
if ((unsigned)tcp_lat_pkt_size < sizeof(*msg)) {
printf("message size is too small\n");
exit(1);
}
pthread_spin_init(&lck, 0);
printf("running client in thread per read/thread per write mode\n");
s = tcp_client_init(addr);
if (!s) {
printf("Failed to create socket\n");
exit(1);
}
// spawn reader thread
pthread_create(&tid, 0, tcp_rep_handler, (void *)(unsigned long)s);
gettimeofday(&_st, 0);
for (i = 0; i < max_n_msgs; i++) {
log_dbg("==> write req: %d\n", i);
//pthread_spin_lock(&lck);
log_dbg("==> write req1: %d\n", i);
msg = (struct tcp_lat_msg *)buf;
if (time_rr)
take_ts(msg);
ret = tcp_write(s, buf, tcp_lat_pkt_size);
log_dbg("==> done write req1: %d\n", i);
//pthread_spin_unlock(&lck);
if (ret < 0) {
printf("partial packet write (%d != %d)\n", ret, tcp_lat_pkt_size);
exit(1);
}
log_dbg("==> done write req: %d\n", i);
tx_cnt++;
}
pthread_join(tid, 0);
timersub(&_et, &_st, &dt);
printf("%d message processed in %u s %u usec\n", max_n_msgs, (unsigned)dt.tv_sec, (unsigned)dt.tv_usec);
printf("Average latency is: %1.2lf usec\n", (double)(dt.tv_sec * 1000000 + dt.tv_usec)/(max_n_msgs+max_n_msgs));
printf("Speed is: %1.2lf msg/sec\n", 1000000*(double)(max_n_msgs + max_n_msgs)/(dt.tv_sec * 1000000 + dt.tv_usec));
if (time_rr) {
printf("Average latency: %1.2f usec\n", (double)_total_usec/(2*_n_rr));
}
}
static void run_tcp_client(struct sockaddr_in *addr)
{
int s;
int ret, i;
char buf[tcp_lat_pkt_size];
char batch_buf[tcp_lat_pkt_size*reqs_per_resp];
struct timeval st, et, dt;
//struct timeval st_rr, et_rr, dt_rr;
struct timespec st_rr, et_rr, dt_rr;
uint64_t total_usec, n_rr;
printf("starting TCP client\n");
s = tcp_client_init(addr);
if (!s) {
printf("Failed to create socket\n");
exit(1);
}
gettimeofday(&st, 0);
total_usec = n_rr = 0;
ts_clear(&st_rr);
ts_clear(&et_rr);
ts_clear(&dt_rr);
//printf("Starting run\n");
for (i = 0; i < max_n_msgs && !g_b_exit; i+=reqs_per_resp) {
log_dbg("==> write req\n");
int k;
if (time_rr) {
gettimefromtsc(&st_rr);
//gettimeofday(&st_rr, 0);
}
for (k = 0; k < reqs_per_resp; k++) {
if (!use_perfect_batch) {
ret = tcp_write(s, buf, tcp_lat_pkt_size);
if (ret < 0) {
printf("partial packet write (%d != %d)\n", ret, tcp_lat_pkt_size);
exit(1);
}
}
else
memcpy(batch_buf + k * tcp_lat_pkt_size, buf, tcp_lat_pkt_size);
}
if (use_perfect_batch) {
ret = tcp_write(s, batch_buf, tcp_lat_pkt_size*reqs_per_resp);
if (ret < 0) {
printf("partial packet write (%d != %d)\n", ret, tcp_lat_pkt_size);
exit(1);
}
}
log_dbg("==> write req done - waiting for resp resp\n");
ret = tcp_read(s, buf, tcp_lat_pkt_size);
if (ret != tcp_lat_pkt_size) {
printf("partial packet read (%d != %d)\n", ret, tcp_lat_pkt_size);
exit(1);
}
if (time_rr) {
gettimefromtsc(&et_rr);
//gettimeofday(&et_rr, 0);
//timersub(&et_rr, &st_rr, &dt_rr);
ts_sub(&et_rr, &st_rr, &dt_rr);
total_usec += dt_rr.tv_sec * 1000000000L + dt_rr.tv_nsec;
n_rr++;
}
log_dbg("all rcvd\n");
if (delay_time)
sleep(delay_time);
}
gettimeofday(&et, 0);
timersub(&et, &st, &dt);
printf("%d message processed in %u s %u usec\n", max_n_msgs, (unsigned)dt.tv_sec, (unsigned)dt.tv_usec);
//printf("Average latency is: %1.2lf usec\n", (double)(dt.tv_sec * 1000000 + dt.tv_usec)/(max_n_msgs+max_n_msgs/reqs_per_resp));
printf("Speed is: %1.2lf msg/sec\n", 1000000*(double)(max_n_msgs + max_n_msgs/reqs_per_resp)/(dt.tv_sec * 1000000 + dt.tv_usec));
if (time_rr) {
printf("Average ***latency: %1.3f usec\n", (double)total_usec/(2*n_rr*1000));
}
close(s);
printf("client done\n");
}
int main(int argc, char *argv[])
{
int op;
int option_index;
int server_mode = -1;
struct sockaddr_in server_addr;
int ret;
int poll_mode = 0;
int testn = 1;
(void)poll_mode;
while ((op = getopt_long(argc, argv, "psc:dhl:n:t:", long_options, &option_index)) != -1) {
switch (op) {
case 'c':
if (server_mode == 1) {
printf("can not run both in server and client mode\n");
exit(1);
}
ret = get_addr(optarg, &server_addr);
if (ret < 0) {
printf("Failed to resolve server address\n");
exit(1);
}
server_mode = 0;
break;
case 's':
if (server_mode == 0) {
printf("can not run both in server and client mode\n");
exit(1);
}
server_mode = 1;
break;
case 'l':
tcp_lat_pkt_size = atoi(optarg);
if (tcp_lat_pkt_size <= 0) {
printf("Invalid packed size value\n");
exit(1);
}
break;
case 'n':
max_n_msgs = atoi(optarg);
if (max_n_msgs <= 0) {
printf("Invalind number of messages\n");
exit(1);
}
break;
case OPT_REQS_PER_RESP:
reqs_per_resp = atoi(optarg);
break;
case OPT_USE_PERFECT_BATCH:
use_perfect_batch = 1;
break;
case OPT_TIME_RR:
time_rr = 1;
break;
case OPT_DELAY_TIME:
delay_time = atoi(optarg);
break;
case OPT_NOBLOCK:
noblock = 1;
printf("using non blocking sockets");
break;
case OPT_SELECT_ON_ACCEPT:
select_on_accept = 1;
printf("Use select to check for new connections\n");
break;
case OPT_TCP_PORT:
tcp_lat_port = atoi(optarg);
printf("Use port %d\n", tcp_lat_port);
break;
case 't':
testn = atoi(optarg);
if (testn <= 0 || testn >= TST_MAX_TEST) {
printf("uknown test number: %d\n", testn);
exit(1);
}
printf("Test number %d\n", atoi(optarg));
break;
case 'd':
debug = 1;
break;
case 'p':
poll_mode = 1;
break;
case 'h':
default:
usage();
}
}
if (server_mode == -1) {
printf("Must choose either client (-c) or server (-s) mode\n");
exit(1);
}
set_signal_action();
// force tsc init
struct timespec ts;
gettimefromtsc(&ts);
switch (testn) {
case TST_BLOCKING_PING_PONG:
if (server_mode) {
run_tcp_server();
}
else {
run_tcp_client(&server_addr);
}
return 0;
case TST_SELECT_PING_PONG:
if (server_mode) {
run_select_server();
}
else {
run_tcp_client(&server_addr);
}
return 0;
case TST_CL_THREADED_PING_PONG:
if (server_mode) {
printf("only works in client mode\n");
exit(1);
}
run_tcp_threaded_client(&server_addr);
return 0;
default:
printf("bad test number %d\n", testn);
}
return 0;
}