使用C和evecvp执行程序自变量无法正确转发

问题描述

我正在尝试在监督者和客户端分布式系统中使用execvp函数执行程序。客户端发送一个带有以下参数执行的程序:

char buf[500];
int bytes_recieved = 0;
char array[1][26];
bytes_recieved = recv(clientfd,buf,5000,0);
buf[bytes_recieved] = '\0';

char buf1[50];
int bytes_recieved1 = 0;
char *array1[4];

for (int i = 0; i < 3; i++){
   bytes_recieved1 = recv(clientfd,buf1,50,0);
   array1[i] = buf1;
   printf("%s = buffer\n",buf1);
} 
buf1[bytes_recieved] = '\0';

if(bytes_recieved != -1){
   printTime();
   fprintf(stdout,"atempting to execute program: %s\n",buf);
   if(execvp(buf,array1) == -1) {
      return 1;
   }
}

我一直想弄清楚当我在程序中打印出参数数组时会发生什么,最后一个参数对于所有参数都是相同的?例如,我在要执行的客户端程序中运行它:

./client 12345 home/user/test_program 1 2 3

简单printf的结果是:

3
3
3

当我在监督程序中手动分配数组中的每个参数时:

array1[0] = "1";
array1[1] = "2";
array1[2] = "3";

并将其发送到正确打印的已执行程序。

我还测试了从文件描述符接收的缓冲区是否正确分配了数组中的变量:

 printf("%s = buffer\n",array1[i]);

在for循环的赋值内,返回:

    1 = buffer
    2 = buffer
    3 = buffer 

我在做什么错? 让我知道是否需要更多信息。

解决方法

这是一些基本代码,基于您的代码片段。我无法测试-您未提供MCVE(Minimal,Complete,Verifiable Example —或MRE或SO现在使用的任何名称) 或 SSCCE(Short,Self-Contained,Correct Example)。

char buf[500];
int bytes_received = recv(clientfd,buf,sizeof(buf)-1,0);
if (bytes_received < 0)
    return 1;
buf[bytes_received] = '\0';

char *array1[5] = { buf };

for (int i = 0; i < 3; i++)
{
    char buf1[50];
    int bytes_received1 = recv(clientfd,buf1,sizeof(buf1)-1,0);
    if (bytes_received1 < 0)
        return 1;
    buf1[bytes_received1] = '\0';
    array1[i + 1] = strdup(buf1);
    printf("argument = [%s]\n",buf1);
}

printTime();
printf("atempting to execute program: %s\n",buf);
for (int i = 0; array1[i] != NULL; i++)
    printf("argv[%d] = [%s]\n",i,array1[i]);
fflush(0);
execvp(array1[0],array1);
fprintf(stderr,"failed to execute '%s'\n",array1[0]);
return 1;

多项更改包括:

  • 使用sizeof确定数组大小。
  • 更改“ receive”的拼写
  • 减去1可以为终端空字节留出空间,以将消息转换为字符串。
  • 使array1足够大以容纳终端NULL指针,并将第零个元素之后的元素初始化(间接)为NULL。这个很重要; execvp()的参数数组必须以NULL指针终止。
  • 如果初始接收失败,则返回该值,以避免索引为负数。
  • 使buf1数组位于循环本地;同上bytes_received1
  • 如果后续接收失败,则返回该值,以避免索引为负数。
  • 复制使用strdup()读取的字符串-这是一个关键更改。
  • 不尝试基于buf1中接收到的数据在某个位置将buf终止。
  • 修改打印,在字符串周围放置方括号,以便可以更轻松地发现尾随空格或换行符。
  • 将所有参数打印到execvp()
  • 讨论调试输出是否应转到stderr而不是stdout。我最终将其留给stdout,但这不一定是最佳选择。
  • 将一个fprintf(stdout,…)更改为printf(…)以保持一致性。
  • 调用fflush(0)将所有待处理的输出发送到其设备。随着调试输出进入stdout,如果将输出通过管道传输到另一个程序,则数据将被完全缓冲,而不是行缓冲,并且除非您强制执行,否则数据不会出现。也可以调用fflush(stdout)。呼叫stdin时,很可能(stdoutstderrexecvp()之外的文件流都不应该打开)。
  • 您应该考虑在到达此处之前是否应该关闭其他流(文件描述符),也许使用O_CLOEXECFC_CLOEXEC选项以确保成功执行后文件描述符被关闭,以便执行的进程无法获取未知的活动文件描述符。
  • 不必费心检查execvp()的返回值。如果返回,则失败;如果成功,它不会返回。
  • execvp()执行失败时报告错误消息。
  • return 1;失败之后,将execvp()保留为代码的一部分。通常最好使用exit(EXIT_FAILURE);或类似的符号(也许是_exit(EXIT_FAILURE))。如果没有调用该函数片段的更大的函数上下文,就不可能知道什么是最好的。
  • 请注意,如果execvp()失败并返回而不是退出,则表示您正在泄漏strdup()分配的内存。可能应该有一个循环for (int i = 0; i < 3; i++) free(array1[i+1]);,以便在return 1;之前释放复制的内存。
  • 该代码不会检查是否存在数据截断-它不知道recv()调用之一是否会读取比其更多的数据,因为没有足够的空间来存储所有数据。您可能需要检查实际数据大小是否小于可用空间,以确保没有截断。
  • 尚不清楚为什么程序名称可以比参数大十倍。通常,自变量可以大于程序名称,但是,由于示例数据中的自变量具有123之类的参数,因此这不是问题。