#ifndef gkfSocket_INCLUDED #define gkfSocket_INCLUDED /* * Created: George Kelly Flanagin, sometime before 2006 but after 2002. * me@georgeflanagin.com * * Note: this code was freely adapted from examples of socket code written in * C and C++ that appeared in various "how to" articles strewn around the * Internet. This socket interface was written for simplicity not * performance in the hope that when performance problems arise, one would * switch to either boost::asio or go into this code, and make appropriate * adjustments to this code. * * It is intended as a /teaching example/. */ /*** * Examples: * To use this code (which is entirely inlined in this single translation unit) you need only: #include "cppsocket.h" If you are creating a server side socket, you may take this approach: ServerSocket myNewSock; // a socket for the "next" connection. ServerSocket myListenSock(iPort); // create a server side socket listening on port iPort. do { myListenSock.accept(myNewSock); // this will block until a new connection is made. ... and continue reading and writing with myNewSock myNewSock >> stringRequest; ... do something important and I/O related ... myNewSock << stringReply; } while (TerminationConditionIsFalse); ***** For a client side socket, you may do this: ClientSocket mySock(hostName, port); do { mySock << stringRequest; mySock >> stringReply; } while (ThereIsStillMoreToDo); */ /*** * Caveats: * [1] You will need a try-block to catch SocketError objects. [2] On the server side, you will need to spawn threads or processes (whichever is appropriate for your use) to avoid blocking other requests that arrive on the listen port. Of course, if you are trying to write simple test code, this complication is probably not necessary. [3] There are additional constructor parameters; the examples only cover the most basic operations. [4] Keep in mind that any network communication is unbelievably rich in errors, and there are many waits and non-fatal errors that you will need to deal with. I am unsure if it is fortunate or unfortunate, but you could go for weeks without seeing one of the errors, and then (** KABOOM! **) and you will have no idea why your code broke if you are not catching the errors and checking the return values of the functions. */ // Set this to the value you in /proc/sys/net/core/somaxconn #define MAXCONNECTIONS (5) // Larger values will read larger chunks of data. #define MAXRECV (8192) // C level network includes #include #include #include #include #include #include // posix #include // memset. #include // errno, just like it says. #include // symbolic names for socket flags. // C++ creatures #include #include #include #include #include using namespace std; class SocketError { public: SocketError(const string & Message, const char * TranslationUnitName = 0, int LineNumber = 0, bool Fatal = true) { fileName = TranslationUnitName != 0 ? TranslationUnitName : string(); m_Message = Message; m_Fatal = Fatal; m_LineNumber = LineNumber; } virtual ~SocketError(void) { } virtual void tombstone(ostream & Out = cerr) { Out << endl << (m_Fatal ? "SocketError reports an unrecoverable error has occurred." : "System message from SocketError ") << endl; Out << m_Message << endl; if (fileName != string()) Out << "Problem detected near line " << m_LineNumber << " of file " << fileName << endl; } string m_Message; bool m_Fatal; string fileName; int m_LineNumber; }; using namespace std; class Socket { public: //--- // Constructor & Destructor. //--- Socket(void) { if (sysconf(_SC_2_VERSION) < 200801) throw SocketError("POSIX version must be at least 200801", __FILE__, __LINE__, true); memset (&m_addr, 0, sizeof(m_addr)); } virtual ~Socket(void) { if (is_valid()) ::close (m_sock); } //--- // Inline const accees the state functions. //--- int GetSocketFD(void) const { return m_sock; } int GetIPAddr(void) const { return m_addr.sin_addr.s_addr; } int GetLastError(void) const { return m_lastError; } bool is_valid(void) const { return m_sock != -1; } int GetRealPort(void) const { return m_port; } //--- // Server initialization //--- bool create(void) { m_sock = socket(AF_INET, SOCK_STREAM, 0); if (!is_valid()) return false; int32_t on = 1; return setsockopt (m_sock, SOL_SOCKET, SO_REUSEADDR, reinterpret_cast(&on), sizeof(on)) == -1 ? false : true; } bool bind (const int port) { if (!is_valid()) return false; m_addr.sin_family = AF_INET; m_addr.sin_addr.s_addr = INADDR_ANY; m_addr.sin_port = htons(port); return ::bind(m_sock, reinterpret_cast(&m_addr), sizeof(m_addr)) ? false : true; } bool listen(void) const { if (!is_valid()) return false; return ::listen(m_sock, MAXCONNECTIONS) ? false : true; } bool accept (Socket & new_socket) const { socklen_t addr_length = static_cast(sizeof(m_addr)); new_socket.m_sock = ::accept(m_sock, (sockaddr *)(&m_addr), &addr_length); OtherEnd = m_addr.sin_addr.s_addr; return new_socket.m_sock > 0; } //--- // Client initialization //--- bool connect (const string host, const int port) { if (!is_valid()) return false; m_host = host; m_port = port; m_addr.sin_family = AF_INET; m_addr.sin_port = htons (port); return lowLevelConnect(); } //--- // Data Transimission //--- bool send (const string & input) const { return ::send(m_sock, input.c_str(), input.size(), 0) != -1; } int recv (string & output) const { char buf [MAXRECV + 1]; memset(buf, 0, sizeof(buf)); output = string(); ssize_t status(0); // changed from int to ssize_t // Most likely, we will read a packet, or if the message // is very short, we will receive the entire message in // a short packet. But it might be a long one. while (status = ::recv(m_sock, buf, MAXRECV, MSG_DONTWAIT), status > 0) { cerr << getpid() << ": reading: " << buf << endl; output.append(buf, static_cast(status)); memset(buf, 0, sizeof(buf)); } // Now, let's investigate how we got here. if (status == 0 || errno == EWOULDBLOCK || errno == EAGAIN) ; // common conditions.. nothing to read, get outta d' way, etc. else // unusual reasons ... { cerr << getpid() << ": status == " << status << " errno == " << errno << " in Socket::recv()" << endl; perror("text: "); } // return the status code, or the number of bytes read. return status < 1 ? status : output.size(); } //--- // Socket manipulators. //--- bool reset (bool CloseAndReopen = true) { return (lowLevelClose() && CloseAndReopen) ? lowLevelConnect() : true; } void set_non_blocking (const bool bNonBlock) { int opts = fcntl (m_sock, F_GETFL); if (opts < 0) return; opts = bNonBlock ? (opts|O_NONBLOCK) : (opts&~O_NONBLOCK); fcntl(m_sock, F_SETFL, opts); } //--- // Pure convenience .... Inquiring minds sometimes want to know. // See the ServerSocket declaration to see how this is used. Note // that it is not generally meaningful in the ClientSocket context: // If you are a client and you don't know to whom you are talking, // it is a rather unusual situation. //--- mutable unsigned int OtherEnd; protected: //--- // Functions that work the bare metal. //--- bool lowLevelConnect(void) { //--- // Unlike most POSIX functions, inet_pton does not return 0 on success. //--- int status = inet_pton (AF_INET, m_host.c_str(), &m_addr.sin_addr); switch (status) { case -1: m_lastError = errno; return false; break; case 0: return false; default: break; } //--- // OTOH, ::connect behaves `normally.' //--- status = ::connect(m_sock, reinterpret_cast(&m_addr), sizeof(m_addr)); if (status) m_lastError = errno; return !!status; } bool lowLevelClose(void) { if (!is_valid()) return true; int iResult = ::close(m_sock); if (iResult) m_lastError = errno; return !!iResult; } string m_host; int m_port; int m_sock; sockaddr_in m_addr; int m_lastError; }; class ServerSocket : public Socket { public: //--- // Two ctors are offered; one where you know and one where you do // not yet know the port. //--- ServerSocket (int port, int bufsize=4096, int exactly = 1) { bool bound(false); if (!Socket::create()) throw SocketError ("Could not create server socket.", __FILE__, __LINE__, errno); //--- // Added the "exactly" parameter to allow for the fact that a server // side port might already be in listen mode and owned by another // process. //--- if (exactly) { if (!Socket::bind(port)) throw SocketError ("Could not bind to port.", __FILE__, port, errno); } else { do if (bound = Socket::bind(port), !bound) port += exactly; while (!bound); } if (!Socket::listen()) throw SocketError ("Could not listen to socket.", __FILE__, port, errno); BufSize = bufsize < MAXRECV ? bufsize : MAXRECV; } ServerSocket (void) { } virtual ~ServerSocket(void) { } //--- // These stream-ops are members because the LHS is /this/ type. //--- const ServerSocket& operator << (const string & s) const { unsigned int uResult = 0; if (!Socket::send(s)) { perror("errno is "); throw SocketError ( "Could not write to socket.", __FILE__, __LINE__, errno ); } uResult = ::close(GetSocketFD()); if (uResult) throw SocketError ("Socket close failed.", __FILE__, __LINE__, errno); return *this; } const ServerSocket& operator >> (string & s) const { if (!Socket::recv(s)) throw SocketError ("Could not read from socket.", __FILE__, __LINE__, false); // if (s.length() > static_cast(BufSize)) s = s.substr(0,BufSize-1); return *this; } //--- // Hides the system call to ::accept(). //--- void accept (ServerSocket & sock) { if (!Socket::accept(sock)) throw SocketError ( "Could not accept socket.", __FILE__, __LINE__, errno ); } unsigned int FromWhom(void) { return htonl(OtherEnd); } private: int BufSize; }; class ClientSocket : public Socket { public: //--- // We might want to have a ClientSocket and attach the I/O later, so // a default ctor is provided. //--- ClientSocket (string host, int port) { if (!Socket::create()) throw SocketError ( "Could not create client socket.", __FILE__, __LINE__, errno ); if (!Socket::connect(host, port)) throw SocketError ( "Could not bind to port." ); } ClientSocket (void) { if (!Socket::create()) throw SocketError ( "Could not create client socket.", __FILE__, __LINE__, errno ); } ~ClientSocket() { } //--- // Once again, we have our stream-like I/O. //--- const ClientSocket& operator << ( const string & s) const { if (!Socket::send(s)) throw SocketError ( "Could not write to socket.", __FILE__, __LINE__, errno ); return *this; } const ClientSocket& operator >> ( string & s) const { if (!Socket::recv(s)) throw SocketError ( "Could not read from socket.", __FILE__, __LINE__, errno ); return *this; } //--- // This allows a ClientSocket to be connected to something. //--- bool AttachIO(string host, int port) { return connect(host,port); } bool Reset(void) { return reset(true); } }; #endif