如何将 system() 调用产生的所有 STDOUT 读取到用 C 编写的套接字中?

How to read all of STDOUT produced by system() call into a socket written in C?

以下是服务器套接字的代码片段,它读取客户端发送的 linux 命令,执行它并将输出发送回客户端:

    while(1){
          char command[200];
          message_read = read(sock, command, sizeof(command));

        if(message_read > 0){
            command[message_read] = '[=10=]';
            dup2(sock, STDOUT_FILENO);
            dup2(sock, STDERR_FILENO);
            system(command);
        }
    }

以下是客户端向服务器发送命令并接收返回输出的代码片段:

          char output[10240];

          send(sock, command, strlen(command), MSG_NOSIGNAL);
          if((message_read = read(sock, output, sizeof(output)))>0){
          output[message_read] = '[=11=]';
          //print the output somewhere
          }

虽然“ls -al”、“pwd”或“whoami”等命令一次性给出输出,但客户端无法读取“ping”、“[=”等命令产生的全部输出27=]" 或 "du"。然而,当我多次调用上面的代码片段时,它会得到上面命令产生的其余输出(以块的形式。)

我尝试修改客户端函数如下:

       send(sock, command, strlen(command), MSG_NOSIGNAL);
       do{
          if((message_read = read(sock, output, sizeof(output))) > 0){
          output[message_read] = '[=12=]';
          //print the output somewhere
          }
        }while(message_read);

以上解决方法挂掉了客户端程序。但是,在我终止服务器后,输出确实出现在客户端的 window 中! 另外,这次的输出都是零散的,缩进也很差。

Q1。发生什么事了?

Q2。如何解决?

您的代码发送和读取字符串的方式不够完善。

TCP是字节流。发送和读取之间没有一对一的关系。因此,发件人必须:

  1. 在发送字符串数据之前先发送字符串长度。
  2. 在字符串数据后发送一个唯一的终止符。

接收方必须:

  1. 读取长度后读取指定的数据量
  2. 阅读直到到达终止符。

此外,send()/write()recv()/read() 可以比请求的字节少 return,因此需要在循环中调用它们(或者,在 recv() 的情况下,您可以使用 MSG_WAITALL 标志)。

尝试更像这样的东西:

// common functions ...

bool sendRaw(int sock, void *data, size_t len)
{
    char *ptr = (char*) data;
    while (len > 0) {
        int sent = send(sock, ptr, len, MSG_NOSIGNAL);
        if (sent < 0) return false;
        ptr += sent;
        len -= sent;
    }
    return true;
}

int recvRaw(int sock, void *data, size_t len)
{
    char *ptr = (char*) data;
    while (len > 0) {
        int recvd = recv(sock, ptr, len, MSG_NOSIGNAL);
        if (recvd <= 0) return recvd;
        ptr += recvd;
        len -= recvd;
    }
    return 1;
}

bool sendUInt32(int sock, uint32_t value)
{
    value = htonl(value);
    return sendRaw(sock, &value, sizeof(value));
}

uint32_t recvUInt32(int sock)
{
    uint32_t value;
    if (recvRaw(sock, &value, sizeof(value)) <= 0) return -1;
    return ntohl(value);
}

bool sendString(int sock, const char *str)
{
    uint32_t len = strlen(str);
    if (!sendUInt32(sock, len)) return false;
    return sendRaw(sock, str, len);

    /* alternatively:
    return sendRaw(sock, str, strlen(len) + 1);
    */
}

/*
bool grow(char **str, size_t *cap, size_t stepBy)
{
    size_t newcap = cap + stepBy;
    char *newstr = (char*) realloc(*str, newcap);
    if (!newstr) return false;
    *str = newstr;
    *cap = newcap;
    return true;
}
*/

char* recvString(int sock)
{
    uint32_t len = recvUInt32(sock);
    if (len == -1) return NULL;

    char *str = (char*) malloc(len+1);
    if (!str) return NULL;

    if (recvRaw(sock, str, len) <= 0){
        free(str);
        return NULL;
    }

    str[len] = '[=10=]';
    return str;

    /* alternatively:

    char ch, *str = NULL;
    size_t len = 0, cap = 0;

    do{
        if (recvRaw(sock, &ch, 1) <= 0){
            free(str);
            return NULL;
        }

        if (ch == '[=10=]') break;

        if (len == cap){
            if (!grow(&str, &cap, 256)){
                free(str);
                return NULL;
            }
        }

        str[len++] = ch;
    }
    while (1);

    if (len == cap){
        if (!grow(&str, &cap, 1)){
            free(str);
            return NULL;
        }
    }

    str[len] = '[=10=]';
    return str;
    */
}
// server ...

char *command;

while ((command = recvString(sock)) != NULL){
    // ...
    system(command);
    free(command);

    // read from command's stdout until finished ...

    if (!sendString(sock, output, outputLength)) break;
}
// client ...

if (sendString(sock, command)){
    char *output = recvString(sock);
    if (output){
        //print the output somewhere
        free(output);
    }
}

或者,如果您提前不知道命令响应的长度,and/or不想将其全部缓冲在单个内存缓冲区中,那么您可以分块读取,边走边发送每个块,例如:

// common functions, see above ...

typedef struct _chunk
{
    uint8_t size;
    char data[256];
} chunk;

bool sendChunk(int sock, const chunk *chk)
{
    uint8_t size = chk ? chk->size : 0;
    if (!sendRaw(sock, &size, 1)) return false;
    if (chk) return sendRaw(sock, chk->data, size);
    return true;
}

bool recvChunk(int sock, chunk *chk)
{
    if (recvRaw(sock, &(chk->size), 1) <= 0) return false;
    if (chk->size) return recvRaw(sock, chk->data, chk->size);
    return true;
}
// server ...

bool sendOutput(int sock)
{
    chunk chk;
    int size;

    do{
        // read from command's stdout ...
        size = read(..., chk.data, sizeof(chk.data));
        if (size <= 0) break;

        chk.size = (uint8_t) size;
        if (!sendChunk(sock, &chk)) return false;
    }
    while(1);
 
    // tell client the data is finished ...
    return sendChunk(sock, NULL);
}

char *command;

while ((command = recvString(sock)) != NULL){
    // ...
    system(command);
    free(command);
    if (!sendOutput(sock)) break;
}
// client ...

if (sendString(sock, command)){
    chunk chk;
    do{
        if (!recvChunk(sock, &chk)) break;
        if (chk.size == 0) break;
        //print the chk.data somewhere
    }
    while (1);
}