diff --git a/address.h b/address.h index 4b339ec..2481349 100644 --- a/address.h +++ b/address.h @@ -2,9 +2,18 @@ #define ADDRESS_H +#include + #include +enum : ErrorCode { + ERR_ADDRESS_COMMON = ERR_ADDRESS, + ERR_ADDRESS_INCORRECT, + ERR_ADDRESS_NOT_FOUND, +}; + + class Address { public: enum Type { @@ -15,16 +24,31 @@ public: Type type; std::string text; + size_t size; char data[512]; - inline Address(): type(NONE), data() { } + inline Address(): type(NONE), size(), data() { } + + inline void clear() + { type = NONE; text.clear(); size = 0; memset(data, 0, sizeof(data)); } + + inline void set(Type type, const char *text = nullptr) { + this->type = type; + if (text) this->text = text; + size = 0; + memset(data, 0, sizeof(data)); + } + + template + inline void set(Type type, const char *text, const T& data) + { set(type, text); as() = data; size = sizeof(T); } template inline const T& as() const { assert(sizeof(T) < sizeof(data)); return *reinterpret_cast(data); } template - inline T& as() const + inline T& as() { assert(sizeof(T) < sizeof(data)); return *reinterpret_cast(data); } }; diff --git a/common.h b/common.h new file mode 100644 index 0000000..75175a7 --- /dev/null +++ b/common.h @@ -0,0 +1,9 @@ +#ifndef COMMON_H +#define COMMON_H + + +#include "error.h" +#include "handle.h" + + +#endif diff --git a/connection.cpp b/connection.cpp index 5d81c1d..87b5e27 100644 --- a/connection.cpp +++ b/connection.cpp @@ -1,10 +1,13 @@ +#include + +#include "utils.h" #include "protocol.h" #include "connection.h" Connection::Connection(): - socket(), lastId() { } + socket(), lastId(), closeRequested(), closeTimeUs() { } Connection::~Connection() { close(); } @@ -14,7 +17,7 @@ Protocol& Connection::getProtocol() const { assert(socket); return socket->protocol; } const Address& Connection::getRemoteAddress() const { assert(socket); return socket->address; } -Server* Connection::getServer() const +const ::Handle& Connection::getServer() const { assert(socket); return socket->server; } @@ -27,10 +30,6 @@ void Connection::open(Socket *socket) { } -ErrorCode Connection::open(Protocol &protocol, const Address &remoteAddress) - { return protocol.connect(*this, remoteAddress); } - - void Connection::close(ErrorCode errorCode) { Socket *socketCopy; { @@ -46,8 +45,11 @@ void Connection::close(ErrorCode errorCode) { writeQueue.clear(); socketCopy = socket; socket = nullptr; + closeRequested = false; + closeTimeUs = 0; + closeAwaitCondition.notify_all(); } - socketCopy->close(); + socketCopy->finalize(); } @@ -69,7 +71,34 @@ Connection::ReqId Connection::readReq(void *data, size_t size, void *userData) { } +void Connection::closeReq() { + Lock lock(mutex); + if (!socket || closeRequested) return; + closeRequested = true; + onCloseRequested(); +} + + +void Connection::closeAwait(unsigned long long timeoutUs, bool withRequest) { + std::unique_lock uniqlock(mutex); + + if (!socket) return; + if (withRequest) closeReq(); + unsigned long long timeUs = monotonicTimeUs() + timeoutUs; + if (closeTimeUs > timeUs) + { closeTimeUs = timeUs; closeAwaitCondition.notify_all(); } + + while(!socket) { + unsigned long long timeUs = monotonicTimeUs(); + if (timeUs >= closeTimeUs) + { close(); break; } + closeAwaitCondition.wait_for(uniqlock, std::chrono::microseconds(closeTimeUs - timeUs)); + } +} + + void Connection::onOpen() { } +void Connection::onCloseRequested() { } void Connection::onClose(ErrorCode) { } void Connection::onReadReady(const ReadReq&) { } void Connection::onWriteReady(const WriteReq&) { } diff --git a/connection.h b/connection.h index 70c6156..b74b45d 100644 --- a/connection.h +++ b/connection.h @@ -6,8 +6,9 @@ #include #include +#include -#include "error.h" +#include "common.h" #include "address.h" @@ -24,8 +25,10 @@ class Socket; class Server; -class Connection { +class Connection: public Shared { public: + typedef ::Handle Handle; + typedef unsigned long long ReqId; typedef std::recursive_mutex Mutex; typedef std::lock_guard Lock; @@ -111,23 +114,31 @@ private: ReadQueue readQueue; WriteQueue writeQueue; + bool closeRequested; + unsigned long long closeTimeUs; + std::condition_variable_any closeAwaitCondition; + public: Connection(); virtual ~Connection(); void open(Socket *socket); - ErrorCode open(Protocol &protocol, const Address &remoteAddress); void close(ErrorCode errorCode = ERR_NONE); ReqId writeReq(const void *data, size_t size, void *userData = nullptr); ReqId readReq(void *data, size_t size, void *userData = nullptr); + void closeReq(); + + void closeAwait(unsigned long long timeoutUs, bool withReqest = true); protected: + // mutex must be locked before calling of all following methods Protocol& getProtocol() const; const Address& getRemoteAddress() const; - Server* getServer() const; + const ::Handle& getServer() const; virtual void onOpen(); + virtual void onCloseRequested(); virtual void onClose(ErrorCode errorCode); virtual void onReadReady(const ReadReq &req); virtual void onWriteReady(const WriteReq &req); diff --git a/error.h b/error.h index 3741b2d..38f55f3 100644 --- a/error.h +++ b/error.h @@ -10,9 +10,10 @@ typedef unsigned int ErrorCode; enum : ErrorCode { ERR_NONE = 0, - ERR_CONNECTION = 1000, - ERR_SERVER = 2000, - ERR_TCP = 3000, + ERR_ADDRESS = 1000, + ERR_CONNECTION = 2000, + ERR_SERVER = 3000, + ERR_TCP = 4000, }; enum : ErrorCode { @@ -20,4 +21,5 @@ enum : ErrorCode { ERR_NOT_IMPLEMENTED, }; + #endif diff --git a/handle.h b/handle.h index 2d3e14e..c5402b7 100644 --- a/handle.h +++ b/handle.h @@ -26,6 +26,7 @@ private: inline explicit Counter(Shared &shared): refCount(1), ptrRefCount(0), pointer(&shared) { } inline ~Counter() { assert(!refCount && !ptrRefCount); } + Counter& operator=(const Counter&) = delete; }; @@ -34,8 +35,10 @@ class Shared { private: friend class HandleBase; friend class WeakHandleBase; - Counter *counter; + Counter * const counter; + Shared(const Shared&) = delete; + Shared& operator=(const Shared&) = delete; public: inline Shared(): counter(new Counter(*this)) { } virtual ~Shared(); @@ -141,13 +144,14 @@ template class WeakHandle: public WeakHandleBase { public: typedef T Type; - typedef Handle Handle; + typedef ::Handle Handle; private: inline void typeChecker(const Type*) { } public: inline WeakHandle() { } + inline WeakHandle(const std::nullptr_t) { } inline WeakHandle(WeakHandle &&other) { WeakHandleBase::swap(other); } inline WeakHandle(const WeakHandle &other) { WeakHandleBase::set(other.get()); } inline explicit WeakHandle(const Handle &handle) { WeakHandleBase::set(handle.pointer()); } @@ -186,6 +190,7 @@ private: public: inline Handle() { } + inline Handle(const std::nullptr_t) { } inline Handle(Handle &&other) { HandleBase::swap(other); } inline Handle(const Handle &other) { HandleBase::set(other.get()); } inline explicit Handle(const Weak &weak) { HandleBase::set(weak); } @@ -204,8 +209,8 @@ public: { HandleBase::swap(other); return *this; } inline Type* pointer() const { return (Type*)HandleBase::get(); } - inline Type* operator->() const { return get(); } - inline Type& operator*() const { return *get(); } + inline Type* operator->() const { return pointer(); } + inline Type& operator*() const { return *pointer(); } template inline operator Handle&() diff --git a/protocol.cpp b/protocol.cpp index b282c11..f457467 100644 --- a/protocol.cpp +++ b/protocol.cpp @@ -4,44 +4,91 @@ #include "connection.h" -Socket::Socket(Protocol &protocol, Connection *connection, Server *server, const Address &address): - closed(), prev(), next(), protocol(protocol), connection(connection), server(server), address(address) + +Socket::Socket( + Protocol &protocol, + const Connection::Handle &connection, + const Server::Handle &server, + const Address &address +): + protocol(protocol), connection(connection), server(server), address(address), + flags(CREATED), prev(), next(), chPrev(), chNext() { - Protocol::Lock lock(protocol.mutex); prev = protocol.last; (prev ? prev->next : protocol.first) = this; + setChanged(true); } -Socket::~Socket() - { assert(closed); } - -void Socket::onClose() - { } -void Socket::close() { - Protocol::Lock lock(protocol.mutex); - onClose(); +Socket::~Socket() { + setChanged(false); (prev ? prev->next : protocol.first) = next; (next ? next->prev : protocol.last) = prev; - closed = true; - delete this; + prev = next = nullptr; +} + + +void Socket::setChanged(bool changed) { + if ((bool)(flags & CHANGED) == changed) return; + if (changed) { + flags |= CHANGED; + chPrev = protocol.chLast; + (chPrev ? chPrev->chNext : protocol.chFirst) = this; + protocol.wakeup(); + } else { + flags &= ~CHANGED; + (chPrev ? chPrev->chNext : protocol.chFirst) = chNext; + (chNext ? chNext->chPrev : protocol.chLast) = chPrev; + chPrev = chNext = nullptr; + } +} + + +void Socket::setWantRead(bool want) { + Protocol::Lock lock(protocol.mutex); + if ((bool)(flags & WANTREAD) == want) return; + flags = want ? flags | WANTREAD : flags & ~WANTREAD; + setChanged(true); +} + + +void Socket::setWantWrite(bool want) { + Protocol::Lock lock(protocol.mutex); + if ((bool)(flags & WANTWRITE) == want) return; + flags = want ? flags | WANTWRITE : flags & ~WANTWRITE; + setChanged(true); +} + + +void Socket::finalize() { + Protocol::Lock lock(protocol.mutex); + if (flags & REMOVED) return; + flags |= REMOVED; + setChanged(true); } Protocol::Protocol(): - first(), last() { } + first(), last(), chFirst(), chLast() { } Protocol::~Protocol() { assert(!first); } void Protocol::closeAll() { Lock lock(mutex); - while(first) first->close(); + while(first) + if (first->connection) first->connection->close(); else + if (first->server) first->server->close(); else + first->finalize(); } -ErrorCode Protocol::connect(Connection&, const Address&) +void Protocol::wakeup() + { } +ErrorCode Protocol::resolve(Address&) + { return ERR_ADDRESS_INCORRECT; } +ErrorCode Protocol::connect(const Connection::Handle&, const Address&) { return ERR_CONNECTION_FAILED; } -ErrorCode Protocol::listen(Server&, const Address&) +ErrorCode Protocol::listen(const Server::Handle&, const Address&) { return ERR_SERVER_LISTENING_FAILED; } diff --git a/protocol.h b/protocol.h index 4a2485b..4e33d97 100644 --- a/protocol.h +++ b/protocol.h @@ -4,54 +4,116 @@ #include -#include "error.h" +#include "common.h" #include "address.h" +#include "connection.h" +#include "server.h" + + +enum : ErrorCode { + ERR_PROTOCOL_COMMON = ERR_SERVER, + ERR_PROTOCOL_NOT_INITIALIZED, +}; class Protocol; -class Connection; -class Server; class Socket { -private: - bool closed; - Socket *prev, *next; - public: + enum : unsigned int { + CHANGED = 1 << 0, + CREATED = 1 << 1, + REMOVED = 1 << 2, + WANTREAD = 1 << 3, + WANTWRITE = 1 << 4, }; + Protocol &protocol; - Connection * const connection; - Server * const server; + const Connection::Handle connection; + const Server::Handle server; const Address &address; +private: + friend class Protocol; + + // these vars protected by protocol.mutex + unsigned int flags; + Socket *prev, *next; + Socket *chPrev, *chNext; + + // protocol.mutex must be already locked + void setChanged(bool changed); + protected: + // protocol.mutex must be already locked Socket(const Socket&) = delete; - Socket(Protocol &protocol, Connection *connection, Server *server, const Address &address); + Socket( + Protocol &protocol, + const Connection::Handle &connection, + const Server::Handle &server, + const Address &address ); + ~Socket(); public: - void close(); - virtual ~Socket(); - -protected: - virtual void onClose(); + // thread-saef methods + void setWantRead(bool want); + void setWantWrite(bool want); + void finalize(); }; -class Protocol { +class Protocol: public Shared { public: + typedef ::Handle Handle; + typedef std::recursive_mutex Mutex; typedef std::lock_guard Lock; private: friend class Socket; - Mutex mutex; Socket *first, *last; + Socket *chFirst, *chLast; + + inline void socketCheck(Socket *socket, bool mustBeChanged = false) { + assert(socket); + assert(&socket->protocol == this); + assert(!mustBeChanged || (socket->flags & Socket::CHANGED)); + } + +protected: + Mutex mutex; + bool allowNewConnections; + + // any class derived from Protocol may access to Socket private members via these methods + // mutex must be locked before call + inline Socket* socketFirst() + { return first; } + inline Socket* socketNext(Socket *socket) + { socketCheck(socket); return socket->next; } + inline Socket* socketChFirst() + { return chFirst; } + inline Socket* socketChNext(Socket *socket) + { socketCheck(socket, true); return socket->chNext; } + inline unsigned int socketFlags(Socket *socket) + { socketCheck(socket); return socket->flags; } + + inline Socket* socketRemove(Socket *socket) + { socketCheck(socket); Socket *next = socket->next; delete socket; return next; } + inline Socket* socketRemoveChanged(Socket *socket) + { socketCheck(socket, true); Socket *chNext = socket->chNext; delete socket; return chNext; } + inline Socket* socketSetUnchanged(Socket *socket) + { socketCheck(socket, true); Socket *chNext = socket->chNext; socket->setChanged(false); return chNext; } + + virtual void wakeup(); + public: + // thread-safe methods Protocol(); virtual ~Protocol(); - virtual ErrorCode connect(Connection &connection, const Address &address); - virtual ErrorCode listen(Server &server, const Address &address); + virtual ErrorCode resolve(Address &address); + virtual ErrorCode connect(const Connection::Handle &connection, const Address &address); + virtual ErrorCode listen(const Server::Handle &server, const Address &address); void closeAll(); }; diff --git a/server.cpp b/server.cpp index 9752b6a..3e2983f 100644 --- a/server.cpp +++ b/server.cpp @@ -22,10 +22,6 @@ void Server::open(Socket *socket) { } -ErrorCode Server::open(Protocol &protocol, const Address &localAddres) - { return protocol.listen(*this, localAddres); } - - void Server::close(ErrorCode errorCode) { Socket *socketCopy; { @@ -35,7 +31,7 @@ void Server::close(ErrorCode errorCode) { socketCopy = socket; socket = nullptr; } - socketCopy->close(); + socketCopy->finalize(); } Protocol& Server::getProtocol() const @@ -48,7 +44,7 @@ void Server::onOpen() { } void Server::onClose(ErrorCode) { } -Connection* Server::onConnect(const Address&) +Connection::Handle Server::onConnect(const Address&) { return nullptr; } -void Server::onDisconnect(Connection *connection) - { delete connection; } +void Server::onDisconnect(const Connection::Handle&) + { } diff --git a/server.h b/server.h index 90d50bd..d9c5d27 100644 --- a/server.h +++ b/server.h @@ -4,7 +4,7 @@ #include -#include "error.h" +#include "common.h" #include "address.h" @@ -21,8 +21,10 @@ enum : ErrorCode { }; -class Server { +class Server: public Shared { public: + typedef ::Handle Handle; + typedef std::recursive_mutex Mutex; typedef std::lock_guard Lock; @@ -35,7 +37,6 @@ public: ~Server(); void open(Socket *socket); - ErrorCode open(Protocol &protocol, const Address &localAddres); void close(ErrorCode errorCode = ERR_NONE); protected: @@ -44,8 +45,8 @@ protected: void onOpen(); void onClose(ErrorCode errorCode); - Connection* onConnect(const Address &remoteAddres); - void onDisconnect(Connection *connection); + ::Handle onConnect(const Address &remoteAddres); + void onDisconnect(const ::Handle &connection); }; diff --git a/tcpprotocol.cpp b/tcpprotocol.cpp new file mode 100644 index 0000000..a18d182 --- /dev/null +++ b/tcpprotocol.cpp @@ -0,0 +1,153 @@ + +#include +#include +#include +#include +#include +#include +#include +#include + + +#include "protocol.h" +#include "server.h" +#include "connection.h" +#include "tcpprotocol.h" + + + +TcpSocket::TcpSocket( + Protocol &protocol, + const Connection::Handle &connection, + const Server::Handle &server, + const Address &address, + int sockId +): + Socket(protocol, connection, server, address), + sockId(sockId) +{ + int tcp_nodelay = 1; + setsockopt(sockId, IPPROTO_TCP, TCP_NODELAY, &tcp_nodelay, sizeof(int)); + fcntl(sockId, F_SETFL, fcntl(sockId, F_GETFL, 0) | O_NONBLOCK); + + struct epoll_event event = {}; + event.data.ptr = this; + epoll_ctl(getProtocol().epollId, EPOLL_CTL_ADD, sockId, &event); +} + + +TcpProtocol::TcpProtocol(): + thread(), stopping() { } + + +ErrorCode TcpProtocol::resolve(Address &address) { + if (address.type == Address::SOCKET) + return ERR_NONE; + + if ( address.type != Address::COMMON_STRING + || address.text.empty() ) + return ERR_ADDRESS_INCORRECT; + + // split to host and port + const char *addrString = address.text.c_str(); + const char *colon = addrString; + while(*colon && *colon != ':') ++colon; + + std::string hostBuf; + const char *host = nullptr; + const char *port = nullptr; + if (*colon) { + hostBuf.assign(addrString, colon); + host = hostBuf.c_str(); + port = colon + 1; + } else { + bool allDigits = true; + for(const char *c = addrString; *c && allDigits; ++c) + if (*c < '0' || *c > '9') allDigits = false; + (allDigits ? port : host) = addrString; + } + + // get ipv4 + unsigned char ip[4] = {}; + if (host) { + if (4 != sscanf(host, "%hhu.%hhu.%hhu.%hhu", &ip[0], &ip[1], &ip[2], &ip[3])) { + hostent *he; + in_addr **addr_list; + if ( (he = gethostbyname(host)) + && (addr_list = (struct in_addr**)he->h_addr_list) + && (*addr_list) ) + { + memcpy(ip, *addr_list, sizeof(ip)); + } else { + return ERR_ADDRESS_NOT_FOUND; + } + } + } + + unsigned short portNum = 0; + if (!port || 1 != sscanf(port, "%hu", &portNum) || !portNum) + return ERR_ADDRESS_INCORRECT; + + address.set(Address::SOCKET); + sockaddr_in &addr = address.as(); + addr.sin_family = AF_INET; + memcpy(&addr.sin_addr, ip, sizeof(ip)); + addr.sin_port = htons(portNum); + + return ERR_NONE; +} + + +void TcpProtocol::threadRun() { + const int count = 1024; + struct epoll_event events[count] = {}; + while(true) { + epoll_wait(epollId, events, count, 1000); + break; + } + closeAll(); +} + + +ErrorCode TcpProtocol::connect(const Connection::Handle &connection, const Address &address) { + if (!thread) return ERR_PROTOCOL_NOT_INITIALIZED; + + Address remoteAddres = address; + ErrorCode errorCode = resolve(remoteAddres); + if (errorCode) return errorCode; + + Lock lock(mutex); + + int sockId = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); + if (0 != ::connect(sockId, &remoteAddres.as(), remoteAddres.size)) { + ::close(sockId); + return ERR_CONNECTION_FAILED; + } + + TcpSocket *socket = new TcpSocket(*this, connection, nullptr, remoteAddres, sockId); + connection->open(socket); + return ERR_NONE; +} + + +ErrorCode TcpProtocol::listen(const Server::Handle &server, const Address &address) { + return ERR_NOT_IMPLEMENTED; +} + + +void TcpProtocol::start() { + stop(); + epollId = ::epoll_create(32); + thread = new std::thread(&TcpProtocol::threadRun, this); +} + + +void TcpProtocol::stop() { + if (!thread) return; + { Lock lock2(mutex); stopping = true; } + thread->join(); + delete thread; + ::close(epollId); + epollId = 0; +} + diff --git a/tcpprotocol.h b/tcpprotocol.h index a508e8c..5f2ef2f 100644 --- a/tcpprotocol.h +++ b/tcpprotocol.h @@ -2,7 +2,52 @@ #define SOCKET_H +#include +#include +#include "protocol.h" + + +class TcpProtocol; + + +class TcpSocket: public Socket { +public: + const int sockId; +protected: + friend class TcpProtocol; + + TcpSocket( + Protocol &protocol, + const Connection::Handle &connection, + const Server::Handle &server, + const Address &address, + int sockId ); + + TcpProtocol& getProtocol() + { return *(TcpProtocol*)&protocol; } +}; + + +class TcpProtocol: public Protocol { +private: + friend class TcpSocket; + + std::thread *thread; + bool stopping; + int epollId; + + void threadRun(); + +public: + TcpProtocol(); + + ErrorCode resolve(Address &address) override; + ErrorCode connect(const ::Handle &connection, const Address &address) override; + ErrorCode listen(const ::Handle &server, const Address &address) override; + void start(); + void stop(); +}; #endif diff --git a/threadpool.cpp b/threadpool.cpp index 2584d3c..3a83c8d 100644 --- a/threadpool.cpp +++ b/threadpool.cpp @@ -47,38 +47,41 @@ ThreadPool::TaskId ThreadPool::enqueue(const Task &task, const void *owner) { } -int ThreadPool::cancelById(TaskId id) { +int ThreadPool::cancelById(TaskId id, bool wait) { std::unique_lock lock(mutex); for(Queue::iterator i = queue.begin(); i != queue.end(); ++i) if (i->id == id) { i = queue.erase(i); return 1; } - for(ThreadList::iterator i = threads.begin(); i != threads.end(); ++i) - while(i->taskId == id) - i->condition.wait(lock); + if (wait) + for(ThreadList::iterator i = threads.begin(); i != threads.end(); ++i) + while(i->taskId == id) + i->condition.wait(lock); return 0; } -int ThreadPool::cancelByOwner(const void *owner) { +int ThreadPool::cancelByOwner(const void *owner, bool wait) { std::unique_lock lock(mutex); int count = 0; for(Queue::iterator i = queue.begin(); i != queue.end(); ++i) if (i->owner == owner) { i = queue.erase(i); ++count; } - for(ThreadList::iterator i = threads.begin(); i != threads.end(); ++i) - while(i->taskOwner == owner) - i->condition.wait(lock); + if (wait) + for(ThreadList::iterator i = threads.begin(); i != threads.end(); ++i) + while(i->taskOwner == owner) + i->condition.wait(lock); return count; } -int ThreadPool::cancelAll() { +int ThreadPool::cancelAll(bool wait) { std::unique_lock lock(mutex); int count = queue.size(); queue.clear(); - for(ThreadList::iterator i = threads.begin(); i != threads.end(); ++i) - while(i->taskId) - i->condition.wait(lock); + if (wait) + for(ThreadList::iterator i = threads.begin(); i != threads.end(); ++i) + while(i->taskId) + i->condition.wait(lock); return count; } diff --git a/threadpool.h b/threadpool.h index 62883f4..5ab5448 100644 --- a/threadpool.h +++ b/threadpool.h @@ -10,6 +10,14 @@ #include + +class TaskOwner { +public: + TaskOwner(); + virtual ~TaskOwner(); +}; + + class ThreadPool { public: typedef unsigned long long TaskId; @@ -55,9 +63,9 @@ public: ~ThreadPool(); TaskId enqueue(const Task &task, const void *owner = nullptr); - int cancelById(TaskId id); - int cancelByOwner(const void *owner); - int cancelAll(); + int cancelById(TaskId id, bool wait = true); + int cancelByOwner(const void *owner, bool wait = true); + int cancelAll(bool wait = true); void start(int count, bool cancelAllTasks = false); inline void stop(bool cancelAllTasks = false) diff --git a/utils.cpp b/utils.cpp new file mode 100644 index 0000000..29a8682 --- /dev/null +++ b/utils.cpp @@ -0,0 +1,14 @@ + +#include + +#include "utils.h" + + +unsigned long long monotonicTimeUs() { + return + std::chrono::duration_cast + + (std::chrono::steady_clock::now().time_since_epoch()) + .count(); +} + diff --git a/utils.h b/utils.h new file mode 100644 index 0000000..3be13be --- /dev/null +++ b/utils.h @@ -0,0 +1,8 @@ +#ifndef UTILS_H +#define UTILS_H + + +unsigned long long monotonicTimeUs(); + + +#endif