Передача большого массива через TCP
Требуется передать большой массив int'ов с клиента на сервер, однако передается только 16370 элементов массива. Это примерно равно 64 кБ, что является максимальной длиной одного пакета ТСР, если я правильно все понял. Аналогичный код, только с использованием WinSock2 работал на windows, но при переносе на Linux и смене библиотеки сокета, получилось то, что получилось.
UPD. Я понимаю, что можно вручную разбить на куски и высылать их, но насколько я понял, так упадёт скорость передачи, а смысл этого всего был в получении максимальной скорости(просто это часть кода).
Сервер
#include <iostream>
#include <sys/types.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netdb.h>
#include <arpa/inet.h>
#include <string.h>
#include <string>
using namespace std;
int main()
{
// Create a socket
int listening = socket(AF_INET, SOCK_STREAM, 0);
if (listening == -1)
{
cerr << "Can't create a socket! Quitting" << endl;
return -1;
}
// Bind the ip address and port to a socket
sockaddr_in hint;
hint.sin_family = AF_INET;
hint.sin_port = htons(54000);
inet_pton(AF_INET, "0.0.0.0", &hint.sin_addr);
bind(listening, (sockaddr*)&hint, sizeof(hint));
// Tell Winsock the socket is for listening
listen(listening, SOMAXCONN);
// Wait for a connection
sockaddr_in client;
socklen_t clientSize = sizeof(client);
int clientSocket = accept(listening, (sockaddr*)&client, &clientSize);
char host[NI_MAXHOST]; // Client's remote name
char service[NI_MAXSERV]; // Service (i.e. port) the client is connect on
memset(host, 0, NI_MAXHOST); // same as memset(host, 0, NI_MAXHOST);
memset(service, 0, NI_MAXSERV);
if (getnameinfo((sockaddr*)&client, sizeof(client), host, NI_MAXHOST, service, NI_MAXSERV, 0) == 0)
{
cout << host << " connected on port " << service << endl;
}
else
{
inet_ntop(AF_INET, &client.sin_addr, host, NI_MAXHOST);
cout << host << " connected on port " << ntohs(client.sin_port) << endl;
}
// Close listening socket
close(listening);
int size = 32000000;
int *arr = new int[size];
// Wait for client to send data
int bytesReceived = recv(clientSocket, (char*)arr, (size * sizeof(int)), 0);
if (bytesReceived == -1)
{
cerr << "Error in recv(). Quitting" << endl;
}
if (bytesReceived == 0)
{
cout << "Client disconnected " << endl;
}
for (int i = 9000;i < 18000;i++)
{
cout << arr[i] << endl;
}
cout << arr[99999] << endl;
// Close the socket
close(clientSocket);
return 0;
}
Клиент
#include <iostream>
#include <sys/types.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netdb.h>
#include <arpa/inet.h>
#include <string.h>
#include <string>
//8521
using namespace std;
int main()
{
// Create a socket
int sock = socket(AF_INET, SOCK_STREAM, 0);
if (sock == -1)
{
return 1;
}
// Create a hint structure for the server we're connecting with
int port = 54000;
string ipAddress = "127.0.0.1";
sockaddr_in hint;
hint.sin_family = AF_INET;
hint.sin_port = htons(port);
inet_pton(AF_INET, ipAddress.c_str(), &hint.sin_addr);
// Connect to the server on the socket
int connectRes = connect(sock, (sockaddr*)&hint, sizeof(hint));
if (connectRes == -1)
{
return 1;
}
int size = 32000000;
int *arr = new int[size];
for (int i = 0;i < size;i++)
{
arr[i] = i;
}
cout << arr[31999998] << endl;
// Send to server
int sendRes = send(sock, (char*)arr,(size * sizeof(int)), 0);
if (sendRes == -1)
{
cout << "Could not send to server! Whoops!\r\n";
}
// Close the socket
delete [] arr;
close(sock);
return 0;
}
Ответы (2 шт):
recv выдает меньше данных чем отправил клиент и его нужно обязательно дочитывать
int *arr = new int[size];
char *pointerarr = (char*)arr;
int toread = (size * sizeof(int))
do {
int bytesReceived = recv(clientSocket, pointerarr, toread, 0);
pointerarr += bytesReceived;
toread -= bytesReceived;
} while (bytesReceived!=0 || toread<=0) // или другой признак конца передачи
Потоковый сокет (SOCK_STREAM вообще и tcp в частности) представляет собой непрерывный поток данных разделённый на произвольные сегменты, так что отдельные сообщения могут приходить частями или слипаться. При чтении из такового может быть выдан произвольный объём данных меньше запрошенного, так что при приёме любого количества данных больше одного байта следует использовать цикл чтения. В типовом варианте с обработкой ошибок он будет выглядеть примерно так:
size_t dataSz = size * sizeof(int);
void *data = (void *)arr;
bool failed = 0;
while (dataSz>0) {
ssize_t recvSz = recv(clientSocket, data, dataSz, 0);
if(recvSz>0) {
dataSz -= recvSz;
data += recvSz;
} else if (recvSz==0) { // Удалённая сторона закрыла соединение
cerr << "Connection was shutdown before data read was finished" << endl;
failed = 1;
break;
} else { // recvSz<0
if (errno == EINTR) {
continue; // recv() был прерван сигналом: продолжить чтение.
// В примитивных примерах этот случай обычно опускают,
// но, вообще говоря, его желательно иметь в виду.
} else {
cerr << "Error in recv(): '" << strerror(errno) << "'. Quitting!\n" << endl;
failed = 1;
break;
}
}
}
if (failed) {
exit(EXIT_FAILURE);
}
В совсем примитивных случаев можно обойтись и простой передачей в recv флага MSG_WAITALL:
ssize bytesReceived = recv(clientSocket, (char*)arr, (size * sizeof(int)), MSG_WAITALL);
Для отправки такой цикл обычно не обязателен т.к. если сокет не является неблокирующим, то send, когда данные не помещаются в буфер, по умолчанию блокирует дальнейшее исполнение до полного копирования данных в буфер ядра (и отправки всех предыдущих данных).
Стоит также иметь в виду, что и recv() c MSG_WAITALL, и send() могут считать/записать в сокет меньший объём, если соединение будет разорвано. И эту ситуацию стоит отдельно обрабатывать.
А ещё если оные будут прерваны сигналом, то они вернут управление недочитав/записав запрошенный объём, так что, строго говоря, в достаточно сложном случае цикл чтения/записи всё равно будет необходим...
Мелкие придирки
- Вообще говоря, при передаче по сети нет гарантии что размер типа
intили порядок байт на разных хостах будет одинаков, так что, вообще говоря, при записи в сокет стоит использовать сериализацию.