Передача большого массива через 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 шт):

Автор решения: eri

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) // или другой признак конца передачи
→ Ссылка
Автор решения: Fat-Zer

Потоковый сокет (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 или порядок байт на разных хостах будет одинаков, так что, вообще говоря, при записи в сокет стоит использовать сериализацию.
→ Ссылка