Linux socket:如何使send()等待recv()

时间:2013-11-05 17:10:52

标签: c sockets send recv

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

我知道默认情况下。 recv()将阻塞,直到另一方呼叫send()到此套接字。 但send()是否有可能阻止自己,直到另一方recv()编辑了msg,而不是将send()保留到传出队列,然后找到另一端recv()多个send() s

发送的一大堆msg

换句话说。是否可以让每个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;
}

server.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()完成,并且由于某种原因,服务器端{{1}循环比客户端recv()慢。因此,客户端上的几个send()可以堆叠在一起并由服务器作为整体接收。 (我的解释是对吗?)

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

那么是否有更好的方法让双方相互沟通,以确保由单个recv()收到的单个send()发送的消息?

3 个答案:

答案 0 :(得分:7)

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

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

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

在任何情况下,结果都会产生误导,因为接收端也有一个缓冲区,如果处理是瓶颈,接收缓冲区将是第一个填满的。低级数据确认仅指示数据已到达接收TCP堆栈(即,低级IP接口,通常在内核内)。它并不表示数据已被接收进程处理,并且发送方没有机制来查询接收缓冲区的使用情况。

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

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

答案 1 :(得分:6)

<强> 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;
}

答案 2 :(得分:2)

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

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缓冲区中。

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