【问题标题】:How do I read from a socket and return individual lines?如何从套接字读取并返回单独的行?
【发布时间】:2010-11-29 18:36:26
【问题描述】:

我应该阅读每个字符直到它到达 \n 字符,将它们全部连接在一起并返回还是有更好的方法?我应该为此使用 std::string 还是 char ?

我尝试了以下两个示例,但我需要将它们作为单独的行来阅读

示例 1:

std::string sockread()
{
    std::string s;
    s.resize(DEFAULT_BUFLEN);
    int result = recv(m_socket, &s[0], DEFAULT_BUFLEN, 0);

    if (result > 0) {
        return s;
    } else if (result == 0) {
        connected = false;
    } else {
        std::cout << "recv failed with error " << WSAGetLastError() << "\n";
    }
    throw std::runtime_error("Socket connection failed!");
}

示例 2:

char sockread(void)

  {
    int result;
    char buffer[DEFAULT_BUFLEN];
        result = recv(m_socket, buffer, DEFAULT_BUFLEN, 0);

        if (result > 0) {
             return *buffer;
              }
          else if (result == 0)
              {
             connected = false;
                 return *buffer;
              }
          else {
         printf("recv failed with error: %d\n", WSAGetLastError());
         return *buffer;
        }

   }

【问题讨论】:

    标签: visual-studio-2010 visual-c++ visual-c++-2010 winsock2


    【解决方案1】:

    您有几个选择,具体取决于您的套接字代码的其余部分是如何布局的。

    从编码的角度来看,最简单的方法是一次读取 1 个字符,直到遇到您要查找的字符。从性能的角度来看,这不是最好的方法,尽管您可以使用本地缓冲区来帮助您至少避免内存碎片,例如:

    std::string sockread(void) 
    { 
        char buffer[DEFAULT_BUFLEN]; 
        int buflen = 0;
        char c;
        std::string s;
    
        do
        {
            int result = recv(m_socket, &c, 1, 0); 
            if (result > 0)
            { 
                if (c == '\n')
                    break;
    
                if (buflen == DEFAULT_BUFLEN)
                {
                    s += std::string(buffer, buflen);
                    buflen = 0;
                }
    
                buffer[buflen] = c;
                ++buflen;
    
                continue;
            } 
    
            if (result == SOCKET_ERROR) 
            {
                if (WSAGetLastError() == WSAEWOULDBLOCK)
                    continue;
    
                std::cout << "recv failed with error " << WSAGetLastError() << "\n";
            }
            else
                connected = false; 
    
            throw std::runtime_error("Socket connection failed!");
        }
        while (true);
    
        if (buflen > 0)
            s += std::string(buffer, buflen);
    
        return s;
    }
    

    另一方面,将原始套接字数据读入一个中间缓冲区,以便其他读取函数在需要时访问该缓冲区,可以更有效地读取套接字,从而更快地将数据从套接字缓冲区中取出(减少阻塞另一边),例如:

    std::vector<unsigned char> buffer;
    
    std::string sockread(void) 
    { 
        unsigned char buf[DEFAULT_BUFLEN];
        int result;
        std:vector<unsigned char>::iterator it;
    
        do
        {
            it = std::find(buffer.begin(), buffer.end(), '\n');
            if (it != buffer.end())
                break;
    
            result = recv(m_socket, buf, DEFAULT_BUFLEN, 0); 
            if (result > 0)
            { 
                std::vector<unsigned char>::size_type pos = buffer.size();
                buffer.resize(pos + result);
                memcpy(&buffer[pos], buf, result);
                continue;
            } 
    
            if (result == SOCKET_ERROR) 
            {
                if (WSAGetLastError() == WSAEWOULDBLOCK)
                    continue;
    
                std::cout << "recv failed with error " << WSAGetLastError() << "\n";
            }
            else
                connected = false; 
    
            throw std::runtime_error("Socket connection failed!");
        }
        while (true);
    
        std::string s((char*)&buffer[0], std::distance(buffer.begin(), it));
        buffer.erase(buffer.begin(), it);
        return s;
    }
    

    【讨论】:

    • 使用第二种方法,我收到此错误 - 错误 C2664: 'recv' : cannot convert parameter 2 from 'unsigned char [512]' to 'char *' 指向的类型不相关;转换需要 reinterpret_cast、C-style cast 或 function-style cast
    • 如果我将其更改为 buf[DEFAULT_BUFLEN] (不确定我这样做是否不好),那么它可以工作,但只返回第一行,其余为空字符串。
    • 就像错误信息说的那样,使用类型转换:result = recv(m_socket, (char*)buf, DEFAULT_BUFLEN, 0);
    【解决方案2】:

    使用 Boost.ASIO - 涵盖here 的基于行的操作。

    许多常用的互联网协议 是基于行的,这意味着它们 有协议元素是 由字符序列分隔 “\r\n”。示例包括 HTTP、SMTP 和 FTP。为了更容易地允许 基于行的实现 协议,以及其他协议 使用分隔符,Boost.Asio 包括函数 read_until() 和 async_read_until()。

    【讨论】:

    • 感谢您的建议。我也试试看。
    • 掌握库可能需要做更多的工作,但一旦启动并运行,工作量就会减少,因为库代码是稳定的。
    猜你喜欢
    • 1970-01-01
    • 2018-11-19
    • 2013-04-29
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多