【问题标题】:Proper way to send data when using select() and write_fds in C在 C 中使用 select() 和 write_fds 时发送数据的正确方法
【发布时间】:2016-04-03 14:30:53
【问题描述】:

所以我正在构建一个聊天服务器,现在我正在尝试回显来自客户端的所有消息。目前,我一收到消息,就会在readData() 内将其发回。但是,一旦我发送它,select() 就会通知write_fds 和sendData() 被调用,即使我已经调用了send()

我发送数据的大部分调用都在readData() 内。

  • 这是使用select()和write_fds的正确方法吗?
  • 如何通知select() 我要发送数据而无需两次调用send()?

对我来说,必须处理两个对send() 的调用似乎是多余的。

int readData(int j){
    // get message from the client
    recv(j, client_buffer , 6000 , 0);
    // echo message to the client
    send(j, client_buffer, strlen(client_buffer));
}   
int sendData(int j){
    send(j, buf, nbytes, 0);
}   

for(;;){
    read_fds = master; 
    write_fds = master;
    if(select(fdmax+1, &read_fds, &write_fds, NULL, NULL) == -1){
        exit(4);
    }   
    for(i = 0; i <= fdmax; i++){
        if(FD_ISSET(i, &read_fds)){
            if(i == listener){
                // handle new connections
                addrlen = sizeof remoteaddr;
                newfd = accept(listener, (struct sockaddr *)&addr, &addrlen);
                FD_SET(newfd, &master);
                if(newfd > fdmax) fdmax = newfd;
            }else{  
                // we got some data from a client
                readData(i); 
            }   
        }  
        if(FD_ISSET(i, &write_fds)){
            if(i != listener){
                // send data when notified
                sendData(i);
            }
        }    
    }   
}

【问题讨论】:

  • 1) (假设这里是 TCP) send() 和 recv 有一个返回值。用它。并处理部分读/写。 2) 接收后不能在缓冲区上使用 strlen(),因为它不一定是 NUL 终止的。 3) 从 send/recv/select 返回 -1 并不总是错误。它可能是中断的系统调用、EWOULDBLOCK,甚至是资源不足的情况。 4) 在 accept() 之后,您可能希望将 new_fd 添加到读取的 fd_fds 中。
  • TCP,没错。感谢您指出strlen()。关于返回值,所以你是说,总是会有两次对send() 的调用,但我只需要跟踪第一次调用发送了多少数据,然后在第二次或第三次调用中发送其余数据?
  • 我只是指出了明显的错误。恕我直言,永远不会有第二次或第三次发送调用,因为新的 fd 不在 write fd_set 中,也不在 read_set 中。 (两者都是主集的副本)
  • 固定接受()。谢谢。
  • 没错。使用返回值来计算你走了多远,以及接下来需要做什么。请不要纠正问题中的错误。

标签: c sockets select


【解决方案1】:

我不建议直接在readData() 内部调用sendData()。它们应该分开存放。让readData() 将接收到的数据返回给调用者,让调用者决定如何处理。如果调用者想发送数据,则可以根据需要调用sendData()。

要解决select() 问题,您需要为传出数据创建每个套接字缓冲区。并确保套接字以非阻塞模式运行。

  1. 如果在缓冲区为空时调用sendData(),则send() 尽可能多的调用者的数据。 send() 将返回它实际接受的字节数。如果send() 报告EWOULDBLOCK 或EAGAIN 错误,请停止发送并将所有未发送的数据附加到缓冲区的末尾。

  2. 如果在缓冲区不为空时调用sendData(),只需将新数据追加到缓冲区的末尾并退出,根本不调用send()。

每当select() 报告一个套接字是可写的,send() 任何当前缓存在该套接字缓冲区中的内容,如果有的话。对于每个成功的send(),从缓冲区的前面删除报告的字节数。如果缓冲区耗尽或send() 失败,则停止发送。

【讨论】:

  • 谢谢,现在明白了。顺便说一句,你知道什么是输出数据的合适大小的缓冲区,例如 BUFSIZ?还是应该是动态的,并不断调整大小,即realloc?我很好奇这方面的常见做法是什么。
  • @PeteDarrow 传出缓冲区应该是动态的,因为您不知道最终需要缓存多少数据,但您可以根据需要以合理的增量增长/重新分配它。例如,以 1kb 为增量。或者可能是增量数量,比如 256 字节,然后是 512 字节,然后是 1kb,等等。或者四舍五入到一个不错的值的倍数。这实际上取决于您将发送多少数据。尝试尽可能少地重新分配它,但尽可能地重复使用它。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多