使用C语言的TCP套接字,'send'函数是否可能返回零?

21

在使用TCP套接字时,C send 函数是否可能返回零?手册只说它会返回发送的字节数,但我不确定当无法发送任何数据时它是否只会返回 -1。

6个回答

21

我十分肯定,尽管这个记忆已经深深埋在时间的迷雾中,我以前曾经遇到过它返回零的情况,当时是在传输大量数据的情况下,对端无法跟上的情况下。

根据我的记忆,在那种情况下,远端TCP堆栈缓冲区已经填满,堆栈通知本地端必须等待一些空间腾出来,然而本地缓冲区也随之填满了。

此时,虽然技术上不算错误(因此没有返回-1),但本地堆栈无法接受任何数据。

我不太确定现在是否还是这种情况,因为当前的Posix标准似乎表明会在这种情况下阻塞(或者如果设置了非阻塞则会失败)。

但是,我认为这已经不重要了。你有可能会收到少于所请求发送字节数的数据,因此你应该有相应的代码来处理这种情况。

而且,既然处理“比请求的少一个字节”和处理“零字节”的逻辑基本相同,你也可以假定它会返回0。


2
我该如何处理?我的程序应该继续尝试发送,还是应该失败? - Adrian
3
你应该继续尝试发送,因为这不是一个错误条件。如果它无法恢复,那么最终会返回一个错误。这是你应该指示失败的时候。你可能想考虑的一件事是,在重新尝试之前,在零返回代码后引入延迟。这将为临时问题提供更多时间来解决。有许多策略可以遵循。 - paxdiablo
9
@paxdiablo:我不认为你的解释是正确的。如果套接字处于阻塞模式,并且接收方报告其接收缓冲区已满,则send()会一直阻塞,直到可以再次发送数据(或发生致命错误/超时)。相反,如果套接字处于非阻塞模式,则它会立即返回-1和EWOULDBLOCK错误代码。根据我的经验,返回值为0始终意味着要么将0字节传递给了send(),要么另一方已经正常关闭了套接字(或至少调用了shutdown(0))。 - Remy Lebeau
8
@RemyLebeau 我同意。与此答案所断言的相反,send() 的行为并不依赖于实现。它是几十年前由 BSD 定义,然后被 POSIX 接受。但是你关于“对方已经优雅关闭”的评论是不正确的。如果你继续发送,这将导致 ECONNRESET,而不是返回零的代码。 - user207421
当然,如果您能够实际确定答案的一部分是错误的,并引用来源,我很乐意承认我是错的并进行更改。 - paxdiablo
显示剩余8条评论

5

有时候你传递的字节数为零,这种情况下,“返回发送的字节数”会指示应该返回零字节。

最好还是正确处理返回零的情况;这不会有害,但可能会有帮助。


4
这可能因实现方式而异,因此取决于操作系统。
当您请求传输0字节时,期望的情况是返回0。

实际上,这并不依赖于具体的实现。在接收TCP的接收窗口已满或发送TCP的发送缓冲区完全满时,你很可能被要求发送某些内容。在这些情况下,send() 函数很容易返回零。 - Chris Cleeland
2
在这种情况下,send() 要么会阻塞直到有空间可用(然后返回大于零的某个值),要么如果套接字处于非阻塞模式,则会返回 -1/EWOULDBLOCK。 - Jeremy Friesner

3
BSD的man页面指出:
如果套接字中没有足够的空间来保存要发送的消息,则send()通常会阻塞,除非将套接字置于非阻塞I/O模式。
Posix规范进一步指出,在阻塞模式下,所有数据都会被传输,除非发生中断。
在这两种情况下,只有在提供的计数为零时才不能返回零。

在第一次传输之前,是否可能发生中断,导致发送零字节? - chux - Reinstate Monica

2

send 在你使用 send(socket, buf, 0, 0) 时返回 0。

我想提供简单的代码供其他人测试。

服务器:

在一个终端中:

nc -l localhost 10086

客户端:

在另一个终端中:

#include <cstdio>
#include <cstdlib>

#include <unistd.h>
#include <errno.h>

#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>

#include <netdb.h>
#include <arpa/inet.h>

int create_socket()
{
    addrinfo  hints = {};
    addrinfo* servinfo;

    int sockfd = -1;
    int rv;

    hints.ai_family   = AF_INET;
    hints.ai_socktype = SOCK_STREAM;
    hints.ai_flags    = 0;

    if ((rv = getaddrinfo("localhost", "10086", &hints, &servinfo)))
    {
        printf("getaddrinfo failed: %s\n", gai_strerror(rv));
        exit(1);
    }

    for(auto p = servinfo; p; p = p->ai_next)
    {
        if ((sockfd = socket(p->ai_family, p->ai_socktype, p->ai_protocol)) == -1)
        {
            perror("socket");
            continue;
        }

        else
        {
            if(connect(sockfd, p->ai_addr, p->ai_addrlen) == -1)
            {
                close(sockfd);
                perror("connect");
                continue;
            }
            else
                puts("client: connected");
        }

        break;
    }

    freeaddrinfo(servinfo);
    return sockfd;
}

void client()
{

    int socket = create_socket();

    if(socket == -1)
    {
        puts("no good");
        exit(1);
    }
    char buf[100] = {0};

    while(1){
        int ret = send(socket, buf, 0, 0);
        if (ret == 0){
            printf(".");
        }else{
            printf("xxxxxxx\n");
        }
    }
}


int main()
{
    setvbuf(stdout, NULL, _IONBF, 0);
    client();
    return 0;
}

g++ -g -Wall -o send_zero_bytes send_zero_bytes.cpp
./send_zero_bytes 

2
我现在观察到在AF_UNIX类型的套接字上从send(2)返回了零值。
是的,这是由于零值的size字段引起的。
所以,供您参考。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接