C ++ TCP Socket коммуникация - соединение работает как положено, происходит сбой через пару секунд, новые данные не принимаются и блоки read () и recv () - PullRequest
0 голосов
/ 28 сентября 2018

Я использую 64-битную Ubuntu 16.04 LTS.Как я уже сказал, я пытаюсь установить соединение через сокет TCP с другим устройством.Программа запускается с чтения данных из сокета для инициализации переменной last_recorded_data (как показано ниже, к нижней части myStartProcedure()), и я знаю, что это работает точно так, как ожидалось.Затем запускается остальная часть программы, которая управляется обратными вызовами.Когда я делаю UPDATE_BUFFER_MS что-то меньшее, например, 8, через пару секунд происходит сбой.Частота этого значения - это желаемое значение, но если я увеличу его для целей тестирования (что-то вроде 500), то оно будет работать немного дольше, но в конечном итоге тоже не получится.

Ошибкавыглядит следующим образом: устройство, с которого я пытаюсь считывать данные, последовательно отправляет данные каждые 8 ​​миллисекунд, и в этом пакете данных первые несколько байтов зарезервированы для сообщения клиенту размера пакета в байтах.Во время нормальной работы полученное количество байтов и размер, как описано в этих первых нескольких байтах, равны.Однако пакет, полученный непосредственно до начала блокировки read(), всегда на 24 байта меньше ожидаемого размера, но пакет говорит, что отправленный пакет данных должен по-прежнему иметь ожидаемый размер.Когда делается следующая попытка получить данные, вызовы read() и по истечении времени ожидания устанавливают errno равным EAGAIN (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, и его размер возвращаемого пакета является правильным / ожидаемым размером ...

1 Ответ

0 голосов
/ 28 сентября 2018

Попробуйте закомментировать настройку тайм-аута.Я никогда не использую это со своей стороны и не испытываю проблемы, о которой вы говорите.

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

Я поставил поле "sync".В TCP часто люди добавляют такое поле, поэтому, если вы потеряете позицию, вы можете перейти к следующей синхронизации, считывая по одному байту за раз.Честно говоря, с TCP вы никогда не получите такую ​​ошибку передачи.Вы можете потерять соединение, но никогда не потеряете данные из потока (т. Е. TCP походит на идеальный FIFO в вашей сети.) При этом, если вы работаете с критически важным программным обеспечением, синхронизация, а также контрольная сумма будет очень кстати.

Далее мы read() просто заголовок.Теперь мы знаем точный размер этого пакета, поэтому мы можем использовать этот конкретный размер и считывать ровно столько байтов в нашем буфере пакетов:

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

Очевидно, read() может вернуть ошибку и размерв заголовке может быть задан байтовый порядок байтов (поэтому вам нужно поменять местами байты на процессорах x86)Но это должно помочь вам.

Кроме того, если размер, найденный в заголовке, включает количество байтов в заголовке, обязательно вычтите это количество при чтении остальной части пакета.


Также неверно следующее:

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

Вы хотели очистить структуру нулями, а не символом ноль.Хотя, если он соединяется, это значит, что это не имеет большого значения.Просто используйте 0 вместо '0'.

...