Поделиться через


Уведомление о явной перегрузке Winsock (ECN)

Знакомство

Некоторые приложения и(или) протоколы, основанные на протоколе UDP (например, QUIC), стремятся использовать явные точки кода уведомления о перегрузке (ECN), чтобы повысить задержку и дрожание в перегруженных сетях.

ИНТЕРФЕЙСы API Winsock ECN расширяют интерфейс getsockopt/setsockopt, а также интерфейс WSASendMsg/LPFN_WSARECVMSG (WSARecvMsg) интерфейса управления с поддержкой изменения и получения точек кода ECN в заголовках IP-адресов. Предоставляемые функциональные возможности позволяют получать и задавать точки кода ECN на основе каждого пакета.

Дополнительные сведения об ECN см. в разделе Добавление явного уведомления о перегрузке (ECN) к IP-.

Приложению не разрешено указывать точку кода с перегрузкой (CE) при отправке диаграмм данных. Отправка возвращается с ошибкой WSAEINVAL.

Запрос ECN с помощью WSAGetRecvIPEcn

WSAGetRecvIPEcn является встроенной функцией, определенной в ws2tcpip.h.

Вызовите WSAGetRecvIPEcn, чтобы запросить текущую возможность получения сообщения IP_ECN (или IPV6_ECN) с помощьюLPFN_WSARECVMSG (WSARecvMsg).

Также см. структуруWSAMSG.

  • протокола: IPv4

  • Cmsg_level: IPPROTO_IP

  • Cmsg_type: IP_ECN (50 десятичных разрядов)

  • описание: указывает/получает точку кода ECN в поле заголовка IPv4 типа службы (TOS).

  • протокол : IPv6

  • Cmsg_level: IPPROTO_IPV6

  • Cmsg_type: IPV6_ECN (50 десятичных)

  • описание: указывает/получает точку кода ECN в поле заголовка IPv6 класса трафика.

Указание ECN с помощью WSASetRecvIPEcn

WSASetRecvIPEcn является встроенной функцией, определенной в ws2tcpip.h.

Вызовите WSASetRecvIPEcn, чтобы указать, должен ли стек IP заполнять буфер управления сообщением, содержащим кодовую точку ECN поля заголовка типа IPv4 службы (или поля заголовка класса IPv6 трафика) на полученной диаграмме данных. Если задано значение TRUE, функция LPFN_WSARECVMSG (WSARecvMsg) возвращает необязательные данные управления, содержащие кодовую точку ECN полученной диаграммы данных. Возвращаемый тип сообщения элемента управления будет IP_ECN (или IPV6_ECN) с уровнем IPPROTO_IP (или 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);
}

См. также