为什么我在linux上看不到MSG_EOR for SOCK_SEQPACKET?

时间:2010-08-29 17:00:03

标签: c++ c linux

我有两个进程通过使用socketpair()和SOCK_SEQPACKET创建的一对套接字进行通信。像这样:

int ipc_sockets[2];
socketpair(PF_LOCAL, SOCK_SEQPACKET, 0, ipc_sockets);

据我了解,我应该在收到SOCK_SEQPACKET记录时在“struct msghdr”的msg_flags成员中看到MSG_EOR。我在sendmsg()中设置MSG_EOR以确保记录标记为MSG_EOR,但在recvmsg()中接收时我看不到它。我甚至试图在发送记录之前在msg_flags字段中设置MSG_EOR,但这根本没有区别。

我想我应该看到MSG_EOR,除非记录被缩短,例如一个信号,但我没有。那是为什么?

我在下面粘贴了我的发送和接收代码。

谢谢,   朱

int
send_fd(int fd,
        void *data,
        const uint32_t len,
        int fd_to_send,
        uint32_t * const bytes_sent)
{
    ssize_t n;
    struct msghdr msg;
    struct iovec iov;

    memset(&msg, 0, sizeof(struct msghdr));
    memset(&iov, 0, sizeof(struct iovec));

#ifdef HAVE_MSGHDR_MSG_CONTROL
    union {
        struct cmsghdr cm;
        char control[CMSG_SPACE_SIZEOF_INT];
    } control_un;
    struct cmsghdr *cmptr;

    msg.msg_control = control_un.control;
    msg.msg_controllen = sizeof(control_un.control);
    memset(msg.msg_control, 0, sizeof(control_un.control));

    cmptr = CMSG_FIRSTHDR(&msg);
    cmptr->cmsg_len = CMSG_LEN(sizeof(int));
    cmptr->cmsg_level = SOL_SOCKET;
    cmptr->cmsg_type = SCM_RIGHTS;
    *((int *) CMSG_DATA(cmptr)) = fd_to_send;
#else
    msg.msg_accrights = (caddr_t) &fd_to_send;
    msg.msg_accrightslen = sizeof(int);
#endif
    msg.msg_name = NULL;
    msg.msg_namelen = 0;

    iov.iov_base = data;
    iov.iov_len = len;
    msg.msg_iov = &iov;
    msg.msg_iovlen = 1;

#ifdef __linux__
    msg.msg_flags = MSG_EOR;
    n = sendmsg(fd, &msg, MSG_EOR);
#elif defined __APPLE__
    n = sendmsg(fd, &msg, 0); /* MSG_EOR is not supported on Mac                                                                                                                                                                        
                               * OS X due to lack of                                                                                                                                                                                    
                               * SOCK_SEQPACKET support on                                                                                                                                                                              
                               * socketpair() */
#endif
    switch (n) {
    case EMSGSIZE:
        return EMSGSIZE;
    case -1:
        return 1;
    default:
        *bytes_sent = n;
    }

    return 0;
}

int
recv_fd(int fd,
        void *buf,
        const uint32_t len,
        int *recvfd,
        uint32_t * const bytes_recv)
{
    struct msghdr msg;
    struct iovec iov;
    ssize_t n = 0;
#ifndef HAVE_MSGHDR_MSG_CONTROL
    int newfd;
#endif
    memset(&msg, 0, sizeof(struct msghdr));
    memset(&iov, 0, sizeof(struct iovec));

#ifdef HAVE_MSGHDR_MSG_CONTROL
    union {
        struct cmsghdr  cm;
        char control[CMSG_SPACE_SIZEOF_INT];
    } control_un;
    struct cmsghdr *cmptr;

    msg.msg_control = control_un.control;
    msg.msg_controllen = sizeof(control_un.control);
    memset(msg.msg_control, 0, sizeof(control_un.control));
#else
    msg.msg_accrights = (caddr_t) &newfd;
    msg.msg_accrightslen = sizeof(int);
#endif
    msg.msg_name = NULL;
    msg.msg_namelen = 0;

    iov.iov_base = buf;
    iov.iov_len = len;
    msg.msg_iov = &iov;
    msg.msg_iovlen = 1;

    if (recvfd)
        *recvfd = -1;

    n = recvmsg(fd, &msg, 0);
    if (msg.msg_flags) { // <== I should see MSG_EOR here if the entire record was received
        return 1;
    }
    if (bytes_recv)
        *bytes_recv = n;
    switch (n) {
    case 0:
        *bytes_recv = 0;
        return 0;
    case -1:
        return 1;
    default:
        break;
    }

#ifdef HAVE_MSGHDR_MSG_CONTROL
    if ((NULL != (cmptr = CMSG_FIRSTHDR(&msg))) 
        && cmptr->cmsg_len == CMSG_LEN(sizeof(int))) {
        if (SOL_SOCKET != cmptr->cmsg_level) {
            return 0;
        }
        if (SCM_RIGHTS != cmptr->cmsg_type) {
            return 0;
        }
        if (recvfd)
            *recvfd = *((int *) CMSG_DATA(cmptr));
    }
#else
    if (recvfd && (sizeof(int) == msg.msg_accrightslen))
        *recvfd = newfd;
#endif
    return 0;
}

3 个答案:

答案 0 :(得分:5)

使用SOCK_SEQPACKET unix域套接字消息被剪短的唯一方法是你给recvmsg()的缓冲区不够大(在这种情况下你会得到MSG_TRUNC)。

POSIX说SOCK_SEQPACKET套接字必须在记录末尾设置MSG_EOR,但Linux unix域套接字不能。

(参考:POSIX 2008 2.10.10表示SOCK_SEQPACKET必须支持记录,2.10.6表示接收器通过MSG_EOR标志可以看到记录边界。)

“记录”对于给定协议的意义取决于要定义的实现。

如果Linux确实为unix域套接字实现了MSG_EOR,我认为唯一明智的方法就是说每个数据包本身都是一个记录,所以总是设置MSG_EOR(或者在不设置MSG_TRUNC时总是设置它),所以无论如何它都不会提供信息。

答案 1 :(得分:2)

这不是MSG_EOR的用途。

请记住,套接字API是对许多不同协议的抽象,包括UNIX文件系统套接字,套接字对,TCP,UDP和许多不同的网络协议,包括X.25和一些完全被遗忘的协议。

MSG_EOR用于表示对底层协议有意义的记录结束。即它将消息传递给下一层,即“这完成了一条记录”。这可能会影响例如缓冲,导致冲洗缓冲区。但如果协议本身没有“记录”的概念,则没有理由期望该标志被传播。

其次,如果使用SEQPACKET,必须立即读取整个消息。如果不这样做,剩余部分将被丢弃。这是记录在案的。特别是,MSG_EOR 一个标志,告诉您这是数据包的最后一部分。

建议:您显然是在编写非SEQPACKET版本,以便在MacOS上使用。我建议您转储SEQPACKET版本,因为它只会使维护和编码负担加倍。 SOCK_STREAM适用于所有平台。

答案 2 :(得分:0)

阅读文档时,SOCK_SEQPACKETSOCK_STREAM有两个不同的方面。首先-

  

用于固定最大长度的数据报的顺序,可靠,基于双向连接的数据传输路径;消费者需要在每次输入系统调用时读取整个数据包。

     

-socket(2)来自Linux联机帮助页项目

aka

  

对于基于消息的套接字,例如SOCK_DGRAM和SOCK_SEQPACKET,应在单个操作中读取整个消息。如果消息太长而无法容纳在提供的缓冲区中,并且在flags参数中未设置MSG_PEEK,则应丢弃多余的字节,并在msghdr结构的msg_flags成员中设置MSG_TRUNC。

     

-recvmsg()在POSIX标准中。

从这个意义上讲,它类似于SOCK_DGRAM

第二,每个“数据报”(Linux)/“消息”(POSIX)都带有一个称为MSG_EOR的标志。

但是,针对SOCK_SEQPACKET的Linux AF_UNIX未实现MSG_EOR。当前文档与现实不符:-)​​


据称某些SOCK_SEQPACKET实现实现了另一个实现。某些同时实现。这样就涵盖了所有可能的不同组合:-)

  

[1]面向分组的协议通常使用具有以下特征的分组级别读取:   截断/丢弃语义,没有MSG_EOR。 X.25,蓝牙,IRDA,   和Unix域套接字以这种方式使用SOCK_SEQPACKET。

     

[2]面向记录的协议通常使用字节流读取和MSG_EOR   -没有数据包级别的可见性,没有截断/丢弃。 DECNet和ISO TP这样使用SOCK_SEQPACKET。

     

[3]数据包/记录混合通常使用带有截断/的SOCK_SEQPACKET   丢弃数据包级别的语义,并记录终止数据包   标有MSG_EOR。 SPX和XNS SPP通过这种方式使用SOCK_SEQPACKET。

     

https://mailarchive.ietf.org/arch/msg/tsvwg/9pDzBOG1KQDzQ2wAul5vnAjrRkA

您已显示了第1段的示例。

第2款也适用于SOCK_SEQPACKET as defined for SCTP。尽管默认情况下,它在每个MSG_EOR上都设置了sendmsg()。禁用此功能的选项称为SCTP_EXPLICIT_EOR

与文档最一致的第3款似乎是最晦涩的案例。

甚至文档也无法与自己保持一致。

  

SOCK_SEQPACKET套接字类型类似于SOCK_STREAM类型,并且也是面向连接的。 这两种类型之间的唯一区别在于,使用SOCK_SEQPACKET类型维护记录边界。可以使用一个或多个输出操作发送一条记录,并使用一个或多个输入操作接收一条记录,但是单个操作绝不会传输多条记录的一部分。接收者可以通过recvmsg()函数返回的已接收消息标志中的MSG_EOR标志来看到记录边界。 -POSIX标准