Метка времени Winsock
Знакомство
Метки времени пакетов являются важной функцией для многих приложений синхронизации часов, например протокол времени точности. Чем ближе поколение меток времени, так и при получении или отправке пакета аппаратным оборудованием сетевого адаптера, тем точнее может быть приложение синхронизации.
Таким образом, API метки времени, описанные в этом разделе, предоставляют приложению механизм для создания меток времени, которые создаются значительно ниже уровня приложения. В частности, метка времени программного обеспечения в интерфейсе между минипортом и NDIS и меткой времени оборудования в оборудовании сетевого адаптера. API метки времени может значительно повысить точность синхронизации часов. В настоящее время поддержка распространяется на сокеты протокола UDP.
Получение меток времени
Вы настраиваете прием метки времени через SIO_TIMESTAMPING IOCTL. Используйте этот IOCTL, чтобы включить прием метки времени получения. При получении диаграммы данных с помощью функции LPFN_WSARECVMSG (WSARecvMsg) его метка времени (если она доступна) содержится в сообщении элемента управления SO_TIMESTAMP.
SO_TIMESTAMP (0x300A) определяется в mstcpip.h
. Данные сообщения элемента управления возвращаются в виде UINT64.
Метки времени передачи
Прием метки времени передачи также настраивается с помощью SIO_TIMESTAMPING IOCTL. Используйте IOCTL, чтобы включить прием метки времени передачи и указать количество меток времени передачи, которые система будет буферизировать. При создании меток времени передачи они добавляются в буфер. Если буфер заполнен, новые метки времени передачи удаляются.
При отправке диаграммы данных свяжите диаграмму данных с сообщением SO_TIMESTAMP_ID элемента управления. Он должен содержать уникальный идентификатор. Отправьте диаграмму данных вместе с сообщением SO_TIMESTAMP_ID элемента управления с помощью WSASendMsg. Метки времени передачи могут быть недоступны сразу после возврата WSASendMsg. По мере того как метки времени передачи становятся доступными, они помещаются в буфер сокета. Используйте SIO_GET_TX_TIMESTAMP IOCTL для опроса метки времени по идентификатору. Если метка времени доступна, она удаляется из буфера и возвращается. Если метка времени недоступна, WSAGetLastError возвращает WSAEWOULDBLOCK. Если при заполнении буфера создается метка времени передачи, новая метка времени удаляется.
SO_TIMESTAMP_ID (0x300B) определяется в mstcpip.h
. Необходимо указать данные сообщения элемента управления в виде UINT32.
Метки времени представлены как 64-разрядное значение счетчика. Частота счетчика зависит от источника метки времени. Для меток времени программного обеспечения счетчик является значением QueryPerformanceCounter (QPC), и его частоту можно определить с помощью QueryPerformanceFrequency. Для меток времени аппаратного адаптера частота счетчиков зависит от оборудования сетевого адаптера, и вы можете определить его с дополнительной информацией, предоставленной CaptureInterfaceHardwareCrossTimestamp. Чтобы определить источник меток времени, используйте функции GetInterfaceActiveTimestampCapabilities и Функции GetInterfaceSupportedTimestampCapabilities.
Помимо конфигурации на уровне сокета с помощью параметра сокета SIO_TIMESTAMPING для включения приема меток времени для сокета также требуется конфигурация на уровне системы.
Оценка задержки пути отправки сокета
В этом разделе мы будем использовать метки времени передачи для оценки задержки пути отправки сокета. Если у вас есть существующее приложение, использующее метки времени ввода-вывода на уровне приложения , где метка времени должна быть максимально близкой к фактической точке передачи, в этом примере представлено количественное описание того, сколько API метки времени Winsock могут повысить точность приложения.
В этом примере предполагается, что в системе существует только одна сетевая карта (сетевая карта), а interfaceLuid — LUID этого адаптера.
void QueryHardwareClockFrequency(LARGE_INTEGER* clockFrequency)
{
// Returns the hardware clock frequency. This can be calculated by
// collecting crosstimestamps via CaptureInterfaceHardwareCrossTimestamp
// and forming a linear regression model.
}
void estimate_send_latency(SOCKET sock,
PSOCKADDR_STORAGE addr,
NET_LUID* interfaceLuid,
BOOLEAN hardwareTimestampSource)
{
DWORD numBytes;
INT error;
CHAR data[512];
CHAR control[WSA_CMSG_SPACE(sizeof(UINT32))] = { 0 };
WSABUF dataBuf;
WSABUF controlBuf;
WSAMSG wsaMsg;
ULONG64 appLevelTimestamp;
dataBuf.buf = data;
dataBuf.len = sizeof(data);
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;
// Configure tx timestamp reception.
TIMESTAMPING_CONFIG config = { 0 };
config.flags |= TIMESTAMPING_FLAG_TX;
config.txTimestampsBuffered = 1;
error =
WSAIoctl(
sock,
SIO_TIMESTAMPING,
&config,
sizeof(config),
NULL,
0,
&numBytes,
NULL,
NULL);
if (error == SOCKET_ERROR) {
printf("WSAIoctl failed %d\n", WSAGetLastError());
return;
}
// Assign a tx timestamp ID to this datagram.
UINT32 txTimestampId = 123;
PCMSGHDR cmsg = WSA_CMSG_FIRSTHDR(&wsaMsg);
cmsg->cmsg_len = WSA_CMSG_LEN(sizeof(UINT32));
cmsg->cmsg_level = SOL_SOCKET;
cmsg->cmsg_type = SO_TIMESTAMP_ID;
*(PUINT32)WSA_CMSG_DATA(cmsg) = txTimestampId;
// Capture app-layer timestamp prior to send call.
if (hardwareTimestampSource) {
INTERFACE_HARDWARE_CROSSTIMESTAMP crossTimestamp = { 0 };
crossTimestamp.Version = INTERFACE_HARDWARE_CROSSTIMESTAMP_VERSION_1;
error = CaptureInterfaceHardwareCrossTimestamp(interfaceLuid, &crossTimestamp);
if (error != NO_ERROR) {
printf("CaptureInterfaceHardwareCrossTimestamp failed %d\n", error);
return;
}
appLevelTimestamp = crossTimestamp.HardwareClockTimestamp;
}
else { // software source
LARGE_INTEGER t1;
QueryPerformanceCounter(&t1);
appLevelTimestamp = t1.QuadPart;
}
error =
sendmsg(
sock,
&wsaMsg,
0,
&numBytes,
NULL,
NULL);
if (error == SOCKET_ERROR) {
printf("sendmsg failed %d\n", WSAGetLastError());
return;
}
printf("sent packet\n");
// Poll for the socket tx timestamp value. The timestamp may not be available
// immediately.
UINT64 socketTimestamp;
ULONG maxTimestampPollAttempts = 6;
ULONG txTstampRetrieveIntervalMs = 1;
BOOLEAN retrievedTimestamp = FALSE;
for (ULONG i = 0; i < maxTimestampPollAttempts; i++) {
error =
WSAIoctl(
sock,
SIO_GET_TX_TIMESTAMP,
&txTimestampId,
sizeof(txTimestampId),
&socketTimestamp,
sizeof(socketTimestamp),
&numBytes,
NULL,
NULL);
if (error != SOCKET_ERROR) {
ASSERT(numBytes == sizeof(timestamp));
ASSERT(timestamp != 0);
retrievedTimestamp = TRUE;
break;
}
error = WSAGetLastError();
if (error != WSAEWOULDBLOCK) {
printf(“WSAIoctl failed % d\n”, error);
break;
}
Sleep(txTstampRetrieveIntervalMs);
txTstampRetrieveIntervalMs *= 2;
}
if (retrievedTimestamp) {
LARGE_INTEGER clockFrequency;
ULONG64 elapsedMicroseconds;
if (hardwareTimestampSource) {
QueryHardwareClockFrequency(&clockFrequency);
}
else { // software source
QueryPerformanceFrequency(&clockFrequency);
}
// Compute socket send path latency.
elapsedMicroseconds = socketTimestamp - appLevelTimestamp;
elapsedMicroseconds *= 1000000;
elapsedMicroseconds /= clockFrequency.QuadPart;
printf("socket send path latency estimation: %lld microseconds\n",
elapsedMicroseconds);
}
else {
printf("failed to retrieve TX timestamp\n");
}
}
Оценка задержки получения сокета
Ниже приведен аналогичный пример для пути получения. В этом примере предполагается, что в системе существует только одна сетевая карта (сетевая карта), а interfaceLuid — LUID этого адаптера.
void QueryHardwareClockFrequency(LARGE_INTEGER* clockFrequency)
{
// Returns the hardware clock frequency. This can be calculated by
// collecting crosstimestamps via CaptureInterfaceHardwareCrossTimestamp
// and forming a linear regression model.
}
void estimate_receive_latency(SOCKET sock,
NET_LUID* interfaceLuid,
BOOLEAN hardwareTimestampSource)
{
DWORD numBytes;
INT error;
CHAR data[512];
CHAR control[WSA_CMSG_SPACE(sizeof(UINT64))] = { 0 };
WSABUF dataBuf;
WSABUF controlBuf;
WSAMSG wsaMsg;
UINT64 socketTimestamp = 0;
ULONG64 appLevelTimestamp;
dataBuf.buf = data;
dataBuf.len = sizeof(data);
controlBuf.buf = control;
controlBuf.len = sizeof(control);
wsaMsg.name = NULL;
wsaMsg.namelen = 0;
wsaMsg.lpBuffers = &dataBuf;
wsaMsg.dwBufferCount = 1;
wsaMsg.Control = controlBuf;
wsaMsg.dwFlags = 0;
// Configure rx timestamp reception.
TIMESTAMPING_CONFIG config = { 0 };
config.flags |= TIMESTAMPING_FLAG_RX;
error =
WSAIoctl(
sock,
SIO_TIMESTAMPING,
&config,
sizeof(config),
NULL,
0,
&numBytes,
NULL,
NULL);
if (error == SOCKET_ERROR) {
printf("WSAIoctl failed %d\n", WSAGetLastError());
return;
}
error =
recvmsg(
sock,
&wsaMsg,
&numBytes,
NULL,
NULL);
if (error == SOCKET_ERROR) {
printf("recvmsg failed %d\n", WSAGetLastError());
return;
}
// Capture app-layer timestamp upon message reception.
if (hardwareTimestampSource) {
INTERFACE_HARDWARE_CROSSTIMESTAMP crossTimestamp = { 0 };
crossTimestamp.Version = INTERFACE_HARDWARE_CROSSTIMESTAMP_VERSION_1;
error = CaptureInterfaceHardwareCrossTimestamp(interfaceLuid, &crossTimestamp);
if (error != NO_ERROR) {
printf("CaptureInterfaceHardwareCrossTimestamp failed %d\n", error);
return;
}
appLevelTimestamp = crossTimestamp.HardwareClockTimestamp;
}
else { // software source
LARGE_INTEGER t1;
QueryPerformanceCounter(&t1);
appLevelTimestamp = t1.QuadPart;
}
printf("received packet\n");
// Look for socket rx timestamp returned via control message.
BOOLEAN retrievedTimestamp = FALSE;
PCMSGHDR cmsg = WSA_CMSG_FIRSTHDR(&wsaMsg);
while (cmsg != NULL) {
if (cmsg->cmsg_level == SOL_SOCKET && cmsg->cmsg_type == SO_TIMESTAMP) {
socketTimestamp = *(PUINT64)WSA_CMSG_DATA(cmsg);
retrievedTimestamp = TRUE;
break;
}
cmsg = WSA_CMSG_NXTHDR(&wsaMsg, cmsg);
}
if (retrievedTimestamp) {
// Compute socket receive path latency.
LARGE_INTEGER clockFrequency;
ULONG64 elapsedMicroseconds;
if (hardwareTimestampSource) {
QueryHardwareClockFrequency(&clockFrequency);
}
else { // software source
QueryPerformanceFrequency(&clockFrequency);
}
// Compute socket send path latency.
elapsedMicroseconds = appLevelTimestamp - socketTimestamp;
elapsedMicroseconds *= 1000000;
elapsedMicroseconds /= clockFrequency.QuadPart;
printf("RX latency estimation: %lld microseconds\n",
elapsedMicroseconds);
}
else {
printf("failed to retrieve RX timestamp\n");
}
}
Ограничение
Одним из ограничений API метки времени Winsock является то, что вызов SIO_GET_TX_TIMESTAMP всегда является неблокирующей операцией. Даже вызов IOCTL в режиме OVERLAPPED приводит к немедленному возвращению WSAEWOULDBLOCK, если в настоящее время нет доступных меток времени передачи. Так как метки времени передачи могут быть недоступны сразу после того, как WSASendMsg возвращается, приложение должно опрашивает IOCTL до тех пор, пока метка времени не будет доступна. Метка времени передачи гарантированно будет доступна после успешного вызова WSASendMsg, учитывая, что буфер метки времени передачи не заполнен.