简体   繁体   中英

How to set timeout in UDP socket with C/C++ in WINDOWS?

I've been trying to set a timeout in my DatagramSocket implementation class in C/C++ for WINDOWS but I can't, I tried the setsockopt(s, SOL_SOCKET, SO_RCVTIMEO, (char *) &timeout, sizeof(struct timeval)); like linux and using the select function but no delay happened, what am I doing wrong?

This is the interface file:

#ifndef __DatagramSocket_H__
#define __DatagramSocket_H__


#ifdef linux
    #include <sys/socket.h>
    #include <netinet/in.h>
    #include <arpa/inet.h>
#else
    // #include <ws2tcpip.h>
    #include <winsock2.h>
    // #pragma comment(lib,"ws2_32.lib") // Winsock Library, it only works in the microsoft compiler, MinGW ignores it
#endif

#include <iostream>
#include <strings.h>
#include <unistd.h>
#include <sys/time.h>

#include <errno.h>
#include "DatagramPacket.h"

class DatagramSocket {
public:
    DatagramSocket(uint16_t iport, const std::string & addr);
    ~DatagramSocket();

    void unbind();

    int send(DatagramPacket &);
    int receive(DatagramPacket &);

    void setTimeout(long, long);
    int receiveTimeout(DatagramPacket &, time_t, time_t);
private:
    sockaddr_in localAddress, remoteAddress;

    struct timeval timeout;
    bool timeout_set;
    #ifdef linux
        int s;
    #else
        SOCKET s;
        struct fd_set fds;
    #endif
};

#endif

The DatagramPacket is defined as:

class DatagramPacket {
public:
    // data, len, ip, port
    DatagramPacket(char* , size_t, const string &, uint16_t );
    DatagramPacket(char* , size_t);
    DatagramPacket();
    ~DatagramPacket();
    string getAddress();
    char *getData();
    size_t getLength();
    uint16_t getPort();

    void setAddress(const string &);
    void setData(char* , size_t);
    void setLength(size_t);
    void setPort(uint16_t);

private:
    char *data;
    uint16_t port;
    string ip;
    size_t length;
};

The definition of the member functions from the DatagramSocket class :

DatagramSocket::DatagramSocket(uint16_t iport, const std::string &addr): timeout_set(false) {
    #ifdef _WIN32  // detect windows of 32 and 64 bits
        WSAData wsaData;
        WORD word = MAKEWORD(2, 2);
        if (WSAStartup(word, &wsaData) != 0) {
            std::cerr << "Server: WSAStartup failed with error: " << WSAGetLastError() << std::endl;
            exit(1);
        }
    #endif 

    s = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
    // bzero((char *)&localAddress, sizeof(localAddress));
    memset((char *) &localAddress, 0, sizeof(localAddress));
    localAddress.sin_family = AF_INET;
    localAddress.sin_addr.s_addr = inet_addr(addr.c_str());
    localAddress.sin_port = htons(iport);
    bind(s, (struct sockaddr *) &localAddress, sizeof(localAddress));
}

void DatagramSocket::setTimeout(long secs, long u_secs) {
    timeout = { 
        .tv_sec = secs, 
        .tv_usec = u_secs };  
    timeout_set = true;
    #ifdef __linux__
        setsockopt(s, SOL_SOCKET, SO_RCVTIMEO, (char *) &timeout, sizeof(struct timeval));
    #else 
        FD_ZERO(&fds);
        FD_SET(s, &fds);
    #endif
}


int DatagramSocket::receiveTimeout(DatagramPacket &p, time_t secs, time_t u_secs) {
    int len = sizeof(remoteAddress);
    if (!timeout_set) setTimeout(secs, u_secs);  
    #ifdef _WIN32
        int sret = select(0, &fds, NULL, NULL, &timeout);
        if (sret == 0) {
            cout << "Timeout! " << sret << endl;
            return NULL;
        }
    #endif
    int n = recvfrom(s, p.getData(), p.getLength(), 0, (struct sockaddr *) &remoteAddress, &len);
    if (n < 0) {  // deal with errors 
        #ifdef __linux__
            if (errno == EWOULDBLOCK) 
                fprintf(stderr, "Timeout \n");
            else 
                fprintf(stderr, "Error in recvfrom. \n");
        #endif
        return n;
    }
    p.setPort(remoteAddress.sin_port);  
    p.setAddress(string(inet_ntoa(remoteAddress.sin_addr)));
    p.setLength(n);
    return n;
}

I included the ws2tcpip.h and winsock2.h libraries and compiled with the -lwsock32 flag. I'm using MinGW in Windows 10. I already could achieve this task in linux but I couldn't in Windows, I've been looking for an answer in internet but no code worked for me. As I said, setting the timeout do work in linux.

Thanks in advance.

On Windows, SO_RCVTIMEO takes a DWORD specifying milliseconds, not a timeval struct like on other platforms:

void DatagramSocket::setTimeout(long secs, long u_secs) {
    timeout = { 
        .tv_sec = secs, 
        .tv_usec = u_secs };  
    timeout_set = true;

    #ifdef _WIN32
    DWORD dw = (secs * 1000) + ((u_secs + 999) / 1000);
    setsockopt(s, SOL_SOCKET, SO_RCVTIMEO, (char *) &dw, sizeof(dw));
    #else
    setsockopt(s, SOL_SOCKET, SO_RCVTIMEO, (char *) &timeout, sizeof(struct timeval));
    #endif
}

Also, note that SO_RCVTIMEO only applies to blocking reads, not to non-blocking reads. The code you have showed is not putting the socket into non-blocking mode, so there is really no point in using select() if you use SO_RCVTIMEO :

int DatagramSocket::receiveTimeout(DatagramPacket &p, time_t secs, time_t u_secs) {
    setTimeout(secs, u_secs);  
    int len = sizeof(remoteAddress);
    int n = recvfrom(s, p.getData(), p.getLength(), 0, (struct sockaddr *) &remoteAddress, &len);
    if (n < 0) {  // deal with errors 
        #ifdef _WIN32
        if (WSAGetLastError() == WSAETIMEDOUT) 
        #else
        if (errno == EAGAIN || errno == EWOULDBLOCK)
        #endif
            std::cout << "Timeout!" << std::endl;
        else
            std::cerr << "Error in recvfrom." << std::endl;
        return -1;
    }
    p.setPort(remoteAddress.sin_port);  
    p.setAddress(string(inet_ntoa(remoteAddress.sin_addr)));
    p.setLength(n);
    return n;
}

Regarding select() itself, it modifies the passed fd_set (s) on output, so you need to reset your fds variable every time you call select() . As such, if you are going to use select() , then you should move the select() variables into receiveTimeout() locally, and don't use SO_RCVTIMEO :

int DatagramSocket::receiveTimeout(DatagramPacket &p, time_t secs, time_t u_secs) {
    timeval timeout = { 
        .tv_sec = secs, 
        .tv_usec = u_secs };  

    fd_set fds;
    FD_ZERO(&fds);
    FD_SET(s, &fds);

    int nfds;
    #ifdef _WIN32
    nfds = 0;
    #else
    nfds = s + 1;
    #endif

    int sret = select(nfds, &fds, NULL, NULL, &timeout);
    if (sret <= 0) {
        if (sret == 0) {
            std::cout << "Timeout!" << std::endl;
        else
            std::cerr << "Error in select." << std::endl;
        return -1;
    }

    int len = sizeof(remoteAddress);
    int n = recvfrom(s, p.getData(), p.getLength(), 0, (struct sockaddr *) &remoteAddress, &len);
    if (n < 0) {  // deal with errors 
        std::cerr << "Error in recvfrom." << std::endl;
        return -1;
    }
    p.setPort(remoteAddress.sin_port);  
    p.setAddress(string(inet_ntoa(remoteAddress.sin_addr)));
    p.setLength(n);
    return n;
}

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