Horodatage winsock
Introduction
Les horodatages de paquets sont une fonctionnalité cruciale pour de nombreuses applications de synchronisation d’horloges, par exemple, le protocole de temps de précision. Plus la génération d’horodatage est proche du moment où un paquet est reçu/envoyé par le matériel de la carte réseau, plus l’application de synchronisation peut être précise.
Par conséquent, les API d’horodatage décrites dans cette rubrique fournissent à votre application un mécanisme permettant de signaler les horodatages générés bien au-dessous de la couche application. Plus précisément, un horodatage logiciel à l’interface entre le miniport et NDIS et un horodatage matériel dans le matériel de carte réseau. L’API d’horodatage peut considérablement améliorer la précision de synchronisation des horloges. Actuellement, la prise en charge est étendue aux sockets UDP (User Datagram Protocol).
Horodatages de réception
Vous configurez la réception de l’horodatage par le biais de la SIO_TIMESTAMPING IOCTL. Utilisez ce IOCTL pour activer la réception de l’horodatage de réception. Lorsque vous recevez un datagramme à l’aide de la fonctionLPFN_WSARECVMSG (WSARecvMsg), son horodatage (le cas échéant) est contenu dans le message de contrôle SO_TIMESTAMP.
SO_TIMESTAMP (0x300A) est défini dans mstcpip.h
. Les données de message de contrôle sont retournées sous la forme d’un UINT64.
Horodatages de transmission
La réception de l’horodatage de transmission est également configurée via la SIO_TIMESTAMPING IOCTL. Utilisez ce IOCTL pour activer la réception de l’horodatage de transmission et spécifiez le nombre d’horodatages de transmission que le système met en mémoire tampon. Lorsque les horodatages de transmission sont générés, ils sont ajoutés à la mémoire tampon. Si la mémoire tampon est pleine, les nouveaux horodatages de transmission sont ignorés.
Lors de l’envoi d’un datagramme, associez le datagramme à un message de contrôle SO_TIMESTAMP_ID. Il doit contenir un identificateur unique. Envoyez le datagramme, ainsi que son message de contrôle SO_TIMESTAMP_ID, à l’aide de WSASendMsg. Les horodatages de transmission peuvent ne pas être immédiatement disponibles après WSASendMsg. À mesure que les horodatages de transmission deviennent disponibles, ils sont placés dans une mémoire tampon par socket. Utilisez le SIO_GET_TX_TIMESTAMP IOCTL pour interroger l’horodatage par son ID. Si l’horodatage est disponible, il est supprimé de la mémoire tampon et retourné. Si l’horodatage n’est pas disponible, WSAGetLastError retourne WSAEWOULDBLOCK . Si un horodatage de transmission est généré pendant que la mémoire tampon est pleine, le nouvel horodatage est ignoré.
SO_TIMESTAMP_ID (0x300B) est défini dans mstcpip.h
. Vous devez fournir les données de message de contrôle en tant que UINT32.
Les horodatages sont représentés sous forme de valeur de compteur 64 bits. La fréquence du compteur dépend de la source de l’horodatage. Pour les horodatages logiciels, le compteur est une valeur QueryPerformanceCounter (QPC), et vous pouvez déterminer sa fréquence via QueryPerformanceFrequency. Pour les horodatages matériels de carte réseau, la fréquence du compteur dépend du matériel de la carte réseau et vous pouvez la déterminer avec des informations supplémentaires fournies par CaptureInterfaceHardwareCrossTimestamp. Pour déterminer la source des horodatages, utilisez la GetInterfaceActiveTimestampCapabilities et Fonctions GetInterfaceSupportedTimestampCapabilities.
Outre la configuration au niveau du socket à l’aide de l’option de socket SIO_TIMESTAMPING pour activer la réception d’horodatage pour un socket, la configuration au niveau du système est également nécessaire.
Estimation de la latence du chemin d’envoi du socket
Dans cette section, nous allons utiliser les horodatages de transmission pour estimer la latence du chemin d’envoi du socket. Si vous disposez d’une application existante qui consomme des horodatages d’E/S au niveau de l’application , où l’horodatage doit être le plus proche possible du point de transmission réel, cet exemple fournit une description quantitative de la quantité d’API d’horodatage Winsock qui peut améliorer la précision de votre application.
L’exemple suppose qu’il n’existe qu’une seule carte d’interface réseau (NIC) dans le système, et que interfaceLuid est le LUID de cette carte.
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");
}
}
Estimation de la latence du chemin de réception du socket
Voici un exemple similaire pour le chemin d’accès de réception. L’exemple suppose qu’il n’existe qu’une seule carte d’interface réseau (NIC) dans le système, et que interfaceLuid est le LUID de cette carte.
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");
}
}
Limitation
L’une des limitations des API d’horodatage Winsock est que l’appel de SIO_GET_TX_TIMESTAMP est toujours une opération non bloquante. Même l’appel du IOCTL d’une manière QUI SE CHEVAUCHE entraîne un retour immédiat de WSAEWOULDBLOCK s’il n’existe actuellement aucun horodatage de transmission disponible. Étant donné que les horodatages de transmission peuvent ne pas être immédiatement disponibles après WSASendMsg retourne, votre application doit interroger le IOCTL tant que l’horodatage n’est pas disponible. Un horodatage de transmission est garanti qu’il soit disponible après une WSASendMsg réussie appel étant donné que la mémoire tampon d’horodatage de transmission n’est pas complète.