TCP客户端/服务器:停止读取套接字时

时间:2017-02-07 18:19:18

标签: c sockets tcp dup2 execv

我在创建像客户端/服务器(tcp)这样的小ftp时面临多个问题

客户有提示。

如何停止接收问题。通过我的套接字从我的客户端向服务器发送数据,反之亦然。

例如,从服务器向客户端发送一些字符串。客户端如何知道何时停止读取套接字并离开recv()循环以打印提示

这就是为什么我创建transfert functions知道何时通过预先发送停止,如果它是最后一次发送(CONT - DONE)并且它工作得很好。 (以下代码)

然后我需要在服务器上执行ls之类的命令并将结果发送到客户端,所以我认为有2个选项。

  • execv()中复制char*并使用我的转移功能。
  • dup execv()直接在我的socket中输出。

第二个选项更清晰,但它让我面临关于如何停止recv()循环的第一个问题。

红利问题:如何将execv()复制到字符串中。由于mmap参数,我认为使用fd,但我仍需要事先知道尺寸。

# define BUFFSIZE 512
# define DONE "DONE"
# define CONT "CONT"

int     send_to_socket(int sock, char *msg)
{
    size_t  len;
    int     ret[2];
    char    buff[BUFFSIZE+1];

    len = strlen(msg);
    bzero(buff, BUFFSIZE+1);
    strncpy(buff, msg, (len <= BUFFSIZE) ? len : BUFFSIZE);
    /*strncpy(buff, msg, BUFFSIZE);*/
    ret[0] = send(sock, (len <= BUFFSIZE) ? DONE : CONT, 4, 0);
    ret[1] = send(sock, buff, BUFFSIZE, 0);
    if (ret[0] <= 0 || ret[1] <= 0)
    {
        perror("send_to_socket");
        return (-1);
    }
    // recursive call
    if (len > BUFFSIZE)
        return (send_to_socket(sock, msg + BUFFSIZE));
    return (1);
}

char    *recv_from_socket(int cs)
{
    char    state[5];
    char    buff[BUFFSIZE+1];
    char    *msg;
    int     ret[2];

    msg = NULL;
    while (42)
    {
        bzero(state, 5);
        bzero(buff, BUFFSIZE+1);
        ret[0] = recv(cs, state, 4, 0);
        ret[1] = recv(cs, buff, BUFFSIZE, 0);
        if (ret[0] <= 0 || ret[1] <= 0)
        {
            perror("recv_from_socket");
            return (NULL);
        }
        // strfljoin(); concat the strings and free the left parameter
        msg = (msg) ? strfljoin(msg, buff) : strdup(buff);
        if (strnequ(state, DONE, 4))
            break ;
    }
    return (msg);
}

2 个答案:

答案 0 :(得分:1)

您已经正确地判断,要通过面向流的套接字传递除了无差别流之外的任何内容,您需要在通信方之间应用某种应用层协议。这是send_to_socket()recv_from_socket()函数正在做的事情,尽管它们存在缺陷。 *

假设您确实需要使用应用程序层协议,它不是让子进程直接写入套接字的选项,除非您的特定ALP可以将整个程序输出封装为单个块,你正在使用的那个不能做。

话虽如此,你还有至少一个你未考虑的其他选项:让孩子将孩子的输出发送到套接字,因为孩子产生,而不是全部收集然后才发送它。这将涉及在子和父之间建立管道,并且可能是send_to_socket()的单独版本,它读取要从FD而不是从字符串发送的数据。你可能会一次积累一个适度大小的缓冲区。这种方法将是我的建议。

  

红利问题:如何将execv()复制到字符串。感谢fd参数,我想使用mmap,但我还是需要提前知道它的大小。

mmap()采用文件描述符参数指定要映射的文件,但这并不意味着它只适用于任何文件描述符。它只能保证与指定常规文件和共享内存对象的FD一起工作,你不能指望它适用于指定瞬态数据管道的FD。要在内存中捕获子进程的输出,您需要像我为第三个选项所描述的那样操作,但是将数据读取存储在动态分配(并根据需要重新分配)缓冲区而不是将其发送到客户端它是读。这可能既昂贵又杂乱。

* 您职能部门的缺陷

  1. 他们假设可以依赖send()recv()函数来准确传输所请求的字节数,或者失败。
  2. 事实上,send()recv()都可以执行部分​​转移。为了避免丢失数据和/或不同步,您必须将这些函数的返回值与您尝试传输以检测部分传输的字节数进行比较,如果是部分传输发生转移时,必须发出另一个调用来发送数据的余额。由于同样的事情可能再次发生,你通常需要把整个事情放在一个循环中,一直调用send()recv(),直到所有数据都被发送或真正的失败发生。

    1. 递归是send函数的一个糟糕的实现选择。如果您要发送大量数据,那么您可能会耗尽堆栈,而且每次递归函数调用都会比仅仅循环返回更多开销。

    2. 不需要在固定长度的块中发送数据,并且需要在发送之前将数据复制到单独的缓冲区的开销。

    3. 请考虑发送邮件长度而不是&#34; CONT&#34;或者&#34; DONE&#34;,然后是那么多字节(但见上文)。您还可以将标志位合并到消息长度中以传达其他信息 - 例如,指示当前块是否是最后一个的位。

      1. 您的send()recv()来电可能会因与连接无关的原因及其持续可行性而失败。例如,它们可能被信号中断。由于您无法在发送方和接收方之间重新同步通信,如果它被中断,您应该确保在发生错误时终止通信,尽管实际上您的发送并不需要处理和recv功能本身。

答案 1 :(得分:0)

有什么意义     而(42)

没有什么不同     而(1) 如果你只是希望它永远循环

如果我理解正确,你想使用execv执行&#39; ls&#39;并捕获输出,以便您可以发送它。

看看&#39; popen&#39;相反,它将创建一个管道文件描述符(并返回它),然后执行连接到管道输出的命令。

然后你可以使用常规的read()系统调用来读取文件描述符的输出。

像这样的东西

#include <stdio.h>
FILE *f
char buf[BUF_MAX]
f = popen("ls", "r")

if (!f) 
    // send error message or whatever
    return

while (fgets(buf, BUF_MAX, f)) 
    // send buf with your other socket function

pclose(f);

如果你不想使用libc stdio,只需要低级系统例程,那么要做的就是检查fork(),pipe()和dup()系统调用,

使用pipe()(首先为每个结尾提供一个文件描述符)

然后fork()

然后关闭未使用的文件描述符(),

然后使用dup()或dup2()将新进程中的fd 1更改为之前管道调用的输出fd编号。

然后最后你execv()运行&#34; LS&#34;命令及其输出将进入管道

在原始进程中,您从之前通过pipe()调用返回的第一个fd中读取()

由于您希望客户端知道服务器何时完成,您可以执行以下操作:

const int MSG_DATA = 1;
const int MSG_DONE = 2;
strict message {
  int messagetype;
  int len;
}
char buf[BUF_SIZE]


message msg;
// Put data into buf
// set msg.len
msg.messagetype= MSG_DATA;
send(msg,size of(msg))
send (buf,msg.len)

然后当你完成了。

msg.messagetype=MSG_DONE
msg.len = 0
send(msg,sizeof(msg))

在客户端:

Message msg;
Char buf[]

while()
   ...
   recv(&msg)
   ...
   if(msg.messagetype == MSG_DATA)
      recv(buf)
   elif msg.message type == MSG_DONE)
      DoSomething()

但是,请记住,无论如何,您的客户端应始终接收数据包。

如果不是你不能做你想做的学校练习,那么最好使用现有的库,例如消息排队库,也许是zeromq这样做很难。

还有简单的curl C库,你可以用它来发送HTTP,它已经整理了所有这些东西,也可以做不确定的长度内容。

此外(我假设您使用的是TCP套接字,而不是UDP),连接的两端都不能发送N个字节,并且期望另一个站点上的recv将获得N个字节。 recv将只获取操作系统的网络堆栈当前在其TCP缓冲区中接收和缓冲的内容。并且接收器也可以在一次recv中获得已经发送的几个块。网络上的数据包大小可能大约为1400-1500字节,并且您的服务器可能会发送一条几KB的消息,该消息将被分成几个数据包,并且可能在第一个数据包之后由接收方处理,其余部分进入之前,或者您的服务器可以使用您的DONE或CONT标头发送几条小消息,接收方可以在一个recv()中获取所有这些消息。即使你很幸运,DONE或CONT实际上是在TCP缓冲区的开头,你的代码也将读取OS TCP缓冲区中的所有内容,并且只检查前4个字节以查看DONE或CONT第一条消息,并将所有其余信息视为数据。所以你真的需要发送一个长度字段。您可以完全废弃DONE或CONT并发送长度,并发送0长度来表示DONE。然后你的客户端收到它后,可以将它可以获得的所有东西recv()放入缓冲区,然后使用length字段依次处理该缓冲区中包含的每条消息。