winsock 显式拥塞通知 (ECN)

简介

例如,基于用户数据报协议 (UDP) (的某些应用程序和/或协议,QUIC) 寻求利用显式拥塞通知 (ECN) 码位的使用,以改善拥堵网络中延迟和抖动。

Winsock ECN API 扩展 了 getsockopt/setsockopt 接口,以及 WSASendMsg/LPFN_WSARECVMSG (WSARecvMsg) 控制消息接口,支持修改和接收 IP 标头中的 ECN 代码点。 通过提供的功能,可以按数据包获取和设置 ECN 码位。

有关 ECN 的详细信息,请参阅向 IP 添加显式拥塞通知 (ECN)

不允许应用程序在发送数据报时指定“遇到拥塞 (CE) 码位。 发送将返回错误 WSAEINVAL

使用 WSAGetRecvIPEcn 查询 ECN

WSAGetRecvIPEcn 是在 中 ws2tcpip.h定义的内联函数。

调用 WSAGetRecvIPEcn,以通过LPFN_WSARECVMSG (WSARecvMsg) 查询接收IP_ECN (或IPV6_ECN) 控制消息的当前启用。

另请参阅 WSAMSG 结构。

  • 协议:IPv4

  • Cmsg_level:IPPROTO_IP

  • Cmsg_type:IP_ECN (50 个小数)

  • 说明:在“服务类型” (TOS) IPv4 标头字段中指定/接收 ECN 代码点。

  • 协议:IPv6

  • Cmsg_level:IPPROTO_IPV6

  • Cmsg_type:IPV6_ECN (50 十进制)

  • 说明:在“流量类 IPv6 标头”字段中指定/接收 ECN 代码点。

使用 WSASetRecvIPEcn 指定 ECN

WSASetRecvIPEcn 是在 中 ws2tcpip.h定义的内联函数。

调用 WSASetRecvIPEcn 以指定 IP 堆栈是否应使用包含接收的数据报上) 的服务 IPv4 标头 (字段的类型或流量类 IPv6 标头字段的 ECN 代码点的消息填充控制缓冲区。 TRUE设置为 时,LPFN_WSARECVMSG (WSARecvMsg) 函数返回包含所接收数据报的 ECN 代码点的可选控制数据。 返回的控制消息类型将IP_ECN级别IPPROTO_IP (或IPV6_ECN) 级别 (或IPPROTO_IPV6) 。 控制消息数据作为 INT 返回。 此选项仅在数据报套接字上有效, (套接字类型必须 SOCK_DGRAM) 。

代码示例 1 - 应用程序广告 ECN 支持

#define ECN_ECT_0 2

void sendEcn(SOCKET sock, PSOCKADDR_STORAGE addr, LPFN_WSASENDMSG sendmsg, PCHAR data, INT datalen)
{
    DWORD numBytes;
    INT error;

    CHAR control[WSA_CMSG_SPACE(sizeof(INT))] = { 0 };
    WSABUF dataBuf;
    WSABUF controlBuf;
    WSAMSG wsaMsg;
    PCMSGHDR cmsg;

    dataBuf.buf = data;
    dataBuf.len = datalen;
    controlBuf.buf = control;
    controlBuf.len = sizeof(control);
    wsaMsg.name = (PSOCKADDR)addr;
    wsaMsg.namelen = (INT)INET_SOCKADDR_LENGTH(addr->ss_family);
    wsaMsg.lpBuffers = &dataBuf;
    wsaMsg.dwBufferCount = 1;
    wsaMsg.Control = controlBuf;
    wsaMsg.dwFlags = 0;

    cmsg = WSA_CMSG_FIRSTHDR(&wsaMsg);
    cmsg->cmsg_len = WSA_CMSG_LEN(sizeof(INT));
    cmsg->cmsg_level = (addr->ss_family == AF_INET) ? IPPROTO_IP : IPPROTO_IPV6;
    cmsg->cmsg_type = (addr->ss_family == AF_INET) ? IP_ECN : IPV6_ECN;
    *(PINT)WSA_CMSG_DATA(cmsg) = ECN_ECT_0;

    error =
        sendmsg(
            sock,
            &wsaMsg,
            0,
            &numBytes,
            NULL,
            NULL);
    if (error == SOCKET_ERROR) {
        printf("sendmsg failed %d\n", WSAGetLastError());
    }
}

代码示例 2 - 应用程序检测拥塞

#define ECN_ECT_CE 3

int recvEcn(SOCKET sock, PSOCKADDR_STORAGE addr, LPFN_WSARECVMSG recvmsg, PCHAR data, INT datalen, PBOOLEAN congestionEncountered)
{
    DWORD numBytes;
    INT error;
    INT ecnVal;
    SOCKADDR_STORAGE remoteAddr = { 0 };

    CHAR control[WSA_CMSG_SPACE(sizeof(INT))] = { 0 };
    WSABUF dataBuf;
    WSABUF controlBuf;
    WSAMSG wsaMsg;
    PCMSGHDR cmsg;

    dataBuf.buf = data;
    dataBuf.len = datalen;
    controlBuf.buf = control;
    controlBuf.len = sizeof(control);
    wsaMsg.name = (PSOCKADDR)&remoteAddr;
    wsaMsg.namelen = sizeof(remoteAddr);
    wsaMsg.lpBuffers = &dataBuf;
    wsaMsg.dwBufferCount = 1;
    wsaMsg.Control = controlBuf;
    wsaMsg.dwFlags = 0;

    *congestionEncountered = FALSE;

    error =
        recvmsg(
            sock,
            &wsaMsg,
            &numBytes,
            NULL,
            NULL);
    if (error == SOCKET_ERROR) {
        printf("recvmsg failed %d\n", WSAGetLastError());
        return -1;
    }

    cmsg = WSA_CMSG_FIRSTHDR(&wsaMsg);
    while (cmsg != NULL) {
        if ((cmsg->cmsg_level == IPPROTO_IP && cmsg->cmsg_type == IP_ECN) ||
            (cmsg->cmsg_level == IPPROTO_IPV6 && cmsg->cmsg_type == IPV6_ECN)) {
            ecnVal = *(PINT)WSA_CMSG_DATA(cmsg);
            if (ecnVal == ECN_ECT_CE) {
                *congestionEncountered = TRUE;
            }
            break;
        }
        cmsg = WSA_CMSG_NXTHDR(&wsaMsg, cmsg);
    }

    return numBytes;
}

void receiver(SOCKET sock, PSOCKADDR_STORAGE addr, LPFN_WSARECVMSG recvmsg)
{
    DWORD numBytes;
    INT error;
    DWORD enabled;
    CHAR data[512];
    BOOLEAN congestionEncountered;

    error = bind(sock, (PSOCKADDR)addr, sizeof(*addr));
    if (error == SOCKET_ERROR) {
        printf("bind failed %d\n", WSAGetLastError());
        return;
    }

    enabled = TRUE;
    error = WSASetRecvIPEcn(sock, enabled);
    if (error == SOCKET_ERROR) {
        printf(" WSASetRecvIPEcn failed %d\n", WSAGetLastError());
        return;
    }

    do {
        numBytes = recvEcn(sock, addr, recvmsg, data, sizeof(data), &congestionEncountered);
        if (congestionEncountered) {
            // Tell sender to slow down
        }
    } while (numBytes > 0);
}

另请参阅