UDP winsock服务器c++带有阻塞

UDP winsock server c++ with blocking

本文关键字:c++ winsock 服务器 UDP      更新时间:2023-10-16

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

"由于执行进程意外退出,活动的测试运行被中止。要进一步调查,请在计算机级别或为进程vstest.executionengine.x86.exe启用本地崩溃转储。转到更多详细信息:http://go.microsoft.com/fwlink/?linkid=232477"

如果我调试,我发现服务器中的recvfrom函数返回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, '', 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 TIMErn") == 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"语句更改了超时的方式。

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返回数据包大小,您可以使用它)。此外,您正在将发送的大小与sizeof(current_time)进行比较,这毫无意义。您应该与发送的缓冲区的大小进行比较。

还有其他一些小问题,但这些都是突出的大问题。

您有这行代码:

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

如果由于未接收到数据而经过10秒超时,则recvfrom()将返回-1,WSAGetLastError()将返回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应用程序发送数据。