通过 C 中的套接字快速接收不同长度数据包的连续流?

Posted

技术标签:

【中文标题】通过 C 中的套接字快速接收不同长度数据包的连续流?【英文标题】:Receive continuous stream of varying length packets over sockets in C at a quick rate? 【发布时间】:2017-09-25 11:35:11 【问题描述】:

过去几天我一直在研究套接字(使用 C 语言,之前没有套接字编程方面的经验)。 实际上我必须在树莓派上收集 WiFi 数据包,进行一些处理,并且必须通过套接字将格式化的信息发送到另一个设备(两个设备都连接在网络中)。

我面临的挑战是通过套接字接收数据时。

在发送数据时,数据从发送端通过套接字成功发送,但在接收端,有时会收到一些垃圾数据或以前的数据。

在发送方(客户端):

int server_socket = socket(AF_INET, SOCK_STREAM, 0);
//connecting to the server with connect function
send(server_socket, &datalength, sizeof(datalength),0);  //datalength is an integer containing the number of bytes that are going to be sent next
send(server_socket, actual_data, sizeof(actual_data),0); //actual data is a char array containing the actual character string data

在接收端(服务器端):

int server_socket = socket(AF_INET, SOCK_STREAM, 0);
//bind the socket to the ip and port with bind function
//listen to the socket for any clients
//int client_socket = accept(server_socket, NULL, NULL);
int bytes;
recv(client_socket, &bytes, sizeof(bytes),0);
char* actual_message = malloc(bytes);
int rec_bytes = recv(client_socket, actual_message, bytes,0);

*以上代码行不是实际代码行,但流程和过程类似(异常处理和cmets)。

有时,我可以快速获取所有数据包的实际数据(没有任何错误和数据包丢失)。但有时字节(发送以告知下一个事务的字节流大小的整数)作为垃圾值被接收,所以我的代码在那个时候被破坏了。 此外,有时我在接收端收到的字节数少于预期的字节数(从收到的整数 bytes 得知)。因此,在这种情况下,我会检查该条件并检索剩余的字节。

实际上数据包到达的速率非常高(大约1000 个数据包不到一秒钟,我必须剖析、格式化并通过套接字发送)。我正在尝试不同的想法(使用 SOCK_DGRAMS 但这里有一些数据包丢失,在事务之间插入一些延迟,为每个数据包打开和关闭一个新套接字,在接收数据包后添加一个确认)但它们都不符合我的要求(快速传输丢包率为 0 的数据包)。

请提出一种通过套接字快速发送和接收不同长度数据包的方法。

【问题讨论】:

什么是actual_data?它是否全部充满了数据?没有部分填充的缓冲区?因为,您知道,TCP 是一种流式传输 协议,没有固定大小的数据包或消息边界。这意味着您可能不会总是在单个 recv 调用中获得所有发送的数据,您必须循环以确保收到所有数据。 此外,由于您要发送 int 值(我假设)作为数据长度,您确定没有任何 endianness 问题吗?因为典型 ARM 平台(如 R-PI)和典型 x86 PC 平台(如大多数现代台式计算机)上的字节序通常不一样。 @Someprogrammerdude actual data 就像英文文本(WiFi 数据包已剖析和格式化)是的...我知道它可能不会在一次调用中收到,因为我添加了支票,如果没有收到在一次调用中,我添加了代码来获取剩余的数据字节。实际问题在于字节数,有时它会收到一些垃圾值 查看我的第二条评论。 @infiniteloop - 我认为你应该重新考虑 Someprogrammerdude 的评论。当问题可能持续存在时(取决于您的测试方式),该问题似乎很少见,但如果您不转换网络字节顺序,您的数据包头数据将在不同机器上以不同方式读取......此外,由于 TCP数据包可能会将您的数据碎片化,the recv function could return a partial "read"(取决于某些因素),因此您可能会考虑数据碎片化的情况(用于收集 bytes 长数据的许多 recv 调用)。 【参考方案1】:

我看到了几个主要问题:

    我认为您的代码忽略了send 函数中缓冲区已满的可能性。

    在我看来,您的代码也忽略了recv 收集部分数据的可能性。(没关系,我刚刚看到了新评论)

    换句话说,您需要管理send 的用户级缓冲区并处理recv 中的碎片。

    代码使用sizeof(int),它在不同机器上的长度可能不同(也许改用uint32_t?)。

    代码不能与网络字节顺序相互转换。这意味着您发送的是 int 的内存结构,而不是可以被不同机器读取的整数(有些机器向后存储字节,有些向前存储,有些混合匹配)。

请注意,当您使用 TCP/IP 发送较大的数据时,它将被分割成较小的数据包。

这取决于 MTU 网络值(通常在野外运行大约 500 字节,在您的家庭网络中通常大约 1500 字节)。

要处理这些情况,您可能应该使用事件网络设计而不是阻塞套接字。

考虑通过类似的方式路由send(如果您要使用阻塞套接字):

int send_complete(int fd, void * data, size_t len) 
    size_t act = 0;
    while(act < len) 
        int tmp = send(fd, (void *)((uintptr_t)data + act), len - act);
        if(tmp <= 0 && errno != EWOULDBLOCK && errno != EAGAIN && errno != EINTR) 
            return tmp; // connection error
        act += tmp;
        // add `select` to poll the socket
    
    return (int)act;

至于sizeof的问题,我会将int替换为特定字节长度的整数类型,例如int32_t

更多细节

请注意,单独发送整数并不能保证它会被单独接收或整数本身不会被分片。

send 函数写入系统的套接字缓冲区, 写入网络(就像 recv 从可用缓冲区读取而不是从线路读取一样)。

您无法控制碎片发生的位置或 TCP 数据包的打包方式(除非您实现自己的 TCP/IP 堆栈)。

我相信您很清楚“垃圾”值是服务器发送的数据。这意味着代码不是读取您发送的整数,而是读取另一条数据。

这可能是与消息边界对齐的问题,由不完整的read 或不完整的send 引起。

附言

我会考虑在 TCP/IP 层之上使用 Websocket 协议。

这保证了二进制数据包标头适用于不同的 CPU 架构(字节序),并提供更广泛的客户端连接(例如与浏览器连接等)。

它还将解决您遇到的数据包对齐问题(不是因为它不存在,而是因为它已在您将采用的任何 Websocket 解析器中解决)。

【讨论】:

正如我上面提到的,这不是完整的代码。但是我总是可以通过检查先前接收到的预期数据和实际接收到的数据字节来获取数据包的完整发送数据。但有时,预期的数据长度(作为单独的整数被接收为垃圾值) @infiniteloop 我编辑了我的答案,添加了一些代码审查细节,并在最后添加了关于评论的简短说明。

以上是关于通过 C 中的套接字快速接收不同长度数据包的连续流?的主要内容,如果未能解决你的问题,请参考以下文章

c_cpp 使用原始套接字发送和接收带有ICMP协议的OUTPUT的数据包的小程序是六进制格式的数据包。可以打印为%c来查看数据。 IP heade

如何从套接字连续接收数据?

通过 C 中的套接字编程处理多个客户端

如何编写连接到本地文件以发送和接收数据包的客户端套接字流

通过 C 中的套接字传递结构

C# Begin/EndReceive - 我如何读取大数据?