简体   繁体   中英

UDP winsock server c++ with blocking

I am trying to program a udp client and server that will return the offset between the ntp time and boxtime. I cannot get my server to correctly receive data. I am testing it with Microsoft Unit tests, and when I try and test the server and client the test actually fails. If I run the test I just get the error message:

"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 "

If I debug I find that recvfrom function in the server returns 0, so it just exits.

Here is my code for the server:

#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, '\0', 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();

}



}

Edit: I changed the way I did the timeout with a select statement, and recvfrom "if" statements.

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);
}

Should be:

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;
}

This aborts the receive loop if a call to recvfrom() fails, but simply ignores invalid packets (those less than the minimum length).

Another issue:

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);
}

You're sending the entire sendBuffer ; you should probably send only the size of the NTP packet. (Hopefully pack_ntp returns the packet size and you can use that). Also, you're comparing the sent size with sizeof(current_time) which makes zero sense. You should compare against the size of the buffer sent.

There are other minor issues, but these are the big ones that jump out.

You have this line of code:

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

If the 10 second timeout elapses because no data was received, recvfrom() will return -1 and WSAGetLastError() will return 10060. Your code is exiting in that situation:

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
}

Even if select() times out, you are exiting as well:

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

Make sure there is another application actually sending data to your UDP app.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM