使用MSG_DONTWAIT的C UDP接收套接字始终失败

时间:2018-12-29 00:08:46

标签: c++ linux udp recvfrom

我正在使用UDP客户端/服务器上的几个应用程序,并且我的发送命令的应用程序运行非常出色-我可以监视通过nc和hexdump发送到端口的内容,并且它们可以完美解码。

在应该接收命令的应用程序上,我使用带有MSG_DONTWAIT标志的recvfrom。我这样做是因为我还需要检查队列中是否有要发送的东西,因此只能将其保留为阻塞状态。如果我删除了MSG_DONTWAIT标志,则可以正确接收和处理消息,但是它将阻止等待,这对我的应用程序无效。使用MSG_DONTWAIT时,它始终返回-1并将errno设置为EAGAIN。虽然什么也没发送就可以预料到,但是它从不接收任何东西。我认为它将返回EAGAIN,直到有可用的东西为止,但事实并非如此。相关代码如下:我想念什么?

uint8_t Receiver::Setup(uint16_t rx_port, uint16_t tx_port)
{

    std::stringstream ss;
    ss << "UDP session manager, setup ports.";
    Logger::Info(ss.str());

    tx_socket_fd = socket(AF_INET, SOCK_DGRAM, 0);
    rx_socket_fd = socket(AF_INET, SOCK_DGRAM, 0);

    if (rx_socket_fd < 0)
    {
        Logger::Error("Could not open an rx UDP socket!");
    }
    else
    {
        std::cout << "rx_socket_fd is " << rx_socket_fd << "\n";
    }
    if (tx_socket_fd < 0)
    {
        Logger::Error("Could not open an tx UDP socket!");
    }
    else
    {
        std::cout << "tx_socket_fd is " << tx_socket_fd << "\n";
    }


    int reuse = 1;
    if (setsockopt(tx_socket_fd, SOL_SOCKET, SO_REUSEADDR, (const char*)&reuse, sizeof(reuse)) < 0)
        Logger::Warn("Could not set socket reuse!");

    #ifdef SO_REUSEPORT
    reuse = 1;
        if (setsockopt(tx_socket_fd, SOL_SOCKET, SO_REUSEPORT, (const char*)&reuse, sizeof(reuse)) < 0)
            Logger::Warn("setsockopt(SO_REUSEPORT) failed");
    #endif

    reuse = 1;
    if (setsockopt(rx_socket_fd, SOL_SOCKET, SO_REUSEADDR, (const char*)&reuse, sizeof(reuse)) < 0)
        Logger::Warn("Could not set socket reuse!");

    #ifdef SO_REUSEPORT
    reuse = 1;
        if (setsockopt(rx_socket_fd, SOL_SOCKET, SO_REUSEPORT, (const char*)&reuse, sizeof(reuse)) < 0)
            Logger::Warn("setsockopt(SO_REUSEPORT) failed");
    #endif

    memset(&tx_sockaddr, 0, sizeof(tx_sockaddr));
    memset(&rx_sockaddr, 0, sizeof(rx_sockaddr));

    tx_sockaddr.sin_family = AF_INET;
    tx_sockaddr.sin_addr.s_addr = INADDR_ANY;
    tx_sockaddr.sin_port = htons(tx_port);

    rx_sockaddr.sin_family = AF_INET;
    rx_sockaddr.sin_addr.s_addr = INADDR_ANY;
    rx_sockaddr.sin_port = htons(rx_port);

    int rva = 0;

    rva = bind(tx_socket_fd, (const struct sockaddr *) &tx_sockaddr, sizeof(tx_sockaddr) );

    if (rva < 0)
    {
        std::stringstream ss;
        ss << "UDP SessionManager: Could not bind to tx socket (bind returned error code " << rva << ", errno is " << errno << ")";
        Logger::Error(ss.str());
    }

    rva = bind(rx_socket_fd, (const struct sockaddr *) &rx_sockaddr, sizeof(rx_sockaddr) );

    if (rva < 0)
    {
        std::stringstream ss;
        ss << "UDP SessionManager: Could not bind to rx socket (bind returned error code " << rva << ", errno is " << errno << ")";
        Logger::Error(ss.str());
    }

    return NO_ERROR;
}


uint8_t Receiver::SendTelemetry(const TelemetryBase * telemetry)
{
    const uint8_t * bytes = EncodeTelemetryToSend(telemetry);

    if (bytes == NULL)
    {
        Logger::Error("Receiver: Something went wrong trying to encode the telemetry.");
        return 1;
    }

    const UDPHeader * header = (const UDPHeader * ) bytes;
    uint16_t numBytes = header->length;

    std::stringstream ss;
    ss << "Receiver::SendTelemetry - bytesToWrite is " << numBytes << "\n";
    Logger::Info(ss.str());

    int rva = sendto(tx_socket_fd, (const char *) bytes, numBytes, 0, (const struct sockaddr *) &tx_sockaddr, sizeof(struct sockaddr_in) );

    std::this_thread::sleep_for(std::chrono::milliseconds(10));

    if (rva == -1  && errno == EINVAL)
    {
        ss.clear();
        ss << "invalid argument!";
        Logger::Warn(ss.str());
    }
    else if (rva < 0)
    {
        ss.clear();

        ss << "Failed to write to the UDP port, errno is " << errno;

        Logger::Warn(ss.str());
        return 1;
    }

    delete bytes;

    return 0;
}



uint8_t Receiver::SendCommand(const CommandBase * command)
{
    const uint8_t * bytes = EncodeCommandToSend(command);

    if (bytes == NULL)
    {
        Logger::Error("Receiver: Something went wrong trying to encode the message.");
        return 1;
    }

    const UDPHeader * header = (const UDPHeader * ) bytes;
    uint16_t numBytes = header->length;

    std::stringstream ss;
    ss << "Receiver::SendCommand - bytesToWrite is " << numBytes << "\n";
    Logger::Info(ss.str());

    int rva = sendto(tx_socket_fd, (const char *) bytes, numBytes, 0, (const struct sockaddr *) &tx_sockaddr, sizeof(struct sockaddr_in) );

    std::this_thread::sleep_for(std::chrono::milliseconds(10));

    if (rva < 0)
    {
        ss.clear();

        ss << "Failed to write to the UDP port, errno is " << errno;

        Logger::Warn(ss.str());
        return 1;
    }

    delete bytes;

    return 0;
}

uint8_t Receiver::Receive()
{
    uint8_t inputBuffer[UDP_BUFFER_BYTES];
    memset(inputBuffer, '\0', UDP_BUFFER_BYTES);

    int totalBytesRead = 0;

    //socklen_t addressLength = sizeof(rx_sockaddr);
    struct sockaddr_in sender;
    socklen_t len;

    totalBytesRead = recvfrom(rx_socket_fd, (char *) inputBuffer, UDP_BUFFER_BYTES,
                          MSG_DONTWAIT, (struct sockaddr *)  &sender, &len );

    if ( totalBytesRead >= 0 )
    {
        std::stringstream ss;
        ss << "UDP port read " << totalBytesRead << " bytes";
        Logger::Info(ss.str() );

        const CommandBase * command = DecodeReceivedCommand(inputBuffer);

        if (command == NULL)
        {
            Logger::Warn("Failed to decode received command from commanding app.");
            return UDP_ERROR_DECODE_FAILED;
        }

        EnqueCommand(command);

    }
    else
    {
        std::stringstream ss;
        ss << "UDP port rva = " << totalBytesRead << ", errno is " << errno;
        Logger::Debug(ss.str());
    }

    return UDP_ERROR_NO_ERROR;
}



void Receiver::ProcessingLoopThread()
{
    while ( GetState() == STATE_RUN )
    {
        const TelemetryBase * telemetry = DequeTelemetry();

        while (telemetry != NULL)
        {
            std::stringstream ss;
            ss << "Receiver sending telemetry with ID: " << telemetry->GetTelemetryID();
            Logger::Debug(ss.str());

            SendTelemetry(telemetry);
            delete telemetry;
            telemetry = DequeTelemetry();
        }

        Receive();

        std::this_thread::sleep_for(std::chrono::milliseconds(10));
    }
}

3 个答案:

答案 0 :(得分:1)

几件事:

这可能不是问题,但是我鼓励您在任何其他代码有机会运行并清除errno之前捕获errno。代替这个:

    std::stringstream ss;
    ss << "UDP port rva = " << totalBytesRead << ", errno is " << errno;

更好:

totalBytesRead = recvfrom(rx_socket_fd,...
int lasterror = errno; // catch errno before anything else can change it


. . .
ss << "UDP port rva = " << totalBytesRead << ", errno is " << lasterror;

回到原始问题。

我猜想在使用非阻塞MSG_DONTWAIT标志时,您需要多次轮询套接字。

看起来您的主循环在每次套接字轮询之间睡眠10毫秒。如果那是您的设计,那么只需执行以下操作即可:

创建套接字时,在其上设置10毫秒的超时时间:

timeval tv;
tv.tv_sec = 0;
tv.tv_usec = 10 * 1000; // 10 milliseconds
setsockopt(rx_socket_fd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv));

然后只需从recvfrom调用中删除MSG_DONTWAIT标志即可。

此外,在主循环中删除sleep语句:

 std::this_thread::sleep_for(std::chrono::milliseconds(10));

然后将超时错误作为可能发生的良性事件进行处理

totalBytesRead = recvfrom(rx_socket_fd, (char *) inputBuffer, UDP_BUFFER_BYTES,
                      0, (struct sockaddr *)  &sender, &len );


if (totalBytesRead >= 0 )
{
    // data available - handle it
}
else
{
    if ((errno == EAGAIN) || (errno == EWOULDBLOCK))
    {
        // socket timed out after waiting 10 milliseconds
    }
    else
    {
        // actual socket error
    }
}

答案 1 :(得分:1)

<?php
require 'db.php';
$mysqli2 = new mysqli('localhost','x','x','x');
session_start();
$_SESSION['message'] ='';

if (isset($_POST['username'])){
    /* User login process, checks if user exists and password is correct */

    // Escape email to protect against SQL injections
    $username= htmlentities($_POST['username']);
    $username = $mysqli->real_escape_string($username);
    $result = $mysqli->query("SELECT * FROM accounts WHERE username ='$username'");

    if ($result->num_rows == 0){ // User doesn't exist
        $_SESSION['message'] = "You should register !";
        header("location: error.php");
    }
    else { // User exists
        $accounts = $result->fetch_assoc();
        $password= htmlentities($_POST['password']);
        $password = $mysqli->real_escape_string($password);
        if ( password_verify($password, $accounts['password']) ) {

            $_SESSION['username'] = $accounts['username'];
            $_SESSION['usercode'] = $accounts['usercode'];
            $usercode= $_SESSION['usercode'];
            $_SESSION['email'] = $accounts['email'];
            $_SESSION['active'] = $accounts['active'];
            $result2 = $mysqli2->query("SELECT * FROM account_info WHERE usercode ='$usercode'");
            $account_info = $result2->fetch_assoc();
            $_SESSION['name']= $account_info['name'];
            $_SESSION['lastname']= $account_info['lastname'];
            $_SESSION['location']= $account_info['location'];
            $_SESSION['phone'] = $account_info['phone'];
            $_SESSION['balance'] = $account_info['balance'];
            // This is how we'll know the user is logged in
            $_SESSION['logged_in'] = true;

            header("location: index.php");
        }
        else {
            $_SESSION['message'] = "You have entered wrong password, try again!";
            header("location: error.php");
        }
        }
    }

?>

您没有为struct sockaddr_in sender; socklen_t len; totalBytesRead = recvfrom(rx_socket_fd, (char *) inputBuffer, UDP_BUFFER_BYTES, MSG_DONTWAIT, (struct sockaddr *) &sender, &len ); 分配一个明智的值。如果不将len初始化为套接字地址的大小,则调用可能会失败。

此外,您捕获len的时间也太晚了。您必须在收到错误的调用后立即捕获它。否则,其他中介操作可以更改其值。因此,您不能依赖于获得明智的价值。

您使用单独的套接字进行发送和接收非常奇怪。如果要发送到同一端点,则应该只使用一个套接字。

答案 2 :(得分:-1)

MSG_DONTWAIT(从 Linux 2.2 开始) 启用非阻塞操作;如果操作会阻塞,则调用失败并显示 错误 EAGAIN 或 EWOULDBLOCK(这也可以使用 O_NONBLOCK 标志启用 F_SETFL fcntl(2)).