/********************************************************************
filename: netstream.cpp
author: Mikael Swartling
e-mail: e98msv@du.se
description:
------------
Network streams is a set of easy-to-use classes to deal with peer-to-peer
communication over the Internet. It uses << and >> operators to stream
pre/user defined datatypes, and commands for sending/receiving arbitrary
buffers of data over a connection. It operates on a very low level, making
it independent of any application-level protocol.
-----
This software is provided 'as-is', without any express or implied warranty.
In no event will the author be held liable for any damages arising from the
use of this software.
Permission is hereby granted to use, copy, modify, and distribute this
source code, or portions hereof, for any purpose, without fee, subject to
the following restrictions:
1. The origin of this source code must not be misrepresented. You must not
claim that you wrote the original software. If you use this software in
a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered versions must be plainly marked as such and must not be
misrepresented as being the original source.
3. This notice must not be removed or altered from any source distribution.
*********************************************************************/
#ifdef _WIN32
#include <winsock2.h>
#else
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/time.h>
#include <netinet/in.h>
#include <netdb.h>
#include <arpa/inet.h>
#endif
#include <sstream>
#include "netstream.h"
// --------------------------------------------------------------------------------
// Winsock
// --------------------------------------------------------------------------------
#ifdef _WIN32
// --------------------------------------------------------------------------------
// Used to start/clean up WinSock on Windows platforms.
// WinsockStartup() must be called once before you connect/listen.
// WinsockCleanup() is called before program is closing.
//
// return value:
// true if no error occured
// false otherwise
// --------------------------------------------------------------------------------
bool WinsockStartup()
{
WSADATA versionData;
int versionRequested = MAKEWORD(1, 0);
return (WSAStartup(versionRequested, &versionData) == 0) ? true : false;
}
bool WinsockCleanup()
{
return (WSACleanup() == 0) ? true : false;
}
#endif // _WIN32
// --------------------------------------------------------------------------------
// Local utility function.
// Convert a hostname into a network byteorder integer.
// --------------------------------------------------------------------------------
bool getinetaddr(const string &host, sockaddr_in *inAddr)
{
// inet_addr converts a string containing an IP address
// to network byteorder integer version of the IP.
inAddr->sin_addr.s_addr = inet_addr(host.c_str());
// INADDR_NONE is returned if no valid IP could be extracted.
if(inAddr->sin_addr.s_addr == INADDR_NONE)
{
// If no valid IP was extracted, chanses are that we have
// a domain name instead, so we try to translate the name
// to an IP.
hostent *hostEntry = gethostbyname(host.c_str());
// If this also fails, we are unable to determine the address.
if(hostEntry == NULL) return false;
// Take the IP address out of the hostent structure
unsigned long hostAddress = *((unsigned long*)*hostEntry->h_addr_list);
inAddr->sin_addr.s_addr = hostAddress;
}
return true;
}
// --------------------------------------------------------------------------------
// netsocket class
// --------------------------------------------------------------------------------
// --------------------------------------------------------------------------------
//
// --------------------------------------------------------------------------------
netsocket::netsocket()
{
m_socket = 0;
}
// --------------------------------------------------------------------------------
//
// --------------------------------------------------------------------------------
netsocket::~netsocket()
{
return;
}
// --------------------------------------------------------------------------------
// Allocate a new socket.
// --------------------------------------------------------------------------------
bool netsocket::create()
{
close();
m_socket = socket(AF_INET, SOCK_STREAM, 0);
return m_socket ? true: false;
}
// --------------------------------------------------------------------------------
// Close the socket
// It will gracefully (as it's called in the MSDN docs) close the connection.
//
// for netserver: socket is closed, and no more connections can be accepted.
// for netstream: stream is closed, and no more data can be sent/received.
//
// return value:
// true if socket/stream was successfully closed.
// --------------------------------------------------------------------------------
bool netsocket::close()
{
if(isopen() == false) return true;
int res = 0;
#ifdef _WIN32
res = closesocket(m_socket);
#else
res = ::close(m_socket);
#endif
m_socket = 0;
return res ? false: true;
}
// --------------------------------------------------------------------------------
// Determines wether the socket is open or not.
//
// return value:
// for netserver: true if socket is listening for connections.
// for netstream: true if stream is open.
// --------------------------------------------------------------------------------
bool netsocket::isopen() const
{
return m_socket ? true : false;
}
// --------------------------------------------------------------------------------
// return value:
// The name of the host. Host is the computer on which the function was called.
// --------------------------------------------------------------------------------
string netsocket::gethostname() const
{
sockaddr_in hostAddress;
int addressLength = sizeof hostAddress;
if(::getsockname(m_socket, (sockaddr*)&hostAddress, &addressLength) == 0)
{
return string(inet_ntoa(hostAddress.sin_addr));
}
return string();
}
// --------------------------------------------------------------------------------
// netstream class
// --------------------------------------------------------------------------------
// --------------------------------------------------------------------------------
// Automatically closes the stream on desrtuction.
// For example, if the user forgot to close the connection before exiting
// the program, the desturctor will close the connection when it goes out of scope.
// --------------------------------------------------------------------------------
netstream::~netstream()
{
close();
}
// --------------------------------------------------------------------------------
// Attach a socket to this stream
// --------------------------------------------------------------------------------
bool netstream::attach(SOCKET socket)
{
if(isopen() == true) return false;
m_socket = socket;
return m_socket ? true : false;
}
// --------------------------------------------------------------------------------
// Connect the stream to a remote host.
// There are two types of connect-functions, one takes hostname and port
// as two different parameters, while the other takes them both in one parameter.
// If you have the portnumber as a separate integer value, you call this
// two-parameter variant.
//
// port:
// Port number as an unsigned integer.
//
// name:
// Name of the host to connect to as a string.
// name can either be an IP address or a domain name.
// ex. "192.168.1.1" or "host.domain.com"
//
// return value:
// true if connection was successfull (server accepted connection)
// false if connection failed, possible causes:
// - server rejected connection attempt
// - unable to resolve domain name or find IP
// - already connected
// --------------------------------------------------------------------------------
bool netstream::connect(const string &hostName, int hostPort)
{
if(isopen() == true) return false;
sockaddr_in inAddr;
inAddr.sin_family = AF_INET;
inAddr.sin_port = htons(hostPort);
if(getinetaddr(hostName, &inAddr) == false) return false;
if(create() == false) return false;
if(::connect(m_socket, (sockaddr*)&inAddr, sizeof inAddr) != 0) return false;
return true;
}
// --------------------------------------------------------------------------------
// (see above for more information)
//
// This function requires the name/port to be of a certain format. The format is
// the host name followed by the portnumber, separated by a space (' ')
// and/or a colon (':').
//
// name and port are as described above
//
// valid formats are (among a few other combinations of spaces and colons):
// "192.168.1.1:1234" - name and port separated by a colon
// "host.domain.com 1234" - name and port separated by a space
// " host.domain.com : 1234 " - extra spaces does (should) not affect the result
//
// return value:
// same as above
// --------------------------------------------------------------------------------
bool netstream::connect(const string &hostNameAndPort)
{
string t = hostNameAndPort;
int pos;
// Replace all colons with spaces
while((pos = t.find(":")) != string::npos);
{
t[pos] = ' ';
}
std::stringstream s;
// stringstream is like a custom I/O stream.
// We put the userformated input (but with no colons) into the stream...
s << t;
string hostName;
int hostPort;
// ... and extract a string and a portnumber just like
// we do from a regular input stream.
s >> hostName >> hostPort;
return connect(hostName, hostPort);
}
// --------------------------------------------------------------------------------
// Send a buffer to the stream
//
// data:
// Pointer to buffer of data to send.
//
// len:
// Number of bytes to send.
//
// return value:
// true if successfully sent
// false if failed to send
// --------------------------------------------------------------------------------
bool netstream::send(const void *data, int len) const
{
if(isopen() == false) return false;
if(::send(m_socket, (const char *)data, len, 0) == SOCKET_ERROR)
{
return false;
}
return true;
}
// --------------------------------------------------------------------------------
// Receive data from the stream
//
// data:
// Pointer to buffer where the data is to be stored.
//
// len:
// Number of bytes to receive.
//
// len number of bytes is read from the stream. If len is less than the number
// of available bytes in the stream, recv will block until more data has arrived
// and the entire buffer is filled.
//
// return value:
// number of bytes received
// --------------------------------------------------------------------------------
int netstream::recv(void *data, int len) const
{
if(isopen() == false) return false;
int bytesReceived = 0;
// Keep on extracting until the whole buffer is filled
while(bytesReceived < len)
{
bytesReceived += ::recv(m_socket, ((char *)data) + bytesReceived, len - bytesReceived, 0);
}
return bytesReceived;
}
// --------------------------------------------------------------------------------
// Check wether there are data to be read from the stream or not.
//
// This function will also check wether the peer is alive or not.
// If the connection was interrupted for some reason, then connection
// is automatically closed on this side aswell, to prevent handling a closed
// connection.
//
// Possible causes of an interrupted connection:
// - peer closed connection
// - lost connection to peer
// - remote computer halted/rebooted
//
// return value:
// number of bytes pending to receive
//
// If a connection is interrupted, canrecv() will return zero and close
// the connection. Any following call to isopen() will return false, and you
// can assume the connection was interrupted in one way or another.
// --------------------------------------------------------------------------------
int netstream::canrecv()
{
fd_set readFD;
readFD.fd_count = 1;
readFD.fd_array[0] = m_socket;
timeval timeout;
timeout.tv_sec = 0;
timeout.tv_usec = 0;
// Select is used to determine status on one or more sockets.
// readFD contains all sockets we want to look at, in this case we only want
// to look at one socket, the socket used by the stream.
// Select returns the number of sockets who wants to tell us something.
if(select(0, &readFD, NULL, NULL, &timeout) != 0)
{
// This socket want to tell us something, now we need to
// determine what it want.
byte data;
// We use MSG_PEEK so we don't remove any important information
// for the user. ::recv returns the number of bytes pending to receive.
int bytesPending = ::recv(m_socket, &data, 1, MSG_PEEK);
// If no bytes where extracted, it's trying to tell us something else.
// In this case, it can only tell us one thing: the connection was lost/closed.
// We close the connection on our side aswell, and tell the suer no bytes is
// waiting to be read.
if(bytesPending == 0) close();
// Return the number of bytes pending.
return bytesPending;
}
// If we get here, the socket don't have anything to tell us,
// and therefore no bytes can be read.
return 0;
}
// --------------------------------------------------------------------------------
// return value:
// Name of peer. Peer is the computer "in the other end" of the stream.
// --------------------------------------------------------------------------------
string netstream::getpeername() const
{
sockaddr_in hostAddress;
int addressLength = sizeof(hostAddress);
if(::getpeername(m_socket, (sockaddr*)&hostAddress, &addressLength) == 0)
{
return string(inet_ntoa(hostAddress.sin_addr));
}
return string();
}
// --------------------------------------------------------------------------------
// Stream a string to the other computer.
// --------------------------------------------------------------------------------
netstream &netstream::operator <<(const string &data)
{
send(data.c_str(), data.length() + 1);
return *this;
}
// --------------------------------------------------------------------------------
// Extract a string from the stream.
// --------------------------------------------------------------------------------
netstream &netstream::operator >>(string &data)
{
char c;
string t = string();
// Extract byte after byte, untill a null character is extarcted, and insert
// it into the string.
while(true)
{
recv(&c, 1);
if(c == '\0') break;
t += c;
}
data = t;
return *this;
}
// --------------------------------------------------------------------------------
// Stream a null terminated character array.
// --------------------------------------------------------------------------------
netstream &netstream::operator <<(const char *data)
{
string t = data;
// Since char* is naturally ended with a null character, we don't need to
// go through the process of finding extra null characters.
send(t.c_str(), t.length() + 1);
return *this;
}
// --------------------------------------------------------------------------------
// netserver class
// --------------------------------------------------------------------------------
// --------------------------------------------------------------------------------
//
// --------------------------------------------------------------------------------
netserver::~netserver()
{
close();
}
// --------------------------------------------------------------------------------
// Tell socket to listen for incomming connections.
//
// port:
// The port number to listen on.
//
// interface:
// Specifies which interface to use to listen on. Interface is the specific
// adapter to listen on. Usefull if you have more than one network adaper but
// only wants to listen for connection on one of them.
//
// backlog:
// The maximum number of incomming connections that can wait for
// acception at any given time, before connections are automatically rejected
// by the socket handler (this happens on driver/os level, and not by these
// functions).
//
// interface and backlog are optional values and can be ignored.
// interface defaults to an empty string, which will listen to any adapter.
// backlog defaults to 5 incomming connections.
//
// return value:
// true if socket successfully placed into a listening state
// false otherwise
// --------------------------------------------------------------------------------
bool netserver::listen(int port, const string &adapter, int backlog)
{
if(isopen() == true) return false;
sockaddr_in inAddr;
inAddr.sin_family = AF_INET;
inAddr.sin_port = htons(port);
if(adapter.length() == 0)
{
// If no string is passed, we assume any adapter is valid...
inAddr.sin_addr.s_addr = INADDR_ANY;
}
else
{
// ... otherwise we need to get the IP of the adapter
if(getinetaddr(adapter, &inAddr) == false) return false;
}
if(create() == false) return false;
if(::bind(m_socket, (sockaddr*)&inAddr, sizeof(inAddr)) != 0) return false;
if(::listen(m_socket, backlog) != 0) return false;
return true;
}
// --------------------------------------------------------------------------------
// Accept an incomming connection and attach it to a netstream.
//
// socket:
// The stream to which you want to attach the incomming connection.
//
// return value:
// true if socket was successfully attached to stream
// false otherwise
// --------------------------------------------------------------------------------
bool netserver::accept(netstream& socket) const
{
// Accept the incomming socket
SOCKET s = ::accept(m_socket, NULL, 0);
if(s == INVALID_SOCKET) return false;
// Attach the socket to a stream
return socket.attach(s);
}
// --------------------------------------------------------------------------------
// Checks is there is an incomming connection waiting
//
// return value:
// true if there is a connection waiting
// false if no connection is waiting
// --------------------------------------------------------------------------------
bool netserver::canaccept() const
{
fd_set readFD;
FD_ZERO(&readFD);
FD_SET(m_socket, &readFD);
timeval timeout;
timeout.tv_sec = 0;
timeout.tv_usec = 0;
// Select is described in canrecv()
int res = select(1, &readFD, NULL, NULL, &timeout);
// If a socket is trying to tell us something, and this socket is
// in a listening state, it means someone is trying to connect to us.
return res ? true : false;
} |