Отправка сжатой строки через winsock - PullRequest
0 голосов
/ 04 сентября 2018

Привет всем! У меня есть простой TCP-сервер и клиент на winsock2 lib c ++. Сервер просто отправляет строковые сообщения. Клиент просто получает их. Здесь все хорошо. Но когда я использую библиотеку zlib для сжатия строки, данные портятся, и я не могу правильно получить их на клиенте для распаковки. Кто-нибудь может мне помочь?

Сервер:

{
    std::lock_guard<std::mutex> lock(mtx);
    std::cout << "Client connected\n";
    int k = rand() % strings.size();
    msg = strings[k];
    msg_size = msg.size();
    msgl_size = msg_size + msg_size*0.1 + 12;
    msgl = new unsigned char[msgl_size + 1]{0};
    if (Z_OK != compress((Bytef*)msgl, 
                         &msgl_size, 
                         reinterpret_cast<const unsigned char*>(msg.c_str()),
                         msg.size()))
    {
        std::cout << "Compression error! " << std::endl;
        exit(2);
    }
}
std::thread * thread = new std::thread([&newConnection, msgl, msgl_size, msg_size, msg]() {
    std::lock_guard<std::mutex> lock(mtx);
    send(newConnection, (char*)&msgl_size, sizeof(unsigned long), NULL);
    send(newConnection, (char*)&msg_size, sizeof(unsigned long), NULL);
    int res;
    do {
        res = send(newConnection, (char*)(msgl), sizeof(msgl_size), NULL);
    }
    while (msgl_size != res);
});

Клиент:

std::lock_guard<std::mutex> lock(mtxx);
unsigned long msgl_size, msg_size;
recv(Connection, (char*)&msg_size, sizeof(unsigned long), NULL);
recv(Connection, (char*)&msgl_size, sizeof(unsigned long), NULL);
unsigned char * msgl = new unsigned char[msgl_size + 1]{0};
int res;
do {
    res = recv(Connection, reinterpret_cast<char*>(msgl), msgl_size, NULL);
}
while (msgl_size != res);


char * msg = new char[msg_size + 1];
if (Z_OK == uncompress(reinterpret_cast<unsigned char*>(msg), 
                       &msg_size,
                       reinterpret_cast<unsigned char*>(msgl), 
                       msgl_size))
{
    msg[msg_size] = '\0';
    std::cout << msg << std::endl;
    std::cout << "Compress ratio: " << msgl_size / (float)msg_size << std::endl;
}
delete[] msgl;

Ответы [ 2 ]

0 голосов
/ 04 сентября 2018

Клиентская сторона:

recv возвращает только те данные, которые немедленно доступны или блокируются, пока данные не станут доступны, это вряд ли произойдет с большим файлом или медленной сетью. Весьма вероятно, что recv будет блокироваться до тех пор, пока не прибудет первый сетевой пакет, и в зависимости от базовой сети, которая может составлять от нескольких сотен байтов до десятков тысяч. Может быть, сообщение вписывается в это, а может и нет.

Установка параметра recv *1007* на MSG_WAITALL полезна для более коротких сообщений, поскольку вы либо получите именно то количество байтов, которое вы запросили, либо ошибку. Из-за возможной ошибки вы всегда должны проверять возвращаемое значение.

Повторить: всегда проверяйте возвращаемое значение.

Возвращаемое значение

recv является либо отрицательным при отказе сокета, либо 0 при отключении сокета, либо количеством прочитанных байтов. За дополнительной информацией обращайтесь к документации winsock для recv.

Итак ...

recv(Connection, (char*)&msg_size, sizeof(unsigned long), NULL);

и recv (Connection, (char *) & msgl_size, sizeof (unsigned long), NULL);

не проверять возвращаемое значение. Возможно, произошел сбой сокета, или вызов recv мог бы вернуть меньше, чем было запрошено, и остальная часть программы будет работать на мусоре.

Это подходящее место для использования MSG_WAITALL, но возможно, что с розеткой все в порядке, и вы были прерваны сигналом. Не уверен, что это может произойти в Windows, но в Linux. Осторожно.

if (recv(Connection, (char*)&msg_size, sizeof(unsigned long), MSG_WAITALL) != sizeof(unsigned long) &&
    recv(Connection, (char*)&msgl_size, sizeof(unsigned long), NULL) != sizeof(unsigned long)(
{
    // log error 
    // exit function, loop, or whatever.
}

Далее

do {
    res = recv(Connection, reinterpret_cast<char*>(msgl), msgl_size, NULL);
} while (msgl_size != res);

будет повторяться до тех пор, пока один recv не вернет точно правильную сумму за один вызов. Маловероятно, но если это произойдет, это должно произойти при первом чтении, потому что код записывает поверх предыдущего чтения каждый раз.

Скажем, только половина сообщения читается из сокета с первой попытки. Поскольку это не полное сообщение, цикл входит и пытается прочитать снова, перезаписывая первую половину сообщения второй половиной и, возможно, достаточно байтов из последующего сообщения, чтобы удовлетворить запрошенное количество байтов. Эта амальгама из двух сообщений не расшифровывается.

Для полезной нагрузки потенциально большого размера зацикливайтесь, пока программа не получит все это.

char * bufp = reinterpret_cast<char*>(msgl);
int msg_remaining = msgl_size;
while (msg_remaining )
{
    res = recv(Connection, bufp, msg_remaining, NULL);
    if (res <= 0)
    {
        // log error 
        // exit function, loop, or whatever.
    }
    msg_remaining -= res; // reduce message remaining
    bufp += res; // move next insert point in msgl
}

Возможны проблемы с декомпрессией. Я не знаю достаточно об этом, чтобы иметь возможность ответить. Я предлагаю удалить его и отправить легко отлаживаемый текст, пока не будут решены все проблемы с сетью.

Серверная сторона:

Как и recv, send отправляет, что может. Возможно, вам придется зациклить отправку, чтобы убедиться, что вы не переполнили сокет сообщением, слишком большим для сокета, чтобы его можно было съесть за один раз. И снова, как recv, s send может выйти из строя. Всегда проверяйте возвращаемое значение, чтобы увидеть, что на самом деле произошло. Обратитесь к документации для send для получения дополнительной информации.

0 голосов
/ 04 сентября 2018

Мне кажется, у вас есть правильная основная идея: отправить ожидаемый размер данных, а затем сами данные. На принимающей стороне сначала прочитайте размер, затем прочитайте указанный объем данных.

К сожалению, вы допустили одну или две ошибки, когда речь шла о деталях реализации этого намерения. Первый большой, когда вы отправляете данные:

do {
    res = send(newConnection, (char*)(msgl), sizeof(msgl_size), NULL);
}
while (msgl_size != res);

У этого есть пара проблем. Прежде всего, он использует sizeof(msg1_size), поэтому он только пытается отправить размер беззнакового длинного (по крайней мере, я предполагаю, что msg1_size это беззнаковый длинный).

Я уверен, что вы намеревались здесь отправить вместо этого весь буфер:

unsigned long sent = 0;
unsigned long remaining = msg1_size;

do {
    res = send(newConnection, (char*)(msgl + sent), remaining, NULL);
    sent += res;
    remaining -= res;
} while (msgl_size != sent);

При этом мы начинаем отправку с начала буфера. Если send возвращается после отправки только части этого (как это разрешено), мы записываем, сколько было отправлено. Затем на следующей итерации мы возобновляем отправку с того места, где она была прервана. Между тем, мы отслеживаем, сколько еще предстоит отправить, и пытаемся отправить это только на каждой последующей итерации.

По крайней мере, на первый взгляд, похоже, что ваш цикл приема, вероятно, нуждается в таком же ремонте, отслеживая полученную сумму, а не пытаясь дождаться одной передачи всей суммы.

Да, и, конечно, для реального кода вы также хотите проверить, что res равно 0 или отрицательно. В настоящее время он даже не пытается обнаружить или правильно реагировать на большинство сетевых ошибок.

...