Примечание.
Для доступа к этой странице требуется авторизация. Вы можете попробовать войти или изменить каталоги.
Для доступа к этой странице требуется авторизация. Вы можете попробовать изменить каталоги.
Знакомство
Некоторые приложения и(или) протоколы, основанные на протоколе UDP (например, QUIC), стремятся использовать явные точки кода уведомления о перегрузке (ECN), чтобы повысить задержку и дрожание в перегруженных сетях.
ИНТЕРФЕЙСы API Winsock ECN расширяют интерфейс getsockopt/setsockopt, а также интерфейс WSASendMsg/LPFN_WSARECVMSG (WSARecvMsg) интерфейса управления с поддержкой изменения и получения точек кода ECN в заголовках IP-адресов. Предоставляемые функциональные возможности позволяют получать и задавать точки кода ECN на основе каждого пакета.
Дополнительные сведения об ECN см. в разделе Добавление явного уведомления о перегрузке (ECN) к IP-.
Приложению не разрешено указывать точку кода с перегрузкой (CE) при отправке диаграмм данных. Отправка возвращается с ошибкой WSAEINVAL.
Запрос ECN с помощью WSAGetRecvIPEcn
Это важно
WSAGetRecvIPEcn устарел и неправильно поддерживает сокеты с двумя стеками. Используйте параметры сокета IP_RECVECN и IPV6_RECVECN напрямую с методом getsockopt . В сокете с двойным стеком, который не привязан к подстановочному адресу, приложениям необходимо получить как IP_RECVECN (уровень IPPROTO_IP), так и IPV6_RECVECN (уровень IPPROTO_IPV6) отдельно. Если сокет привязан к конкретному IPv6-адресу, необходимо получить только параметр IPV6_RECVECN . Если сокет привязан к IPv4-сопоставленный IPv6-адрес (например, ::ffff:192.0.2.1), необходимо извлечь только параметр IP_RECVECN .
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 устарел и неправильно поддерживает сокеты с двумя стеками. Используйте параметры сокета IP_RECVECN и IPV6_RECVECN напрямую с помощью setsockopt . В сокете с двумя стеками, который не привязан к подстановочному адресу, приложениям необходимо задать как IP_RECVECN (уровень IPPROTO_IP), так и IPV6_RECVECN ( уровень IPPROTO_IPV6) отдельно. Если сокет привязан к конкретному IPv6-адресу, необходимо задать только параметр IPV6_RECVECN . Если сокет привязан к IPv4-сопоставленный IPv6-адрес (например, ::ffff:192.0.2.1), необходимо задать только параметр IP_RECVECN .
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;
if (addr->ss_family == AF_INET) {
// IPv4 socket: only IP_RECVECN is needed.
error =
setsockopt(
sock,
IPPROTO_IP,
IP_RECVECN,
(PCHAR)&enabled,
sizeof(enabled));
if (error == SOCKET_ERROR) {
printf("setsockopt(IP_RECVECN) failed %d\n", WSAGetLastError());
return;
}
} else if (INETADDR_ISV4MAPPED((PSOCKADDR)addr)) {
// AF_INET6 socket bound to an IPv4-mapped address
// (e.g. ::ffff:192.0.2.1): only IP_RECVECN is needed.
error =
setsockopt(
sock,
IPPROTO_IP,
IP_RECVECN,
(PCHAR)&enabled,
sizeof(enabled));
if (error == SOCKET_ERROR) {
printf("setsockopt(IP_RECVECN) failed %d\n", WSAGetLastError());
return;
}
} else if (INETADDR_ISANY((PSOCKADDR)addr)) {
// AF_INET6 socket bound to the any address (::). Check
// IPV6_V6ONLY to determine if the socket is dual-stack.
DWORD v6Only;
INT v6OnlyLen = sizeof(v6Only);
error =
getsockopt(
sock,
IPPROTO_IPV6,
IPV6_V6ONLY,
(PCHAR)&v6Only,
&v6OnlyLen);
if (error == SOCKET_ERROR) {
printf("getsockopt(IPV6_V6ONLY) failed %d\n", WSAGetLastError());
return;
}
// Always set IPV6_RECVECN for v6 traffic.
error =
setsockopt(
sock,
IPPROTO_IPV6,
IPV6_RECVECN,
(PCHAR)&enabled,
sizeof(enabled));
if (error == SOCKET_ERROR) {
printf("setsockopt(IPV6_RECVECN) failed %d\n", WSAGetLastError());
return;
}
if (!v6Only) {
// Dual-stack socket: also set IP_RECVECN for v4 traffic.
error =
setsockopt(
sock,
IPPROTO_IP,
IP_RECVECN,
(PCHAR)&enabled,
sizeof(enabled));
if (error == SOCKET_ERROR) {
printf("setsockopt(IP_RECVECN) failed %d\n", WSAGetLastError());
return;
}
}
} else {
// AF_INET6 socket bound to a specific IPv6 address: v6 only.
error =
setsockopt(
sock,
IPPROTO_IPV6,
IPV6_RECVECN,
(PCHAR)&enabled,
sizeof(enabled));
if (error == SOCKET_ERROR) {
printf("setsockopt(IPV6_RECVECN) 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);
}