UDP winsock 服务器 c++ 与阻塞

UDP winsock server c++ with blocking

我正在尝试编写一个 udp 客户端和服务器,它将 return ntp 时间和 boxtime 之间的偏移量。我无法让我的服务器正确接收数据。我正在使用 Microsoft 单元测试对其进行测试,当我尝试测试服务器和客户端时,测试实际上失败了。如果我 运行 测试我只会收到错误消息:

"The active Test Run was aborted because the execution process exited unexpectedly. To investigate further, enable local crash dumps either at the machine level or for process vstest.executionengine.x86.exe. Go to more details: http://go.microsoft.com/fwlink/?linkid=232477"

如果我调试,我发现服务器中的 recvfrom 函数 returns 0,所以它就退出了。

这是我的服务器代码:

#pragma once
#include <iostream>
#include "NtpServer.h"
#include "stdafx.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <winsock.h>
#include <errno.h>


using std::chrono::system_clock;

namespace ntp
{


struct sockaddr_in server;
struct sockaddr_storage client;



//constructor to create ntp server
 NtpServer::NtpServer(u_short portnum, const std::chrono::nanoseconds                 desiredOffset) : portnum(0), client_length(0), bytes_received(0), current_time(0), desiredOffset(0)
{

    WSADATA wsaData;

    int iResult = WSAStartup(MAKEWORD(2, 2), &wsaData);

    if (iResult != 0)
    {
        std::cerr << "Could not open Windows connection." << std::endl; 
        exit(0);
    }

    memset((void *)&server, '[=10=]', sizeof(struct sockaddr_in));
    server.sin_family = AF_INET;
    server.sin_port = htons(portnum);
    server.sin_addr.s_addr = htonl(INADDR_ANY);


    sd = WSASocket(AF_INET, SOCK_DGRAM, 17, NULL, 0, NULL);

    if (sd == INVALID_SOCKET)
    {
        std::cerr << "Could not create socket." << std::endl;
        WSACleanup();
        exit(0);
    }



if (bind(sd, reinterpret_cast<SOCKADDR *>(&server),
        sizeof(server)) == -1)
    {
        std::cerr << "Could not bind name to socket" << std::endl;
        closesocket(sd);
        WSACleanup();
        exit(0);
    }



    getResult(desiredOffset);
}

NtpServer::~NtpServer()
{
    closesocket(sd);
    WSACleanup();

}   

void NtpServer::getResult(const std::chrono::nanoseconds desiredOffset)
{
    ntp_data ntpData = ntp_data();

    //set up timeout with blocking
    fd_set fds;
    int n;
    struct timeval tv;
    FD_ZERO(&fds);
    FD_SET(sd, &fds);
    tv.tv_sec = 10;  // 10 Secs Timeout 
    tv.tv_usec = 0;
    n = select(sd, &fds, NULL, NULL, &tv);
    if (n == 0)
    {
        exit(0);
    }

    while (1)
    {
        //client_length = sizeof(client); 
        int len = (int)sizeof(struct sockaddr_in);

        /* Receive bytes from client */
        bytes_received = recvfrom(sd, sendBuffer, NTP_PACKET_MAX, 0, (struct sockaddr *)&client, &len);

        if (bytes_received == SOCKET_ERROR)
        {
            std::cerr << "Could not receive datagram." << std::endl;
            closesocket(sd);
            WSACleanup();
            exit(0);
        }
        if (bytes_received < NTP_PACKET_MIN)
        {
            continue; 
        }



        /* Check for time request */
        if (strcmp(readBuffer, "GET TIME\r\n") == 0)
        {
            /* Get current time */
            system_clock::time_point now = std::chrono::system_clock::now();
            auto timepointoffset = (now + desiredOffset).time_since_epoch();
            double current_value = std::chrono::duration_cast<std::chrono::duration<double>>(timepointoffset).count();

            unpack_ntp(&ntpData, (unsigned char *)readBuffer, bytes_received);
            make_packet(&ntpData, NTP_CLIENT, current_value);
            pack_ntp((unsigned char *)sendBuffer, NTP_PACKET_MIN, &ntpData);


            /* Send data back */
            if (sendto(sd, sendBuffer,
                (int)sizeof(sendBuffer), 0,
                (struct sockaddr *)&client, client_length) !=
                (int)sizeof(current_time))
            {
                std::cerr << "Error sending datagram." << std::endl;
                closesocket(sd);
                WSACleanup();
                exit(0);
            }
        }
    }
    closesocket(sd);
    WSACleanup();

}



}

编辑:我用 select 语句和 recvfrom "if" 语句改变了超时的方式。

你有这行代码:

setsockopt(sd, SOL_SOCKET, SO_RCVTIMEO, (char *)&tv, sizeof(struct timeval));

如果 10 秒超时已过,因为没有收到数据,recvfrom() 将 return -1,WSAGetLastError() 将 return 10060。您的代码将退出情况:

bytes_received = recvfrom(sd, sendBuffer, NTP_PACKET_MAX, 0, (struct sockaddr *)&client, &len);

if (bytes_received == SOCKET_ERROR)
{
    std::cerr << "Could not receive datagram." << std::endl;
    closesocket(sd);
    WSACleanup();
    exit(0); // <-- here
}

即使 select() 超时,您也会退出:

n = select(sd, &fds, NULL, NULL, &tv);
if (n == 0)
{
    exit(0); // <-- here
}

确保有另一个应用程序实际向您的 UDP 应用程序发送数据。

bytes_received = recvfrom(sd, sendBuffer, NTP_PACKET_MAX, 0, (struct sockaddr *)&client, &client_length);

if (bytes_received < NTP_PACKET_MIN)
{
    std::cerr << "Could not receive datagram." << std::endl;
    closesocket(sd);
    WSACleanup();
    exit(0);
}

应该是:

bytes_received = recvfrom(sd, sendBuffer, NTP_PACKET_MAX, 0, (struct sockaddr *)&client, &client_length);

if (bytes_received == SOCKET_ERROR)
{
    int err = WSAGetLastError();

    // Handle WSAETIMEDOUT here if necessary

    std::cerr << "Could not receive datagram, error: " << err << std::endl;
    closesocket(sd);
    WSACleanup();
    exit(0);
}

if (bytes_received < NTP_PACKET_MIN)
{
    // print/log a warning here
    continue;
}

如果对 recvfrom() 的调用失败,这会中止接收循环,但会简单地忽略无效数据包(那些小于最小长度的数据包)。

另一个问题:

unpack_ntp(&ntpData, (unsigned char *)readBuffer, bytes_received);
make_packet(&ntpData, NTP_CLIENT, current_value);
pack_ntp((unsigned char *)sendBuffer, NTP_PACKET_MIN, &ntpData);

/* Send data back */
if (sendto(sd, sendBuffer,
    (int)sizeof(sendBuffer), 0,
    (struct sockaddr *)&client, client_length) != (int)sizeof(current_time))
{
    std::cerr << "Error sending datagram." << std::endl;
    closesocket(sd);
    WSACleanup();
    exit(0);
}

您要发送整个 sendBuffer;您应该只发送 NTP 数据包的大小。 (希望 pack_ntp returns 数据包大小,您可以使用它)。此外,您将发送的大小与 sizeof(current_time) 进行比较,这毫无意义。您应该与发送的缓冲区大小进行比较。

还有其他小问题,但这些是跳出来的大问题。