summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorFlorent Le Coz <louiz@louiz.org>2013-11-02 03:19:24 +0100
committerFlorent Le Coz <louiz@louiz.org>2013-11-02 03:19:24 +0100
commit7869ef2ace9a487abb0b489ca432b0a8878c5083 (patch)
treefbb112c8f49266b1bee8303b169b512871d1bd84
downloadbiboumi-7869ef2ace9a487abb0b489ca432b0a8878c5083.tar.gz
biboumi-7869ef2ace9a487abb0b489ca432b0a8878c5083.tar.bz2
biboumi-7869ef2ace9a487abb0b489ca432b0a8878c5083.tar.xz
biboumi-7869ef2ace9a487abb0b489ca432b0a8878c5083.zip
First step of the connection skeleton
Basic connect, socket creating, polling, recving, etc.
-rw-r--r--src/libirc/irc_client.cpp102
-rw-r--r--src/libirc/irc_client.hpp67
-rw-r--r--src/network/poller.cpp96
-rw-r--r--src/network/poller.hpp72
-rw-r--r--src/network/socket_handler.hpp62
-rw-r--r--src/utils/scopeguard.hpp89
6 files changed, 488 insertions, 0 deletions
diff --git a/src/libirc/irc_client.cpp b/src/libirc/irc_client.cpp
new file mode 100644
index 0000000..a29e588
--- /dev/null
+++ b/src/libirc/irc_client.cpp
@@ -0,0 +1,102 @@
+#include <libirc/irc_client.hpp>
+#include <network/poller.hpp>
+#include <utils/scopeguard.hpp>
+
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <cstring>
+#include <netdb.h>
+#include <unistd.h>
+
+#include <iostream>
+#include <stdexcept>
+
+IrcClient::IrcClient()
+{
+ std::cout << "IrcClient()" << std::endl;
+ if ((this->socket = ::socket(AF_INET, SOCK_STREAM, 0)) == -1)
+ throw std::runtime_error("Could not create socket");
+}
+
+IrcClient::~IrcClient()
+{
+ std::cout << "~IrcClient()" << std::endl;
+}
+
+void IrcClient::on_recv()
+{
+ char buf[4096];
+
+ ssize_t size = ::recv(this->socket, buf, 4096, 0);
+ if (0 == size)
+ this->on_connection_close();
+ else if (-1 == static_cast<ssize_t>(size))
+ throw std::runtime_error("Error reading from socket");
+ else
+ {
+ this->in_buf += std::string(buf, size);
+ this->parse_in_buffer();
+ }
+}
+
+void IrcClient::on_send()
+{
+}
+
+socket_t IrcClient::get_socket() const
+{
+ return this->socket;
+}
+
+void IrcClient::connect(const std::string& address, const std::string& port)
+{
+ std::cout << "Trying to connect to " << address << ":" << port << std::endl;
+ struct addrinfo hints;
+ memset(&hints, 0, sizeof(struct addrinfo));
+ hints.ai_flags = 0;
+ hints.ai_family = AF_INET;
+ hints.ai_socktype = SOCK_STREAM;
+ hints.ai_protocol = 0;
+
+ struct addrinfo* addr_res;
+ const int res = ::getaddrinfo(address.c_str(), port.c_str(), &hints, &addr_res);
+ // Make sure the alloced structure is always freed at the end of the
+ // function
+ utils::ScopeGuard sg([&addr_res](){ freeaddrinfo(addr_res); });
+
+ if (res != 0)
+ {
+ perror("getaddrinfo");
+ throw std::runtime_error("getaddrinfo failed");
+ }
+ for (struct addrinfo* rp = addr_res; rp; rp = rp->ai_next)
+ {
+ std::cout << "One result" << std::endl;
+ if (::connect(this->socket, rp->ai_addr, rp->ai_addrlen) == 0)
+ {
+ std::cout << "Connection success." << std::endl;
+ return ;
+ }
+ std::cout << "Connection failed:" << std::endl;
+ perror("connect");
+ }
+ std::cout << "All connection attempts failed." << std::endl;
+ this->close();
+}
+
+void IrcClient::on_connection_close()
+{
+ std::cout << "Connection closed by remote server." << std::endl;
+ this->close();
+}
+
+void IrcClient::close()
+{
+ this->poller->remove_socket_handler(this->get_socket());
+ ::close(this->socket);
+}
+
+void IrcClient::parse_in_buffer()
+{
+ std::cout << "Parsing: [" << this->in_buf << "]" << std::endl;
+}
diff --git a/src/libirc/irc_client.hpp b/src/libirc/irc_client.hpp
new file mode 100644
index 0000000..73e7efd
--- /dev/null
+++ b/src/libirc/irc_client.hpp
@@ -0,0 +1,67 @@
+#ifndef IRC_CLIENT_INCLUDED
+# define IRC_CLIENT_INCLUDED
+
+#include <network/socket_handler.hpp>
+
+#include <string>
+
+/**
+ * Represent one IRC client, i.e. an endpoint connected to a single IRC
+ * server, through a TCP socket, receiving and sending commands to it.
+ *
+ * TODO: TLS support, maybe, but that's not high priority
+ */
+class IrcClient: public SocketHandler
+{
+public:
+ explicit IrcClient();
+ ~IrcClient();
+ /**
+ * We read the data, try to parse it and generate some event if
+ * one or more full message is available.
+ */
+ void on_recv();
+ /**
+ * Just write as much data as possible on the socket.
+ */
+ void on_send();
+ socket_t get_socket() const;
+ /**
+ * Connect to the remote server
+ */
+ void connect(const std::string& address, const std::string& port);
+ /**
+ * Close the connection, remove us from the poller
+ */
+ void close();
+ /**
+ * Called when we detect an orderly close by the remote endpoint.
+ */
+ void on_connection_close();
+ /**
+ * Parse the data we have received so far and try to get one or more
+ * complete messages from it.
+ */
+ void parse_in_buffer();
+
+private:
+ socket_t socket;
+ /**
+ * Where data read from the socket is added, until we can parse a whole
+ * IRC message, the used data is then removed from that buffer.
+ *
+ * TODO: something more efficient than a string.
+ */
+ std::string in_buf;
+ /**
+ * Where data is added, when we want to send something to the client.
+ */
+ std::string out_buf;
+
+ IrcClient(const IrcClient&) = delete;
+ IrcClient(IrcClient&&) = delete;
+ IrcClient& operator=(const IrcClient&) = delete;
+ IrcClient& operator=(IrcClient&&) = delete;
+};
+
+#endif // IRC_CLIENT_INCLUDED
diff --git a/src/network/poller.cpp b/src/network/poller.cpp
new file mode 100644
index 0000000..c7d9eb2
--- /dev/null
+++ b/src/network/poller.cpp
@@ -0,0 +1,96 @@
+#include <network/poller.hpp>
+
+#include <assert.h>
+#include <cstring>
+#include <iostream>
+
+
+Poller::Poller()
+{
+ std::cout << "Poller()" << std::endl;
+#if POLLER == POLL
+ memset(this->fds, 0, sizeof(this->fds));
+ this->nfds = 0;
+#endif
+}
+
+Poller::~Poller()
+{
+ std::cout << "~Poller()" << std::endl;
+}
+
+void Poller::add_socket_handler(std::shared_ptr<SocketHandler> socket_handler)
+{
+ // Raise an error if that socket is already in the list
+ const auto it = this->socket_handlers.find(socket_handler->get_socket());
+ if (it != this->socket_handlers.end())
+ throw std::runtime_error("Trying to insert SocketHandler already managed");
+
+ this->socket_handlers.emplace(socket_handler->get_socket(), socket_handler);
+ socket_handler->set_poller(this);
+
+ // We always watch all sockets for receive events
+#if POLLER == POLL
+ this->fds[this->nfds].fd = socket_handler->get_socket();
+ this->fds[this->nfds].events = POLLIN;
+ this->nfds++;
+#endif
+}
+
+void Poller::remove_socket_handler(const socket_t socket)
+{
+ const auto it = this->socket_handlers.find(socket);
+ if (it == this->socket_handlers.end())
+ throw std::runtime_error("Trying to remove a SocketHandler that is not managed");
+ this->socket_handlers.erase(it);
+ for (size_t i = 0; i < this->nfds; i++)
+ {
+ if (this->fds[i].fd == socket)
+ {
+ // Move all subsequent pollfd by one on the left, erasing the
+ // value of the one we remove
+ for (size_t j = i; j < this->nfds - 1; ++j)
+ {
+ this->fds[j].fd = this->fds[j+1].fd;
+ this->fds[j].events= this->fds[j+1].events;
+ }
+ this->nfds--;
+ }
+ }
+}
+
+void Poller::poll()
+{
+#if POLLER == POLL
+ std::cout << "Polling:" << std::endl;
+ for (size_t i = 0; i < this->nfds; ++i)
+ std::cout << "pollfd[" << i << "]: (" << this->fds[i].fd << ")" << std::endl;
+ int res = ::poll(this->fds, this->nfds, -1);
+ if (res < 0)
+ {
+ perror("poll");
+ throw std::runtime_error("Poll failed");
+ }
+ // We cannot possibly have more ready events than the number of fds we are
+ // watching
+ assert(static_cast<unsigned int>(res) <= this->nfds);
+ for (size_t i = 0; i <= this->nfds && res != 0; ++i)
+ {
+ if (this->fds[i].revents == 0)
+ continue;
+ else if (this->fds[i].revents & POLLIN)
+ {
+ auto socket_handler = this->socket_handlers.at(this->fds[i].fd);
+ socket_handler->on_recv();
+ res--;
+ }
+ else if (this->fds[i].revents & POLLOUT)
+ {
+ auto socket_handler = this->socket_handlers.at(this->fds[i].fd);
+ socket_handler->on_send();
+ res--;
+ }
+ }
+#endif
+}
+
diff --git a/src/network/poller.hpp b/src/network/poller.hpp
new file mode 100644
index 0000000..46a184e
--- /dev/null
+++ b/src/network/poller.hpp
@@ -0,0 +1,72 @@
+#ifndef POLLER_INCLUDED
+# define POLLER_INCLUDED
+
+#include <network/socket_handler.hpp>
+
+#include <unordered_map>
+#include <memory>
+
+#define POLL 1
+#define EPOLL 2
+#define KQUEUE 3
+
+#define POLLER POLL
+
+#if POLLER == POLL
+ #include <poll.h>
+ // TODO, dynamic size, without artificial limit
+ #define MAX_POLL_FD_NUMBER 4096
+#endif
+
+/**
+ * We pass some SocketHandlers to this the Poller, which uses
+ * poll/epoll/kqueue/select etc to wait for events on these SocketHandlers,
+ * and call the callbacks when event occurs.
+ *
+ * TODO: support for all these pollers:
+ * - poll(2) (mandatory)
+ * - epoll(7)
+ * - kqueue(2)
+ */
+
+
+class Poller
+{
+public:
+ explicit Poller();
+ ~Poller();
+ /**
+ * Add a SocketHandler to be monitored by this Poller. All receive events
+ * are always automatically watched.
+ */
+ void add_socket_handler(std::shared_ptr<SocketHandler> socket_handler);
+ /**
+ * Remove (and stop managing) a SocketHandler, designed by the given socket_t.
+ */
+ void remove_socket_handler(const socket_t socket);
+ /**
+ * Wait for all watched events, and call the SocketHandlers' callbacks
+ * when one is ready.
+ */
+ void poll();
+
+private:
+ /**
+ * A "list" of all the SocketHandlers that we manage, indexed by socket,
+ * because that's what is returned by select/poll/etc when an event
+ * occures.
+ */
+ std::unordered_map<socket_t, std::shared_ptr<SocketHandler>> socket_handlers;
+
+#if POLLER == POLL
+ struct pollfd fds[MAX_POLL_FD_NUMBER];
+ nfds_t nfds;
+#endif
+
+ Poller(const Poller&) = delete;
+ Poller(Poller&&) = delete;
+ Poller& operator=(const Poller&) = delete;
+ Poller& operator=(Poller&&) = delete;
+};
+
+#endif // POLLER_INCLUDED
diff --git a/src/network/socket_handler.hpp b/src/network/socket_handler.hpp
new file mode 100644
index 0000000..165f732
--- /dev/null
+++ b/src/network/socket_handler.hpp
@@ -0,0 +1,62 @@
+#ifndef SOCKET_HANDLER_INCLUDED
+# define SOCKET_HANDLER_INCLUDED
+
+typedef int socket_t;
+
+class Poller;
+
+/**
+ * An interface, with a series of callbacks that should be implemented in
+ * subclasses that deal with a socket. These callbacks are called on various events
+ * (read/write/timeout, etc) when they are notified to a poller
+ * (select/poll/epoll etc)
+ */
+class SocketHandler
+{
+public:
+ explicit SocketHandler():
+ poller(nullptr)
+ {}
+ /**
+ * Set the pointer to the given Poller, to communicate with it.
+ */
+ void set_poller(Poller* poller)
+ {
+ this->poller = poller;
+ };
+ /**
+ * Happens when the socket is ready to be received from.
+ */
+ virtual void on_recv() = 0;
+ /**
+ * Happens when the socket is ready to be written to.
+ */
+ virtual void on_send() = 0;
+ /**
+ * Returns the socket that should be handled by the poller.
+ */
+ virtual socket_t get_socket() const = 0;
+ /**
+ * Close the connection.
+ */
+ virtual void close() = 0;
+
+protected:
+ /**
+ * A pointer to the poller that manages us, because we need to communicate
+ * with it, sometimes (for example to tell it that he now needs to watch
+ * write events for us). Do not ever try to delete it.
+ *
+ * And a raw pointer because we are not owning it, it is owning us
+ * (actually it is sharing our ownership with a Bridge).
+ */
+ Poller* poller;
+
+private:
+ SocketHandler(const SocketHandler&) = delete;
+ SocketHandler(SocketHandler&&) = delete;
+ SocketHandler& operator=(const SocketHandler&) = delete;
+ SocketHandler& operator=(SocketHandler&&) = delete;
+};
+
+#endif // SOCKET_HANDLER_INCLUDED
diff --git a/src/utils/scopeguard.hpp b/src/utils/scopeguard.hpp
new file mode 100644
index 0000000..df78831
--- /dev/null
+++ b/src/utils/scopeguard.hpp
@@ -0,0 +1,89 @@
+#ifndef SCOPEGUARD_HPP
+#define SCOPEGUARD_HPP
+
+#include <functional>
+#include <vector>
+
+/**
+ * A class to be used to make sure some functions are called when the scope
+ * is left, because they will be called in the ScopeGuard's destructor. It
+ * can for example be used to delete some pointer whenever any exception is
+ * called. Example:
+
+ * {
+ * ScopeGuard scope;
+ * int* number = new int(2);
+ * scope.add_callback([number]() { delete number; });
+ * // Do some other stuff with the number. But these stuff might throw an exception:
+ * throw std::runtime_error("Some error not caught here, but in our caller");
+ * return true;
+ * }
+
+ * In this example, our pointer will always be deleted, even when the
+ * exception is thrown. If we want the functions to be called only when the
+ * scope is left because of an unexpected exception, we can use
+ * ScopeGuard::disable();
+ */
+
+namespace utils
+{
+
+class ScopeGuard
+{
+public:
+ /**
+ * The constructor can take a callback. But additional callbacks can be
+ * added later with add_callback()
+ */
+ explicit ScopeGuard(std::function<void()>&& func):
+ enabled(true)
+ {
+ this->add_callback(std::move(func));
+ }
+ /**
+ * default constructor, the scope guard is enabled but empty, use
+ * add_callback()
+ */
+ explicit ScopeGuard():
+ enabled(true)
+ {
+ }
+ /**
+ * Call all callbacks in the desctructor, unless it has been disabled.
+ */
+ ~ScopeGuard()
+ {
+ if (this->enabled)
+ for (auto& func: this->callbacks)
+ func();
+ }
+ /**
+ * Add a callback to be called in our destructor, one scope guard can be
+ * used for more than one task, if needed.
+ */
+ void add_callback(std::function<void()>&& func)
+ {
+ this->callbacks.emplace_back(std::move(func));
+ }
+ /**
+ * Disable that scope guard, nothing will be done when the scope is
+ * exited.
+ */
+ void disable()
+ {
+ this->enabled = false;
+ }
+
+private:
+ bool enabled;
+ std::vector<std::function<void()>> callbacks;
+
+ ScopeGuard(const ScopeGuard&) = delete;
+ ScopeGuard& operator=(ScopeGuard&&) = delete;
+ ScopeGuard(ScopeGuard&&) = delete;
+ ScopeGuard& operator=(const ScopeGuard&) = delete;
+};
+
+}
+
+#endif