问题:使用TCP和套接字C API从客户端向服务器发送和接收整数数组(以后的浮点数)。必须在Winsock和UNIX中运行。
将来可以处理客户端/服务器的不同endianess,但现在测试是在两台具有相同endianess的计算机之间进行的(Windows客户端,Linux服务器)。
我实现了客户端和服务器,一切似乎都有效,但问题是send()
(客户端)和recv()
(服务器)调用如何处理我的实现方式。或者,换句话说,如果方法有缺陷。
方法是:
在客户端上根据预定义算法生成uint8_t的向量(N个值从0到254的序列)。该序列在服务器中再现以进行比较 使用传入的数据(通过比较2个向量)。
在客户端上,发送数组大小
在客户端上,使用数组上的循环发送数组,为每个元素调用send()
。
在服务器上,recv()
数组大小。
在服务器上,recv()
数组使用数组大小的循环,为每个元素调用recv()
。
检查我的方法,
我将服务器上收到的字节保存到上一个recv()
循环内的文件
循环后,读取此文件,根据步骤1)生成另一个具有相同大小的向量,比较2个向量。 它们匹配,使用测试发送和接收的255,000,000个数组元素。
问题:
然后可以假设服务器recv()
循环保证与客户端send()
循环匹配?
或者换句话说,数组索引的顺序是否相同?
我正在关注优秀的“C中的TCP / IP套接字”(Donahoo,Calvert)以及echo客户端/服务器的示例
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?
由于
编辑:使用main()函数和用法编辑的代码,以及为清晰起见而使用变量名称
用法示例:将N个序列1次发送到服务器的IP地址
./ 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;
}
答案 0 :(得分:1)
TCP是流协议,它保证在接收方准确复制已发送的流。所以是的,订购总是匹配。如果消息出现故障,协议栈将重新排序消息。因此,如果您可靠地捕获流的开头和流的结尾,那么介于两者之间的所有内容都将按顺序排列并保持良好状态。
我不确定你是否曾想要发送一个号码而不是将它们预先编组到一个大缓冲区中。您将获得几个数量级的性能提升。
答案 1 :(得分:1)
正如@usr指出的那样,循环构造得很糟糕。需要的是&#34;发送所有&#34;并且&#34;收到所有&#34;功能
这些是基于史蒂文斯的书和#34; UNIX网络编程:套接字介绍&#34;
http://www.informit.com/articles/article.aspx?p=169505&seqNum=9
从客户端发送所有功能和发送功能:
void send_all(int sock, const void *vbuf, size_t size_buf)
{
const char *buf = (char*)vbuf; // can't do pointer arithmetic on void*
int send_size; // size in bytes sent or -1 on error
size_t size_left; // size left to send
const int flags = 0;
size_left = size_buf;
while (size_left > 0)
{
if ((send_size = send(sock, buf, size_left, flags)) == -1)
{
std::cout << "send error: " << strerror(errno) << std::endl;
exit(1);
}
if (send_size == 0)
{
std::cout << "all bytes sent " << std::endl;
break;
}
size_left -= send_size;
buf += send_size;
}
return;
}
///////////////////////////////////////////////////////////////////////////////////////
//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
send_all(sock, (void *)&arr_size, sizeof(uint32_t));
std::cout << "client sent array size: " << (int)arr_size << std::endl;
//send array
//std::vector.data() returns the address of the initial element in the container (C++11)
send_all(sock, (void *)val8.data(), sizeof(uint8_t) * val8.size());
std::cout << "client sent array: " << std::endl;
#if defined (_MSC_VER)
closesocket(sock);
WSACleanup();
#else
close(sock);
#endif
}
接收所有功能
void recv_all(int sock, void *vbuf, size_t size_buf, FILE *file)
{
char *buf = (char*)vbuf; // can't do pointer arithmetic on void*
int recv_size; // size in bytes received or -1 on error
size_t size_left; // size left to send
const int flags = 0;
size_left = size_buf;
while (size_left > 0)
{
if ((recv_size = recv(sock, buf, size_left, flags)) == -1)
{
std::cout << "recv error: " << strerror(errno) << std::endl;
exit(1);
}
if (recv_size == 0)
{
std::cout << "all bytes received " << std::endl;
break;
}
//save to local file
fwrite(buf, recv_size, 1, file);
size_left -= recv_size;
buf += recv_size;
}
return;
}
///////////////////////////////////////////////////////////////////////////////////////
//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
#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;
const 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 data and save to local file as received
///////////////////////////////////////////////////////////////////////////////////////
//save local file
file = fopen(check_file_name.c_str(), "wb");
//receive/save array size
recv_all(client_socket, &arr_size, sizeof(uint32_t), file);
std::cout << "server received array size: " << (int)arr_size << std::endl;
//receive/save array
uint8_t *buf = new uint8_t[arr_size];
recv_all(client_socket, buf, sizeof(uint8_t) * arr_size, file);
delete[] buf;
fclose(file);
std::cout << "server received array: " << std::endl;
//check
check_file(arr_size, verbose, slab_size);
// close client socket
#if defined (_MSC_VER)
closesocket(client_socket);
#else
close(client_socket);
#endif
}
}