|
| 1 | +// |
| 2 | +// Created by netcan on 2021/11/30. |
| 3 | +// |
| 4 | + |
| 5 | +#ifndef ASYNCIO_START_SERVER_H |
| 6 | +#define ASYNCIO_START_SERVER_H |
| 7 | +#include <asyncio/asyncio_ns.h> |
| 8 | +#include <asyncio/stream.h> |
| 9 | +#include <asyncio/addrinfo_guard.h> |
| 10 | +#include <asyncio/schedule_task.h> |
| 11 | +#include <list> |
| 12 | +#include <sys/types.h> |
| 13 | + |
| 14 | +ASYNCIO_NS_BEGIN |
| 15 | +namespace concepts { |
| 16 | +template<typename CONNECT_CB> |
| 17 | +concept ConnectCb = requires(CONNECT_CB cb) { |
| 18 | + { cb(std::declval<Stream>()) } -> concepts::Awaitable; |
| 19 | +}; |
| 20 | +} |
| 21 | + |
| 22 | +constexpr static size_t max_connect_count = 16; |
| 23 | + |
| 24 | +template<concepts::ConnectCb CONNECT_CB> |
| 25 | +struct Server: NonCopyable { |
| 26 | + Server(CONNECT_CB cb, int fd): connect_cb_(cb), fd_(fd) {} |
| 27 | + Server(Server&& other): connect_cb_(other.connect_cb_), |
| 28 | + fd_{std::exchange(other.fd_, -1) } {} |
| 29 | + ~Server() { close(); } |
| 30 | + |
| 31 | + Task<void> serve_forever() { |
| 32 | + Event ev { .fd = fd_, .events = EPOLLIN }; |
| 33 | + auto& loop = get_event_loop(); |
| 34 | + std::list<Task<>> connected; |
| 35 | + while (true) { |
| 36 | + co_await loop.wait_event(ev); |
| 37 | + sockaddr_storage remoteaddr{}; |
| 38 | + socklen_t addrlen = sizeof(remoteaddr); |
| 39 | + int clientfd = ::accept(fd_, reinterpret_cast<sockaddr*>(&remoteaddr), &addrlen); |
| 40 | + if (clientfd == -1) { continue; } |
| 41 | + connected.emplace_back(schedule_task(connect_cb_(Stream{clientfd, remoteaddr}))); |
| 42 | + // garbage collect |
| 43 | + clean_up_connected(connected); |
| 44 | + } |
| 45 | + } |
| 46 | + |
| 47 | +private: |
| 48 | + void clean_up_connected(std::list<Task<>>& connected) { |
| 49 | + if (connected.size() < 100) [[likely]] { return; } |
| 50 | + for (auto iter = connected.begin(); iter != connected.end(); ) { |
| 51 | + if (iter->done()) { |
| 52 | + iter = connected.erase(iter); |
| 53 | + } else { |
| 54 | + ++iter; |
| 55 | + } |
| 56 | + } |
| 57 | + } |
| 58 | + |
| 59 | +private: |
| 60 | + void close() { |
| 61 | + if (fd_ > 0) { ::close(fd_); } |
| 62 | + fd_ = -1; |
| 63 | + } |
| 64 | + |
| 65 | +private: |
| 66 | + [[no_unique_address]] CONNECT_CB connect_cb_; |
| 67 | + int fd_{-1}; |
| 68 | +}; |
| 69 | + |
| 70 | +template<concepts::ConnectCb CONNECT_CB> |
| 71 | +Task<Server<CONNECT_CB>> start_server(CONNECT_CB cb, std::string_view ip, uint16_t port) { |
| 72 | + addrinfo hints { .ai_family = AF_UNSPEC, .ai_socktype = SOCK_STREAM }; |
| 73 | + addrinfo *server_info {nullptr}; |
| 74 | + auto service = std::to_string(port); |
| 75 | + // TODO: getaddrinfo is a blocking api |
| 76 | + if (int rv = getaddrinfo(ip.data(), service.c_str(), &hints, &server_info); |
| 77 | + rv != 0) { |
| 78 | + throw std::system_error(std::make_error_code(std::errc::address_not_available)); |
| 79 | + } |
| 80 | + AddrInfoGuard _i(server_info); |
| 81 | + |
| 82 | + int serverfd = -1; |
| 83 | + for (auto p = server_info; p != nullptr; p = p->ai_next) { |
| 84 | + if ( (serverfd = socket(p->ai_family, p->ai_socktype | SOCK_NONBLOCK, p->ai_protocol)) == -1) { |
| 85 | + continue; |
| 86 | + } |
| 87 | + int yes = 1; |
| 88 | + // lose the pesky "address already in use" error message |
| 89 | + setsockopt(serverfd, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(yes)); |
| 90 | + if ( bind(serverfd, p->ai_addr, p->ai_addrlen) == 0) { |
| 91 | + break; |
| 92 | + } |
| 93 | + close(serverfd); |
| 94 | + serverfd = -1; |
| 95 | + } |
| 96 | + if (serverfd == -1) { |
| 97 | + throw std::system_error(std::make_error_code(std::errc::address_not_available)); |
| 98 | + } |
| 99 | + |
| 100 | + if (listen(serverfd, max_connect_count) == -1) { |
| 101 | + throw std::system_error(std::make_error_code(static_cast<std::errc>(errno))); |
| 102 | + } |
| 103 | + |
| 104 | + co_return Server{cb, serverfd}; |
| 105 | +} |
| 106 | + |
| 107 | +ASYNCIO_NS_END |
| 108 | + |
| 109 | +#endif // ASYNCIO_START_SERVER_H |
0 commit comments