Linux and C++ Programming Sockets

By Mario Giannini

Overview

A Socket represents a connection between two processes that permits them to exhange data. Internet programs like FTP, Web Browsers, and chat programs all comminucate via sockets. Sockets may also be used to permit to processes on the same machine to communicate as well.

In this document, we will address sockets in a cursor fashion, while attempting to present a re-usable C++ socket class. It is worth noting that sockets provide a great deal more flexibility than we can get into here, and warrant additional reading. This document will specifically address the TCP/IP style sockets (not Unix or Xerox NS type). It will also demonstrate only the SOCKET_STREAM family of sockets, but the class should be able to handle others such as datagrams, and raw sockets.

 

TCP/IP Internet Sockets

Many programmers are already aware of the fact that when a computer is connected to the internet, they get an 'IP' address. This address uniquely identifies your computer on the net. Both you, and the web server at www.ibm.com, are represented as a single IP address (though larger sites like www.ibm.com may split there IP address on geographical divisions).

In order for a process to connect to another, it most know the IP address of the host machine (you can use 127.0.0.1 to refer to your own computer, the 'loopback' address). In addition to the IP address, the process must know what 'port' the other process is listening on. If your IP address is like your building address, then you can think of the port address as your apartment number in your building.

Throughout this discussion, we will use the term 'server' and 'client'. A server is the process that is constantly running, listening for socket connections. The client process, attempts to make contact with the

server, when needed.

 

The MSocket class

The MSocket class presented here can be used by both a client and server. It provides functionality for establishing a socket connection to a specific internet host, as well as listening for a connection. It also provides some basic functionality for sending and receiving data, error handling, and reference counting. As functions in the class are discusses, we will also discuss the Linux system calls that are used in sockets.

A Note about error handling

Now would be a good time to take note of how the MSocket class handles errors. It has two different modes, Normal, and Exception. In normal mode, when the class encounters an error with a system call, that function will return the same error code that the system call returned. In Exception mode however, it

will throw a C++ exception.

We won't go into detail about exception handling here. It is assumed that the reader has a basic knowledge of what it is, and how it works with the 'try', 'throw', and 'catch' keywords. Some compilers may require additional switches to implement exceptions, so check your man pages. Finally, it would most likely br prudent to create an exception class, derived from the standard 'exception' class for reporting our errors, but in the interest of keeping this document brief, I will be using simple strings as the exception data

type.

 

Socket Creation

The first step in using a socket, simply enough, is to create one. You can use the 'socket' system call to create a socket. In one of the two MSocket constructors, you will find the following code:

// Start of the MSocket class functions

MSocket::MSocket( int nFamily, int nType, int nProtocol )

{

m_nTimeOut=0;

// Try to intialize the MSocket, using the socket system call

m_nSocket = ::socket( nFamily, nType, nProtocol );

if( m_nSocket < 0 && m_bThrowException ) // If an error, and we should throw it

ThrowErrMsg( "socket() call failed: %d", m_nSocket );

if( m_nSocket >= 0 )

IncRef();

}

Note the call to ::socket, below the comment. This is how the MSocket class initializes it's socket. As you can tell, the Family, Type, and Protocol parameters for the constructor are simply passed onto the ::socket system call. These parameters let you create sockets other than the default type of internet (IF_INET), and stream (SOCK_STREAM) types. The protocol is left at zero here, and we will define it later.

The m_nSocket variable is a data member for the MSocket class, and represents the Linux socket handle that the class 'wraps'. The m_bThrowException is a static member of MSocket (all instances of the class see the same variable), and is used to determine if the class is working in normal-, or exception-mode

error handling.

As you can tell by looking at the code, the function checks to see if the m_nSocket is <0 (which indicates that the ::socket call failed), and if the m_bThrowException member is true. If this is true, then the ThrowErrMsg function is called which will throw an exception.

 

Reference Counting

Now would also be a good time to discuss reference counting. In writing this class, I wanted to take advantage of some basic C++ class functionality, such as having the MSocket destructor automatically close the socket it wrapped when the object went out of scope. But, this offered a bit of a challenge to

do this, and also provide functionality for things like copy constructors, and assignment operators.

For example, image 2 MSocket Objects:

MSocket A, B;

A = B;

In the above code, A has been set to the same socket that B contains. The problem is, that when the destructor for A is called and it's socket is closed, the B object now has an invalid socket handle because the two shared the same socket handle. In order to work around this, I implemented reference counting, using the STL 'map' class.

With reference counting, when you have a resource like a socket handle, you also keep track of how many objects are using it, or, have a reference to it. Then, in your destructor, you would decrement the reference count for that handle, and if the count went to zero, that would mean that no other class

instances are using that specific socket, and it can be safely closed.

The map class was selected because it lets you store two pieces of data, called the 'key' and the 'value' for that key. I implemented it so that the socket handle was the key, and it's reference count is the key's value. You can look at the simple IncRef, DecRef, and GetRef functions in the protected section of the class definition for the details.

 

Big and Little Endian

HP, IBM and Motorola 68000 systems store multibyte values in Big Endian order, while Intel 80x86 and DEC VAX systems store them in Little Endian order. Big Endian stores the high-order byte at the starting address while

Little Endian stores the low-order byte at the starting address.

Since the internet is used by all kinda of computers, when you want to call something an 'int', what order is it in? Internet protocol uses Big Endian, like the Motorola. This means that functions, like bind, that need integer

values, must have their values stored in the correct format (particularly, Intel machines). Luckily, there are several standard functions to help you to convert from your native integer type with that of the internet, they are:

unsigned long int htonl(unsigned long int hostlong); 'Host to network long'

unsigned short int htons(unsigned short int hostshort); 'Host to network short'

unsigned long int ntohl(unsigned long int netlong); 'network to host long'

unsigned short int ntohs(unsigned short int netshort); 'network to host short'

You can examine the MSocket::Bind and MSocket::Accept functions for examples on how to use these functions.

Server-side Sockets

A socket server (like an FTP server) would also call the 'bind', 'listen', and 'accept' system functions, in addition to the 'socket' function. The MSocket class has these functions defined as Bind, Listen, and Accept (note first letter is capitalized).

 

Bind

int Bind( struct sockaddr * sapSrvrAdrs, int nLenAddr ) const;

int Bind( int nPort, int nFamily=AF_INET, int nAddr=INADDR_ANY) const;

The Bind function, like the MSocket constructor, is simply a wrapper function to a system call, 'bind'. The bind system call is used to bind a socket to a particular port, IP address, and socket type (again, we default to TCP/IP sockets). The second version of Bind above, is simply an interface function that calls the first version.

The bind function call accepts a sockaddr structure pointer. The important members of this struct for us are the following:

sin_family = The family type of the socket, we default to IF_INET for TCP/IP.

sin_addr.s_addr = The IP address we will accept connections from. Servers typically use the INADDR_ANY address, meaning any IP address is ok.

sin_port = The port we will be listening on.

As you can see from the second Bind function above, the last two parameters have default values. This means that if 'S' were an MSocket class, we could do:

S.Bind( 81 );

To bind that socket to port 81, using the IF_INET protocol, and accept connections from any IP address (INADDR_ANY).

There are two Bind functions for simplicity, an flexibilty. Using the second version make the function easy to use, such as in the '81' example above. The first version permits you to use your own sockaddr struct, if you wanted to

create one. This approach is taken by several functions in this class.

 

Listen

int Listen( int nBackLog=5 ) const;

The listen system call informs the operating system that the process will be listening for connections on a particular socket (which has already had 'bind' called). The parameters to listen are the socket that will be listening, and

the 'backlog' of user connections the operating system should buffer, if the process gets busy handling other clients. The backlog parameter is normally 5.

As with Bind, the MSocket class provides a wrapper function for listen, called Listen. Also, if an error occurs it may either return an error code, or throw an exception.

Accept

int Accept( MSocket& msrRet, struct sockaddr* sapClientAddr=0, int* npLen=0 ) const;

The accept system call is called by a server to wait and listen for a connection for a client. Normally, the program will 'block' here, waiting until a socket connection is established.

Like the bind system call, accept needs a pointer to a sock_addr structure. Unlike bind however, this structure is used as an output value, and not an input value. When accept returns, it has populated the structure with

information about it's client, such as it's IP address. The third parameter is a pointer to an integer, that accept will use to store the number of bytes it used in the sockaddr structure.

By calling the accept system call, the server will sit and wait for a client connection request. The client (discussed next) makes a call to 'connect', and it will then be synchronized with the server, so that when the server

returns from it's accept call (meaning a conneciton was established), the client will also return from it's 'connect' call. From this point on, the two processes will start communicating.

 

Client Programming

In addition to calling the socket system call, a client must call the 'connect' system call, which will attempt to make a connection with a server. The connect function's prototype looks like:

int connect(int sockfd, struct sockaddr *serv_addr, int addrlen );

Here again, the sockfd parameter is the socket we want to connect. The sockaddr struct (pointer) holds information about the desired server. The data members in this structure used in this call are the same as used in

the 'bind' call above: sin_addr.s_addr, sin_family, and sin_port, and you will note a similar use of htons().

In the Connect function for the MSocket class, we added a nice feature: The server IP is a string that can contain either the IP addres such as "127.0.0.1", or a machine name or URL, such as "www.acme.com". If the parameter is not an IP address string, then the MSocket::Connect function will call the gethostbyname system call which converts it's name into an IP address for us.

 

Reading and Writing Sockets

A socket handle is actually a type of file handle in Linux. In order to read or write from it, you can call the 'read' or 'write' system calls. The only trick to this, is that normally, when you read from a data file, an EOF might

mean that you hit the end of the file. On a socket however, it could mean that you hit the end of the amount of data that the OS buffered for the data transmission.

What this means is that a socket is normally read, until the read function returns a zero, indicating there was no more data. So, if you look at the ReadLine function in the class, you will see that the read function to get a

single string of text actually loops, reading a character at a time. WriteLine has a similar operation, if you attempt to write 1024 bytes, but only wrote 128 bytes, you should try one more time to resend the remaining 896 bytes (this is what WriteLine does).

Sockets, blocking an non-blocking

When a socket function awaits completion of some event, like a read functions waits for data to appear, it will normally wait forever for that data to appear, like any program waiting for the user to press the ENTER key. But, in

socket communications, we might want a bit more flexibility, and implement a time-out definition. A function that will wait forever for an even, is termed 'blocking'. One that returns immediately or after a certain time period, is

termed 'non-blocking'.

The MSocket defaults to blocking mode, but implements a 'SetTimeOut' function to permit you to specify a certain time period to wait before the function times out. This only applies to the Accept and ReadLine functions of the

class. In normal mode, these functions return an error on time out, in exception mode they will generate an exception.

 

Basic Socket Review

Ok, with all that discussed, lets just have a quick summary of the socket system calls, what they do, and the MSocket class:

Linux Socket System Calls

socket - Used to create a socket, both client and server

bind - Used to bind a socket to a particular port, for servers only

listen - Used to tell the operating system that the process is ready to listen for connections. For servers only.

connect - Used by a client, to make a connection to a server.

read - Used to read data to a socket, in a fashion similar to file I/O, but not exactly like it. Used by both client and server.

write - Used to write data to a socket, in a fashion similar to file I/O, but not exactly like it. Used by both client and server.

select - Used to determine if a socket is ready for reading, writing, or connection accepted, with a time-out period. Avoids blocked I/O and used by both client and server.

gethostname - converts a host name or URL into an IP address.

A server will typically call socket, bind, listen, then accept. A client will typically call call socket, and then connect.

The MSocket class provides the following functionality:

MSocket - Constructor, initializes a socket

Bind - Similar to bind, with fewer parameters needed.

Listen - Similar to listen, with fewer parameters needed.

Accept - similar to accept, only it returns another MSocket class, and

requires less parameters.

Server - Calls Bind and Listen

SetThrowExeceptions - Changes MSocket from normal error handling mode to exception throwing, and vica-verca.

WasConTermErr - Determines if an exception (error) was the result of a lost connection.

operator= = Lets you assign one socket to another, with reference

counting. SetTimeOut = Sets timeout period, in microseconds. 0 means no timeout (the defualt)

ReadLine = Reads a line from a socket

Internally, the class uses:

IncRef - Increments reference count for that socket

DecRef - Decrements reference count for that socket

GetRef - Returns the reference count for that socket.

TimedOut - Waits for an event to occur for the time out period. Will return if event occurs, or if it timed out waiting.

WriteLine = Writes a line from a socket

operator<< = Lets the MSocket class work like a stream (with strings only)

operator>> = Lets the MSocket class work like a stream (with strings only)

The following is a simple server demo program, that gets input from it's client, converts it to upper case, then sends back out to the client. It does a fork() call, so it can handle multiple clients:

#include <iostream>

#include "msocket.h"

#define TESTPORT 5000

int main(int argc, char* argv[])

{

MSocket::SetExceptions(true);

try {

MSocket S, C;

S.Server( TESTPORT );

cout << "Server waiting for connections..." << endl;

while( 1 )

{

S.Accept(C);

cout << "Client connection accepted" << endl;

if( fork() == 0 ) // ID is 0 for child, non-0 for parent

{ // New child handles the socket here

char Buffer[128];

S.Close(); // Client doesn't need server socket anymore

while( 1 )

{

C >> Buffer;

for( int z=0; Buffer[z]; z++ )

Buffer[z] = toupper( Buffer[z] );

if( strcmp( Buffer, "QUIT\n" )==0 || strcmp( Buffer, "SHUTDOWN\n" )==0 )

break;

C << Buffer;

cout << "Child process sent out: " << Buffer << endl ;

}

break; // Terminates client, gets out of loop

}

else

C.Close(); // Server doesn't need client socket anymore

}

}

catch( char* Msg )

{

if( MSocket::WasConTermErr( Msg ) )

cout << "Ok, no biggie, connection terminated" << endl;

else

cout << "Error: " << Msg << endl;

}

return(0);

}

A Client program for the above is (remember, if your testing on a single machine, use IP address 127.0.0.1):

#include <iostream>

#include "msocket.h"

#define TESTPORT 23

int main(int argc, char* argv[])

{

char Buffer[128];

MSocket::m_bThrowException = true;

try {

if( argc != 2 )

{

cout << "Must specify IP address / Name of server " << endl;

return(0);

}

MSocket X;

X.Connect( argv[1], TESTPORT );

cout << "Got connection" << endl;

do {

cout << "Enter string: " << endl;

cin >> Buffer;

X << Buffer << "\n";

Buffer[0]='\0';

X >> Buffer;

cout << "Reponse: " << Buffer << endl;

} while( 1 );

}

catch( char* Msg )

{

cout << "Error: " << Msg;

exit(1);

}

return(0);

}

 

The following are the remainder of the files for the class and project:

msocket.h

=========

#ifndef _MSOCKT_H_DEFINE

#define _MSOCKET_H_DEFINE

// Needed by MSocket class:

#include <ctype.h> // for isdigit

#include <sys/types.h> // for connect(), bind(), accept(),

#include <sys/socket.h> // for connect(), listen(), bind() accept(),

#include <netinet/in.h> // for ntons(), htonl(), etc...

#include <arpa/inet.h> // for inet_addre()

#include <unistd.h> // for close(), read(), write()

#include <netdb.h> // for gethostbyname

#include <sys/time.h> // for timeval struct, and select()

#include <string.h> // for memset()

#include <map> // for STL map class (Reference counting)

// The MSocket class

class MSocket

{

public:

MSocket( int nFamily=AF_INET, int nType=SOCK_STREAM, int nProtocol=0 );

MSocket( const MSocket& msSrc ); // Copy Constructor

~MSocket();

operator int() { return( m_nSocket ); }

int Close();

// Socket operations (Client)

int Connect( struct sockaddr * sapSrvrAdrs, int nLenAddr ) const;

int Connect( const char* szHostAddr, short sPort ) const;

// Socket operation (Server)

int Bind( struct sockaddr * sapSrvrAdrs, int nLenAddr ) const;

int Bind( int nPort, int nFamily=AF_INET, int nAddr=INADDR_ANY) const;

int Listen( int nBackLog=5 ) const;

int Server( int nPort ) const; // Helper Function

int Accept( MSocket& msrRet, struct sockaddr* sapClientAddr=0, int* npLen=0 ) const;

// Atributes for normal- or exception-mode error reporting

static bool m_bThrowException;

static bool SetExceptions( bool bMode );

// Operators

MSocket& operator=( MSocket& msrSrc );

MSocket& operator=( int Socket );

// I/O functions

int SetTimeOut( int MicroSeconds );

int ReadLine( char *cpDest, int nSize, char Term='\n' ) const;

int WriteLine( const char* cpSrc, char Term='\n' ) const;

// Helper functions

void ThrowErrMsg( const char* cpMsg, int nCode=0 ) const;

void ThrowErrMsg( const char* cpMsg, const char* cpInfo ) const { ThrowErrMsg( cpMsg, (int) cpInfo); }

static bool WasConTermErr( const char* ErrMsg );

protected:

int m_nSocket, m_nTimeOut;

int TimedOut() const;

// Support for reference counting, using STL map class

static map<int,int> RefCount;

void IncRef()

{ if( RefCount.find( m_nSocket )!=RefCount.end() ) RefCount[m_nSocket]++; else RefCount[m_nSocket]=1; }

void DecRef()

{ if( RefCount.find( m_nSocket )!=RefCount.end() ) RefCount[m_nSocket]--; }

bool GetRef()

{ return( RefCount.find(m_nSocket)!=RefCount.end()? RefCount[m_nSocket] : 0 ); }

};

// Some prototypes for over-ridden operators, to make class look like a cout/cin object

MSocket& operator<<( MSocket& msrOut, const char* cpStr );

MSocket& operator>>( MSocket& msrIn, char* cpStr );

#endif

msocket.cxx

===========

#include <iostream>

#include <stdio.h> // For sprintf

#include "msocket.h"

// Common error strings, to save some space

static char ErrNotInit[] = "Socket not initialized";

static char ErrConTerm[] = "Connection terminated";

// m_szErrMsg is not part of the class, because of the const functions throwing exceptions

// might modify it. Size is probably overkill. Unfortunately, my g++ compiler doesn't

// support the sstream class.

static char m_szErrMsg[256];

// Statics for the class

bool MSocket::m_bThrowException = true;

map<int,int> MSocket::RefCount;

// Start of the MSocket class functions

MSocket::MSocket( int nFamily, int nType, int nProtocol )

{

m_nTimeOut=0;

// Try to intialize the MSocket, using the socket system call

m_nSocket = ::socket( nFamily, nType, nProtocol );

if( m_nSocket < 0 && m_bThrowException ) // If an error, and we should throw it

ThrowErrMsg( "socket() call failed: %d", m_nSocket );

if( m_nSocket >= 0 )

IncRef();

}

MSocket::MSocket( const MSocket& msrSrc ) // Copy Constructor

{

m_nTimeOut=0;

m_nSocket = msrSrc.m_nSocket;

IncRef();

}

MSocket::~MSocket() //Destructor

{

if( m_nSocket >= 0 )

Close();

}

int MSocket::Close()

{

int Ret = m_nSocket;

if( Ret < 0 && m_bThrowException )

ThrowErrMsg( "Close called on a closed socket" );

if( Ret > 0 )

{

DecRef(); // Check reference count, if we are the last

if( !GetRef() ) // using the socket, then close it.

close( m_nSocket );

m_nSocket = -1;

}

return( Ret );

}

int MSocket::Connect( struct sockaddr * sapSrvrAdrs, int nLenAddr ) const

{

if( m_nSocket < 0 )

{

if( m_bThrowException ) // If not setup correctly

ThrowErrMsg( ErrNotInit );

return(-1);

}

// Call the connect system function to actually do connect.

int Ret = ::connect( m_nSocket, (struct sockaddr*)sapSrvrAdrs, nLenAddr );

if( Ret < 0 && m_bThrowException ) // If there was an error, and we should throw it.

ThrowErrMsg( "connect() failed: %d", Ret );

return( Ret );

}

int MSocket::Connect( const char* cpHostAddr, short sPort ) const

{

// Setup and init the sock_addr_in struct, needed by real connect function

struct sockaddr_in SrvrAdrs;

memset( &SrvrAdrs, 0, sizeof( SrvrAdrs ) );

// If szHostAddr looks like '127.0.0.1' then it's easy, just

// call inet_addr to convert the string into a netword address ID integer

if( isdigit(cpHostAddr[0]) )

SrvrAdrs.sin_addr.s_addr = inet_addr( cpHostAddr );

else

{

// Otherwise, it may be a host name. Get it's IP address and use that.

// For example, szHostAddr may be www.acme.com

struct hostent * pHostEnt = gethostbyname( cpHostAddr );

if( pHostEnt == NULL )

{

if( m_bThrowException )

ThrowErrMsg( "Unable to determine IP for %s", cpHostAddr );

return( -1 );

}

SrvrAdrs.sin_addr.s_addr = *(long*)pHostEnt->h_addr_list[0];

}

SrvrAdrs.sin_family = AF_INET;

// Call htons, to convert our local pc short to format compatible with the 'net'

SrvrAdrs.sin_port = htons( sPort );

// finally, call the other version of MSocket::Connect, with the struct we set up

return( Connect( (struct sockaddr*) &SrvrAdrs , (int)sizeof( SrvrAdrs ) ) );

}

int MSocket::Bind( struct sockaddr* sapMyAddr, int nAddrLen ) const

{

if( m_nSocket < 0 ) // Some called Bind with a closed socket?

{

if( m_bThrowException ) // Should we throw exception on an error?

ThrowErrMsg( ErrNotInit );

return(-1);

}

// Call the bind system function to actually do connect.

int Ret = ::bind( m_nSocket, sapMyAddr, nAddrLen );

if( Ret < 0 && m_bThrowException ) // If there was an error, and we should throw it.

ThrowErrMsg( "bind() failed: %d", Ret );

return( Ret );

}

int MSocket::Bind( int nPort, int nFamily, int nAddr ) const

{

struct sockaddr_in SrvrAdrs;

memset( &SrvrAdrs, 0, sizeof( SrvrAdrs ) );

SrvrAdrs.sin_family = nFamily;

SrvrAdrs.sin_addr.s_addr = htonl( nAddr );

SrvrAdrs.sin_port = htons( nPort );

// Here, we call our 'other' Bind member function

return( Bind( (struct sockaddr*)&SrvrAdrs, (int)sizeof( SrvrAdrs ) ) );

}

int MSocket::Listen( int nBackLog ) const

{

if( m_nSocket < 0 )

{

if( m_bThrowException )

ThrowErrMsg( ErrNotInit );

return( -1 );

}

// Call the system 'listen' function

int Ret = ::listen( m_nSocket, nBackLog );

if( Ret < 0 && m_bThrowException )

ThrowErrMsg( "listen() failed: %d", Ret );

return( Ret );

}

int MSocket::Server( int nPort ) const // Just a convenient function

{

int Ret;

Ret = Bind( nPort );

if( Ret >= 0 )

Ret = Listen();

return( Ret );

}

int MSocket::Accept( MSocket& msrRet, struct sockaddr* sapClientAddr, int* npLen ) const

{

int MyLen;

struct sockaddr_in Client;

if( sapClientAddr == 0 ) // To make things easier on our caller, we can create the struct

{

sapClientAddr = (sockaddr*)& Client;

npLen = &MyLen;

}

if( m_nTimeOut && TimedOut() )

return( -1 );

// Call the system 'accept' function

int nRet = ::accept( m_nSocket, sapClientAddr, (unsigned int*)npLen );

if( nRet < 0 && m_bThrowException )

ThrowErrMsg( "accept failed: %d", nRet );

msrRet = nRet;

return( nRet );

}

MSocket& MSocket::operator=( MSocket& msrSrc )

{

if( &msrSrc != this ) // Make sure caller didn't to X = X;

*this = msrSrc.m_nSocket;

return( *this );

}

MSocket& MSocket::operator=( int nSocket )

{

if( nSocket < 0 && m_bThrowException )

throw "operator= called with bad socket";

if( m_nSocket >= 0 )

Close();

m_nSocket = nSocket;

IncRef();

return( *this );

}

int MSocket::TimedOut() const

{

fd_set fdSet;

struct timeval TimeToWait;

TimeToWait.tv_sec = m_nTimeOut / 10000;

TimeToWait.tv_usec = m_nTimeOut % 10000;

FD_ZERO( &fdSet );

FD_SET( m_nSocket, &fdSet );

int Ret = select( m_nSocket+1, &fdSet, &fdSet, &fdSet, &TimeToWait );

if( Ret <=0 && m_bThrowException )

ThrowErrMsg( Ret==0?"Time out":"Select error: %d", Ret );

return( Ret==1?0:1 );

}

bool MSocket::SetExceptions( bool bMode )

{

bool Ret;

Ret = m_bThrowException;

m_bThrowException = bMode;

return( Ret );

}

int MSocket::SetTimeOut( int nMicroSeconds )

{

int Ret = m_nTimeOut;

m_nTimeOut = nMicroSeconds;

return( Ret );

}

 

int MSocket::ReadLine( char *cpDest, int nSize, char Term ) const

{

int ReadIn=0, Stat;

if( m_nSocket < 0 )

{

if( m_bThrowException )

ThrowErrMsg( ErrNotInit );

return( -1 );

}

while( ReadIn < nSize-2 )

{

if( m_nTimeOut && TimedOut() )

return( -1 );

Stat = read( m_nSocket, cpDest+ReadIn, 1 );

if( Stat <= 0 )

{

if( m_bThrowException )

ThrowErrMsg( Stat==0?ErrConTerm:"ReadLine error: %d", Stat );

return( Stat );

}

if( cpDest[ReadIn]==Term )

break;

ReadIn++;

}

if( Term != '\0' )

ReadIn++;;

cpDest[ReadIn]='\0';

}

int MSocket::WriteLine( const char *cpSrc, char Term ) const

{

int Written=0, Len;

Len = strlen(cpSrc);

if( Term=='\0' ) // If terminator is '\0', include that in data out

Len++;

if( m_nSocket < 0 )

{

if( m_bThrowException )

ThrowErrMsg( ErrNotInit );

return( -1 );

}

while( Len>0 )

{

Written=write( m_nSocket, cpSrc, Len );

if( Written <=0 )

{

if( m_bThrowException )

ThrowErrMsg( Written==0?ErrConTerm:"Writing socket: %d", Written );

return( Written );

}

cpSrc += Written;

Len -= Written;

}

return( 0 );

}

void MSocket::ThrowErrMsg( const char* cpMsg, int nCode=0 ) const

{

sprintf( m_szErrMsg, cpMsg, nCode );

throw m_szErrMsg ;

}

bool MSocket::WasConTermErr( const char* ErrMsg )

{

return( strcmp(ErrMsg,ErrConTerm)==0);

}

// A cout 'look alike' function, for simplicty. ostream was not really used.

MSocket& operator<<( MSocket& msrOut, const char* cpStr )

{

msrOut.WriteLine( cpStr );

return( msrOut );

}

// A cin 'look alike' function, for simplicity. istream is not really used.

MSocket& operator>>( MSocket& msrIn, char* cpStr )

{

msrIn.ReadLine( cpStr, 0x0FFFF ); // No good number here, really

return( msrIn );

}

makefile

========

#

# A very simple makefile

#

all : srvr clnt mglin1.0.a

 

mglin1.0.a : msocket.o

ar -cr mglin1.0.a msocket.o

msocket.o : msocket.cxx msocket.h

g++ -c msocket.cxx

clnt : clnt.o mglin1.0.a

g++ -o clnt clnt.o mglin1.0.a

clnt.o : clnt.cxx

g++ -c clnt.cxx

srvr : srvr.o mglin1.0.a

g++ -o srvr srvr.o mglin1.0.a

srvr.o : srvr.cxx

g++ -c srvr.cxx