在按下 Enter 之前线程函数不会终止

thread function doesn't terminate until Enter is pressed

以下代码(最后)表示从远程客户端接收 ls 命令并将当前工作目录发送到该客户端的线程函数。

发送成功但有一个问题: 当它完全停止发送时,我希望它重新开始收听。在行:

printf("Enter 1 if you want to exit or 0 if you don't: "); 
                 fgets(exit_status,MAX_SIZE,stdin);

当我按 Enter

时卡住并终止(并启动另一个线程)

我不明白为什么?在调试时,我看到上面的打印语句在按 Enter 后执行,尽管调试器处于函数末尾(意味着它通过了此打印语句)。

我想让它在发送完数据后自动重新开始监听。

如果有人想在这里查看我的完整代码是 link:https://pastebin.com/9UmTkPge

void  *server_socket_ls(void *arg) {
    int* exit_status = (int*)malloc(sizeof(int));
    *exit_status = 0;   
    while (*exit_status == 0) {
    //socket code is here
        
    //code for ls
        
    char buffer[BUFFSIZE];
    int received = -1;
    char data[MAX];
    memset(data,0,MAX);
       // this will make server wait for another command to run until it receives exit
        data[0] = '[=11=]';
        if((received = recv(new_socket, buffer,BUFFSIZE,0))<0){
    
            perror("Failed");
        }
    
        buffer[received] = '[=11=]';
    
        strcat (data,  buffer);
        if (strcmp(data, "exit")==0) // this will force the code to exit
            exit(0);
    
        puts (data);
            char *args[100];
            setup(data,args,0);
            int pipefd[2],lenght;
    
            if(pipe(pipefd))
                perror("Failed to create pipe");
    
            pid_t pid = fork();
            char path[MAX];
    
            if(pid==0)
            {
                close(1); // close the original stdout
                dup2(pipefd[1],1); // duplicate pipfd[1] to stdout
                close(pipefd[0]); // close the readonly side of the pipe
                close(pipefd[1]); // close the original write side of the pipe
                execvp(args[0],args); // finally execute the command
            }
            else
                if(pid>0)
                {
                    close(pipefd[1]);
                    memset(path,0,MAX);
    
                    while(lenght=read(pipefd[0],path,MAX-1)){
                        printf("Data read so far %s\n", path);
                        if(send(new_socket,path,strlen(path),0) != strlen(path) ){
                            perror("Failed");
                        }
                        //fflush(NULL);
                        printf("Data sent so far %s\n", path);
                        memset(path,0,MAX);
                        
                    }
    
                    close(pipefd[0]);
                    //removed so server will not terminate
                              
                }
                else 
                {
                    printf("Error !\n");
                    exit(0);
                }
                 printf("Enter 1 if you want to exit or 0 if you don't: "); 
                 fgets(exit_status,MAX_SIZE,stdin);    
                
        }
    
    }

许多个错误:

  1. terminal_thread中,input_command在每次循环迭代时分配——内存泄漏
  2. 去除换行符的代码已损坏
  3. 使用 .l 指定 IP 地址会导致段错误,因为 tokenNULL
  4. terminal_thread.l的端口号是5126,匹配相应服务器代码
  5. 中的9191
  6. 连接后,server_socket_file做任何事情。
  7. server_socket_ls 中,它 socketbindlistenaccept 上循环 。循环应该在 listen 之后 开始(即只在循环中执行 accept 并且 重用 监听套接字)。
  8. 代码中标记的其他错误

我不得不重构代码并添加一些调试。它用错误注释。我使用 cpp 条件来表示旧代码与新代码:

#if 0
// old code
#else
// new code
#endif

#if 1
// new code
#endif

这是代码。我得到了 minimal .l(远程 ls)工作:

编辑: 由于下面的更新 运行 超出了 SO space 限制,我删除了我在此处发布的第一个代码块。


这是 debug.txt 输出:

term term: PROMPT
term term: FGETS
  ls ls: ENTER
  ls ls: SOCKET
file file: ENTER
  ls ls: BIND prtNum=9191
file file: BIND portNum=6123
  ls ls: LISTEN
term term: COMMAND '.l'
term term: port=9191
  ls ls: ACCEPTED
term term: PROMPT

This program is exiting as soon as its stops sending data at exit(0) and so doesn't ask for exit_status. Is there a way somehow to make it not stop and instead the terminal prompt reappears along with servers listening at the back? – Dragut

因为我感觉到了紧迫性,所以我犯了错误,现在部分解决方案比为时已晚的完美解决方案更好。

我可能在 ls 服务器父进程(现已修复)中引入了一个带有无关 exit 调用的错误。

但是,还有其他问题...

主要问题是服务器(对于ls)提示用户是否继续(在stdout/stdin).这不太管用。

应该提示用户的是客户端(即terminal_thread)。或者,正如我所做的那样,客户端将在命令提示符处看到 exit,然后 发送 一个带有“exit”的数据包到服务器,然后终止。然后,服务器将看到此命令并终止。

我在没有完全重做所有内容的情况下尽可能多地进行了重构。

我将一些代码拆分成函数。一些 can/could 被重用来实现“文件”服务器。

但是,我会将两个 函数放入单个服务器线程中。我会让服务器查看它获取的“命令”,并根据命令执行任一操作。由于在“文件”服务器中没有实际执行某些操作的代码[还],因此很难返工。

有一件事要解决[我有时间]:

.l 命令的格式为:.l [ip_address]ip_address 的默认值为 127.0.0.1。但是,这应该是 split 成两个命令(例如):

  1. 附加 [ip_address]
  2. ls [ls 参数]

无论如何,这是更新后的代码。我不得不移动得有点快,所以它没有我想要的那么干净。

#include <netinet/in.h>
#include <pthread.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <stdbool.h>
#include <stdarg.h>

#if 1
#include <time.h>
#endif

#define BACKLOG 10
#define MAX_SIZE 200
#define BACKLOG 10
#define BUFFSIZE 2048
#define MAXPENDING 5
#define MAX 2048

__thread char *tid;
__thread char dbgstrbuf[1000];

FILE *xfdbg;
double tsczero = 0.0;

typedef struct server_arg {
    int portNum;
} server_arg;
typedef struct server_arg1 {
    int portNum;
} server_arg1;

double
tscgetf(void)
{
    struct timespec ts;
    double sec;

    clock_gettime(CLOCK_MONOTONIC,&ts);

    sec = ts.tv_nsec;
    sec /= 1e9;
    sec += ts.tv_sec;

    sec -= tsczero;

    return sec;
}

void
dbgprt(const char *fmt,...)
{
    va_list ap;
    char msg[1000];
    char *bp = msg;

    bp += sprintf(bp,"[%.9f/%4s] ",tscgetf(),tid);
    va_start(ap,fmt);
    bp += vsprintf(bp,fmt,ap);
    va_end(ap);

    fputs(msg,xfdbg);
}

const char *
dbgstr(const char *str,int len)
{
    char *bp = dbgstrbuf;

    if (len < 0)
        len = strlen(str);

    bp += sprintf(bp,"'");

    for (int i = 0;  i < len;  ++i) {
        int chr = str[i];
        if ((chr > 0x20) && (chr <= 0x7E))
            bp += sprintf(bp,"%c",chr);
        else
            bp += sprintf(bp,"{%2.2X}",chr);
    }

    bp += sprintf(bp,"'");

    return dbgstrbuf;
}

void
setup(char inputBuffer[], char *args[], int *background)
{
    const char s[4] = " \t\n";
    char *token;

    token = strtok(inputBuffer, s);
    int i = 0;

    while (token != NULL) {
        args[i] = token;
        i++;
        // printf("%s\n", token);
        token = strtok(NULL, s);
    }
    args[i] = NULL;
}

int
open_remote(const char *ip,unsigned short port)
{
    int sock;
    struct sockaddr_in echoserver;

    dbgprt("open_remote: ENTER ip=%s port=%u\n",dbgstr(ip,-1),port);

    if ((sock = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP)) < 0) {
        perror("Failed to create socket");
        exit(1);
    }
    int enable = 1;

    if (setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &enable,
        sizeof(int)) < 0) {
        perror("error");
    }
    memset(&echoserver, 0, sizeof(echoserver));
    echoserver.sin_family = AF_INET;
    echoserver.sin_addr.s_addr = inet_addr(ip);

// NOTE/BUG: this port number does _not_ match any server port
#if 0
    echoserver.sin_port = htons(5126);
#else
    dbgprt("term: port=%u\n",port);
    echoserver.sin_port = htons(port);
#endif

    if (connect(sock, (struct sockaddr *) &echoserver,
        sizeof(echoserver)) < 0) {
        perror("Failed to connect with server");
        exit(1);
    }

    dbgprt("open_remote: EXIT sock=%d\n",sock);

    return sock;
}

void *
terminal_thread(void *arg)
{
// NOTE/FIX: do this _once_
#if 1
    char *input_command = malloc(MAX_SIZE);
#endif

    tid = "term";

    char buffer[BUFFSIZE];
    int sock_ls = -1;

    while (1) {
        dbgprt("term: PROMPT\n");

        printf(">> ");
//memset(input_command,0,strlen(str));
// NOTE/BUG: this is a memory leak
#if 0
        char *input_command = malloc(MAX_SIZE);
#endif

        dbgprt("term: FGETS\n");
        fgets(input_command, MAX_SIZE, stdin);

// NOTE/BUG: code is broken to strip newline
#if 0
        if ((strlen(input_command) > 0) &&
            (input_command[strlen(input_command) - 1] == '\n'))
            input_command[strlen(input_command) - 1] = '[=12=]';
#else
        input_command[strcspn(input_command,"\n")] = 0;
#endif

        dbgprt("term: COMMAND %s\n",dbgstr(input_command,-1));

        char list[] = "ls";
        char cp[] = "cp";

#if 0
        char s[100];
        printf("%s\n", getcwd(s,100));
        chdir("Desktop");
        printf("%s\n", getcwd(s,100));
#endif

        // exit program (and exit server)
        if (strcmp(input_command,"exit") == 0) {
            if (sock_ls >= 0) {
                dbgprt("term: SENDEXIT\n");
                if (send(sock_ls,"exit",4,0) < 0) {
                    perror("send/exit");
                    exit(1);
                }
                break;
            }
        }

        if (strcmp(input_command, list) == 0) {
            // ls code will run here

        }

        if ((input_command[0] == '.') && (input_command[1] == 'l')) {
            printf("remote ls\n");
            char ip[20];
            const char c[2] = " ";

            // strcpy(str,input_command);
            char *token;

            // get the first token
            token = strtok(input_command, c);

            // walk through other tokens
            int i = 0;

            while (token != NULL && i != -1) {
                token = strtok(NULL, c);
                i--;
            }

#if 1
            if (token == NULL) {
                token = "127.0.0.1";
                printf("no IP address found -- using %s\n",token);
            }
#endif

            if (sock_ls < 0)
                sock_ls = open_remote(token,9191);

            char s[100];

            strcpy(s, "ls");

// NOTE/BUG: this blows away the "s" in "ls" because s is _set_ with strcpy
#if 0
            s[strlen(s) - 1] = '[=12=]';    // fgets doesn't automatically discard '\n'
#endif
            unsigned int echolen;
            echolen = strlen(s);

            int received = 0;

            /* send() from client; */
            if (send(sock_ls, s, echolen, 0) != echolen) {
                perror("Mismatch in number of sent bytes");
            }

            fprintf(stdout, "Message from server: ");

            int bytes = 0;

            /* recv() from server; */
            if ((bytes = recv(sock_ls, buffer, echolen, 0)) < 1) {
                perror("Failed to receive bytes from server");
            }
            received += bytes;
            buffer[bytes] = '[=12=]';
            /* Assure null terminated string */
            fprintf(stdout, buffer);

            bytes = 0;
// this d {...} while block will receive the buffer sent by server
            do {
                buffer[bytes] = '[=12=]';
                printf("%s\n", buffer);
            } while ((bytes = recv(sock_ls, buffer, BUFFSIZE - 1, 0)) >= BUFFSIZE - 1);
            buffer[bytes] = '[=12=]';
            printf("%s\n", buffer);
            printf("\n");

            continue;
        }
    }

    dbgprt("term: EXIT\n");

    return (void *) 0;
}

int
ls_loop(int new_socket)
{

    dbgprt("ls_loop: ENTER new_socket=%d\n",new_socket);

//code for ls

    char buffer[BUFFSIZE];
    int received = -1;
    char data[MAX];

    int stop = 0;

    while (1) {
        memset(data, 0, MAX);
        // this will make server wait for another command to run until it
        // receives exit
        data[0] = '[=12=]';

        if ((received = recv(new_socket, buffer, BUFFSIZE, 0)) < 0) {
            perror("Failed");
        }
        buffer[received] = '[=12=]';

        strcpy(data, buffer);
        dbgprt("ls_loop: COMMAND %s\n",dbgstr(data,-1));

        // this will force the code to exit
#if 0
        if (strcmp(data, "exit") == 0)
            exit(0);
        puts(data);
#else
        if (strncmp(data, "exit", 4) == 0) {
            dbgprt("ls_loop: EXIT/COMMAND\n");
            stop = 1;
            break;
        }
#endif

        char *args[100];

        setup(data, args, 0);
        int pipefd[2], length;

        if (pipe(pipefd))
            perror("Failed to create pipe");

        pid_t pid = fork();
        char path[MAX];

        if (pid == 0) {
// NOTE/BUG: no need to close before dup2
#if 0
            close(1);                   // close the original stdout
#endif
            dup2(pipefd[1], 1);         // duplicate pipfd[1] to stdout
            close(pipefd[0]);           // close the readonly side of the pipe
            close(pipefd[1]);           // close the original write side of the pipe
            execvp(args[0], args);      // finally execute the command
            exit(1);
        }

        if (pid < 0) {
            perror("fork");
            exit(1);
        }

        dbgprt("ls_loop: PARENT\n");
        close(pipefd[1]);

        while (length = read(pipefd[0], path, MAX - 1)) {
            dbgprt("ls_loop: DATAREAD %s\n",dbgstr(path,length));

            if (send(new_socket, path, length, 0) != length) {
                perror("Failed");
            }

            memset(path, 0, MAX);
        }

        close(pipefd[0]);
    }

    dbgprt("ls_loop: EXIT stop=%d\n",stop);
}

void *
server_socket_ls(void *arg)
{

    tid = "ls";

    dbgprt("lsmain: ENTER\n");

    do {
        server_arg *s = (server_arg *) arg;
        int server_fd, new_socket;
        struct sockaddr_in address;
        int addrlen = sizeof(address);

        dbgprt("lsmain: SOCKET\n");

        // Creating socket file descriptor
        if ((server_fd = socket(AF_INET, SOCK_STREAM, 0)) == 0) {
            perror("socket failed");
            exit(EXIT_FAILURE);
        }
        int enable = 1;

        if (setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR, &enable,
            sizeof(int)) < 0) {
            perror("error");
        }

        address.sin_family = AF_INET;
        address.sin_addr.s_addr = INADDR_ANY;
        address.sin_port = htons(s->portNum);

        dbgprt("lsmain: BIND prtNum=%u\n",s->portNum);
        if (bind(server_fd, (struct sockaddr *) &address, sizeof(address))
            < 0) {
            perror("bind failed");
        }

        dbgprt("lsmain: LISTEN\n");
        if (listen(server_fd, 3) < 0) {
            perror("listen");
        }

        while (1) {
            if ((new_socket = accept(server_fd, (struct sockaddr *) &address,
                (socklen_t *) & addrlen)) < 0) {
                perror("accept");
            }

            dbgprt("lsmain: ACCEPTED\n");

            int stop = ls_loop(new_socket);
            close(new_socket);

            if (stop) {
                dbgprt("lsmain: STOP\n");
                break;
            }
        }
    } while (0);

    dbgprt("lsmain: EXIT\n");

    return (void *) 0;
}

void *
server_socket_file(void *arg)
{

    tid = "file";

    dbgprt("file: ENTER\n");

    server_arg1 *s1 = (server_arg1 *) arg;
    int server_fd, new_socket;
    struct sockaddr_in address;
    int addrlen = sizeof(address);

    // Creating socket file descriptor
    if ((server_fd = socket(AF_INET, SOCK_STREAM, 0)) == 0) {
        perror("socket failed");
        exit(EXIT_FAILURE);
    }
    int enable = 1;

    if (setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR, &enable, sizeof(int))
        < 0) {
        perror("error");
    }

    address.sin_family = AF_INET;
    address.sin_addr.s_addr = INADDR_ANY;
    address.sin_port = htons(s1->portNum);

    dbgprt("file: BIND portNum=%u\n",s1->portNum);

    if (bind(server_fd, (struct sockaddr *) &address, sizeof(address)) < 0) {
        perror("bind failed");

    }
    if (listen(server_fd, 3) < 0) {
        perror("listen");
    }

    if ((new_socket = accept(server_fd, (struct sockaddr *) &address,
        (socklen_t *) & addrlen)) < 0) {
        perror("accept");
    }

    printf("Server Connected\n");
}

int
main(int argc, char const *argv[])
{

    tid = "main";

    tsczero = tscgetf();

    server_arg *s = (server_arg *) malloc(sizeof(server_arg));
    server_arg1 *s1 = (server_arg1 *) malloc(sizeof(server_arg1));
    pthread_t id_1;
    pthread_t id_2;
    pthread_t id_3;

    xfdbg = fopen("debug.txt","w");
    setlinebuf(xfdbg);

    if (pthread_create(&id_3, NULL, terminal_thread, NULL) != 0) {
        perror("pthread_create");
    }

// NOTE/BUG: this port (or the one below) doesn't match the client code
// port of 5126
    s->portNum = 9191;
    pthread_create(&id_1, NULL, server_socket_ls, s);

    s1->portNum = 6123;
    if (0)
        pthread_create(&id_2, NULL, server_socket_file, s1);

    pthread_join(id_1, NULL);
    if (0)
        pthread_join(id_2, NULL);
    pthread_join(id_3, NULL);

// NOTE/BUG: pthread_exit in main thread is wrong
#if 0
    pthread_exit(0);
#else

    fclose(xfdbg);

    return 0;
#endif
}

更新:

Feedback 2: the program does make terminal thread to reappear, but it doesn't listen anymore. When I tried to send ls command again from remote pc, it just blocks (and debugging shows it is because it gets stuck at blocking receive function). – Dragut

我试图避免过多的重构,但现在,我添加了更多更改。这个版本几乎是一个完整的重构:

  1. pthread_create 在测试时没问题,但如果服务器在 不同的 系统上,则不够通用。
  2. 通常,客户端和服务器是独立的程序(例如,我们在不同的window或从systemd启动服务器)。
  3. 服务器通常会创建一个subprocess/subthread来传输请求(下面,我做了一个fork但是服务器可以做pthread_create).
  4. 此子进程处理 accept 之后的所有内容,因此服务器主进程可以自由循环 accept 并同时拥有多个客户端。
  5. 因为我们正在使用 stream 套接字(例如 TCP),每一方都需要知道何时 停止 读取。通常是创建一个 struct ,它是要遵循的数据的描述符(例如下面的 xmsg_t ),它具有“类型”和“有效负载长度”。
  6. sent/received 的有效载荷数据的每一位都以这样的描述符为前缀。
  7. 换句话说,我们需要一个简单的“协议”

现在,我们需要两个 windows(它们可以在不同的系统上):

  1. 启动服务器:./myprogram -s
  2. 启动客户端:./myprogram

这是重构后的代码。注释为:

#include <netinet/in.h>
#include <pthread.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <stdbool.h>
#include <stdarg.h>

#if 1
#include <errno.h>
#include <time.h>
#include <sys/file.h>
#include <sys/wait.h>
#endif

#define MAXBUFF     2048                // max buffer size
#define MAXPENDING  5                   // max number of connections (listen)
#define MAXARG      100                 // max number of args
#define PORTNO      9191                // default port number
#if 0
#define STOP_SIGNO  SIGTERM             // stop signal to use
#else
#define STOP_SIGNO  SIGHUP              // stop signal to use
#endif

#define CLOSEME(_fd) \
    do { \
        dbgprt("CLOSEME fd=%d (" #_fd ")\n",_fd); \
        if (_fd >= 0) \
            close(_fd); \
        _fd = -1; \
    } while (0)

int opt_h;                              // 1=send HELO message
int opt_s;                              // 1=doserver, 0=doclient
int opt_n;                              // 1=run server command in foreground

char ipaddr[100] = { "127.0.0.1" };
unsigned short portno = PORTNO;
pid_t server_pid;                       // pid of server main process
volatile int server_signo;              // signal received by server main

__thread char *tid;
__thread char dbgstrbuf[MAXBUFF + 1];

int dbgfd = -1;
double tsczero = 0.0;

typedef struct {
    int xmsg_type;
    int xmsg_paylen;
} xmsg_t;

enum {
    XMSG_NOP,
    XMSG_CMD,
    XMSG_DATA,
    XMSG_EOF,
};

double
tscgetf(void)
{
    struct timespec ts;
    double sec;

    clock_gettime(CLOCK_MONOTONIC,&ts);

    sec = ts.tv_nsec;
    sec /= 1e9;
    sec += ts.tv_sec;

    sec -= tsczero;

    return sec;
}

#if _USE_ZPRT_
#ifndef DEBUG
#define DEBUG   1
#endif
#endif

#if DEBUG
#define dbgprt(_fmt...) \
    xdbgprt(__FUNCTION__,_fmt)
#else
#define dbgprt(_fmt...) \
    do { } while (0)
#endif

void
xdbgprt(const char *fnc,const char *fmt,...)
{
    va_list ap;
    char msg[MAXBUFF * 4];
    char *bp = msg;
    int sverr = errno;

    bp += sprintf(bp,"[%.9f/%4s] %s: ",tscgetf(),tid,fnc);
    va_start(ap,fmt);
    bp += vsprintf(bp,fmt,ap);
    va_end(ap);

    // when doing forks, we have to lock the stream to guarantee atomic,
    // non-interspersed messages that are sequential
    flock(dbgfd,LOCK_EX);
    lseek(dbgfd,0,2);

    ssize_t remlen = bp - msg;
    ssize_t curlen;
    for (bp = msg;  remlen > 0;  remlen -= curlen, bp += curlen) {
        curlen = write(dbgfd,bp,remlen);
        if (curlen < 0) {
            perror("xdbgprt");
            break;
        }
    }

    flock(dbgfd,LOCK_UN);

    errno = sverr;
}

const char *
dbgstr(const char *str,int len)
{
    char *bp = dbgstrbuf;

    if (len < 0)
        len = strlen(str);

    bp += sprintf(bp,"'");

    for (int i = 0;  i < len;  ++i) {
        int chr = str[i];
        if ((chr > 0x20) && (chr <= 0x7E))
            bp += sprintf(bp,"%c",chr);
        else
            bp += sprintf(bp,"{%2.2X}",chr);
    }

    bp += sprintf(bp,"'");

    return dbgstrbuf;
}

// tokenize -- convert buffer to tokens
int
tokenize(char **argv,const char *cmdbuf)
{
    static char tokbuf[MAXBUFF];
    char **av = argv;

    strcpy(tokbuf,cmdbuf);

    char *token = strtok(tokbuf," ");
    while (token != NULL) {
        *av++ = token;
        token = strtok(NULL," ");
    }

    *av = NULL;

    return (av - argv);
}

// xsend -- send buffer (guaranteed delivery)
ssize_t
xsend(int sock,const void *vp,size_t buflen,int flags)
{
    const char *buf = vp;
    ssize_t curlen;
    ssize_t totlen = 0;

    dbgprt("ENTER buflen=%zu flags=%8.8X\n",buflen,flags);

    for (;  totlen < buflen;  totlen += curlen) {
        dbgprt("LOOP totlen=%zd\n",totlen);
        curlen = send(sock,&buf[totlen],buflen - totlen,flags);
        if (curlen <= 0)
            break;
    }

    dbgprt("EXIT totlen=%zd\n",totlen);

    return totlen;
}

// xrecv -- receive buffer (guaranteed delivery)
ssize_t
xrecv(int sock,void *vp,size_t buflen,int flags)
{
    char *buf = vp;
    ssize_t curlen;
    ssize_t totlen = 0;

    dbgprt("ENTER buflen=%zu flags=%8.8X\n",buflen,flags);

    for (;  totlen < buflen;  totlen += curlen) {
        dbgprt("LOOP totlen=%zu\n",totlen);
        curlen = recv(sock,&buf[totlen],buflen - totlen,flags);
        if (curlen <= 0)
            break;
    }

    dbgprt("EXIT totlen=%zd\n",totlen);

    return totlen;
}

// open_remote -- client open connection to server
int
open_remote(const char *ip,unsigned short port)
{
    int sock;
    struct sockaddr_in echoserver;

    dbgprt("ENTER ip=%s port=%u\n",dbgstr(ip,-1),port);

    if ((sock = socket(PF_INET,SOCK_STREAM,IPPROTO_TCP)) < 0) {
        perror("Failed to create socket");
        exit(1);
    }

// NOTE/BUG: only server (who does bind) needs to do this
#if 0
    int enable = 1;
    if (setsockopt(sock,SOL_SOCKET,SO_REUSEADDR,&enable,sizeof(enable)) < 0) {
        perror("error");
    }
#endif

    memset(&echoserver,0,sizeof(echoserver));
    echoserver.sin_family = AF_INET;
    echoserver.sin_addr.s_addr = inet_addr(ip);

    echoserver.sin_port = htons(port);

    if (connect(sock,(struct sockaddr *) &echoserver,sizeof(echoserver)) < 0) {
        perror("Failed to connect with server");
        exit(1);
    }

    dbgprt("EXIT sock=%d\n",sock);

    return sock;
}

// send_cmd -- client send command to server and process reply
void
send_cmd(int type,const char *cmd,int paylen)
{
    int sock;
    xmsg_t xmsg;
    char buffer[MAXBUFF];

    dbgprt("ENTER type=%d\n",type);

    // open socket to remote server
    sock = open_remote(ipaddr,portno);

    // send command descriptor
    xmsg.xmsg_type = type;
    if (paylen < 0)
        paylen = strlen(cmd);
    xmsg.xmsg_paylen = paylen;
    xsend(sock,&xmsg,sizeof(xmsg),0);

    // send command payload
    xsend(sock,cmd,xmsg.xmsg_paylen,0);

    fprintf(stdout,"Message from server:\n");

    int received = 0;
    int bytes;

    // get all data that the server sends back
    while (1) {
        dbgprt("LOOP\n");

        // get descriptor for next chunk
        xrecv(sock,&xmsg,sizeof(xmsg),0);

        // handle EOF from server
        if (xmsg.xmsg_paylen <= 0)
            break;

        // get payload
        bytes = recv(sock,buffer,xmsg.xmsg_paylen,0);
        dbgprt("RCVD bytes=%d\n",bytes);

#if 0
        if (bytes == 0)
            break;
#endif

        /* recv() from server; */
        if (bytes < 0) {
            perror("Failed to receive bytes from server");
            break;
        }

        received += bytes;

        dbgprt("PAYLOAD %s\n",dbgstr(buffer,bytes));

        // send payload to terminal
        fwrite(buffer,1,bytes,stdout);
    }

    close(sock);

    dbgprt("EXIT\n");
}

void
doclient(void)
{
    char cmdbuf[MAXBUFF];
    char *argv[MAXARG];

    tid = "clnt";

    while (1) {
        dbgprt("PROMPT\n");

        printf(">> ");
        fflush(stdout);

        dbgprt("FGETS\n");
        fgets(cmdbuf,sizeof(cmdbuf),stdin);
        cmdbuf[strcspn(cmdbuf,"\n")] = 0;
        dbgprt("COMMAND %s\n",dbgstr(cmdbuf,-1));

        // tokenize the line
        int argc = tokenize(argv,cmdbuf);
        if (argc <= 0)
            continue;

        // set/display remote server IP address
        if (strcmp(argv[0],"remote") == 0) {
            if (argc >= 2)
                strcpy(ipaddr,argv[1]);
            if (ipaddr[0] != 0)
                printf("REMOTE: %s\n",ipaddr);
            continue;
        }

        // stop server
        if (strcmp(argv[0],"stop") == 0) {
            if (ipaddr[0] != 0) {
                dbgprt("STOP/SERVER\n");
                send_cmd(XMSG_CMD,cmdbuf,-1);
            }
            ipaddr[0] = 0;
            continue;
        }

        // exit client program
        if (strcmp(argv[0],"exit") == 0) {
            dbgprt("STOP/CLIENT\n");
            break;
        }

        // send command and echo response to terminal
        send_cmd(XMSG_CMD,cmdbuf,-1);
    }

    dbgprt("EXIT\n");
}

// server_cmd -- process command on server
void
server_cmd(int new_socket)
{
    xmsg_t xmsg;
    char cmdbuf[MAXBUFF];
    char *argv[MAXARG];

    dbgprt("ENTER new_socket=%d\n",new_socket);

    do {
        // get command descriptor
        xrecv(new_socket,&xmsg,sizeof(xmsg),0);

        // get command text
        xrecv(new_socket,cmdbuf,xmsg.xmsg_paylen,0);
        cmdbuf[xmsg.xmsg_paylen] = 0;
        dbgprt("COMMAND %s\n",dbgstr(cmdbuf,-1));

        // tokenize the command
        int argc = tokenize(argv,cmdbuf);
        if (argc <= 0)
            break;

        // stop the server
        if (strcmp(argv[0],"stop") == 0) {
            dbgprt("KILL server_pid=%d\n",server_pid);

            // FIXME -- we could send a "stopping server" message here

            // send EOF to client
            xmsg.xmsg_type = XMSG_EOF;
            xmsg.xmsg_paylen = 0;
            xsend(new_socket,&xmsg,sizeof(xmsg),0);

            // signal the server main process to stop (cleanly)
            if (opt_s)
                server_signo = STOP_SIGNO;
            else
                kill(server_pid,STOP_SIGNO);
            break;
        }

        int pipefd[2];
        int length;

        if (pipe(pipefd))
            perror("Failed to create pipe");

        pid_t pid = fork();
        dbgprt("FORK pid=%d\n",pid);

        // invoke the target program (under a pipe)
        if (pid == 0) {
            tid = "exec";

            dbgprt("DUP2\n");

            fflush(stdout);
            int err = dup2(pipefd[1],1);    // duplicate pipefd[1] to stdout
            if (err < 0)
                perror("dup2");

            CLOSEME(pipefd[0]);         // close the readonly side of the pipe
            CLOSEME(pipefd[1]);         // close the write side of the pipe
            dbgprt("EXECVP\n");

            CLOSEME(dbgfd);

            if (opt_h) {
                int len = sprintf(cmdbuf,"HELO\n");
                write(1,cmdbuf,len);
            }

            execvp(argv[0],argv);       // finally execute the command
            perror("execvp");
            exit(1);
        }

        // fork error
        if (pid < 0) {
            perror("fork");
            exit(1);
        }

        dbgprt("PARENT\n");
        CLOSEME(pipefd[1]);

        // grab all output from the target program and send in packets to
        // client
        while (1) {
            dbgprt("READBEG\n");
            length = read(pipefd[0],cmdbuf,sizeof(cmdbuf));
            dbgprt("READEND length=%d\n",length);

            if (length < 0) {
                perror("readpipe");
                break;
            }

            if (length == 0)
                break;

            dbgprt("READBUF %s\n",dbgstr(cmdbuf,length));

            // send descriptor for this chunk
            xmsg.xmsg_type = XMSG_DATA;
            xmsg.xmsg_paylen = length;
            xsend(new_socket,&xmsg,sizeof(xmsg),0);

            // send the payload
            if (xsend(new_socket,cmdbuf,length,0) != length) {
                perror("Failed");
            }
        }

        CLOSEME(pipefd[0]);

        // tell client we have no more data
        xmsg.xmsg_paylen = 0;
        xmsg.xmsg_type = XMSG_EOF;
        xsend(new_socket,&xmsg,sizeof(xmsg),0);
    } while (0);

    CLOSEME(new_socket);

    dbgprt("EXIT\n");
}

void
sighdr(int signo)
{

    server_signo = signo;
}

void
doserver(void)
{
    int server_fd,new_socket;
    struct sockaddr_in address;
    pid_t pid;

    tid = "serv";

    dbgprt("ENTER\n");

    server_pid = getpid();

#if 0
    signal(STOP_SIGNO,(void *) sighdr);
#else
    struct sigaction act;
    sigaction(STOP_SIGNO,NULL,&act);
    act.sa_sigaction = (void *) sighdr;
    sigaction(STOP_SIGNO,&act,NULL);

    sigset_t set;
    sigemptyset(&set);
    sigaddset(&set,STOP_SIGNO);
    sigprocmask(SIG_UNBLOCK,&set,NULL);
#endif

#if 0
    int addrlen = sizeof(address);
#else
    socklen_t addrlen = sizeof(address);
#endif

    dbgprt("SOCKET\n");

    // Creating socket file descriptor
    if ((server_fd = socket(AF_INET,SOCK_STREAM,0)) == 0) {
        perror("socket failed");
        exit(EXIT_FAILURE);
    }

    int enable = 1;
    if (setsockopt(server_fd,SOL_SOCKET,SO_REUSEADDR,&enable,sizeof(int)) < 0) {
        perror("error");
    }

    address.sin_family = AF_INET;
    address.sin_addr.s_addr = INADDR_ANY;
    address.sin_port = htons(portno);

    dbgprt("BIND portno=%u\n",portno);
    if (bind(server_fd,(struct sockaddr *) &address,sizeof(address)) < 0) {
        perror("bind failed");
    }

    dbgprt("LISTEN\n");
    if (listen(server_fd,MAXPENDING) < 0) {
        perror("listen");
    }

    int pending = 0;
    int status;

    while (1) {
        dbgprt("LOOP\n");

        // reap all finished children
        while (1) {
            pid = waitpid(-1,&status,WNOHANG);
            if (pid <= 0)
                break;
            dbgprt("REAP pid=%d pending=%d\n",pid,pending);
            --pending;
        }

        // one of the children was given a stop command and it signaled us
        if (server_signo) {
            dbgprt("SIGNO server_signo=%d\n",server_signo);
            break;
        }

        // wait for new connection from a client
        // FIXME -- sending us a signal to stop cleanly is _broken_ because
        // we do _not_ get an early return here (e.g. EINTR) -- we may need
        // select with timeout
        dbgprt("WAITACCEPT\n");
        new_socket = accept(server_fd,(struct sockaddr *) &address,
            (socklen_t *) &addrlen);

        // stop cleanly
        if (server_signo) {
            dbgprt("SIGNO server_signo=%d\n",server_signo);
            break;
        }

        if (new_socket < 0) {
            if (errno == EINTR)
                break;
            perror("accept");
        }

        dbgprt("ACCEPTED\n");

        // do command execution in main process (i.e. debug)
        if (opt_n) {
            server_cmd(new_socket);
            continue;
        }

        pid = fork();

        if (pid < 0) {
            CLOSEME(new_socket);
            continue;
        }

        // process the command in the child
        if (pid == 0) {
            server_cmd(new_socket);
            exit(0);
        }

        ++pending;

        dbgprt("CHILD pid=%d\n",pid);

        // server main doesn't need this after fork
#if 1
        CLOSEME(new_socket);
#endif
    }

    // reap all children
    while (pending > 0) {
        pid = waitpid(-1,&status,0);
        if (pid <= 0)
            break;
        dbgprt("REAP pid=%d pending=%d\n",pid,pending);
        --pending;
    }

    dbgprt("EXIT\n");
}

int
main(int argc,char **argv)
{

    --argc;
    ++argv;

    for (;  argc > 0;  --argc, ++argv) {
        char *cp = *argv;
        if (*cp != '-')
            break;

        cp += 2;
        switch (cp[-1]) {
        case 'h':
            opt_h = ! opt_h;
            break;

        case 'n':  // do _not_ fork server
            opt_n = ! opt_n;
            break;

        case 'p':
            portno = (*cp != 0) ? atoi(cp) : PORTNO;
            break;

        case 's':  // invoke server
            opt_s = ! opt_s;
            break;
        }
    }

    tsczero = tscgetf();

#if DEBUG
    int flags = O_WRONLY | O_APPEND;
    if (opt_s)
        flags |= O_TRUNC | O_CREAT;
    dbgfd = open("debug.txt",flags,0644);
    if (dbgfd < 0) {
        perror("debug.txt");
        exit(1);
    }
#endif

    if (opt_s)
        doserver();
    else
        doclient();

#if DEBUG
    if (dbgfd >= 0)
        close(dbgfd);
#endif

    return 0;
}