【问题标题】:garbage text with c++ strings on send recv calls发送 recv 调用时带有 C++ 字符串的垃圾文本
【发布时间】:2016-05-28 02:39:35
【问题描述】:
// In server.cpp after connection has established

std::string input;
input.reserve(5);
std::cout << "Enter message to send: ";

std::cin.ignore();  // =====(1)=====

std::getline(std::cin, input);
std::cout << "Sending..." << std::endl;
auto len        = input.length();

auto bytes_sent = send(newFD, input.data(), len, 0); // =====(2)=====

std::cout << "Input length : " << input.length() << std::endl
          << "Input bytes sent : " << bytes_sent << std::endl;

我的目标是在简单的tcp 客户端服务器 程序中使用std::string 而不是普通的旧char[fixed]。所以在server.cpp 我有两个疑问。到目前为止,我最初的猜测按预期工作。我在上面的代码中标记了它们。

  1. cin.ignore() vs cin.clear() + cin.sync()
  2. std::string.data() vs std::string.c_str()

我应该使用哪个?我什至不确定其中任何一个的黑白差异,我不知道它们是否会导致我的问题。


// In client.cpp 

std::string message;
message.reserve(5);
auto len        = message.capacity();

auto bytes_recv = recv(sockFD, &message.front(), len - 1, 0);  // =====(1)=====

message[len] = 0; // =====(2)=====

close(sockFD);
freeaddrinfo(res);
std::cout << "Bytes recieved :" << bytes_recv << std::endl;

std::cout << message.c_str() << std::endl;  // =====(3)=====

在 client.cpp 中,当我尝试发送更大的字符串时,一切都会出错。但我可能知道原因,但解决方案实施起来有些棘手。

  1. 我是否做正确的事情来传递&amp;std::string.front() 来写入传入数据?
  2. 这是错误的,字符串类应该管理这个,对吧?但是,由于我直接写信给&amp;front(),我猜length 不会得到更新,或者我不确定会发生什么,但使用std::cout &lt;&lt; message; 输出时数据肯定会丢失。
  3. 我这样做只是因为我直接写信给&amp;front,如果返回的数据以某种方式小于总长度,它仍然会产生垃圾,可能是因为它在正确的位置找不到终止字符?

server.cpp

// compile as 'g++ server.cpp -o server.app -std=c++14'

// run as : './server.app 8080'

#include <iostream>
#include <string>
#include <cstring>

extern "C" {
#include <unistd.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <netdb.h>
#include <arpa/inet.h>
#include <netinet/in.h>
}

int main(int argc, char *argv[])
{
    if(argc != 2) {
        std::cerr << "Run program as 'program port'" << std::endl;
        return -1;
    }
    auto &portNum              = argv[1];
    const unsigned int backLog = 5;

    struct addrinfo hints, *res, *p;
    memset(&hints, 0, sizeof(hints));
    hints.ai_family   = AF_UNSPEC;
    hints.ai_socktype = SOCK_STREAM;
    hints.ai_flags    = AI_PASSIVE;

    int gAddRes = getaddrinfo(NULL, portNum, &hints, &res);
    if(gAddRes != 0) {
        std::cerr << gai_strerror(gAddRes) << std::endl;
        return -2;
    }

    std::cout << "Detecting addresses" << std::endl;

    unsigned int numOfAddr = 0;
    char ipStr[INET6_ADDRSTRLEN];

    for(p = res; p != NULL; p = p->ai_next) {
        void *addr;
        std::string ipVer = "IPv0";

        if(p->ai_family == AF_INET) {
            ipVer                    = "IPv4";
            struct sockaddr_in *ipv4 = (struct sockaddr_in *)p->ai_addr;
            addr                     = &(ipv4->sin_addr);
            ++numOfAddr;
        }

        else {
            ipVer                     = "IPv6";
            struct sockaddr_in6 *ipv6 = (struct sockaddr_in6 *)p->ai_addr;
            addr                      = &(ipv6->sin6_addr);
            ++numOfAddr;
        }

        inet_ntop(p->ai_family, addr, ipStr, sizeof(ipStr));
        std::cout << "(" << numOfAddr << ") " << ipVer << " : " << ipStr
                  << std::endl;
    }

    if(!numOfAddr) {
        std::cerr << "Found no host address to use" << std::endl;
        return -3;
    }

    std::cout << "Enter the number of host address to bind with:" << std::endl;
    unsigned int choice = 0;
    bool madeChoice     = false;
    do {
        std::cin >> choice;
        if(choice > (numOfAddr + 1) || choice < 1) {
            madeChoice = false;
            std::cout << "Wrong choice, try again!" << std::endl;
        }
        else
            madeChoice = true;
    } while(!madeChoice);

    p           = res;
    bool isIPv4 = true;
    if(choice > 1) {
        unsigned int temp = 1;
        while(choice < temp) {
            p = p->ai_next;
            ++temp;
        }
        if(p->ai_family == AF_INET) {
            isIPv4 = true;
        }
        else
            isIPv4 = false;
    }

    int sockFD = socket(p->ai_family, p->ai_socktype, p->ai_protocol);
    if(sockFD == -1) {
        std::cerr << "Error while creating socket" << std::endl;
        freeaddrinfo(res);
        return -4;
    }

    int bindR = bind(sockFD, p->ai_addr, p->ai_addrlen);
    if(bindR == -1) {
        std::cerr << "Error while binding socket" << std::endl;
        close(sockFD);
        freeaddrinfo(res);
        return -5;
    }

    int listenR = listen(sockFD, backLog);
    if(listenR == -1) {
        std::cerr << "Error while Listening on socket" << std::endl;
        close(sockFD);
        freeaddrinfo(res);
        return -6;
    }

    struct sockaddr_storage client_addr;
    socklen_t client_addr_size = sizeof(client_addr);
    int newFD =
        accept(sockFD, (struct sockaddr *)&client_addr, &client_addr_size);
    if(newFD == -1) {
        std::cerr << "Error while Accepting on socket" << std::endl;
        close(sockFD);
        freeaddrinfo(res);
        return -7;
    }


    std::string input;
    input.reserve(5);
    std::cout << "Enter message to send: ";
    std::cin.ignore();
    std::getline(std::cin, input);
    std::cout << "Sending..." << std::endl;
    auto len        = input.length();
    auto bytes_sent = send(newFD, input.data(), len, 0);
    std::cout << "Input length : " << input.length() << std::endl
              << "Input bytes sent : " << bytes_sent << std::endl;
    close(newFD);
    close(sockFD);
    freeaddrinfo(res);

    return 0;
}

client.cpp

// compile as 'g++ client.cpp -o client.app -std=c++14'

// run as : './client.app 0 8080'

#include <iostream>
#include <cstring>

extern "C" {
#include <unistd.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <netdb.h>
#include <arpa/inet.h>
#include <netinet/in.h>
}

int main(int argc, char *argv[])
{
    if(argc != 3) {
        std::cerr << "Run program as 'program ipaddress port'" << std::endl;
        return -1;
    }
    auto &ipAddress = argv[1];
    auto &portNum   = argv[2];

    struct addrinfo hints, *res, *p;
    memset(&hints, 0, sizeof(hints));
    hints.ai_family   = AF_UNSPEC;
    hints.ai_socktype = SOCK_STREAM;
    hints.ai_flags    = AI_PASSIVE;

    int gAddRes = getaddrinfo(ipAddress, portNum, &hints, &res);
    if(gAddRes != 0) {
        std::cerr << gai_strerror(gAddRes) << std::endl;
        return -2;
    }

    std::cout << "Detecting addresses" << std::endl;

    unsigned int numOfAddr = 0;
    char ipStr[INET6_ADDRSTRLEN];

    for(p = res; p != NULL; p = p->ai_next) {
        void *addr;
        std::string ipVer = "IPv0";

        if(p->ai_family == AF_INET) {
            ipVer                    = "IPv4";
            struct sockaddr_in *ipv4 = (struct sockaddr_in *)p->ai_addr;
            addr                     = &(ipv4->sin_addr);
            ++numOfAddr;
        }

        else {
            ipVer                     = "IPv6";
            struct sockaddr_in6 *ipv6 = (struct sockaddr_in6 *)p->ai_addr;
            addr                      = &(ipv6->sin6_addr);
            ++numOfAddr;
        }

        inet_ntop(p->ai_family, addr, ipStr, sizeof(ipStr));
        std::cout << "(" << numOfAddr << ") " << ipVer << " : " << ipStr
                  << std::endl;
    }

    if(!numOfAddr) {
        std::cerr << "Found no host address to use" << std::endl;
        return -3;
    }

    std::cout << "Enter the number of host address to bind with:" << std::endl;
    unsigned int choice = 0;
    bool madeChoice     = false;
    do {
        std::cin >> choice;
        if(choice > (numOfAddr + 1) || choice < 1) {
            madeChoice = false;
            std::cout << "Wrong choice, try again!" << std::endl;
        }
        else
            madeChoice = true;
    } while(!madeChoice);

    p           = res;
    bool isIPv4 = true;
    if(choice > 1) {
        unsigned int temp = 1;
        while(choice < temp) {
            p = p->ai_next;
            ++temp;
        }
        if(p->ai_family == AF_INET) {
            isIPv4 = true;
        }
        else
            isIPv4 = false;
    }

    int sockFD = socket(p->ai_family, p->ai_socktype, p->ai_protocol);
    if(sockFD == -1) {
        std::cerr << "Error while creating socket" << std::endl;
        return -4;
    }

    int connectR = connect(sockFD, p->ai_addr, p->ai_addrlen);
    if(connectR == -1) {
        close(sockFD);
        std::cerr << "Error while connecting socket" << std::endl;
        return -5;
    }

    std::string message;
    message.reserve(5);
    auto len        = message.capacity();
    auto bytes_recv = recv(sockFD, &message.front(), len - 1, 0);
    message[len] = 0;
    close(sockFD);
    freeaddrinfo(res);
    std::cout << "Bytes recieved :" << bytes_recv << std::endl;
    std::cout << message.c_str() << std::endl;

    return 0;
}

【问题讨论】:

  • 至少评论一下你为什么投反对票,这样我就可以改进我的问题,而不是删除并再次发布。我提供了最小 + 可运行且没有错误的代码示例以及我认为出错的相关代码。
  • 你没有理解我的意思。创建一个接受front() 的函数。在该函数中,您填写数据。然后看看你调用这个函数后的结果是什么。调用 sendrecv 并没有神奇的属性可以使 std::string 以不同的方式工作。我试图通过完全不引入套接字调用以及所有不必要的样板代码来简化您的代码测试。
  • @AbhinavGauniyal 正如前面的评论所建议的,您不应该在返回的字符串中粘贴 0。您唯一可以使用的是来自recv 的返回值,然后缩短std::string 的大小,尊重这个值。因此,std::stringnot 以 null 结尾的。 std::string 中的数据长度由size() 确定。对 std::string::resize() 的调用将调整 std::string 的大小。
  • string 类没有更新大小,因为你绕过了它的界面......你必须手动调整它的大小(见我的回答)。
  • recvstd::string 和“更新字符串的大小”一无所知。老实说,那个函数甚至不知道 C++。它是一个 C 库函数。您所说的所有调整大小都必须在字符串本身上“手动”完成,而您是唯一可以做到这一点的人。如果recv 返回一个值,那么必须将字符串的大小调整为该值。

标签: c++ sockets unix c++11 networking


【解决方案1】:
  1. 您的问题过于模糊,无法提供有用的答案。

  2. data()c_str() 自 C++11 起为 effectively the same thing。你使用哪一个并不重要。 编辑In C++17, data() will have a non-const overload that returns a non-const char*,因此您无需执行&amp;message.front() 即可访问底层缓冲区的可修改形式。 c_str() 将保留为 const

  3. &amp;message.front() 是对的……也是错的。这是将非常量char* 获取到std::string 内容的方法。但是message 未初始化并且在代码中的那个点有0size(),所以我什至不确定那行代码是明确定义的行为。而不是做一个reserve(5)我会像这样构造你的stringauto message = std::string(5, ' ');然后当你将它传递给recv时,实际上会有有效的东西可以覆盖,你就可以从中读取它之后message

  4. 是的,这是错误的。您应该将字符串设置为您需要的 actual 大小。如果你这样做,我怀疑你可以传入len 而不是len - 1。关于这个话题,您确定您收到的所有内容都只有 4 个字节长吗?还是您故意一次只读取 4 个字节?

  5. a) 您无需将c_str() 传递给std::cout&lt;&lt; 也被重载以接受 std::string。 b) recv 返回您收到的字节数。如果该值小于您将message 初始化为的大小,则字符串中的剩余字符将是垃圾(如果您遵循我的建议,则为' 'chars:#3)。收到消息后我会message.resize(bytes_recv);

【讨论】:

  • 传递len 而不是len - 1 因为string 类如何自动处理字符串结尾?是的,在这个例子中,我有意将缓冲区大小设置为 4。
  • std::string 跟踪它自己的结束,是的,所以你不需要使len 比你将收到的消息更长。只需将len 设置为您将收到的消息的大小(或者更确切地说,您将接受的最大消息长度的大小),然后将len 传递给recv,而不是传递len - 1。收到消息后别忘了resize(bytes_recv);
  • data()c_str() 在 c++17 中再次出现分歧。
  • 你能给个简洁的参考吗?我看到 en.cppreference.com 已将更改添加到他们的 std::string 参考,但能够链接到更直接的东西会很好。
【解决方案2】:

您的问题已通过上限解决,但我的 2 美分。拥有自己的发送/接收功能并隐藏复杂性怎么样?

例如:

ssize_t recv(int sockfd, std::string &buf, size_t len, int flags) {
    buf.resize(len);  // current status unknown -> make it fit
    ssize_t n = ::recv(sockfd, (void *)buf.data(), len, flags);
    buf.resize(n >= 0 ? n : 0);  // take error into account
    return n;
}

【讨论】:

  • 我也有包装函数。在我的情况下,我实际上分配了一个巨大的缓冲区并反复调用recv,直到它返回 0。它还会检查错误代码等。
猜你喜欢
  • 1970-01-01
  • 2010-09-21
  • 1970-01-01
  • 2013-02-25
  • 1970-01-01
  • 1970-01-01
  • 2015-01-28
  • 1970-01-01
  • 2021-11-05
相关资源
最近更新 更多