Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Branch: master
Fetching contributors…

Cannot retrieve contributors at this time

executable file 1129 lines (856 sloc) 22.352 kB
#ifndef com_sleepless_net_socket_cpp
#define com_sleepless_net_socket_cpp
// Copyright 2002
// Sleepless Software Inc.
// All Rights Reserved
#include <stdio.h>
#include <string.h>
#ifdef WIN32
# include <time.h>
#else
# include <sys/time.h>
#endif
#ifdef __APPLE__
#include <sys/types.h>
#endif
#include "str.cpp"
#ifdef WIN32
// For some idiotic reason (big surprise), win32 needs to have
// it's sockets stuff initialized before you can do any socket calls
# include <windows.h>
# include <winsock.h>
static bool socketsInitOK = false;
static bool socketInitSockets()
{
if(!socketsInitOK)
{
WSADATA wsdat;
socketsInitOK = (WSAStartup(0x0101, &wsdat) == 0);
if(!socketsInitOK)
return false;
}
return true;
}
typedef int socklen_t;
# define strncasecmp _strnicmp
# define strcasecmp _stricmp
#else
# include <sys/ioctl.h>
# include <unistd.h>
# include <sys/socket.h>
# include <netinet/in.h>
# include <arpa/inet.h>
# include <netdb.h>
static const int INVALID_SOCKET = -1;
static const int SOCKET_ERROR = -1;
typedef int SOCKET;
typedef const struct sockaddr SOCKADDR;
# define closesocket close
# define ioctlsocket ioctl
static bool socketInitSockets()
{
return true;
}
#endif
typedef int SERVERSOCKET;
#if (defined(__APPLE__) && defined(__GNUC__))
# define NOPIPESIG(sock) \
{ \
int one = 1; \
setsockopt(sock, SOL_SOCKET, SO_NOSIGPIPE, (void *)&one, sizeof(one)); \
}
#else
# define NOPIPESIG(sock)
#endif
#ifdef WIN32
//
// XXX someone needs to fix this for linux
//
// General purpose function for resolving a hostname to an IP addr.
// Returns 0 if the name can't be resolved.
// Otherwise, it returns an unsigned long IP number.
//
// Example:
//
// char *host = "www.sleepless.com";
// unsigned long ip = resolve(host);
// if(ip == 0)
// {
// printf("Unable to resolve \"%s\"", host);
// }
// else
// {
// printf("\"%s\" resolves to %d.%d.%d.%d\n",
// host,
// (ip >> 24) & 0xff,
// (ip >> 16) & 0xff,
// (ip >> 8) & 0xff,
// (ip >> 0) & 0xff
// );
// }
//
unsigned long socketResolve(const char *host)
{
if(!socketInitSockets())
return 0;
struct hostent *he = gethostbyname(host);
if(he != NULL)
{
in_addr ia = *((struct in_addr *)he->h_addr);
unsigned long ip = ntohl(ia.S_un.S_addr);
return ip;
}
return 0;
}
#endif
/////////////////////////////////////////////////////////////////////////////
// Returns the number of IP's for the local host.
// Though not common for individual workstations, it is entirely
// possible for one computer to have multiple IP numbers. For example,
// if you computer has two network cards, or one card and a wireless
// network interface, or a dialup, there may be more than one IP (other
// than 127.0.0.1) for you computer. This function returns the number
// of IPs that this computer has, not counting 127.0.0.1.
// This is intended for use in calling socketLocalIP() below.
int socketLocalIPCount()
{
if(!socketInitSockets())
return 0;
// Fetch local host name
char host[100];
if(gethostname(host, sizeof(host)) == SOCKET_ERROR)
return 0;
// Get data about local net interfaces
struct hostent *phe = gethostbyname(host);
if(phe == 0)
return 0;
// Count the IPs
int numaddrs = 0;
for(int i = 0; phe->h_addr_list[i] != 0; i++)
numaddrs++;
return numaddrs;
}
// Fills 'buf' with a local IP number, in ASCII, specified by index 'n'.
// The number of IP's that the localhost has can be acquired with
// socketLocalIPCount() above. IP's are indexed from 0.
// For example, most of the time, there will be only one IP for
// the localhost and passing a 0 for 'n' will get you that number.
// Returns 0 if buf was filled in, or 1 otherwise.
int socketLocalIP(char *buf, int bufsize, int n)
{
if(!socketInitSockets())
return 1;
// Fetch local host name
char host[100];
if(gethostname(host, sizeof(host)) == SOCKET_ERROR)
return 1;
// Get data about local net interfaces
struct hostent *phe = gethostbyname(host);
if(phe == 0)
return 1;
// Count the number of IP's for this host
int numaddrs = 0;
for(int i = 0; phe->h_addr_list[i] != 0; i++)
numaddrs++;
// Make sure 'n' is valid
if((n < 0) || (n >= numaddrs))
return 1;
struct in_addr addr;
memcpy(&addr, phe->h_addr_list[n], sizeof(struct in_addr));
Str::copy(buf, inet_ntoa(addr), bufsize);
return 0;
}
// Attempts to retrieve this computer's hostname and store it into 'buf'.
// Return 0 on success, non-zero on failure.
int socketLocalHostName(char *buf, int buflen)
{
buf[0] = 0;
if(!socketInitSockets())
return -1;
if(socketLocalIPCount() < 1)
return -1;
return gethostname(buf, buflen);
}
// Returns 1 if the dotted decimal, ASCII IP string 'ip' is
// "public", 0 otherwise. "Public" means that the IP is a valid Internet
// IP addresses vs. one of the defined private network addresses.
//
// Reserved for private networks:
//
// 10.0.0.0 to 10.255.255.255
// 172.16.0.0 to 172.31.255.255
// 192.168.0.0 to 192.168.255.255
// and as of July 2001
// 169.254.0.0 to 169.254.255.255 rfc
//
// XXX I don't think that just excluding the above addresses is adequate
// to know for SURE if an IP is in fact a valid public IP, as there are a
// lot of other "reserved" ip blocks that are "valid routable" IPs, but
// which are still "reserved" as defined by iana.org and so should really
// cause a return of 0 from this function.
// But the list is rather huge, and I'm sure this will work fine in most
// cases.
int socketIsPublicIP(const char *ip)
{
if(Str::startsWith(ip, "10."))
return 0;
if(Str::startsWith(ip, "169.254."))
return 0;
if(Str::startsWith(ip, "192.168."))
return 0;
if(Str::startsWith(ip, "172."))
{
int o;
if(sscanf(ip, "172.%d.%*d.%*d", &o) == 1)
{
if((o >= 16) && (o <= 31))
return 0;
}
}
return 1;
}
/////////////////////////////////////////////////////////////////////////////
/*
Returns an open and connected socket descriptor or
-1 on failure.
*/
SOCKET socketOpen(const char *host, int port)
{
if(!socketInitSockets())
return INVALID_SOCKET;
struct hostent *he = gethostbyname(host);
if(he != NULL)
{
SOCKET sock = socket(AF_INET, SOCK_STREAM, 0);
if(sock != INVALID_SOCKET)
{
struct sockaddr_in sin;
memset(&(sin), 0, sizeof(sin));
sin.sin_family = AF_INET; // host byte order
sin.sin_port = htons(port); // short, network byte order
sin.sin_addr = *((struct in_addr *)he->h_addr);
NOPIPESIG(sock);
if(connect(sock, (struct sockaddr *)&sin, sizeof(struct sockaddr)) != -1)
{
return sock;
}
closesocket(sock);
}
}
return INVALID_SOCKET;
}
/*
Returns now many bytes are available to read without blocking.
*/
int socketAvailable(SOCKET sock)
{
unsigned long a;
if(ioctlsocket(sock, FIONREAD, &a) == 0)
return a;
return -1;
}
/*
Writes some bytes to a socket.
Returns the number of bytes written or -1 if
an error occured.
*/
int socketWrite(SOCKET sock, const char *buf, int len)
{
int sigs = 0;
#if defined(linux)
sigs = MSG_NOSIGNAL;
#endif
int r = send(sock, buf, len, sigs);
//fprintf(stdout, "socketWrite: sent %d bytes: ", r);
//fwrite(buf, len, 1, stdout);
//fflush(stdout);
return r;
}
/*
Writes a null terminated string to a socket, not including the
terminating null.
Returns the number of bytes written or -1 if
an error occured.
*/
int socketWrite(SOCKET sock, const char *str)
{
int r = socketWrite(sock, str, (int)strlen(str));
return r;
}
// Waits for some data to arrive on the socket.
// If 'tosecs' is greater than 0, it represents a timeout in seconds.
// if 'tosecs' is 0, then wait indefinately for data to arrive.
// Returns 0 if the timeout of tosecs expires or if EOF has been reached.
// Returns -1 if an error occured.
// Otherwise, returns # of bytes available to read on the socket.
int socketWaitForData(SOCKET sock, int tosecs)
{
struct timeval tv;
tv.tv_sec = tosecs;
tv.tv_usec =0;
fd_set rfds;
FD_ZERO(&rfds);
fd_set efds;
FD_ZERO(&efds);
FD_SET(sock, &rfds);
// XXX I think I maybe should be doing an FD_SET(sock, &efds); here
// XXX shouldn't I?
FD_SET(sock, &efds);
int r = select((int)sock + 1, &rfds, NULL, &efds, (tosecs > 0) ? &tv : NULL);
if(r)
{
if(FD_ISSET(sock, &efds))
return -1;
if(FD_ISSET(sock, &rfds))
return socketAvailable(sock);
}
return r;
}
/*
Reads 'len' bytes from socket into 'buf'.
If 'tosecs' is greater than 0, it represents a timeout in seconds.
Returns the number of bytes read or -1 if an error occured or the
timeout expires while reading.
NOTE: This function calls recv(), but differs
from recv() in that it is guaranteed not
to return until the requested number of bytes
is read, an error occurs, or a timeout expires.
*/
int socketRead(SOCKET sock, char *buf, int len, int tosecs)
{
int sigs = 0;
#if defined(linux)
sigs = MSG_NOSIGNAL;
#endif
int r = 0;
int n;
while(r < len)
{
n = socketWaitForData(sock, tosecs);
//TRACE("waitfordata returns %d\n", n);
if(n == -1)
return -1;
if((len - r) < n)
n = len - r;
n = recv(sock, buf + r, n, sigs);
//TRACE("recv returns %d\n", n);
if(n == -1)
return -1;
if(n == 0)
break;
r += n;
}
//TRACE("socketRead() returns %d\n", r);
return r;
}
/*
Reads a newline terminated string into 'buf'
from a socket, up to a maximum of 'len' bytes.
Returns the number of bytes read or -1 if
an error occured.
The contents of buf will be 0-terminated and will have
the trailing newline like fgets().
NOTE: This function is very inefficient as
it reads one character at a time until it sees
the newline.
*/
int socketReadLine(SOCKET sock, char *buf, int len)
{
int rr = 0;
int n = 0;
int r;
for( ; n < (len - 1); n++)
{
r = socketRead(sock, &(buf[n]), 1, 0);
if(r != 1)
{
buf[n] = 0; // Terminate the string.
break;
}
rr += 1;
if(buf[n] == 13) // Discard CR's
{
n--;
continue;
}
if(buf[n] == 10) // LF marks the end of the line
{
n++;
buf[n] = 0; // Terminate the string.
break;
}
}
buf[len - 1] = 0; // ensure termination
return rr;
}
/* Similar to readLine, except that a null terminats the string */
int socketReadString(SOCKET sock, char *buf, int len)
{
int rr = 0;
int n = 0;
int r;
for( ; n < (len - 1); n++)
{
r = socketRead(sock, &(buf[n]), 1, 0);
if(r != 1)
{
buf[n] = 0; // Terminate the string.
break;
}
rr += 1;
if(buf[n] == 0) // null marks the end of the string
{
n++;
buf[n] = 0; // Terminate the string.
break;
}
}
buf[len - 1] = 0; // ensure termination
return rr;
}
/*
Closes an open socket descripter.
*/
void socketClose(SOCKET sock)
{
closesocket(sock);
}
/////////////////////////////////////////////////////////////////////////////
/*
Creates a server socket and binds it to the port with
the given backlog value.
Returns the new server socket or -1 if an error occured.
*/
SERVERSOCKET serverSocketOpen(const char *ipstr, int port, int backlog)
{
if(!socketInitSockets())
return INVALID_SOCKET;
SOCKET ss = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if(ss == INVALID_SOCKET)
{
return INVALID_SOCKET;
}
// Bind to port number
struct sockaddr_in sin;
sin.sin_family = AF_INET;
//sin.sin_addr.s_addr = htonl (INADDR_ANY);
sin.sin_addr.s_addr = inet_addr(ipstr);
sin.sin_port = htons((short)port);
int t = 1;
if(setsockopt(ss, SOL_SOCKET, SO_REUSEADDR, (const char *)&t, sizeof(t)))
{
closesocket(ss);
return INVALID_SOCKET;
}
NOPIPESIG(ss);
if(bind(ss, (struct sockaddr *)&sin, sizeof (sin)) == SOCKET_ERROR)
{
closesocket(ss);
return INVALID_SOCKET;
}
if(listen(ss, backlog))
{
closesocket(ss);
return INVALID_SOCKET;
}
return (SERVERSOCKET)ss;
}
SERVERSOCKET serverSocketOpen(int port, int backlog)
{
return serverSocketOpen("0.0.0.0", port, backlog);
}
/*
Returns 1 if a new connection is pending (waiting to be accepted)
Returns 0 if there is not.
Returns -1 if an error ocurred while trying to find out.
*/
int serverSocketPending(SERVERSOCKET ss)
{
struct timeval tv;
tv.tv_sec = 0;
tv.tv_usec = 0;
fd_set rfds;
FD_ZERO(&rfds);
FD_SET(ss, &rfds);
int r = select(ss + 1, &rfds, NULL, NULL, &tv);
if(r)
{
if(FD_ISSET(ss, &rfds))
r = 1;
else
r = 0;
}
return r;
}
/*
Returns a SOCKET for a new incoming connection on the server
socket or -1 if an error occured.
NOTE: This call is going to block until a new
connection comes in. It just calls accept().
*/
SOCKET serverSocketAccept(SERVERSOCKET ss, struct sockaddr_in *sa, socklen_t *salen)
{
return accept(ss, (struct sockaddr *)sa, salen);
}
/*
This is the same as serverSocketAccept except that it takes a maxsecs
arg which is the maximum # of seconds to wait for a connection.
If the time expires before a connection comes in, INVALID_SOCKET is
returned.
*/
SOCKET serverSocketAccept(SERVERSOCKET ss, struct sockaddr_in *sa, socklen_t *salen, int maxsecs)
{
struct timeval tv;
tv.tv_sec = maxsecs;
tv.tv_usec = 0;
fd_set rfds;
FD_ZERO(&rfds);
FD_SET(ss, &rfds);
int r = select(ss + 1, &rfds, NULL, NULL, &tv);
if(r)
return serverSocketAccept(ss, sa, salen);
return INVALID_SOCKET;
}
/*
Closes a server socket.
*/
void serverSocketClose(SERVERSOCKET ss)
{
closesocket(ss);
}
/////////////////////////////////////////////////////////////////////////////
#ifdef SOCKETS_TEST
#include <stdio.h>
#include <stdlib.h>
#include "../io/HexDump.cpp"
#include "../sys/Thread.cpp"
#include "../sys/System.cpp"
static SOCKET sock;
static int done;
static void *reader(void *)
{
printf("reader running\n");
char line[256];
while(!done)
{
if(!fgets(line, sizeof(line) - 2, stdin))
break;
line[strlen(line) - 1] = 0;
strcat(line, "\r\n");
int l = strlen(line);
//printf("Writing to socket %d:\n", sock);
//HexDump::dump(stdout, 0, (const unsigned char *)line, l);
int r = socketWrite(sock, line, l);
if(r != l)
{
printf("l == %d, r == %d socket write error\n", l, r);
break;
}
}
printf("reader exiting\n");
done = 1;
return 0;
}
static void *writer(void *)
{
printf("writer running\n");
char buf[512];
while(!done)
{
int r = socketReadLine(sock, buf, sizeof(buf));
//printf("Received from socket %d bytes:\n", r);
//HexDump::dump(stdout, 0, (const unsigned char *)buf, r);
if(r == 0)
{
printf("socket EOF\n");
break;
}
if(r < 0)
{
printf("socket read error\n");
break;
}
fwrite(buf, 1, r, stdout);
}
printf("writer exiting\n");
done = 1;
return 0;
}
int main(int argc, char **argv)
{
int port = 9000;
char *host = "localhost";
if(argc >= 2)
host = argv[1];
if(argc >= 3)
port = atoi(argv[2]);
sock = socketOpen(host, port);
if(sock == -1)
{
printf("#### Can't connect to %s on port %d\n", host, port);
return 1;
}
printf("Connected to %s on port %d\n", host, port);
done = 0;
Thread rt(reader);
rt.start(0);
Thread wt(writer);
wt.start(0);
while(!done)
{
System::ssleep(500);
}
Thread::join(&rt);
Thread::join(&wt);
socketClose(sock);
printf("Disconnected\n");
return 0;
}
#endif
/*
This header file defines a class/struct called Socket, which
envelopes the simple functions of opening, reading, writing
and closing of a tcp socket connection.
This code works should compile and work for both linux/unix and win32
Public methods:
Socket(const char *host, int port)
Tries to create an open and connected socket.
Call the method isConnected() to determine of
the connection was established.
See: com/sleepless/sockets.cpp
Socket(SOCKET s, unsigned long addr)
Constructs a Socket objects given an already
connected socket.
NOTE: The 'addr' argument is accepted as the
actual IP number of the host to which the
socket is connected. There may be a way to
find out for real, what host a socket is
connected to given just the descriptor, but I
don't know what it is, so this is how I
get round that.
~Socket()
Closes and disconnects the socket.
bool isConnected()
Returns true if the socket object is open
and connected.
int write(const char *buf, int len)
Writes some bytes to the socket.
Returns the number of bytes written or -1 if
an error occured.
See: com/sleepless/sockets.cpp
int write(const char *str)
Writes a null terminated string to the socket.
Returns the number of bytes written or -1 if
an error occured.
See: com/sleepless/sockets.cpp
int write(int n)
Writes an integer to the socket in ascii form.
For example, write(10) would write 2 characters
to the socket, '1' and '0'.
See: com/sleepless/sockets.cpp
int read(char *buf, int len)
Reads 'len' bytes from the socket into 'buf'.
Returns the number of bytes read or -1 if
an error occured.
NOTE: This function calls recv(), but differs
in that it is guaranteed not to return
until the requested number of bytes is read
or an error occurs.
See: com/sleepless/sockets.cpp
Public data members:
SOCKET sock;
The socket descriptor
See: com/sleepless/sockets.cpp
char remoteIP[3+1+3+1+3+1+3+1 + 1]; // In dotted decimal
The dotted quad IP number to which the socket
is connected in ascii form.
For example the string "127.0.0.1".
*/
struct Socket
{
SOCKET sock;
char remoteIP[32]; // In dotted decimal
virtual bool isConnected()
{
return sock != INVALID_SOCKET;
}
virtual int available()
{
return socketAvailable(sock);
}
virtual int waitForData(int tosecs)
{
return socketWaitForData(sock, tosecs);
}
virtual int write(const char *buf, int len)
{
if(isConnected())
return socketWrite(sock, buf, len);
return -1;
}
virtual int writeStr(const char *str)
{
return write(str, (int)strlen(str));
}
virtual int writeInt(int n)
{
char buf[64];
sprintf(buf, "%d", n);
return writeStr(buf);
}
virtual int writeUInt(int n)
{
char buf[64];
sprintf(buf, "%u", n);
return writeStr(buf);
}
virtual int writeFloat(float n)
{
char buf[64];
sprintf(buf, "%f", n);
return writeStr(buf);
}
int read(char *buf, int len, int to)
{
if(isConnected())
return socketRead(sock, buf, len, to);
return -1;
}
int read(unsigned char *buf, int len, int to)
{
return read((char *)buf, len, to);
}
virtual int read(char *buf, int len)
{
return read(buf, len, 0);
}
virtual int read(unsigned char *buf, int len)
{
return read((char *)buf, len);
}
/*
Reads a newline terminated string into 'buf'
from the socket, up to a maximum of 'len' bytes.
Returns the number of bytes read or -1 if
an error occured.
NOTE: If the entire buffer is filled before a
newline is read, the buffer will not be null
terminated.
NOTE: This function is very inefficient as
it reads one character at a time until it sees
the newline.
NOTE: Carriage return chars (CR) are silently discarded,
as they are read.
*/
virtual int readLine(char *buf, int len)
{
if(isConnected())
return socketReadLine(sock, buf, len);
return -1;
}
/* Similar to readLine, except that a 0-byte is the terminator instead
* of new line and newlines and CRs are both returned and do not
* act as terminators. */
virtual int readString(char *buf, int len)
{
if(isConnected())
return socketReadString(sock, buf, len);
return -1;
}
Socket(const char *host, int port)
{
sock = socketOpen(host, port);
}
Socket(SOCKET s, unsigned long addr)
{
sock = s;
sprintf(remoteIP, "%ld.%ld.%ld.%ld",
(addr >> 24) & 0xff,
(addr >> 16) & 0xff,
(addr >> 8) & 0xff,
(addr >> 0) & 0xff
);
}
void disconnect()
{
if(isConnected())
{
socketClose(sock);
sock = INVALID_SOCKET;
}
}
virtual ~Socket()
{
if(isConnected())
{
socketClose(sock);
sock = INVALID_SOCKET;
}
}
};
#ifdef SOCKET_TEST
#include <stdio.h>
#include <stdlib.h>
int main(int argc, char **argv)
{
int port = 9000;
char *host = "localhost";
if(argc >= 2)
host = argv[1];
if(argc >= 3)
port = atoi(argv[2]);
Socket *sock = new Socket(host, port);
if(!sock->isConnected())
{
printf("#### Can't connect to %s on port %d\n", host, port);
return 1;
}
printf("Connected to %s on port %d\n", host, port);
sleep(1);
char line[256];
int r;
#if 0
r = sock->readLine(line, sizeof(line));
int rr = 0;
while(r > 0)
{
if(isprint(line[rr]))
printf("%c", line[rr]);
else
printf("<0x%02x>", line[rr]);
rr++;
r--;
}
#endif
while(fgets(line, sizeof(line), stdin))
{
r = sock->writeStr(line);
if(r == -1)
{
printf("#### sock->writeStr() returned -1\n");
break;
}
if(r == 0)
{
printf("#### sock->writeStr() returned 0\n");
break;
}
r = sock->readLine(line, sizeof(line));
if(r == -1)
{
printf("#### sock->read() returned -1\n");
break;
}
if(r == 0)
{
printf("#### sock->read() returned 0\n");
break;
}
printf("ECHO: %s", line);
}
delete sock;
printf("Disconnected\n");
return 0;
}
#endif
#endif // com_sleepless_net_socket_cpp
Jump to Line
Something went wrong with that request. Please try again.