[英]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()
調用將阻塞,並在超時時將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,並且其返回數據包大小為正確/預期的大小...
嘗試注釋掉超時設置。 我從沒有用過它,也沒有遇到您正在談論的問題。
// 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.