2016-07-20 3 views
2

Я пишу сервер на Windows на C++, и я столкнулся с странным поведением, используя recv().Пустой буфер после успешного 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

Определить «буфер по-прежнему пуст». Каковы ваши доказательства этого утверждения? И вы игнорируете возможность возврата 'recv()' -1. – EJP

+0

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

+0

Я отлаживал программу, значение, возвращаемое recv, положительно, но содержимое буфера «\ 0», но все же вы правы, я не обрабатываю случай -1. –

ответ

2

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

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

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

  3. в конце readN() перед возвращением true, вы выводя параметр std::coutbuffer, однако buffer будет указывать на END данных, а не НАЧАЛА, так как она была выдвинута чтения петля. Вероятно, это происходит из-за вашей путаницы в отношении «пустого буфера». readN() сам не должен даже выводить данные вообще, так как вы делаете это после завершения readN(), иначе вы получите избыточные выходные сообщения.

  4. если readN() возвращает истину, вы передаете окончательный 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; 
} 
0

В конце 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; 
}