【发布时间】:2015-09-07 09:10:29
【问题描述】:
问题:使用 TCP 和套接字 C API 从客户端向服务器发送和接收整数数组(后来是浮点数)。必须在 Winsock 和 UNIX 中运行。
将来可以处理客户端/服务器的不同字节序,但现在在两台具有相同字节序的机器(Windows客户端,Linux服务器)之间进行测试。
我实现了客户端和服务器,似乎一切正常,但问题是send()(客户端)和recv()(服务器)调用如何处理我的实现方式。或者,换句话说,如果方法有缺陷。
方法是:
在客户端根据预定义的算法生成一个 uint8_t 的向量(从 0 到 254 的 N 个值序列)。此序列在服务器中复制以进行比较 与传入的数据(通过比较 2 个向量)。
在客户端,发送数组大小
在客户端,使用数组上的循环发送数组,为每个元素调用
send()。在服务器上,
recv()数组大小。在服务器上,
recv()对数组大小使用循环的数组,为每个元素调用recv()。
要检查我的方法,
我将服务器接收到的字节保存到上一个
recv()循环中的文件中循环之后,读取这个文件,按照步骤1)生成另一个相同大小的向量,比较这2个向量。 它们匹配,使用测试发送和接收了 255,000,000 个数组元素。
问题:
然后可以假设服务器recv()循环保证匹配客户端send()循环?
或者,换句话说,数组索引以相同的顺序到达?
我正在关注优秀的“C 语言中的 TCP/IP 套接字”(Donahoo、Calvert)以及回显客户端/服务器的示例
http://cs.baylor.edu/~donahoo/practical/CSockets/
引用:
“在连接的一端调用 send() 发送的字节可能不会全部由另一端的 recv() 调用返回。”
在此示例中,recv() 部分的处理方式有所不同,循环直到接收到的字节总数与发送的字节数(已知大小)匹配,根据:
while (totalBytesRcvd < echoStringLen)
{
bytesRcvd = recv(sock, echoBuffer, RCVBUFSIZE - 1, 0))
totalBytesRcvd += bytesRcvd; /* Keep tally of total bytes */
}
完整示例:
http://cs.baylor.edu/~donahoo/practical/CSockets/code/TCPEchoClient.c
但这种情况适用于 一个 send() 的多字节调用,可能不会一次全部接收到。
在我的例子中,有 N 个发送调用(每个 1 个字节)和 N 个接收调用(每个 1 个字节),它们恰好以相同的顺序进行。
问题: TCP/IP 协议是否保证多个发送调用(具有顺序时间戳) 能保证按顺序收到吗?还是时间不是问题?
一些研究:
When sending an array of int over TCP, why are only the first amount correct?
引用:
“没有什么可以保证 TCP 将如何将您发送到流的数据打包 - 它只保证它会在应用程序级别以正确的顺序结束。”
更多链接
How do I send an array of integers over TCP in C?
谢谢
EDIT : 使用 main() 函数和用法以及变量名编辑代码以清楚起见
使用示例:向IP地址的服务器发送N个序列1次
./client -i IP-address -n N -d
代码:Client.cpp
#if defined (_MSC_VER)
#include <winsock.h>
#else
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#endif
#include <iostream>
#include <stdint.h>
#include <vector>
const unsigned short server_port = 5000; // server port
void client_echo_text(const char *server_ip);
void client_send_data(const char *server_ip, const uint32_t arr_size_mult);
///////////////////////////////////////////////////////////////////////////////////////
//main
///////////////////////////////////////////////////////////////////////////////////////
// usage:
// send N sequences 1 time to server at <IP adress>
// ./client -i <IP adress> -n N -d
// same with infinite loop
// ./client -i <IP adress> -n N -l
int main(int argc, char *argv[])
{
char server_ip[255]; // server IP address (dotted quad)
strcpy(server_ip, "127.0.0.1");
uint32_t arr_size_mult = 10;
//no arguments
if (argc == 1)
{
client_send_data(server_ip, arr_size_mult);
}
for (int i = 1; i < argc && argv[i][0] == '-'; i++)
{
switch (argv[i][1])
{
case 'i':
strcpy(server_ip, argv[i + 1]);
i++;
break;
case 'e':
client_echo_text(server_ip);
exit(0);
break;
case 'n':
arr_size_mult = atoi(argv[i + 1]);
i++;
break;
case 'd':
client_send_data(server_ip, arr_size_mult);
exit(0);
break;
case 'l':
while (true)
{
client_send_data(server_ip, arr_size_mult);
}
break;
}
}
return 0;
}
///////////////////////////////////////////////////////////////////////////////////////
//client_send_data
///////////////////////////////////////////////////////////////////////////////////////
void client_send_data(const char *server_ip, const uint32_t arr_size_mult)
{
int sock; // socket descriptor
struct sockaddr_in server_addr; // server address
//data
const uint32_t arr_size = arr_size_mult * 255; // array size
//construct array
std::vector<uint8_t> val8(arr_size);
uint8_t v8 = 0;
for (size_t i = 0; i < arr_size; ++i)
{
val8[i] = v8;
v8++;
if (v8 == 255)
{
v8 = 0;
}
}
#if defined (_MSC_VER)
WSADATA ws_data;
if (WSAStartup(MAKEWORD(2, 0), &ws_data) != 0)
{
exit(1);
}
#endif
// create a stream socket using TCP
if ((sock = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP)) < 0)
{
exit(1);
}
// construct the server address structure
memset(&server_addr, 0, sizeof(server_addr)); // zero out structure
server_addr.sin_family = AF_INET; // internet address family
server_addr.sin_addr.s_addr = inet_addr(server_ip); // server IP address
server_addr.sin_port = htons(server_port); // server port
// establish the connection to the server
if (connect(sock, (struct sockaddr *) &server_addr, sizeof(server_addr)) < 0)
{
std::cout << "connect error: " << strerror(errno) << std::endl;
exit(1);
}
//send array size
if (send(sock, (char *)&arr_size, sizeof(uint32_t), 0) != sizeof(uint32_t))
{
exit(1);
}
std::cout << "client sent array size: " << (int)arr_size << std::endl;
//send array
for (size_t i = 0; i < arr_size; ++i)
{
v8 = val8[i];
if (send(sock, (char *)&v8, sizeof(uint8_t), 0) != sizeof(uint8_t))
{
exit(1);
}
}
std::cout << "client sent array: " << std::endl;
#if defined (_MSC_VER)
closesocket(sock);
WSACleanup();
#else
close(sock);
#endif
}
代码:Server.cpp
if defined(_MSC_VER)
#include <winsock.h>
#else
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#endif
#include <iostream>
#include <stdint.h>
#include <assert.h>
#include <vector>
const unsigned short server_port = 5000; // server port
void server_echo_text();
void server_recv_data(bool verbose);
void check_file(const uint32_t arr_size, bool verbose, const size_t slab_size);
///////////////////////////////////////////////////////////////////////////////////////
//main
///////////////////////////////////////////////////////////////////////////////////////
int main(int argc, char *argv[])
{
bool verbose = false;
//no arguments
if (argc == 1)
{
server_recv_data(verbose);
}
for (int i = 1; i < argc && argv[i][0] == '-'; i++)
{
switch (argv[i][1])
{
case 'v':
std::cout << "verbose mode: " << std::endl;
verbose = true;
break;
case 'e':
std::cout << "running echo server: " << std::endl;
server_echo_text();
exit(0);
break;
case 'd':
std::cout << "running data server: " << std::endl;
server_recv_data(verbose);
exit(0);
break;
}
}
return 0;
}
///////////////////////////////////////////////////////////////////////////////////////
//server_recv_data
///////////////////////////////////////////////////////////////////////////////////////
void server_recv_data(bool verbose)
{
const int MAXPENDING = 5; // maximum outstanding connection requests
int server_socket; // socket descriptor for server
int client_socket; // socket descriptor for client
sockaddr_in server_addr; // local address
sockaddr_in client_addr; // client address
int recv_size; // size in bytes returned by recv()
#if defined (_MSC_VER)
int len_addr; // length of client address data structure
#else
socklen_t len_addr;
#endif
//data
uint32_t arr_size = 0;
size_t slab_size = 1;
FILE *file;
#if defined (_MSC_VER)
WSADATA ws_data;
if (WSAStartup(MAKEWORD(2, 0), &ws_data) != 0)
{
exit(1);
}
#endif
// create socket for incoming connections
if ((server_socket = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP)) < 0)
{
exit(1);
}
// construct local address structure
memset(&server_addr, 0, sizeof(server_addr)); // zero out structure
server_addr.sin_family = AF_INET; // internet address family
server_addr.sin_addr.s_addr = htonl(INADDR_ANY); // any incoming interface
server_addr.sin_port = htons(server_port); // local port
// bind to the local address
if (bind(server_socket, (sockaddr*)&server_addr, sizeof(server_addr)) < 0)
{
//bind error: Permission denied
//You're probably trying to bind a port under 1024. These ports usually require root privileges to be bound.
std::cout << "bind error: " << strerror(errno) << std::endl;
exit(1);
}
// mark the socket so it will listen for incoming connections
if (listen(server_socket, MAXPENDING) < 0)
{
exit(1);
}
for (;;) // run forever
{
// set length of client address structure (in-out parameter)
len_addr = sizeof(client_addr);
// wait for a client to connect
if ((client_socket = accept(server_socket, (struct sockaddr *) &client_addr, &len_addr)) < 0)
{
exit(1);
}
// convert IP addresses from a dots-and-number string to a struct in_addr and back
char *str_ip = inet_ntoa(client_addr.sin_addr);
std::cout << "handling client " << str_ip << std::endl;
// receive array size
if ((recv_size = recv(client_socket, (char *)&arr_size, sizeof(uint32_t), 0)) != sizeof(uint32_t))
{
exit(1);
}
std::cout << "server received array size: " << (int)arr_size << std::endl;
//save file
file = fopen("file.bin", "wb");
fwrite(&arr_size, sizeof(uint32_t), 1, file);
//receive array
for (size_t i = 0; i < arr_size; ++i)
{
uint8_t v8;
if ((recv_size = recv(client_socket, (char *)&v8, sizeof(uint8_t), 0)) != sizeof(uint8_t))
{
exit(1);
}
//write 1 element
fwrite(&v8, sizeof(uint8_t), slab_size, file);
}
fclose(file);
std::cout << "server received array: " << std::endl;
check_file(arr_size, verbose, slab_size);
// close client socket
#if defined (_MSC_VER)
closesocket(client_socket);
#else
close(client_socket);
#endif
}
}
///////////////////////////////////////////////////////////////////////////////////////
//check_file
///////////////////////////////////////////////////////////////////////////////////////
void check_file(const uint32_t arr_size, bool verbose, const size_t slab_size)
{
//read file
std::vector<uint8_t> val8(arr_size);
std::vector<uint8_t> val8_c(arr_size);
uint32_t arr_size_r;
uint8_t v8;
FILE *file;
file = fopen("file.bin", "rb");
fread(&arr_size_r, sizeof(uint32_t), 1, file);
assert(arr_size_r == arr_size);
for (size_t i = 0; i < arr_size; ++i)
{
fread(&v8, sizeof(uint8_t), slab_size, file);
val8[i] = v8;
if (verbose) std::cout << (int)val8[i] << " ";
}
if (verbose) std::cout << std::endl;
fclose(file);
//check data, define array the same as in client, compare arrays
v8 = 0;
for (size_t i = 0; i < arr_size; ++i)
{
val8_c[i] = v8;
v8++;
if (v8 == 255)
{
v8 = 0;
}
}
//compare arrays
for (size_t i = 0; i < arr_size; ++i)
{
if (val8_c[i] != val8[i])
{
std::cout << "arrays differ at: " << i << " " << (int)val8_c[i] << " " << (int)val8[i] << std::endl;
assert(0);
}
}
std::cout << "arrays match: " << (int)arr_size << " " << (int)arr_size_r << std::endl;
std::cout << std::endl;
}
【问题讨论】:
-
std::cout闻起来不像C。 :) -
如果您稍后考虑使用浮点数(或者可能是更复杂的数据类型组合),您可以考虑使用库将数据结构化为流。 Google Protobufs、Cap'n Proto、MAVlink 或 Apache Thrift 为您执行此操作(后两者也原生支持 c)。它们提供了在发送数据之前将数据转换为字节流并在接收数据后正确读取的有效方法。
-
TCP 保证数据将按照它们在流中发送出去的顺序传递。不过,为
send和recv使用一字节缓冲区效率非常低。 -
@Andreas Gschossmann。感谢您的建议。这是一个个人项目,主要目标是学习,所以我会尽可能从头开始做所有事情,否则就没有乐趣了:-)。你是对的,当来到花车时,这将是一个更大的挑战:-)
-
您假设 recv 将读取“消息”。 TCP 不支持消息。该程序将随机失败。