├── LICENCE ├── README.md └── moustique.hh /LICENCE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Matthieu Garrigues 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Moustique 2 | 3 | Moustique is a tiny C++14 library (~180 LOC) providing an easy to use 4 | interface to non-blocking network IO on linux. 5 | 6 | 7 | # Implementation details 8 | 9 | Moustique relies on linux epoll for non blocking IO and on 10 | boost::context for resuming/suspending handlers. 11 | The source code is rather small, fell free to dive into moustique.hh if you want 12 | to know more about the implementation. 13 | 14 | No dynamic allocations, no buffering is done by moustique internally. The read/write 15 | callbacks are simply forwarding your buffer to the read/write syscalls. 16 | The only overhead is the context switching of boost::context [1]. 17 | 18 | Dependencies: boost::context (-lboost_context), C++14. 19 | Licence: MIT 20 | 21 | ## TCP echo example 22 | 23 | ```c++ 24 | #include "moustique.hh" 25 | 26 | int main() 27 | { 28 | moustique_listen(1234, // Port number 29 | SOCK_STREAM, // TCP socket 30 | 2, // number of threads 31 | [] (int fd, auto read, auto write) { 32 | 33 | printf("new connection: %i\n", fd); 34 | 35 | char buf[1024]; 36 | int received; 37 | 38 | while (received = read(buf, sizeof(buf))) // Suspend until new bytes 39 | // are available for reading. 40 | 41 | if (!write(buf, received)) // Suspend until the socket is ready for a write. 42 | break; 43 | 44 | printf("end of connection: %i\n", fd); 45 | } 46 | ); 47 | } 48 | ``` 49 | 50 | ## Compilation 51 | 52 | ``` 53 | g++/clang++ echo.cc -lboost_context -lpthread -DNDEBUG -O3 54 | ``` 55 | 56 | 57 | [1] https://www.boost.org/doc/libs/1_66_0/libs/context/doc/html/context/performance.html -------------------------------------------------------------------------------- /moustique.hh: -------------------------------------------------------------------------------- 1 | /** 2 | * @file moustique.hh 3 | * @author Matthieu Garrigues 4 | * @date Sat Mar 31 23:52:51 2018 5 | * 6 | * @brief moustique wrapper. 7 | * 8 | * 9 | */ 10 | #pragma once 11 | 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | 23 | #include 24 | #include 25 | #include 26 | 27 | /** 28 | * Open a socket on port \port and call \conn_handler(int client_fd, auto read, auto write) 29 | * to process each incomming connection. This handle takes 3 argments: 30 | * - int client_fd: the file descriptor of the socket. 31 | * - int read(buf, max_size_to_read): 32 | * The callback that conn_handler can use to read on the socket. 33 | * If data is available, copy it into \buf, otherwise suspend the handler until 34 | * there is something to read. 35 | * Returns the number of char actually read, returns 0 if the connection has been lost. 36 | * - bool write(buf, buf_size): return true on success, false on error. 37 | * The callback that conn_handler can use to write on the socket. 38 | * If the socket is ready to write, write \buf, otherwise suspend the handler until 39 | * it is ready. 40 | * Returns true on sucess, false if connection is lost. 41 | * 42 | * @param port The server port. 43 | * @param socktype The socket type, SOCK_STREAM for TCP, SOCK_DGRAM for UDP. 44 | * @param nthreads Number of threads. 45 | * @param conn_handler The connection handler 46 | * @return false on error, true on success. 47 | */ 48 | template 49 | int moustique_listen(int port, 50 | int socktype, 51 | int nthreads, 52 | H conn_handler); 53 | 54 | // Same as above but take an already opened socket \listen_fd. 55 | template 56 | int moustique_listen_fd(int listen_fd, 57 | int nthreads, 58 | H conn_handler); 59 | 60 | namespace moustique_impl 61 | { 62 | static int create_and_bind(int port, int socktype) 63 | { 64 | struct addrinfo hints; 65 | struct addrinfo *result, *rp; 66 | int s, sfd; 67 | 68 | char port_str[20]; 69 | snprintf(port_str, sizeof(port_str), "%d", port); 70 | memset (&hints, 0, sizeof (struct addrinfo)); 71 | hints.ai_family = AF_UNSPEC; /* Return IPv4 and IPv6 choices */ 72 | hints.ai_socktype = socktype; /* We want a TCP socket */ 73 | hints.ai_flags = AI_PASSIVE; /* All interfaces */ 74 | 75 | s = getaddrinfo (NULL, port_str, &hints, &result); 76 | if (s != 0) 77 | { 78 | fprintf (stderr, "getaddrinfo: %s\n", gai_strerror (s)); 79 | return -1; 80 | } 81 | 82 | for (rp = result; rp != NULL; rp = rp->ai_next) 83 | { 84 | sfd = socket (rp->ai_family, rp->ai_socktype, rp->ai_protocol); 85 | if (sfd == -1) 86 | continue; 87 | 88 | int enable = 1; 89 | if (setsockopt(sfd, SOL_SOCKET, SO_REUSEADDR, &enable, sizeof(int)) < 0) 90 | { 91 | close(sfd); 92 | continue; 93 | } 94 | 95 | s = bind (sfd, rp->ai_addr, rp->ai_addrlen); 96 | if (s == 0) 97 | { 98 | /* We managed to bind successfully! */ 99 | break; 100 | } 101 | 102 | close (sfd); 103 | } 104 | 105 | freeaddrinfo (result); 106 | 107 | if (rp == NULL) 108 | { 109 | fprintf (stderr, "Could not bind\n"); 110 | return -1; 111 | } 112 | 113 | return sfd; 114 | } 115 | 116 | } 117 | 118 | #define MOUSTIQUE_CHECK_CALL(CALL) \ 119 | { \ 120 | int ret = CALL; \ 121 | if (-1 == ret) \ 122 | { \ 123 | fprintf(stderr, "Error at %s:%i error is: %s", __PRETTY_FUNCTION__, __LINE__, strerror(ret)); \ 124 | return false; \ 125 | } \ 126 | } 127 | 128 | template 129 | int moustique_listen(int port, 130 | int socktype, 131 | int nthreads, 132 | H conn_handler) 133 | { 134 | return moustique_listen_fd(moustique_impl::create_and_bind(port, socktype), nthreads, conn_handler); 135 | } 136 | 137 | template 138 | int moustique_listen_fd(int listen_fd, 139 | int nthreads, 140 | H conn_handler) 141 | { 142 | namespace ctx = boost::context; 143 | 144 | if (listen_fd < 0) return 0; 145 | int flags = fcntl (listen_fd, F_GETFL, 0); 146 | MOUSTIQUE_CHECK_CALL(fcntl(listen_fd, F_SETFL, flags | O_NONBLOCK)); 147 | MOUSTIQUE_CHECK_CALL(::listen(listen_fd, SOMAXCONN)); 148 | 149 | auto event_loop_fn = [listen_fd, conn_handler] { 150 | 151 | int epoll_fd = epoll_create1(0); 152 | 153 | auto epoll_ctl = [epoll_fd] (int fd, uint32_t flags) -> bool 154 | { 155 | epoll_event event; 156 | event.data.fd = fd; 157 | event.events = flags; 158 | MOUSTIQUE_CHECK_CALL(::epoll_ctl(epoll_fd, EPOLL_CTL_ADD, fd, &event)); 159 | return true; 160 | }; 161 | 162 | epoll_ctl(listen_fd, EPOLLIN | EPOLLET); 163 | 164 | const int MAXEVENTS = 64; 165 | std::vector fibers; 166 | 167 | // Event loop. 168 | epoll_event events[MAXEVENTS]; 169 | while (true) 170 | { 171 | int n_events = epoll_wait (epoll_fd, events, MAXEVENTS, -1); 172 | for (int i = 0; i < n_events; i++) 173 | { 174 | if ((events[i].events & EPOLLERR) || 175 | (events[i].events & EPOLLHUP)) 176 | { 177 | fibers[events[i].data.fd] = fibers[events[i].data.fd].resume(); 178 | continue; 179 | } 180 | else if (listen_fd == events[i].data.fd) // New connection. 181 | { 182 | while(true) 183 | { 184 | struct sockaddr in_addr; 185 | socklen_t in_len; 186 | int infd; 187 | char hbuf[NI_MAXHOST], sbuf[NI_MAXSERV]; 188 | 189 | in_len = sizeof in_addr; 190 | infd = accept (listen_fd, &in_addr, &in_len); 191 | if (infd == -1) 192 | break; 193 | getnameinfo (&in_addr, in_len, 194 | hbuf, sizeof(hbuf), 195 | sbuf, sizeof(sbuf), 196 | NI_NUMERICHOST | NI_NUMERICSERV); 197 | 198 | MOUSTIQUE_CHECK_CALL(fcntl(infd, F_SETFL, fcntl(infd, F_GETFL, 0) | O_NONBLOCK)); 199 | 200 | epoll_ctl(infd, EPOLLIN | EPOLLOUT | EPOLLET); 201 | 202 | if (int(fibers.size()) < infd + 1) 203 | fibers.resize(infd + 10); 204 | 205 | fibers[infd] = ctx::callcc([fd=infd, &conn_handler] 206 | (ctx::continuation&& sink) { 207 | auto read = [fd, &sink] (char* buf, int max_size) { 208 | ssize_t count = ::read(fd, buf, max_size); 209 | while (count <= 0) 210 | { 211 | if ((count < 0 and errno != EAGAIN) or count == 0) 212 | return ssize_t(0); 213 | sink = sink.resume(); 214 | count = ::read(fd, buf, max_size); 215 | } 216 | return count; 217 | }; 218 | 219 | auto write = [fd, &sink] (const char* buf, int size) { 220 | const char* end = buf + size; 221 | ssize_t count = ::write(fd, buf, end - buf); 222 | if (count > 0) buf += count; 223 | while (buf != end) 224 | { 225 | if ((count < 0 and errno != EAGAIN) or count == 0) 226 | return false; 227 | sink = sink.resume(); 228 | count = ::write(fd, buf, end - buf); 229 | if (count > 0) buf += count; 230 | } 231 | return true; 232 | }; 233 | 234 | conn_handler(fd, read, write); 235 | close(fd); 236 | return std::move(sink); 237 | }); 238 | 239 | } 240 | 241 | } 242 | else // Data available on existing sockets. Wake up the fiber associated with events[i].data.fd. 243 | fibers[events[i].data.fd] = fibers[events[i].data.fd].resume(); 244 | } 245 | } 246 | 247 | }; 248 | 249 | std::vector ths; 250 | for (int i = 0; i < nthreads; i++) 251 | ths.push_back(std::thread([&] { event_loop_fn(); })); 252 | 253 | for (auto& t : ths) 254 | t.join(); 255 | 256 | close(listen_fd); 257 | 258 | return true; 259 | } 260 | --------------------------------------------------------------------------------