From 8b76321d3edc33980791711f88c8d23c1eb4da26 Mon Sep 17 00:00:00 2001 From: David Brodsky Date: Thu, 29 Jun 2006 23:06:31 +0200 Subject: [PATCH] Add Tairon::Net::Socket class. This class provides non-blocking socket facility. It provides methods for reading data from the socket and returning them as a Tairon::Core::String as well as reading directly into supplied buffer for minimizing data copying. --- include/tairon/net/socket.h | 1 + src/net-core/socket.cpp | 241 ++++++++++++++++++++++++++++++++++++++++++++ src/net-core/socket.h | 204 +++++++++++++++++++++++++++++++++++++ 3 files changed, 446 insertions(+) create mode 120000 include/tairon/net/socket.h create mode 100644 src/net-core/socket.cpp create mode 100644 src/net-core/socket.h diff --git a/include/tairon/net/socket.h b/include/tairon/net/socket.h new file mode 120000 index 0000000..5eb362b --- /dev/null +++ b/include/tairon/net/socket.h @@ -0,0 +1 @@ +../../../src/net-core/socket.h \ No newline at end of file diff --git a/src/net-core/socket.cpp b/src/net-core/socket.cpp new file mode 100644 index 0000000..60a9789 --- /dev/null +++ b/src/net-core/socket.cpp @@ -0,0 +1,241 @@ +/*************************************************************************** + * * + * Copyright (C) 2006 David Brodsky * + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Library General Public * + * License as published by the Free Software Foundation and appearing * + * in the file LICENSE.LGPL included in the packaging of this file. * + * * + * This library is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Library General Public License for more details. * + * * + ***************************************************************************/ + +#include +#include +#include + +#include + +#include "socket.h" + +#include "poll.h" + +// length of the internal buffer +static const size_t bufLength = 65536; + +namespace Tairon +{ + +namespace Net +{ + +/* {{{ Socket::Socket(Type) */ +Socket::Socket(Type type) +{ + fd = socket(PF_INET, type, 0); + if (fd == -1) + throw SocketException("Cannot create socket", errno); + + init(); +} +/* }}} */ + +/* {{{ Socket::Socket(int sd) */ +Socket::Socket(int sd) : fd(sd) +{ +} +/* }}} */ + +/* {{{ Socket::~Socket() */ +Socket::~Socket() +{ + Poll::self()->delSocketErrorHandler(fd); + if (readyReadFunctor) { + Poll::self()->delSocketToRead(fd); + delete readyReadFunctor; + } + + // the output buffer is not empty or connectedFunctor != 0 => we are + // waiting for the socket to be ready for write => it is necessary to + // remove if from polling + if (outBuffer.length() || connectedFunctor) + Poll::self()->delSocketToWrite(fd); + + close(fd); + delete [] inBuffer; + if (connectedFunctor) + delete connectedFunctor; + delete errorFunctor; + delete readyWriteFunctor; +} +/* }}} */ + +/* {{{ Socket::connected() */ +void Socket::connected() +{ + DEBUG("Socket::connected"); + + Poll::self()->delSocketToWrite(fd); + delete connectedFunctor; + connectedFunctor = 0; + + int err; + socklen_t len = sizeof(err); + getsockopt(fd, SOL_SOCKET, SO_ERROR, &err, &len); + if (err == 0) { + readyReadFunctor = Tairon::Core::methodFunctor(this, &Socket::readyRead); + Poll::self()->addSocketToRead(fd, readyReadFunctor); + connectedSignal.emit(this); + } else { + errorSignal.emit(this, err); + delete this; + } +} +/* }}} */ + +/* {{{ Socket::connect(const String &, uint16_t) */ +void Socket::connect(const String &address, uint16_t port) +{ + if (fcntl(fd, F_SETFL, fcntl(fd, F_GETFL, 0) | O_NONBLOCK) == -1) + throw SocketException("Cannot set nonblocking socket", errno); + + struct hostent *host = gethostbyname(address); + if (!host) + throw SocketException("Cannot resolve host name", h_errno); + + if (host->h_addrtype != AF_INET) + throw SocketException("Invalid address type", host->h_addrtype); + + if (!(*host->h_addr_list)) + throw SocketException("Zero length list of addresses", 0); + + sockaddr_in addr; + addr.sin_family = AF_INET; + addr.sin_port = htons(port); + memcpy(&(addr.sin_addr.s_addr), *(host->h_addr_list), sizeof(addr.sin_addr.s_addr)); + + int ret = ::connect(fd, (sockaddr *) &addr, sizeof(addr)); + if ((ret == -1) && (errno != EINPROGRESS)) + throw SocketException("Error while connecting", errno); + + connectedFunctor = Tairon::Core::methodFunctor(this, &Socket::connected); + Poll::self()->addSocketToWrite(fd, connectedFunctor); +} +/* }}} */ + +/* {{{ Socket::error() */ +void Socket::error() +{ + int err; + socklen_t len = sizeof(err); + getsockopt(fd, SOL_SOCKET, SO_ERROR, &err, &len); + errorSignal.emit(this, err); + delete this; +} +/* }}} */ + +/* {{{ Socket::init() */ +void Socket::init() +{ + inBuffer = new char[bufLength]; + connectedFunctor = 0; + errorFunctor = Tairon::Core::methodFunctor(this, &Socket::error); + readyReadFunctor = 0; + readyWriteFunctor = Tairon::Core::methodFunctor(this, &Socket::readyWrite); + + Poll::self()->addSocketErrorHandler(fd, errorFunctor); +} +/* }}} */ + +/* {{{ Socket::read() */ +String Socket::read() +{ + ssize_t len = recv(fd, inBuffer, bufLength, MSG_DONTWAIT); + if (len == 0) { // connection has been closed + delete this; + throw SocketException("Connection has been closed", 0); + } else if (len < 0) + if (errno != EAGAIN) { + error(); + return String(); + } else + len = 0; + return String(inBuffer, len); +} +/* }}} */ + +/* {{{ Socket::readyRead() */ +void Socket::readyRead() +{ + DEBUG("Socket::readyRead"); + + readyReadSignal.emit(this); +} +/* }}} */ + +/* {{{ Socket::readTo(void *, size_t) */ +size_t Socket::readTo(void *buf, size_t length) +{ + size_t len = recv(fd, buf, length, MSG_DONTWAIT); + if (len == 0) { // connection has been closed + delete this; + throw SocketException("Connection has been closed", 0); + } else if (len < 0) + if (errno != EAGAIN) { + error(); + return 0; + } else + len = 0; + return len; +} +/* }}} */ + +/* {{{ Socket::readyWrite() */ +void Socket::readyWrite() +{ + ssize_t length = send(fd, outBuffer.data(), outBuffer.length(), 0); + if (length == -1) { + error(); + return; + } + outBuffer.erase(0, length); + if (!outBuffer.length()) + Poll::self()->delSocketToWrite(fd); +} +/* }}} */ + +/* {{{ Socket::write(const String &) */ +void Socket::write(const String &data) +{ + outBuffer.append(data); + ssize_t length = send(fd, outBuffer.data(), outBuffer.length(), MSG_DONTWAIT); + if (length == -1) + if (errno == EAGAIN) + Poll::self()->addSocketToWrite(fd, readyWriteFunctor); + else { + error(); + return; + } + outBuffer.erase(0, length); + if (outBuffer.length()) + Poll::self()->addSocketToWrite(fd, readyWriteFunctor); +} +/* }}} */ + + +/* {{{ SocketException::SocketException(const String &, int) */ +SocketException::SocketException(const String &desc, int err) : Tairon::Core::Exception(desc), errorNumber(err) +{ + DEBUG((const char *) String(desc + " (" + String::number(errorNumber) + ")")); +} +/* }}} */ + +}; + +}; + +// vim: ai sw=4 ts=4 noet fdm=marker diff --git a/src/net-core/socket.h b/src/net-core/socket.h new file mode 100644 index 0000000..b766266 --- /dev/null +++ b/src/net-core/socket.h @@ -0,0 +1,204 @@ +/*************************************************************************** + * * + * Copyright (C) 2006 David Brodsky * + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Library General Public * + * License as published by the Free Software Foundation and appearing * + * in the file LICENSE.LGPL included in the packaging of this file. * + * * + * This library is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Library General Public License for more details. * + * * + ***************************************************************************/ + +#ifndef _tairon_net_core_socket_h +#define _tairon_net_core_socket_h + +#include + +#include +#include + +using Tairon::Core::String; + +namespace Tairon +{ + +namespace Net +{ + +/** \brief This class provides non-blocking network connection. + * + * Socket type can be either a stream (TCP) or a datagram (UDP). When new data + * is incoming readyRead signal is emitted and the data can be read with + * readAll() method which reads all the data from socket. In some cases better + * performance can be achieved with readTo() method which reads not more than + * given amount of data into specified location in memory (this can be a mmaped + * file, so no data copying occurs at all). + * + * If any error occurs the socket deletes itself. The socket is closed by + * deleting the object (so if you want the socket to close just delete it). + */ +class Socket +{ + public: + /** Type of the socket. + */ + enum Type { + /** Stream data (TCP connection). + */ + Stream = SOCK_STREAM, + + /** Datagrams (UDP packets). + */ + Datagram = SOCK_DGRAM + }; + + /** Creates new socket of given type. + */ + Socket(Type type); + + /** Creates new socket object associated with given descriptor. + */ + Socket(int sd); + + /** Closes socket and frees all resources. + */ + ~Socket(); + + /** Connects this socket to a host. + * + * \param address Host to which this socket should be connected. + * \param port Target port of the connection in host byte order. + */ + void connect(const String &address, uint16_t port); + + /** Returns descriptor of this socket. + */ + int getDescriptor() { + return fd; + }; + + /** Reads data from the socket. This method never blocks. This + * method throws a SocketException when the socket has been closed. + */ + String read(); + + /** Reads data from the socket and stores them in the buffer. This + * method throws a SocketException when the socket has been closed. + * + * \param buf Target buffer for the data. + * \param length Length of the buffer. + */ + size_t readTo(void *buf, size_t length); + + /** Writes data to the socket as soon as it is possible. + */ + void write(const String &data); + + public: + /** This signal is emitted when the socket is connected to the host. + * The receiver gets as the first parameter pointer to this socket. + */ + Tairon::Core::Signal1 connectedSignal; + + /** This signal is emitted when an error occurs. The receiver gets as + * the first parameter pointer to this socket and the second one is + * error code. + */ + Tairon::Core::Signal2 errorSignal; + + /** This signal is emitted when there is incoming data. The receiver + * gets as the first parameter pointer to this socket. + */ + Tairon::Core::Signal1 readyReadSignal; + + private: + /** Initializes private data. + */ + inline void init(); + + /** Calls connected signal with this class as the first parameter. + */ + void connected(); + + /** Gets an error number end emits errorSignal. + */ + void error(); + + /** Invokes ready read signal with this class as the first parameter. + */ + void readyRead(); + + /** Writes any pending data to the socket. + */ + void readyWrite(); + + private: + /** Functor for calling connected method. + */ + Tairon::Core::Functor0 *connectedFunctor; + + /** Functor for calling error method. + */ + Tairon::Core::Functor0 *errorFunctor; + + /** Socket descriptor. + */ + int fd; + + /** Buffer for input data. + */ + char *inBuffer; + + /** Buffer for output data. + */ + String outBuffer; + + /** Functor for calling readyRead method. + */ + Tairon::Core::Functor0 *readyReadFunctor; + + /** Functor for calling readyWrite method. + */ + Tairon::Core::Functor0 *readyWriteFunctor; + + friend class Tairon::Core::MethodFunctor0; +}; + +/** \brief Exception for socket errors. + */ +class SocketException : public Tairon::Core::Exception +{ + public: + /** Constructor that takes as parameters description of the exception + * and its error number. + */ + SocketException(const String &desc, int err); + + /** Standard destructor. + */ + virtual ~SocketException() {}; + + /** Returns error number of the exception. + */ + int getErrorNumber() const { + return errorNumber; + }; + + private: + /** Error number of this exception. + */ + int errorNumber; +}; + +}; // namespace Net + +}; // namespace Tairon + +#endif + +// vim: ai sw=4 ts=4 noet fdm=marker -- 2.11.4.GIT