Abhinav Gauniyal Abhinav Gauniyal - 3 months ago 13
C++ Question

garbage text with c++ strings on send recv calls

// 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;


My aim is to use
std::string
instead of plain old
char[fixed]
in simple tcp client server program. So in
server.cpp
I have 2 doubts. So far my initial guesses are working as expected. I've marked them above in code.


  1. cin.ignore()
    vs
    cin.clear()
    +
    cin.sync()

  2. std::string.data()
    vs
    std::string.c_str()



Which should I use? I'm not even sure of the difference b/w any of these and I don't know if they're contributing to my problem.




// 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)=====





And in client.cpp, everything goes wrong when I try to send bigger strings. But I probably know the cause, however solution is somewhat tricky to implement.


  1. Am I doing right thing to pass
    &std::string.front()
    to write incoming data?

  2. This is wrong, string class should manage this, right? However since I'm directly writing to
    &front()
    , I guess
    length
    won't get updated or I'm not sure what happens but data surely gets lost when outputting with
    std::cout << message;
    .

  3. I'm doing this just because I'm directly writing to
    &front
    , still it produces garbage if somehow data returned is smaller than total length, probably because it cannot find terminating character at right place?







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;
}

Answer
  1. Your question on this is too vague to provide a helpful answer.

  2. data() and c_str() are effectively the same thing since C++11. It doesn't matter which one you use. EDIT: In C++17, data() will have a non-const overload that returns a non-const char*, so you will not need to do &message.front() to access a modifiable form of the underlying buffer. c_str() will remain const.

  3. &message.front() is right... and wrong. That is the way to get a non-const char* to the contents of your std::string. BUT message is uninitialized and has a size() of 0 at that point in the code, so I'm not even sure that line of code is well-defined behavior. Rather than doing a reserve(5) I would construct your string like this: auto message = std::string(5, ' '); Then when you pass it into recv there will actually be valid stuff there for it to overwrite and you'll be able to read it from message afterwards.

  4. Yes, this is wrong. You should be setting your string up to be the actual size you need. I suspect you can just pass in len instead of len - 1 if you do this. On this topic, are you certain everything you'll ever receive is only 4 bytes long? Or are you intentionally only reading 4 bytes at a time?

  5. a) you don't need to pass c_str() to std::cout. << is overloaded to accept std::string as well. b) recv returns the number of bytes that you received. If that value is less than the size you initialized your message to, then the remaining characters in your string will be garbage (or ' ' chars if you followed my advice re:#3). I would do message.resize(bytes_recv); after receiving the message.

Comments