I'm writing a server on Windows in C++ and I'm facing a strange behavior using recv().
I wrote this function:
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;
}
And I call it like this:
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;
}
The problem is that even if recv() returns a positive number, the buffer is still empty. What am I missing?
I see a few problems in your code.
you are not resetting the readset variable each time you call select(). select() modifies the variable. For a single-socket case, this is not too bad, but you should get in the habit of resetting the variable each time.
you are not checking for errors returned by recv(). You assume any non-graceful-disconnect is success, but that is not always true.
at the end of readN() before returning true, you are outputting the buffer parameter to std::cout, however buffer will be pointing at the END of the data, not the BEGINNING, since it was advanced by the reading loop. This is likely where your confusion about an "empty buffer" is coming from. readN() itself should not even be outputting the data at all, since you do that after readN() exits, otherwise you end up with redundant output messages.
if readN() returns true, you are passing the final buffer to std::cout using an operator<< that expects a null terminated char string, but your buffer is not guaranteed to be null-terminated.
Try something more like this instead:
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;
}
With that said, I would suggest an alternative implementation of readN(), depending on whether you are using a blocking or non-blocking socket.
If blocking, use setsockopt(SO_RCVTIMEO) instead of select(). If recv() fails with a timeout, WSAGetLastError() will report 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;
}
If non-blocking, don't call select() unless recv() asks you to call it:
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;
}
At the end of the readN() there is
std::cout << "\t" << buffer << std::endl;
The problem is that the buffer points now to buffer + size in respect to the original value of buffer. The value has been modified by
buffer += res;
This should output the buffer,
std::cout << "\t" << (buffer - size) << std::endl;
After experimenting readN() with the following main(), it seems that readN() works if the socket is not invalid handle (text/binary data sent by ncat). If the socket is a invalid handle, the function returns quickly.
#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;
}
Related
I'm trying to build a multithread HTTP server using C++ and winsock2.h and sys/socket.h.However, after sending response successfully to first request, accept() function captures other requests and sends through thread pool but those threads hangs up in recv() function so function can never capture data from socket.Here is my code.
void HTTPServer::init()
{
#ifdef _WIN32
WSADATA wsaData;
WSAStartup(0x202,&wsaData);
if((mSock = socket(AF_INET,SOCK_STREAM,IPPROTO_TCP)) == INVALID_SOCKET)
#else
if((mSock = socket(AF_INET,SOCK_STREAM,0)) < 0)
#endif
{
std::cout << "Error while initilazing socket\n";
}
mServerAddr.sin_family = AF_INET;
mServerAddr.sin_port = htons(mPort);
#ifdef _WIN32
mServerAddr.sin_addr.S_un.S_addr = INADDR_ANY;
#else
mServerAddr.sin_addr.S_addr = INADDR_ANY;
#endif
if(bind(mSock, (struct sockaddr*) &mServerAddr, sizeof(mServerAddr)) < 0)
{
std::cout << "Error while binding socket\n";
}
if(listen(mSock, SOMAXCONN))
{
std::cout << "Error while listening socket\n";
}
}
void HTTPServer::fRun()
{
ThreadPool* tPool = new ThreadPool(10);
while(1)
{
int size = sizeof(mClientAddr);
#ifdef _WIN32
if((mAcceptSocket = accept(mSock,(struct sockaddr*) &mClientAddr,&size)) == INVALID_SOCKET)
#else
if((mAcceptSocket = accept(mSock,(struct sockaddr*) &mClientAddr,&size))) < 0)
#endif
{
std::cout << "Error while initilazing accept socket\n";
}
std::cout << "Socket Sent " << mAcceptSocket << std::endl;
tPool->enqueue([&] { this->fOnRequest(mAcceptSocket); });
}
delete tPool;
}
void HTTPServer::fOnRequest(uint64_t socket)
{
const string reqData = fRecieveNext(mAcceptSocket);
const HTTPResponse res = fProcessRequest(reqData);
fSendResponse(res,socket);
std::cout << "Done";
}
const string HTTPServer::fRecieveNext(uint64_t socket)
{
int64_t recieveLength = 0,totalRecieved = 0;
int64_t recieveLengthBeforeBody = -1;
int contentLength = 0;
string rawData;
while(1)
{
memset(mBuffer,'\0',8192);
if((recieveLength = recv(socket,mBuffer,8192, 0)) == SOCKET_ERROR)
{
std::cout << "Error while receiving data from socket\n";
#ifdef _WIN32
std::cout << WSAGetLastError() << std::endl;
#endif
break;
}
if(recieveLength == 0)
break;
totalRecieved += recieveLength;
rawData += string(mBuffer,recieveLength);
if(totalRecieved > 32)
{
if(rawData.find("Content-Length") == string::npos && contentLength == 0)
break;
else
{
if(contentLength == 0)
{
auto pos = rawData.find("Content-Length:");
auto lengthStr = rawData.substr(pos + 16, rawData.find_first_of("\r",pos) - pos - 16);
contentLength = std::stoi(lengthStr);
}
if(rawData.find("\r\n\r\n") != string::npos && recieveLengthBeforeBody == -1)
{
recieveLengthBeforeBody = rawData.find("\r\n\r\n") + 4;
}
if(totalRecieved >= contentLength + recieveLengthBeforeBody)
break;
}
}
}
return rawData;
}
void HTTPServer::fSendResponse(const HTTPResponse& response,const uint64_t socket)
{
string resStr = response.fSerializeResponse();
size_t sent = 0,totalSent = 0;
char* buffer = &(resStr[0]);
while(totalSent < resStr.length())
{
if((sent = send(socket, buffer, resStr.length() - totalSent,0)) < 0)
{
std::cout << "Error while sending response" << std::endl;
}
buffer += sent;
totalSent += sent;
}
delete &response;
#ifdef _WIN32
if(shutdown(socket, SD_BOTH) == SOCKET_ERROR)
{
std::cout << "Error while closing socket" << std::endl;
std::cout << WSAGetLastError() << std::endl;
};
if(closesocket(socket) == SOCKET_ERROR)
{
std::cout << "Error while closing socket" << std::endl;
std::cout << WSAGetLastError() << std::endl;
};
#else
if(shutdown(socket, SHUT_RDWR) < 0)
{
std::cout << "Error while closing socket" << std::endl;
};
if(close(socket) < 0)
{
std::cout << "Error while closing socket" << std::endl;
};
#endif
}
It looks like you are expecting to receive a TCP message all in one recv. TCP doesnt work like that. Tcp is a stream protocol, a message sent to you can be broken into multiple chunks. TCP only guarantees that the bytes will arrive once and in the correct order.
That would make you get out of sync with the input
A third question in the saga: How to correctly implement select to correctly get data from stdin and recv(). I recommend reading this and the other question it links to understand the situation.
Basically, I tried my luck at implementing select() myself. My code:
#include <iostream>
#include <sys/socket.h>
#include <sys/types.h>
#include <sys/select.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <cstring>
#include <stdlib.h>
using namespace std;
int main (int argc, char** argv) {
if (argv[1] == NULL) {
cout << "\033[31mTARGET NOT SPECIFIED - TERMINATING...\033[0m\n";
return -1;
}
if (argv[2] == NULL) {
cout << "\033[31mPORT NOT SPECIFIED - TERMINATING...\033[0m\n";
return -2;
}
string target = argv[1];
int port = atoi(argv[2]);
cout << "GENERATING SOCKET...\n";
int chatter = socket(AF_INET, SOCK_STREAM, 0);
if (chatter == -1) {
cout << "\033[31mSOCKET GENERATION FAILURE - TERMINATING...\033[0m\n";
return -3;
}
cout << "\033[32mSUCCESSFULLY GENERATED SOCKET\033[0m\n";
struct sockaddr_in hint;
hint.sin_family = AF_INET;
hint.sin_port = htons(port);
inet_pton(AF_INET, target.c_str(), &hint.sin_addr);
struct timeval tv;
tv.tv_usec = 0.0;
tv.tv_sec = 5;
int recval;
cout << "CONNECTING TO " << target << " AT PORT " << port << "...\n";
int connection_status = connect(chatter, (sockaddr*)&hint, sizeof(hint));
if (connection_status == -1) {
cout << "\033[31mCONNECTION FAILURE - TERMINATING...\033[0m\n";
return -4;
}
cout << "\033[32mCONNECTED TO HOST\033[0m\n";
char buf[4096] = {0};
string msg;
while (true) {
fd_set rfds;
FD_ZERO(&rfds);
FD_SET(chatter, &rfds);
getline(cin, msg);
msg+"\r\n";
int sendmsg = send(chatter, msg.c_str(), msg.size()+1, 0);
if (sendmsg == -1) {
cout << "\033[31mMESSAGE SENDING FAILURE - TERMINATING...\033[0m\n";
return -5;
}
recval = select(chatter + 1, &rfds, NULL, NULL, &tv);
switch(recval) {
case(0):
cout << "\033[31mTIMEOUT\033[0m\n";
break;
case(-1):
cout << "\033[31mERROR\033[0m\n";
break;
default:
if (recv(chatter, buf, 4096, 0) < 0) {
cout << "\033[31mFAILURE TO RECEIVE MESSAGE - TERMINATING...\033[0m\n";
return -6;
} else {
cout << recv(chatter, buf, 4096, 0) << "\n";
cout << buf << "\n";
}
break;
}
}
close(chatter);
return 0;
}
I keep getting TIMEOUT when trying the program on scanme.nmap.org and my HTTP server. What am I doing wrong?
At this point, after fixing something a user in the first question pointed out, I know that there isn't an issue with how I'm sending data. Just an issue with the way the program handles getting data from getline()/recv().
EDIT: NEW, IMPROVED, WORKING CODE THANKS TO ANSWERER
#include <iostream>
#include <sys/socket.h>
#include <sys/types.h>
#include <sys/select.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <cstring>
#include <stdlib.h>
using namespace std;
int main (int argc, char** argv) {
if (argv[1] == NULL) {
cout << "\033[31mTARGET NOT SPECIFIED - TERMINATING...\033[0m\n";
return -1;
}
if (argv[2] == NULL) {
cout << "\033[31mPORT NOT SPECIFIED - TERMINATING...\033[0m\n";
return -2;
}
string target = argv[1];
int port = atoi(argv[2]);
cout << "GENERATING SOCKET...\n";
int chatter = socket(AF_INET, SOCK_STREAM, 0);
if (chatter == -1) {
cout << "\033[31mSOCKET GENERATION FAILURE - TERMINATING...\033[0m\n";
return -3;
}
cout << "\033[32mSUCCESSFULLY GENERATED SOCKET\033[0m\n";
struct sockaddr_in hint;
hint.sin_family = AF_INET;
hint.sin_port = htons(port);
inet_pton(AF_INET, target.c_str(), &hint.sin_addr);
int recval;
cout << "CONNECTING TO " << target << " AT PORT " << port << "...\n";
int connection_status = connect(chatter, (sockaddr*)&hint, sizeof(hint));
if (connection_status == -1) {
cout << "\033[31mCONNECTION FAILURE - TERMINATING...\033[0m\n";
return -4;
}
cout << "\033[32mCONNECTED TO HOST\033[0m\n";
char buf[4096] = {0};
string msg;
while (true) {
struct timeval tv;
tv.tv_usec = 0.0;
tv.tv_sec = 5;
fd_set rfds;
FD_ZERO(&rfds);
FD_SET(chatter, &rfds);
getline(cin, msg);
msg += "\r\n";
const char *pMsg = msg.c_str();
size_t msgSize = msg.size();
do {
int numSent = send(chatter, pMsg, msgSize, 0);
if (numSent == -1) {
cout << "\033[31mMESSAGE SENDING FAILURE - TERMINATING...\033[0m\n";
close(chatter);
return -5;
}
pMsg += numSent;
msgSize -= numSent;
} while (msgSize > 0);
recval = select(chatter + 1, &rfds, NULL, NULL, &tv);
switch(recval) {
case(0):
cout << "\033[31mTIMEOUT\033[0m\n";
break;
case(-1):
cout << "\033[31mERROR\033[0m\n";
break;
default:
int numRead = recv(chatter, buf, 4096, 0);
if (numRead < 0) {
cout << "\033[31mFAILURE TO RECEIVE MESSAGE - TERMINATING...\033[0m\n";
close(chatter);
return -6;
}
else if (numRead == 0) {
cout << "\033[31mDISCONNECTED - TERMINATING...\033[0m\n";
close(chatter);
break;
} else {
cout << numRead << "\n";
cout.write(buf, numRead);
cout << "\n";
}
break;
}
}
close(chatter);
return 0;
}
On some platforms, select() alters the passed timeval to indicate how much time is remaining. So this is likely the cause of your timeout errors, as you are setting the timeval only once and it will eventually fall to 0. You need to reset your tv variable every time you call select(), so move that inside your while loop.
Also, you have 2 calls to recv() where you should be using only 1 call. You are ignoring the bytes received by the 1st recv(), and if the server happens to send less then 4096 bytes then there won't be any data left for the next call to select() to detect, unless the connection is disconnected.
Change this:
if (recv(chatter, buf, 4096, 0) < 0) {
cout << "\033[31mFAILURE TO RECEIVE MESSAGE - TERMINATING...\033[0m\n";
return -6;
} else {
cout << recv(chatter, buf, 4096, 0) << "\n";
cout << buf << "\n";
}
To this:
int numRead = recv(chatter, buf, 4096, 0);
if (numRead < 0) {
cout << "\033[31mFAILURE TO RECEIVE MESSAGE - TERMINATING...\033[0m\n";
return -6;
}
else if (numRead == 0) {
cout << "\033[32mHOST DISCONNECTED\033[0m\n";
break;
} else {
cout << numRead << "\n";
cout.write(buf, numRead);
cout << "\n";
}
Also, msg+"\r\n"; is a no-op, you probably meant to use msg += "\r\n"; instead.
And, you should not be including the msg's null terminator when calling send(). And you are not accounting for the possibility that send() may not be able to send the whole data in one go. You need to call send() in a loop instead, eg:
const char *pMsg = msg.c_str();
size_t msgSize = msg.size();
do {
int numSent = send(chatter, pMsg, msgSize, 0);
if (numSent == -1) {
cout << "\033[31mMESSAGE SENDING FAILURE - TERMINATING...\033[0m\n";
return -5;
}
pMsg += numSent;
msgSize -= numSent;
}
while (msgSize > 0);
// 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.
cin.ignore() vs cin.clear() + cin.sync()
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.
Am I doing right thing to pass &std::string.front() to write incoming data?
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;.
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;
}
Your question on this is too vague to provide a helpful answer.
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.
&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.
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?
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.
Your questions have been addressed by caps,, but my 2 cents. What about having your own send/recv functions and hiding the complexity?
For example along the lines of:
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;
}
In the following lines of code, I am taking a request line (GET or HEAD <filename> HTTP/1.0), and get the information about the file.
This is working correctly for html/text files and image/gif files.
However, when I put a directory in , the program tells it is a socket, or unknown, while it is supposed to tell it is a directory.
It is strange because I copied the code from UNIX man example, and the example code in man recognizes the directory when I put directory as the argument.
Only difference is that my program takes the file(or directory) name during execution from the user input(client-server), while the UNIX program takes the name from the command line argument.
What is the problem?
class request {
vector<string> requests;
string message;
bool valid, isGET, isHEAD;
public:
explicit request(char line[]): requests(split_string(line)), valid(true) {
struct stat sb;
if(stat(requests[1].c_str(), &sb) == -1) {
cerr << "Usage: GET or HEAD (filename) HTTP/1.0" << endl;
valid = false;
exit(EXIT_FAILURE);
}
string cont_type = "Content-Type: ";
switch (sb.st_mode & S_IFMT) {
case S_IFBLK: printf("block device\n"); break;
case S_IFCHR: printf("character device\n"); break;
case S_IFDIR: cont_type += "directory\n"; cout << "DIRECTORY" <<endl; break;
case S_IFIFO: printf("FIFO/pipe\n"); break;
case S_IFLNK: printf("symlink\n"); break;
case S_IFREG: {
string ext = requests[1].substr(requests[1].size()-6, 6);
if(ext.find(".html") != string::npos || ext.find(".txt") != string::npos) {cont_type += "text/html file\n";}
else if(ext.find(".jpg") != string::npos || ext.find(".jpeg") != string::npos || ext.find(".gif") != string::npos || ext.find(".png") != string::npos) {cont_type += "image/gif file\n";}
else {cont_type += "regular file\n";}
break;
}
case S_IFSOCK: printf("socket\n"); break;
default: printf("unknown?\n"); break;
}
}
Just in case I add the whole code, which is a server that can be connected using telnet localhost 8080:
#define BUF_LEN 8192
#include<queue>
#include<sstream>
#include<vector>
#include<string>
#include<iostream>
#include<cstring>
#include<cstdlib>
extern "C" {
#include<sys/types.h>
#include<sys/stat.h>
#include<sys/socket.h>
#include<netdb.h>
#include<unistd.h>
#include<stdio.h>
#include<sys/time.h>
}
using std::vector;
using std::string;
using std::cerr;
using std::cout;
using std::endl;
using std::stringstream;
using std::queue;
using std::priority_queue;
char* time_stamp();
class request {
vector<string> requests;
string message;
off_t filesize;
bool valid, isGET, isHEAD;
public:
request() : valid(true) {}
explicit request(char line[]): requests(split_string(line)), valid(true) {
if(requests[0] == "GET") isGET = true;
else if(requests[0] == "HEAD") isHEAD = true;
else {
cerr << "Usage: GET or HEAD (filename) HTTP/1.0" << endl;
}
//chdir();
struct stat sb;
if(stat(requests[1].c_str(), &sb) == -1) {
cerr << "Usage: GET or HEAD (filename) HTTP/1.0" << endl;
valid = false;
// exit(EXIT_FAILURE);
}
filesize = sb.st_size;
string lmstring(ctime(&sb.st_mtime));
string lm = "Last-Modified: " + lmstring;
string cont_type = "Content-Type: ";
switch (sb.st_mode & S_IFMT) {
case S_IFBLK: cout << "block device" << endl; break;
case S_IFCHR: cout << "character device" << endl; break;
case S_IFDIR: cont_type += "directory\n"; cout << "DIRECTORY" <<endl; break;
case S_IFIFO: cout << "FIFO/pipe" << endl; break;
case S_IFLNK: cout << "symlink" << endl; break;
case S_IFREG: {
string ext = requests[1].substr(requests[1].size()-6, 6);
if(ext.find(".html") != string::npos || ext.find(".txt") != string::npos) {cont_type += "text/html file\n";}
else if(ext.find(".jpg") != string::npos || ext.find(".jpeg") != string::npos || ext.find(".gif") != string::npos || ext.find(".png") != string::npos) {cont_type += "image/gif file\n";}
else {cont_type += "regular file\n";}
break;
}
case S_IFSOCK: cout << "socket" << endl; break;
default: cout << "unknown?" << endl; break;
}
stringstream cl_ss;
cl_ss << "Content-Length: " << sb.st_size << '\n';
string cont_len = cl_ss.str();
message = lm + cont_type + cont_len;
FILE * pFile;
long lSize;
char * buffer;
size_t result;
/*
if(isGET) {
pFile = fopen ( requests[1].c_str() , "r" );
if (pFile==NULL) {fputs ("File error",stderr); exit (1);}
// obtain file size:
fseek (pFile , 0 , SEEK_END);
lSize = ftell (pFile);
rewind (pFile);
// allocate memory to contain the whole file:
buffer = (char*) malloc (sizeof(char)*lSize);
if (buffer == NULL) {fputs ("Memory error",stderr); exit (2);}
// copy the file into the buffer:
result = fread (buffer,1,lSize,pFile);
if (result != lSize) {fputs ("Reading error",stderr); exit (3);}
// the whole file is now loaded in the memory buffer.
// terminate
fclose (pFile);
free (buffer);
}
cout << buffer << endl;*/
}
bool operator<(const request& rhs) const{
return filesize < rhs.filesize;
}
string getMessage() const {
return this->message;
}
vector<string> split_string(char line[]) {
vector<string> vec_str;
char* token;
token = strtok(line, " ");
while (token != NULL) {
string temp_str(token);
vec_str.push_back(temp_str);
token = strtok(NULL, " ");
}
return vec_str;
}
};
int main() {
int status;
struct addrinfo host_info; // The struct that getaddrinfo() fills up with data.
struct addrinfo *host_info_list; // Pointer to the linked list of host_info's.
// The MAN page of getaddrinfo() states "All the other fields in the structure pointed
// to by hints must contain either 0 or a null pointer, as appropriate." When a struct
// is created in C++, it will be given a block of memory. This memory is not necessarily
// empty. Therefore we use the memset function to make sure all fields are NULL.
memset(&host_info, 0, sizeof host_info);
std::cout << "Setting up the structs..." << std::endl;
host_info.ai_family = AF_UNSPEC; // IP version not specified. Can be both.
host_info.ai_socktype = SOCK_STREAM; // Use SOCK_STREAM for TCP or SOCK_DGRAM for UDP.
host_info.ai_flags = AI_PASSIVE; // IP Wildcard
// Now fill up the linked list of host_info struts with ???????????????
status = getaddrinfo(NULL, "8080", &host_info, &host_info_list);
// getaddrinfo returns 0 on success, or some other value when an error occurred.
if (status != 0) std::cout << "getaddrinfo error" << gai_strerror(status);
std::cout << "Creating a socket..." << std::endl;
int socketfd; // The socket descripter
socketfd = socket(host_info_list->ai_family, host_info_list->ai_socktype, host_info_list->ai_protocol);
if(socketfd == -1) std::cout << "socket error " << std::endl;
std::cout << "Binding socket..." << std::endl;
// we use to make the setsockopt() function to make sure the port is not in use
// by a previous execution of our code.
int yes = 1;
status = setsockopt(socketfd, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(int));
status = bind(socketfd, host_info_list->ai_addr, host_info_list->ai_addrlen);
if (status == -1) std::cout << "bind error" << std::endl;
std::cout << "Listen()ing for connections..." << std::endl;
status = listen(socketfd, 5);
if (status == -1) std::cout << "listen error" << std::endl;
int new_sd;
struct sockaddr_storage their_addr;
socklen_t addr_size = sizeof(their_addr);
new_sd = accept(socketfd, (struct sockaddr *)&their_addr, &addr_size);
if (new_sd == -1) {
std::cout << "listen error" << std::endl;
}
else {
std::cout << "Connection accepted. Using new socketfd : " << new_sd << std::endl;
}
// after success on listen()ing, loop waiting
int done;
ssize_t bytes_received;
char buf[BUF_LEN];
fd_set ready;
while (!done) {
/* fd_set contains the following fields:
typedef struct fd_set {
u_int fd_count; //how many are SET?
SOCKET fd_array[FD_SETSIZE]; //an array of SOCKETs
} fd_set;
*/
FD_ZERO(&ready);
FD_SET(new_sd, &ready);
FD_SET(fileno(stdin), &ready);
if (select((new_sd + 1), &ready, 0, 0, 0) < 0) {
cerr << "Error from select" << endl;
std::exit(1);
}
if (FD_ISSET(fileno(stdin), &ready)) {
if((bytes_received = read(fileno(stdin), buf, BUF_LEN)) <= 0)
done++;
send(new_sd, buf, bytes_received, 0);
}
std::cout << "Waiting to receive data..." << std::endl;
bytes_received = recv(new_sd, buf, BUF_LEN, 0); // recv or recvfrom???
// If no data arrives, the program will just wait here until some data arrives.
if (bytes_received == 0) std::cout << "host shut down." << std::endl;
if (bytes_received == -1) std::cout << "receive error!" << std::endl;
std::cout << bytes_received << " bytes received: " << std::endl;
buf[bytes_received] = '\0';
std::cout << "elements of 'buf'" << buf << std::endl;
std::cout << "Send()ing back a message..." << std::endl;
request tobeadded(buf);
request rq;
if(true) {
queue<request> qr;
qr.push(tobeadded);
rq = qr.front();
qr.pop();
}
else {
priority_queue<request> pqr;
pqr.push(tobeadded);
rq = pqr.top();
pqr.pop();
}
string timeheader = "Time: ";
const char* t_h = timeheader.c_str();
char* timestamp = time_stamp();
string server = "Server: myhttp\n";
const char* serv = server.c_str();
const char* lm_ct_cl = rq.getMessage().c_str();
char *msg = new char[strlen(t_h) + strlen(timestamp) + strlen(serv) + strlen(lm_ct_cl) + 1];
*msg = '\0';
strcat(msg, t_h);
strcat(msg, timestamp);
strcat(msg, serv);
strcat(msg, lm_ct_cl);
int len;
ssize_t bytes_sent;
len = strlen(msg);
bytes_sent = send(new_sd, msg, len, 0);
//write(fileno(stdout), buf, bytes_received);
}
std::cout << "Stopping server..." << std::endl;
freeaddrinfo(host_info_list);
close(new_sd);
close(socketfd);
return 0;
}
char* time_stamp() {
time_t current_time;
char* c_time_string;
/* Obtain current time as seconds elapsed since the Epoch. */
current_time = time(NULL);
if (current_time == ((time_t)-1))
{
(void) fprintf(stderr, "Failure to compute the current time.");
}
/* Convert to local time format. */
c_time_string = asctime(gmtime(¤t_time));
if (c_time_string == NULL)
{
(void) fprintf(stderr, "Failure to convert the current time.");
}
c_time_string[strlen(c_time_string)-1] = ' ';
return strcat(c_time_string, " GMT\n");
}
I have a program that needs to constantly ping a device
However this ping function fails exactlly after 1020 tries
int ping(string target)
{
int s, i, cc, packlen, datalen = DEFDATALEN;
struct hostent *hp;
struct sockaddr_in to, from;
//struct protoent *proto;
struct ip *ip;
u_char *packet, outpack[MAXPACKET];
char hnamebuf[MAXHOSTNAMELEN];
string hostname;
struct icmp *icp;
int ret, fromlen, hlen;
fd_set rfds;
struct timeval tv;
int retval;
struct timeval start, end;
int /*start_t, */end_t;
bool cont = true;
to.sin_family = AF_INET;
// try to convert as dotted decimal address, else if that fails assume it's a hostname
to.sin_addr.s_addr = inet_addr(target.c_str());
if (to.sin_addr.s_addr != (u_int)-1)
hostname = target;
else
{
hp = gethostbyname(target.c_str());
if (!hp)
{
cerr << "unknown host "<< target << endl;
return -1;
}
to.sin_family = hp->h_addrtype;
bcopy(hp->h_addr, (caddr_t)&to.sin_addr, hp->h_length);
strncpy(hnamebuf, hp->h_name, sizeof(hnamebuf) - 1);
hostname = hnamebuf;
}
packlen = datalen + MAXIPLEN + MAXICMPLEN;
if ( (packet = (u_char *)malloc((u_int)packlen)) == NULL)
{
cerr << "malloc error\n";
return -1;
}
/*
if ( (proto = getprotobyname("icmp")) == NULL)
{
cerr << "unknown protocol icmp" << endl;
return -1;
}
*/
if ( (s = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP)) < 0)
{
perror("socket"); /* probably not running as superuser */
return -1;
}
icp = (struct icmp *)outpack;
icp->icmp_type = ICMP_ECHO;
icp->icmp_code = 0;
icp->icmp_cksum = 0;
icp->icmp_seq = 12345; /* seq and id must be reflected */
icp->icmp_id = getpid();
cc = datalen + ICMP_MINLEN;
icp->icmp_cksum = in_cksum((unsigned short *)icp,cc);
gettimeofday(&start, NULL);
i = sendto(s, (char *)outpack, cc, 0, (struct sockaddr*)&to, (socklen_t)sizeof(struct sockaddr_in));
if (i < 0 || i != cc)
{
if (i < 0)
perror("sendto error");
cout << "wrote " << hostname << " " << cc << " chars, ret= " << i << endl;
}
// Watch stdin (fd 0) to see when it has input.
FD_ZERO(&rfds);
FD_SET(s, &rfds);
// Wait up to one seconds.
tv.tv_sec = 1;
tv.tv_usec = 0;
while(cont)
{
retval = select(s+1, &rfds, NULL, NULL, &tv);
if (retval == -1)
{
perror("select()");
return -1;
}
else if (retval)
{
fromlen = sizeof(sockaddr_in);
if ( (ret = recvfrom(s, (char *)packet, packlen, 0,(struct sockaddr *)&from, (socklen_t*)&fromlen)) < 0)
{
perror("recvfrom error");
return -1;
}
// Check the IP header
ip = (struct ip *)((char*)packet);
hlen = sizeof( struct ip );
if (ret < (hlen + ICMP_MINLEN))
{
cerr << "packet too short (" << ret << " bytes) from " << hostname << endl;;
return -1;
}
// Now the ICMP part
icp = (struct icmp *)(packet + hlen);
if (icp->icmp_type == ICMP_ECHOREPLY)
{
cout << "Recv: echo reply"<< endl;
if (icp->icmp_seq != 12345)
{
cout << "received sequence # " << icp->icmp_seq << endl;
continue;
}
if (icp->icmp_id != getpid())
{
cout << "received id " << icp->icmp_id << endl;
continue;
}
cont = false;
}
else
{
cout << "Recv: not an echo reply" << endl;
continue;
}
gettimeofday(&end, NULL);
end_t = 1000000*(end.tv_sec - start.tv_sec) + (end.tv_usec - start.tv_usec);
if(end_t < 1)
end_t = 1;
cout << "Elapsed time = " << end_t << " usec" << endl;
return end_t;
}
else
{
cout << "No data within one seconds.\n";
return 0;
}
}
return 0;
}
If i run the ping function twice it fails at exactly half 510. And the pattern is exact. Here is the output after the 1020 mark
socket: Too many open files
ping returned -1
Im pretty bad at network programming but i dont even know where to begin for the above code.
Am i missing a close somewhere?
The ping function is taken from: http://www.linuxforums.org/forum/networking/60389-implementing-ping-c.html
Edit: Running Ubuntu 10.10, same issue on Fedora 13
Thanks
Yes, you need to close the socket with close(s); before exiting the function.
When allocating resources you need to make sure to free them.
As unwind pointed out, you have to close sockets.
But you also need to free the allocated memory (malloc, line 40-ish).
You should consider using memcpy instead of bcopy.
Agree with "unwind".
Note that you run a function, not a program. Hence your process is still alive, and all the memory/resource leaks are not automatically freed.
As a general rule, if you see in the code a call to a socket function without the corresponding closesocket - this must always scratch your eyes!