簡體   English   中英

C ++ TCP套接字通信-連接按預期工作,幾秒鍾后失敗,未接收到新數據並且read()和recv()塊

[英]C++ TCP Socket communication - Connection is working as expected, fails after a couple of seconds, no new data is received and read() and recv() block

我正在使用64位Ubuntu 16.04 LTS。 就像我說的,我正在嘗試建立與另一台設備的TCP套接字連接。 該程序首先從套接字讀取數據以初始化last_recorded_data變量(如下所示,朝向myStartProcedure()的底部),並且我知道它的工作與預期的完全一樣。 然后,程序的其余部分將啟動,由回調驅動。 當我使UPDATE_BUFFER_MS小於8時,它在幾秒鍾后失敗。 該值的頻率是期望值,但是如果出於測試目的將其增大(大約為500),則它的工作時間會更長一些,但最終也會以相同的方式失敗。

失敗如下:我試圖從中讀取的設備會每8毫秒發送一次數據,並且在此數據包中,保留了前幾個字節,以告知客戶端該數據包的大小(以字節為單位)。 在正常操作期間,接收到的字節數和前幾個字節所描述的大小是相等的。 但是,在read()調用開始阻塞之前直接接收到的數據包始終比預期大小小24個字節,但是該數據包說發送的數據包仍應為預期大小。 進行下一次嘗試獲取數據時, read()調用將阻塞,並在超時時將errnoEAGAIN (Resource temporarily unavailable)

我嘗試通過Python應用程序與此設備進行通訊,但沒有遇到相同的問題。 此外,我在其中另一台設備上嘗試了該C ++應用程序,並且看到了相同的行為,因此我認為這是我的問題。 我的代碼(簡體)如下。 如果您發現任何明顯的錯誤,請告訴我,謝謝!!

#include <string>
#include <unistd.h>
#include <iostream>

#include <stdio.h>
#include <errno.h>
#include <sys/socket.h>
#include <stdlib.h>
#include <netinet/in.h>
#include <arpa/inet.h>

#define COMM_DOMAIN AF_INET
#define PORT        8008

#define TIMEOUT_SECS  3
#define TIMEOUT_USECS 0

#define UPDATE_BUFFER_MS 8

#define PACKET_SIZE_BYTES_MAX 1200

//
// Global variables
//

// Socket file descriptor
int socket_conn;

// Tracks the timestamp of the last time data was recorded
// The data packet from the TCP connection is sent every UPDATE_BUFFER_MS milliseconds
unsigned long last_process_cycle_timestamp;

// The most recently heard data, cast to a double
double last_recorded_data;

// The number of bytes expected from a full packet
int full_packet_size;

// The minimum number of bytes needed from the packet, as I don't need all of the data
int min_required_packet_size;

// Helper to cast the packet data to a double
union PacketAsFloat
{
    unsigned char byte_values[8];
    double decimal_value;
};

// Simple struct to package the data read from the socket
struct SimpleDataStruct
{
    // Whether or not the struct was properly populated
    bool valid;

    // Some data that we're interested in right now
    double important_data;

    //
    // Other, irrelevant members removed for simplicity
    //
};

// Procedure to read the next data packet
SimpleDataStruct readCurrentData()
{
    SimpleDataStruct data;
    data.valid = false;

    unsigned char socket_data_buffer[PACKET_SIZE_BYTES_MAX] = {0};

    int read_status = read(socket_conn, socket_data_buffer, PACKET_SIZE_BYTES_MAX);
    if (read_status < min_required_packet_size)
    {
        return data;
    }

    //for (int i = 0; i < read_status - 1; i++)
    //{
    //  std::cout << static_cast<int>(socket_data_buffer[i]) << ", ";
    //}
    //std::cout << static_cast<int>(socket_data_buffer[read_status - 1]) << std::endl;

    PacketAsFloat packet_union;
    for (int j = 0; j < 8; j++)
    {
        packet_union.byte_values[7 - j] = socket_data_buffer[j + 252];
    }

    data.important_data = packet_union.decimal_value;
    data.valid          = true;

    return data;
}

// This acts as the main entry point
void myStartProcedure(std::string host)
{
    //
    // Code to determine the value for full_packet_size and min_required_packet_size (because it can vary) was removed
    // Simplified version is below
    //

    full_packet_size         = some_known_value;
    min_required_packet_size = some_other_known_value;

    //
    // Create socket connection
    //

    if ((socket_conn = socket(COMM_DOMAIN, SOCK_STREAM, 0)) < 0)
    {
        std::cout << "socket_conn heard a bad value..." << std::endl;
        return;
    }

    struct sockaddr_in socket_server_address;
    memset(&socket_server_address, '0', sizeof(socket_server_address));

    socket_server_address.sin_family = COMM_DOMAIN;
    socket_server_address.sin_port   = htons(PORT);

    // Create and set timeout
    struct timeval timeout_chars;
    timeout_chars.tv_sec  = TIMEOUT_SECS;
    timeout_chars.tv_usec = TIMEOUT_USECS;

    setsockopt(socket_conn, SOL_SOCKET, SO_RCVTIMEO, (const char*)&timeout_chars, sizeof(timeout_chars));

    if (inet_pton(COMM_DOMAIN, host.c_str(), &socket_server_address.sin_addr) <= 0)
    {
        std::cout << "Invalid address heard..." << std::endl;
        return;
    }

    if (connect(socket_conn, (struct sockaddr *)&socket_server_address, sizeof(socket_server_address)) < 0)
    {
        std::cout << "Failed to make connection to " << host << ":" << PORT << std::endl;
        return;
    }
    else
    {
        std::cout << "Successfully brought up socket connection..." << std::endl;
    }

    // Sleep for half a second to let the networking setup properly
    sleepMilli(500); // A sleep function I defined elsewhere

    SimpleDataStruct initial = readCurrentData();
    if (initial.valid)
    {
        last_recorded_data = initial.important_data;
    }
    else
    {
        // Error handling
        return -1;
    }

    //
    // Start the rest of the program, which is driven by callbacks
    //
}

void updateRequestCallback()
{
    unsigned long now_ns = currentTime(); // A function I defined elsewhere that gets the current system time in nanoseconds

    if (now_ns - last_process_cycle_timestamp >= 1000000 * UPDATE_BUFFER_MS)
    {
        SimpleDataStruct current_data = readCurrentData();

        if (current_data.valid)
        {
            last_recorded_data = current_data.important_data;
            last_process_cycle_timestamp = now_ns;
        }
        else
        {
            // Error handling
             std::cout << "ERROR setting updated data, SimpleDataStruct was invalid." << std:endl;
             return;
        }
    }
}

編輯#1

我應該每次都接收一定數量的字節,並且我希望read()的返回值也將返回該值。 但是,我只是嘗試將PACKET_SIZE_BYTES_MAX的值更改為2048,而read()的返回值現在是2048,此時它應該是設備發回的數據包的大小(不是2048)。 Python應用程序還將最大值設置為2048,並且其返回數據包大小為正確/預期的大小...

嘗試注釋掉超時設置。 我從沒有用過它,也沒有遇到您正在談論的問題。

// Create and set timeout
struct timeval timeout_chars;
timeout_chars.tv_sec  = TIMEOUT_SECS;
timeout_chars.tv_usec = TIMEOUT_USECS;

setsockopt(socket_conn, SOL_SOCKET, SO_RCVTIMEO, (const char*)&timeout_chars, sizeof(timeout_chars));

為了避免阻塞,可以將套接字設置為非阻塞套接字,然后使用select()poll()獲取更多數據。 這兩個功能都可以使用上述超時。 但是,對於非阻塞套接字,必須確保讀取按預期進行。 在許多情況下,您將獲得部分讀取的內容,並且必須再次等待( select()poll() )以獲取更多數據。 因此,代碼將更加復雜。

socket_conn = socket(COMM_DOMAIN, SOCK_STREAM | SOCK_NONBLOCK, 0);

如果安全性是一個潛在的問題,我還將設置SOCK_CLOEXEC以防止子進程訪問相同的套接字。

std::vector<struct pollfd> fds;

struct pollfd fd;
fd.fd = socket_conn;
fd.events = POLLIN | POLLPRI | POLLRDHUP; // also POLLOUT for writing
fd.revents = 0; // probably useless... (kernel should clear those)
fds.push_back(fd);

int64_t timeout_chars = TIMEOUT_SECS * 1000 + TIMEOUT_USECS / 1000;

int const r = poll(&fds[0], fds.size(), timeout_chars);
if(r < 0) { ...handle error(s)... }

假設標頭大小定義明確且永不改變,另一種方法是讀取標頭,然后使用標頭信息讀取其余數據。 在這種情況下,您可以保持阻塞套接字而不會超時。 從你的結構,我不知道那會是什么。 所以...讓我們首先定義這樣的結構:

struct header
{
    char sync[4];  // four bytes indicated a synchronization point
    uint32_t size; // size of packet
    ...            // some other info
};

我輸入了“同步”字段。 在TCP中,人們通常會添加一個這樣的字段,因此,如果您不了解自己的位置,可以通過一次讀取一個字節來尋求下一個同步。 坦白說,使用TCP,您永遠都不會遇到這樣的傳輸錯誤。 您可能會丟失連接,但永遠不會丟失流中的數據(即TCP就像網絡上的完美FIFO。)也就是說,如果您正在處理任務關鍵型軟件,那么非常歡迎同步和校驗和。

接下來,我們只read()標頭。 現在我們知道了此數據包的確切大小,因此我們可以使用該特定大小,並在我們的數據包緩沖區中准確讀取那么多字節:

struct header hdr;
read(socket_conn, &hdr, sizeof(hdr));
read(socket_conn, packet, hdr.size /* - sizeof(hdr) */);

顯然, read()可能返回錯誤,並且標頭中的大小可能以big endian定義(因此您需要在x86處理器上交換字節)。 但這應該可以幫助您前進。

另外,如果在標頭中找到的大小包括標頭中的字節數,請確保在讀取包的其余部分時減去該數量。


另外,以下是錯誤的:

memset(&socket_server_address, '0', sizeof(socket_server_address));

您打算用零而不是字符零來清除結構。 盡管如果連接就意味着沒關系。 只需使用0而不是'0'

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM