2016-07-20 84 views
2

我正在用C++在Windows上編寫一個服務器,我正面臨使用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. 你是不是每次調用select()時間重置readset變量。 select()修改變量。對於單插座的情況,這並不算太壞,但你應該養成每次重置變量的習慣。

  2. 您不檢查由recv()返回的錯誤。你認爲任何非優雅斷開都是成功的,但這並非總是如此。

  3. readN()結束返回true之前,你輸出buffer參數std::cout,但buffer將在年底的數據,而不是 BEGINNING被人指指點點,因爲它是由閱讀先進循環。這可能是您對「空緩衝區」的混淆來自何處。 readN()本身甚至不應該輸出數據,因爲您在readN()退出後會這樣做,否則最終會輸出冗餘輸出消息。

  4. 如果readN()返回true,您使用的是operator<<期望一個空值終止字符串char,但你的緩衝區不能保證是空值終止的傳遞最終bufferstd::cout

嘗試一些更喜歡這個:

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; 

具有以下main()試驗readN()後,似乎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; 
}