c - 连接到环回地址时的管道损坏 (EPIPE)

标签 c sockets networking sigpipe epipe

我目前正在测试我的网络代码。这涉及通过 IPv4 环回地址 (127.0.0.1) 建立连接。不幸的是,程序经常(并非总是)在发送数据时给出 EPIPE 错误。

我正在使用伯克利网络套接字和 libevent。我通过以下方式制作了一个非阻塞套接字:

CBSocketReturn CBNewSocket(uint64_t * socketID,bool IPv6){
    *socketID = socket(IPv6 ? PF_INET6 : PF_INET, SOCK_STREAM, 0);
    if (*socketID == -1) {
        if (errno == EAFNOSUPPORT || errno == EPROTONOSUPPORT) {
            return CB_SOCKET_NO_SUPPORT;
        }
        return CB_SOCKET_BAD;
    }
    // Stop SIGPIPE annoying us.
    if (CB_NOSIGPIPE) {
        int i = 1;
        setsockopt(*socketID, SOL_SOCKET, SO_NOSIGPIPE, &i, sizeof(i));
    }
    // Make socket non-blocking
    evutil_make_socket_nonblocking((evutil_socket_t)*socketID);
    return CB_SOCKET_OK;
}

我通过以下方式进行连接事件:
bool CBSocketDidConnectEvent(uint64_t * eventID,uint64_t loopID,uint64_t socketID,void (*onDidConnect)(void *,void *),void * node){
    CBEvent * event = malloc(sizeof(*event));
    event->loop = (CBEventLoop *)loopID;
    event->onEvent.ptr = onDidConnect;
    event->node = node;
    event->event = event_new(((CBEventLoop *)loopID)->base, (evutil_socket_t)socketID, EV_TIMEOUT|EV_WRITE, CBDidConnect, event);
    if (NOT event->event) {
        free(event);
        event = 0;
    }
    *eventID = (uint64_t)event;
    return event;
}
void CBDidConnect(evutil_socket_t socketID,short eventNum,void * arg){
    CBEvent * event = arg;
    if (eventNum & EV_TIMEOUT) {
        // Timeout for the connection
        event->loop->onTimeOut(event->loop->communicator,event->node,CB_TIMEOUT_CONNECT);
    }else{
        // Connection successful
        event->onEvent.ptr(event->loop->communicator,event->node);
    }
}

并通过以下方式添加:
bool CBSocketAddEvent(uint64_t eventID,uint16_t timeout){
    CBEvent * event = (CBEvent *)eventID;
    int res;
    if (timeout) {
        struct timeval time = {timeout,0};
        res = event_add(event->event, &time);
    }else
        res = event_add(event->event, NULL);
    return NOT res;
}

连接:
bool CBSocketConnect(uint64_t socketID,uint8_t * IP,bool IPv6,uint16_t port){
    // Create sockaddr_in6 information for a IPv6 address
    int res;
    if (IPv6) {
        struct sockaddr_in6 address;
        memset(&address, 0, sizeof(address)); // Clear structure.
        address.sin6_family = AF_INET6;
        memcpy(&address.sin6_addr, IP, 16); // Move IP address into place.
        address.sin6_port = htons(port); // Port number to network order
        res = connect((evutil_socket_t)socketID, (struct sockaddr *)&address, sizeof(address));
    }else{
        struct sockaddr_in address;
        memset(&address, 0, sizeof(address)); // Clear structure.
        address.sin_family = AF_INET;
        memcpy(&address.sin_addr, IP + 12, 4); // Move IP address into place. Last 4 bytes for IPv4.
        address.sin_port = htons(port); // Port number to network order
        res = connect((evutil_socket_t)socketID, (struct sockaddr *)&address, sizeof(address));
    }
    if (NOT res || errno == EINPROGRESS)
        return true;
    return false;
}

连接后,会发生 canSend 事件:
bool CBSocketCanSendEvent(uint64_t * eventID,uint64_t loopID,uint64_t socketID,void (*onCanSend)(void *,void *),void * node){
    CBEvent * event = malloc(sizeof(*event));
    event->loop = (CBEventLoop *)loopID;
    event->onEvent.ptr = onCanSend;
    event->node = node;
    event->event = event_new(((CBEventLoop *)loopID)->base, (evutil_socket_t)socketID, EV_TIMEOUT|EV_WRITE|EV_PERSIST, CBCanSend, event);
    if (NOT event->event) {
        free(event);
        event = 0;
    }
    *eventID = (uint64_t)event;
    return event;
}
void CBCanSend(evutil_socket_t socketID,short eventNum,void * arg){
    CBEvent * event = arg;
    if (eventNum & EV_TIMEOUT) {
        // Timeout when waiting to write.
        event->loop->onTimeOut(event->loop->communicator,event->node,CB_TIMEOUT_SEND);
    }else{
        // Can send
        event->onEvent.ptr(event->loop->communicator,event->node);
    }
}

但是发送经常会出现 EPIPE 错误:
int32_t CBSocketSend(uint64_t socketID,uint8_t * data,uint32_t len){
    ssize_t res = send((evutil_socket_t)socketID, data, len, CB_SEND_FLAGS);
    printf("SENT (%li): ",res);
    for (uint32_t x = 0; x < res; x++) {
        printf("%c",data[x]);
    }
    printf("\n");
    if (res >= 0)
        return (int32_t)res;
    if (errno == EAGAIN)
        return 0; // False event. Wait again.
    return CB_SOCKET_FAILURE; // Failure
}

它登陆return CB_SOCKET_FAILURE;并且 errno 设置为 EPIPE。现在为什么会这样?如果设置了发送标志,则它只是 MSG_NOSIGNAL,因为 SIGPIPE 一直以这个错误中断程序。我希望 EPIPE 导致 CBSocketSend 返回 CB_SOCKET_FAILURE 并且不中断程序,但是 EPIPE 没有原因导致发送失败,那么为什么要这样做呢?

上次我收到错误时,我注意到连接的线程仍在 connect() 调用中。使连接事件由单独的线程而不是连接的线程处理是否有危险?

在这些地方查看网络代码:

https://github.com/MatthewLM/cbitcoin/blob/master/test/testCBNetworkCommunicator.c
https://github.com/MatthewLM/cbitcoin/tree/master/src/structures/CBObject/CBNetworkCommunicator
https://github.com/MatthewLM/cbitcoin/tree/master/dependencies/sockets

谢谢你。

编辑:我再次运行它,在 connect() 完成后出现错误。

编辑 2:似乎连接事件是在没有对方接受的情况下给出的。

最佳答案

我不是 TCP/IP 专家,但我确实注意到该文档说 EPIPE即使 MSG_NOSIGNAL 设置为“面向流的套接字”,仍然可以返回。看起来您正在使用 SOCK_STREAM 创建套接字.另一端可能正在断开连接。

CBSocketConnect()看起来如果你得到 EINPROGRESS您只需返回 true - 如果连接成功,您也会返回。您将无法知道是否需要等待连接完成。根据this你可以 select()poll()用于连接完成。

以上是我在@MatthewMitchell 和@user315052 的要求下对OP 的评论的转贴。

编辑: I我正在添加 this answer 的更详细描述,以及随后的一些讨论。

所以,首先尝试做connect() .那么,如果 EINPROGRESS是错误结果,从 libevent 注册一个写事件唤醒.进入 EV_WRITE 的回调函数后,查看与getsockopt() 的连接状态带 socket 选项 SO_ERROR在级别 SOL_SOCKET .如果返回的选项值为0 ,连接成功。否则,将其视为 errno数字。

遵循 this answer 中所示的建议后,您发现客户端遇到错误ECONNREFUSED .这解释了为什么您的写入失败并显示 EPIPE。 .调查你的服务器后,发现服务器无法监听绑定(bind)地址,因为错误EADDRINUSE .这可以通过设置 SO_REUSEADDR 来解决。监听套接字上的选项。

关于c - 连接到环回地址时的管道损坏 (EPIPE),我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/11990700/

相关文章:

c - C 中强制转换指针的含义

c - 简单的 C If 语句

c - 在线程上下文中而不是在默认上下文中运行 g_timeout_add

sockets - 有没有办法在魔兽世界 Lua 脚本中使用套接字?

ios - 低级蓝牙数据包分析

java - 在 Java 中通过 Socket 发送鼠标坐标

networking - 如何将多个 Docker 连接到单个主机中的不同网桥?

c - 从末尾删除一定数量的字符

java.net.SocketException : Connection reset - Between a client and server deployed as webapps in tomcat 异常

networking - 如何从另一台设备测试使用 Angular CLI ng serve 创建的应用程序?