Пустой буфер после успешного восстановления

Я пишу сервер на Windows в C ++, и я сталкиваюсь со странным поведением, используя recv(),

Я написал эту функцию:

bool readN(SOCKET s, int size, char* buffer){
fd_set readset;
struct timeval tv;
int left, res;
FD_ZERO(&readset);
FD_SET(s, &readset);
left = size;
std::cout << "-----called readN to read " << size << " byte" << std::endl;
while (left > 0) {
tv.tv_sec = MAXWAIT;
tv.tv_usec = 0;
res = select(0, &readset, NULL, NULL, &tv);
if (res > 0) {
res = recv(s, buffer, left, 0);
if (res == 0) {//connection closed by client
return false;
}

left -= res;
std::cout << "\treceived " << res << " left " << left << std::endl;
if (left != 0) {
buffer += res;
}

}
else if (res == 0) { //timer expired
return false;
}
else { //socket error
return false;
}
}
std::cout << "\t" << buffer << std::endl;
return true;
}

И я называю это так:

std::unique_ptr<char[]> buffer = std::make_unique<char[]>(size_);
if (readN(sck, size_, buffer.get())) {
std::cout << "----read message----" << std::endl;
std::cout <<"\t"<< buffer.get()<< std::endl;
}

Проблема в том, что даже если recv() возвращает положительное число, буфер все еще пуст. Что мне не хватает?

2

Решение

Я вижу несколько проблем в вашем коде.

  1. Вы не сбрасываете readset переменная каждый раз, когда вы звоните select(), select() изменяет переменную. Для случая с одним сокетом это не так уж плохо, но вы должны привыкнуть к сбросу переменной каждый раз.

  2. Вы не проверяете ошибки, возвращенные recv(), Вы предполагаете, что любое неприемлемое разъединение является успехом, но это не всегда так.

  3. в конце readN() перед возвращением trueвы выводите buffer параметр для std::cout, тем не мение buffer будет указывать на КОНЕЦ данных, а не НАЧАЛА, так как это было продвинуто циклом чтения. Скорее всего, отсюда и ваша путаница с «пустым буфером». readN() сам по себе даже не должен выводить данные, так как вы делаете это после readN() выходы, в противном случае вы получите избыточные выходные сообщения.

  4. если readN() возвращает true, вы проходите финал buffer в std::cout используя operator<< который ожидает, что нулевое окончание char строка, но ваш буфер не гарантированно завершается нулем.

Попробуйте что-то более похожее на это:

bool readN(SOCKET s, int size, char* buffer){
fd_set readset;
struct timeval tv;
int res;
std::cout << "-----called readN to read " << size << " byte(s)" << std::endl;
while (size > 0) {
FD_ZERO(&readset);
FD_SET(s, &readset);
tv.tv_sec = MAXWAIT;
tv.tv_usec = 0;

res = select(0, &readset, NULL, NULL, &tv);
if (res > 0) {
res = recv(s, buffer, size, 0);
if (res == SOCKET_ERROR) {
res = WSAGetLastError();
if (res == WSAEWOULDBLOCK) {
continue; //call select() again
}
return false; //socket error
}

if (res == 0) {
return false;  //connection closed by client
}

buffer += res;
size -= res;

std::cout << "\treceived " << res << " byte(s), " << size << " left" << std::endl;
}

/*
else if (res == 0) {
return false; //timer expired
}
else {
return false; //socket error
}
*/

else {
return false; //timer expired or socket error
}
}

return true;
}

std::unique_ptr<char[]> buffer = std::make_unique<char[]>(size_);
if (readN(sck, size_, buffer.get())) {
std::cout << "----read message----" << std::endl;
std::cout << "\t";
std::cout.write(buffer.get(), size_);
std::cout << std::endl;
}

С учетом сказанного, я бы предложил альтернативную реализацию readN()в зависимости от того, используете ли вы блокирующий или неблокирующий сокет.

Если блокировка, используйте setsockopt(SO_RCVTIMEO) вместо select(), Если recv() терпит неудачу с таймаутом, WSAGetLastError() сообщит WSAETIMEDOUT:

sck = socket(...);

DWORD timeout = MAXWAIT * 1000;
setsockopt(sck, SOL_SOCKET, SO_RCVTIMEO, (char*)&timeout, sizeof(timeout));

bool readN(SOCKET s, int size, char* buffer){
int res;
std::cout << "-----called readN to read " << size << " byte(s)" << std::endl;
while (size > 0) {
res = recv(s, buffer, size, 0);
if (res == SOCKET_ERROR) {
/*
res = WSAGetLastError();
if (res == WSAETIMEDOUT) {
return false; //timer expired
}
else {
return false; //socket error
}
*/
return false; //timer expired or socket error
}

if (res == 0) {
return false; //connection closed by client
}

buffer += res;
size -= res;

std::cout << "\treceived " << res << " byte(s), " << size << " left" << std::endl;
}

return true;
}

Если неблокирующая, не звоните select() если recv() просит вас назвать это:

bool readN(SOCKET s, int size, char* buffer){
fd_set readset;
struct timeval tv;
int res;
std::cout << "-----called readN to read " << size << " byte(s)" << std::endl;
while (size > 0) {
res = recv(s, buffer, size, 0);
if (res == SOCKET_ERROR) {
res = WSAGetLastError();
if (res != WSAEWOULDBLOCK) {
return false; //socket error
}

FD_ZERO(&readset);
FD_SET(s, &readset);
tv.tv_sec = MAXWAIT;
tv.tv_usec = 0;

res = select(0, &readset, NULL, NULL, &tv);
if (res > 0) {
continue; //call recv() again
}

/*
else if (res == 0) {
return false; //timer expired
}
else {
return false; //socket error
}
*/

return false; //timer expired or socket error
}

if (res == 0) {
return false; //connection closed by client
}

buffer += res;
size -= res;

std::cout << "\treceived " << res << " byte(s), " << size << " left" << std::endl;
}

return true;
}
2

Другие решения

В конце readN() есть

std::cout << "\t" << buffer << std::endl;

Проблема в том, что буфер теперь указывает на buffer + size по отношению к первоначальной стоимости buffer, Значение было изменено

buffer += res;

Это должно вывести буфер,

std::cout << "\t" << (buffer - size) << std::endl;

После экспериментов readN() со следующим main(), Кажется, что readN() работает, если сокет не является недействительным дескриптором (текстовые / двоичные данные, отправленные ncat). Если сокет является недействительным дескриптором, функция быстро возвращается.

#include <iostream>
#include <memory>
#include <string.h>

#ifdef _WIN64
#include <ws2tcpip.h>
#else
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#endif

#include <errno.h>

#define MAXWAIT 5000

bool readN(SOCKET fd, int size, char *buffer)
{
fd_set readset;
struct timeval tv;
int left, res;

FD_ZERO(&readset);
FD_SET(fd, &readset);

left = size;
std::cout << "-----called readN to read " << size << " byte" << std::endl;
while (left > 0) {
tv.tv_sec = MAXWAIT;
tv.tv_usec = 0;
res = select(fd + 1, &readset, NULL, NULL, &tv);

if (res > 0) {
res = recv(fd, buffer, left, 0);
if (res == 0) { //connection closed by client
return false;
}

left -= res;
std::cout << "\treceived " << res << " left " << left << std::endl;
buffer += res;
} else if (res == 0) {  //timer expired
std::cout << "\ttimer expired" << std::endl;
return false;
} else {        //socket error
std::cout << "\tsocket error " << WSAGetLastError()  << std::endl;
return false;
}
}
std::cout << "Print the buffer now\n" << (buffer - size) << std::endl;
return true;
}

int main(void)
{
int err;
SOCKET cfd = 0;
SOCKET afd = 0;

struct sockaddr_in addr;
socklen_t clen;
struct sockaddr_in caddr;

#ifdef _WIN64
WORD ver = 0x202;
WSADATA wsa_data;

memset(&wsa_data, 0, sizeof(wsa_data));
std::cout << "WSAStartup" << std::endl;
err = WSAStartup(ver, &wsa_data);
if (err < 0) goto error_exit;
#endif

memset(&addr, 0, sizeof(addr));
memset(&caddr, 0, sizeof(caddr));

std::cout << "socket" << std::endl;
afd = socket(AF_INET, SOCK_STREAM, 0);
if (afd < 0) goto error_exit;

addr.sin_family = AF_INET;
addr.sin_addr.s_addr = INADDR_ANY;
addr.sin_port = htons(1234);

std::cout << "bind" << std::endl;
err = bind(afd, (struct sockaddr *)&addr, sizeof(addr));
if (err < 0) goto error_exit;

std::cout << "listen" << std::endl;
listen(afd, 5);

clen = sizeof(caddr);
std::cout << "accept" << std::endl;
cfd = accept(afd, (struct sockaddr *) &caddr, &clen);
if (cfd == INVALID_SOCKET) goto error_exit;

{
int size_ = 1024;
std::unique_ptr<char[]> buffer2 = std::make_unique<char[]>(size_);

std::cout << "readN" << std::endl;
if (readN(cfd, 1024, buffer2.get())) {
std::cout << "----read message----" << std::endl;
std::cout <<"\t"<< buffer2.get() << std::endl;
}
}
return 0;
error_exit:
std::cout << "Error!" << std::endl;
std::cout << "\tsocket error " << WSAGetLastError()  << std::endl;
return 1;
}
0

По вопросам рекламы ammmcru@yandex.ru