Linux 套接字:如何让 send() 等待 recv()

Posted

技术标签:

【中文标题】Linux 套接字:如何让 send() 等待 recv()【英文标题】:Linux socket: How to make send() wait for recv() 【发布时间】:2013-11-16 16:01:44 【问题描述】:

我正在使用 TCP 协议制作一个简单的客户端-服务器应用程序。

默认情况下我知道。 recv() 将阻塞,直到对方调用 send() 到这个套接字。 但是send() 是否有可能自己阻塞,直到对方有recv()ed 味精,而不是将send()ing 保留到传出队列,然后找到对方recv() 收到一大堆由多个发送的味精send()s

换句话说。是否可以让每个send() 等待对方的recv() 之后才能调用另一个send()

为了说明我的问题。我将在这里发布一个简单的代码:

client.c

#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <netdb.h>
#include <stdio.h>
#include <poll.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <arpa/inet.h> 

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

    int sockfd = 0;
    char sendBuff[1024];
    struct sockaddr_in serv_addr;
    int i;

    if(argc != 2)
    
        printf("\n Usage: %s <ip of server> \n",argv[0]);
        return 1;
     

    if((sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0)
    
        printf("\n Error : Could not create socket \n");
        return 1;
     

    memset(&serv_addr, '0', sizeof(serv_addr)); 

    serv_addr.sin_family = AF_INET;
    serv_addr.sin_port = htons(5000); 

    if(inet_pton(AF_INET, argv[1], &serv_addr.sin_addr)<=0)
    
        printf("\n inet_pton error occured\n");
        return 1;
     

    if( connect(sockfd, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) < 0)
    
        printf("\n Error : Connect Failed \n");
        return 1;
    

    do
        memset(sendBuff, '\0', sizeof(sendBuff));
        sprintf(sendBuff, "This is line %d", i);
        send(sockfd, sendBuff, strlen(sendBuff), 0);
        //sleep(1);
    while(++i<100);

    return 0;

服务器.c

#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <sys/types.h>
#include <time.h> 

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

    int listenfd = 0, connfd = 0;
    struct sockaddr_in serv_addr; 

    char sendBuff[1025];
    char recvBuff[100];

    int i = 0;

    listenfd = socket(AF_INET, SOCK_STREAM, 0);
    memset(&serv_addr, '0', sizeof(serv_addr));
    memset(sendBuff, '0', sizeof(sendBuff)); 

    serv_addr.sin_family = AF_INET;
    serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
    serv_addr.sin_port = htons(5000); 

    bind(listenfd, (struct sockaddr*)&serv_addr, sizeof(serv_addr)); 

    listen(listenfd, 10); 

    connfd = accept(listenfd, (struct sockaddr*)NULL, NULL); 

    do  
        memset(recvBuff, '\0', sizeof(recvBuff));
        recv(connfd, recvBuff, sizeof(recvBuff),0);
        printf( "%s\n", recvBuff);
    while(++i<100); 

    return 0;
 

我对服务器端结果的期望是打印:

This is line 0
This is line 1
This is line 2
This is line 3
...

然而,实际的结果是这样的:

This is line 0
This is line 1This is line 2This is line3This is line 4
This is line 5This is line 6This is line 7This is line 8This is line 9This is line 10
This is line 11This is line 12...

但是这很容易解释:当客户端发出send()时,它并没有等待服务器端的recv()完成,并且由于某种原因,服务器端recv()循环比客户端的send()。因此,客户端的几个send()s 可能会堆积在一起并被服务器作为一个整体接收。 (我的解释对吗?)

实际上似乎有一个非常愚蠢和松散的解决方案。只需在循环中的每个send() 之后添加一个sleep(1)(正如我注释掉的)。我知道这会使代码非常低效,如果recv() 循环将花费更长的时间来实现其他一些复杂的操作(当程序变大时这显然是不可预测的),这将花费超过 1 秒的时间。此解决方案失败!

那么有没有更好的方法可以让双方相互通信,保证一个send()发送的msg被一个recv()接收?

【问题讨论】:

【参考方案1】:

发送者知道接收者收到消息的唯一方法是让接收者回复。

换句话说,如果你想让发送者和接收者保持同步,你需要实现自己的显式流控制。

确实,TCP 协议要求接收方确认接收到的数据包,因此您可能认为接收方可以简单地询问 TCP 实现是否已确认数据。这通常是可能的,尽管我不相信有这样做的跨平台方式。例如,在 Linux 上,您可以使用 ioctl 代码 SIOCOUTQ 检查出站缓冲区的大小。但据我所知,在出站缓冲区为空之前没有阻塞机制,所以你能做的最好的就是定期轮询。这并不令人满意。

无论如何,结果都会产生误导,因为接收端也有一个缓冲区,如果处理是瓶颈,接收缓冲区将最先被填满。低级数据确认只表示数据已经到达接收TCP栈(即低级IP接口,一般在内核内部)。并不表示数据已经被接收进程处理过,也没有发送方查询接收缓冲区使用情况的机制。

此外,等待确认消息到达发送端通常会不必要地减慢通信速度,因为它为每个事务增加了传输延迟(接收方→发送方)。这不像您的 sleep 解决方案那样引人注目,但有点相似。

如果您的消息很短,您可能会遇到Nagle algorithm,这会导致发送者将小数据包保留一小段时间,希望可以合并多个数据包。您可以使用setsockoptTCP_NODELAY 选项为TCP 连接关闭Nagle 算法。但除非你确定它有用,否则不要这样做。 (TCP_NODELAY 的规范用法是 telnet 类型的连接,其中数据由单独的击键组成。)

【讨论】:

【参考方案2】:

client.c

while(condition)

  send() from client;
  recv() from server;

server.c

recv() from client;
while(condition)

  send() from server; //acknowledge to client
  recv() from client;

【讨论】:

我没想到解决方案会这么简单。谢谢大佬! 不客气!!我曾经遇到过类似的情况。我认为这个解决方案可能会有所帮助:-)【参考方案3】:

如果您的问题是一次只处理一条消息,那么包含一个长度字段怎么样? 发件人

int sendLen = strlen(sendBuff);
send(sockfd, &sendLen, sizeof(sendLen), 0);
send(sockfd, sendBuff, sendLen, 0);

接收者

int len = 0;
recv(connfd, &len, sizeof(len), 0);
recv(connfd, recvBuff, len, 0);

这将允许您一次读取一条消息并将其余消息留在 tcp 缓冲区中。

请记住,上面的代码对您的发送和接收程序共享字节序和 int 大小做出了很多假设。 (如果它都在一台机器上运行,你很好)它也不检查任何错误返回,send()recv() 都有错误的可能性。

【讨论】:

以上是关于Linux 套接字:如何让 send() 等待 recv()的主要内容,如果未能解决你的问题,请参考以下文章

linux 网络编程 2---(TCP编程)

如何让我的异步客户端调用套接字服务器并等待响应

linux中read,write和recv,send的区别

如何让观察者在认证后等待 WebSocket 的创建

C++:如何测量非阻塞套接字的实际上传速率

php socket 如何实现非阻塞