]> git.localhorst.tv Git - blank.git/blobdiff - src/net/tcp.cpp
add TCP based CLI
[blank.git] / src / net / tcp.cpp
diff --git a/src/net/tcp.cpp b/src/net/tcp.cpp
new file mode 100644 (file)
index 0000000..f9e039b
--- /dev/null
@@ -0,0 +1,201 @@
+#include "tcp.hpp"
+
+#include "../app/init.hpp"
+
+#include <stdexcept>
+
+using namespace std;
+
+
+namespace blank {
+namespace tcp {
+
+Socket::Socket()
+: sock(nullptr) {
+
+}
+
+Socket::Socket(unsigned short port)
+: sock(nullptr) {
+       IPaddress ip;
+       if (SDLNet_ResolveHost(&ip, nullptr, port) == -1) {
+               throw NetError("failed to resolve local host");
+       }
+       sock = SDLNet_TCP_Open(&ip);
+       if (!sock) {
+               throw NetError("failed to open local socket");
+       }
+}
+
+Socket::Socket(TCPsocket sock)
+: sock(sock) {
+
+}
+
+Socket::~Socket() noexcept {
+       if (sock) {
+               SDLNet_TCP_Close(sock);
+       }
+}
+
+Socket::Socket(Socket &&other) noexcept
+: sock(other.sock) {
+       other.sock = nullptr;
+}
+
+Socket &Socket::operator =(Socket &&other) noexcept {
+       swap(sock, other.sock);
+       return *this;
+}
+
+
+Socket Socket::Accept() noexcept {
+       return Socket(SDLNet_TCP_Accept(sock));
+}
+
+bool Socket::Ready() const noexcept {
+       return SDLNet_SocketReady(sock);
+}
+
+size_t Socket::Recv(void *buf, std::size_t max_len) {
+       const int len = SDLNet_TCP_Recv(sock, buf, max_len);
+       if (len < 0) {
+               throw NetError("TCP socket recv");
+       }
+       return len;
+}
+
+size_t Socket::Send(const void *buf, size_t max_len) {
+       /// TODO: make TCP send non-blocking
+       const int len = SDLNet_TCP_Send(sock, buf, max_len);
+       if (len < int(max_len)) {
+               throw NetError("TCP socket send");
+       }
+       return len;
+}
+
+
+int Socket::AddTo(SDLNet_SocketSet set) {
+       return SDLNet_TCP_AddSocket(set, sock);
+}
+
+int Socket::RemoveFrom(SDLNet_SocketSet set) {
+       return SDLNet_TCP_DelSocket(set, sock);
+}
+
+
+Pool::Pool(int max_conn, size_t buf_siz)
+: set(SDLNet_AllocSocketSet(max_conn))
+, buffer(buf_siz, '\0')
+, connections()
+, use_conn(0)
+, max_conn(max_conn)
+, buf_siz(buf_siz) {
+       if (!set) {
+               throw runtime_error("failed to allocate socket set");
+       }
+}
+
+Pool::~Pool() noexcept {
+       SDLNet_FreeSocketSet(set);
+}
+
+
+void Pool::AddConnection(Socket sock, IOHandler *handler) {
+       if (FreeSlots() == 0) {
+               Resize(TotalSlots() * 2);
+       }
+       int num = sock.AddTo(set);
+       if (num < 0) {
+               throw NetError("failed to add socket to set");
+       }
+       use_conn = num;
+       connections.emplace_back(move(sock), handler);
+       handler->OnCreate(connections.back().first);
+}
+
+void Pool::Send() {
+       for (auto i = connections.begin(); i != connections.end(); ++i) {
+               if (i->second->Closed()) {
+                       continue;
+               }
+
+               try {
+                       i->second->OnSend(i->first);
+               } catch (...) {
+                       i->second->OnError(i->first);
+               }
+       }
+}
+
+bool Pool::Check(unsigned long timeout) {
+       // SDL_net considers checking an empty set an error, so
+       // we're checking that ourselves
+       if (OccupiedSlots() == 0) {
+               return false;
+       }
+
+       int num = SDLNet_CheckSockets(set, timeout);
+       if (num < 0) {
+               throw NetError("error checking sockets");
+       }
+       return num > 0;
+}
+
+void Pool::Receive() {
+       for (auto i = connections.begin(); i != connections.end(); ++i) {
+               if (!i->first.Ready() || i->second->Closed()) {
+                       continue;
+               }
+
+               try {
+                       i->second->OnRecv(i->first);
+               } catch (...) {
+                       i->second->OnError(i->first);
+               }
+       }
+}
+
+void Pool::Clean() {
+       for (auto i = connections.begin(); i != connections.end();) {
+               if (i->second->Closed()) {
+                       int num = i->first.RemoveFrom(set);
+                       if (num < 0) {
+                               throw NetError("failed to remove socket from set");
+                       }
+                       use_conn = num;
+                       i->second->OnRemove(i->first);
+                       i = connections.erase(i);
+               } else {
+                       ++i;
+               }
+       }
+}
+
+
+void Pool::Resize(int new_max) {
+       if (new_max < max_conn) {
+               return;
+       }
+
+       int new_size = max(new_max, max_conn * 2);
+       SDLNet_SocketSet new_set(SDLNet_AllocSocketSet(new_size));
+       if (!new_set) {
+               throw NetError("failed to allocate socket set");
+       }
+
+       for (auto &conn : connections) {
+               if (conn.first.AddTo(new_set) == -1) {
+                       NetError error("failed to migrate socket to new set");
+                       SDLNet_FreeSocketSet(new_set);
+                       throw error;
+               }
+       }
+
+       SDLNet_FreeSocketSet(set);
+       set = new_set;
+       max_conn = new_size;
+}
+
+}
+}