Not
Bu sayfaya erişim yetkilendirme gerektiriyor. Oturum açmayı veya dizinleri değiştirmeyi deneyebilirsiniz.
Bu sayfaya erişim yetkilendirme gerektiriyor. Dizinleri değiştirmeyi deneyebilirsiniz.
Giriş
Paket zaman damgaları, duyarlık zaman protokolü gibi birçok saat eşitleme uygulaması için önemli bir özelliktir. Zaman damgası oluşturma işlemi, ağ bağdaştırıcısı donanımı tarafından bir paket alındığında/gönderildiğinde eşitleme uygulaması o kadar doğru olabilir.
Bu nedenle, bu konuda açıklanan zaman damgası API'leri, uygulama katmanının çok altında oluşturulan zaman damgalarını raporlamak için uygulamanıza bir mekanizma sağlar. Özellikle, miniport ile NDIS arasındaki arabirimde bir yazılım zaman damgası ve NIC donanımındaki bir donanım zaman damgası. Zaman damgası API'si, saat eşitleme doğruluğunu büyük ölçüde iyileştirebilir. Şu anda desteğin kapsamı Kullanıcı Veri Birimi Protokolü (UDP) yuvaları olarak belirlenmiştir.
Zaman damgalarını alma
alma zaman damgası alımını SIO_TIMESTAMPING IOCTL aracılığıyla yapılandırabilirsiniz. Alma zaman damgası alımını etkinleştirmek için bu IOCTL'yi kullanın. LPFN_WSARECVMSG (WSARecvMsg) işlevini kullanarak bir veri birimi aldığınızda, zaman damgası (varsa) SO_TIMESTAMP denetim iletisinde yer alır.
SO_TIMESTAMP (0x300A) mstcpip.h
içinde tanımlanır. Denetim iletisi verileri UINT64olarak döndürülür.
Zaman damgalarını iletme
İletim zaman damgası alımı da SIO_TIMESTAMPING IOCTL aracılığıyla yapılandırılır. İletim zaman damgası alımını etkinleştirmek için bu IOCTL'yi kullanın ve sistemin arabelleğe alacağı aktarım zaman damgalarının sayısını belirtin. İletme zaman damgaları oluşturuldukçe arabelleğe eklenir. Arabellek doluysa, yeni iletme zaman damgaları atılır.
Veri birimi gönderirken, veri birimiyle bir SO_TIMESTAMP_ID denetim iletisi ilişkilendirin. Bu benzersiz bir tanımlayıcı içermelidir. WSASendMsgkullanarak veri birimiyle birlikte SO_TIMESTAMP_ID denetim iletisini gönderin. WSASendMsg döndürdükten sonra iletme zaman damgaları hemen kullanılamayabilir. İletme zaman damgaları kullanılabilir hale geldikçe yuva başına arabelleğe yerleştirilir. Zaman damgasını kimliğine göre yoklama yapmak için SIO_GET_TX_TIMESTAMP IOCTL kullanın. Zaman damgası kullanılabilir durumdaysa arabellekten kaldırılır ve döndürülür. Zaman damgası kullanılamıyorsa WSAGetLastError WSAEWOULDBLOCK döndürür. Arabellek doluyken bir iletme zaman damgası oluşturulursa, yeni zaman damgası atılır.
SO_TIMESTAMP_ID (0x300B) mstcpip.h
içinde tanımlanır. Denetim iletisi verilerini UINT32olarak sağlamanız gerekir.
Zaman damgaları 64 bit sayaç değeri olarak temsil edilir. Sayacın sıklığı zaman damgasının kaynağına bağlıdır. Yazılım zaman damgaları için sayaç bir QueryPerformanceCounter (QPC) değeridir ve sıklığını QueryPerformanceFrequencyaracılığıyla belirleyebilirsiniz. NIC donanım zaman damgaları için sayaç sıklığı NIC donanımına bağlıdır ve CaptureInterfaceHardwareCrossTimestamptarafından verilen ek bilgilerle bunu belirleyebilirsiniz. Zaman damgalarının kaynağını belirlemek için GetInterfaceActiveTimestampCapabilitieskullanın ve GetInterfaceSupportedTimestampCapabilitiesişlevlerini.
Bir yuva için zaman damgası alımını etkinleştirmek için SIO_TIMESTAMPING yuva seçeneğini kullanan yuva düzeyi yapılandırmaya ek olarak, sistem düzeyinde yapılandırma da gereklidir.
Yuva gönderme yolunun gecikme süresini tahmin etme
Bu bölümde, yuva gönderme yolunun gecikme süresini tahmin etmek için iletme zaman damgalarını kullanacağız. Zaman damgasının gerçek iletim noktasına mümkün olduğunca yakın olması gereken uygulama düzeyinde GÇ zaman damgalarını kullanan mevcut bir uygulamanız varsa, bu örnek Winsock zaman damgası API'lerinin uygulamanızın doğruluğunu ne kadar artırabileceğine ilişkin nicel bir açıklama sağlar.
Örnekte sistemde yalnızca bir ağ arabirimi kartı (NIC) olduğu ve interfaceLuid bu bağdaştırıcının LUID değeri olduğu varsayılır.
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");
}
}
Yuva alma yolunun gecikme süresini tahmin etme
Alma yolu için benzer bir örnek aşağıda verilmiştır. Örnekte sistemde yalnızca bir ağ arabirimi kartı (NIC) olduğu ve interfaceLuid bu bağdaştırıcının LUID değeri olduğu varsayılır.
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");
}
}
Bir sınırlama
Winsock zaman damgası API'lerinin bir sınırlaması, SIO_GET_TX_TIMESTAMP çağırmanın her zaman engelleyici olmayan bir işlem olmasıdır. IOCTL'yi ÇAKıŞMALI bir şekilde çağırmak bile, şu anda kullanılabilir iletim zaman damgaları yoksa WSAEWOULDBLOCK hemen döndürülmesine neden olur. WSASendMsg döndürdükten sonra iletme zaman damgaları hemen kullanılamayabileceği için, uygulamanızın zaman damgası kullanılabilir olana kadar IOCTL'yi yoklaması gerekir. başarılı bir WSASendMsg çağrısından sonra, iletme zaman damgası arabelleğinin dolmadığını belirten bir iletme zaman damgasının kullanılabilir olması garanti edilir.