共用方式為


Winsock 明確壅塞通知 (ECN)

介紹

某些以使用者數據報通訊協定 (UDP) (例如 QUIC) 為基礎的應用程式和/或通訊協定,會尋求利用明確壅塞通知 (ECN) 程式代碼點的使用,以改善擁擠網路中延遲和抖動。

Winsock ECN API 會擴充 取得ockopt/setockopt 介面,以及 WSASendMsg/LPFN_WSARECVMSG 控制訊息介面,並支援修改和接收 IP 標頭中的 ECN 程式代碼點。 所提供的功能可讓您根據每個封包取得和設定 ECN 程式代碼點。

如需有關 ECN 的詳細資訊,請參閱 將明確壅塞通知新增至 IP

傳送數據報時,不允許您的應用程式指定壅塞遭遇 (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 標頭欄位之 ECN 代碼點的 ECN 代碼點(或流量類別 IPv6 標頭欄位)。 當設定為 TRUE時,LPFN_WSARECVMSG (WSARecvMsg) 函式會傳回包含所接收數據報之 ECN 字碼點的選擇性控制數據。 傳回的控制訊息類型將會以層級 IPPROTO_IP(或 IPPROTO_IPV6IP_ECN(或 IPV6_ECN)。 控件訊息數據會以 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);
}

另請參閱