diff --git a/examples/concurrent_http_server/ConcurrentHTTPServer.cpp b/examples/concurrent_http_server/ConcurrentHTTPServer.cpp index 0fbd84a..354e1fd 100644 --- a/examples/concurrent_http_server/ConcurrentHTTPServer.cpp +++ b/examples/concurrent_http_server/ConcurrentHTTPServer.cpp @@ -10,9 +10,27 @@ #include #include +class SessionState +{ +public: + fr::HttpRequest partial_request; +}; + +void process_complete_request(const std::shared_ptr &client, fr::HttpRequest request) +{ + //Note: *NEVER* disconnect the client in the handler. Or it will never be removed from + //the socket selector, and its opaque data will never be free'd. You're better off having a + //disconnection queue which is processed by the listening thread, and added to here. + fr::HttpResponse response; + response.set_body("

Hello World!

"); + client->send(response); +} + int main() { - //Bind to port + //Bind to port. Note that it is possible to fork/create new threads and then bind to the same + //port multiple times. Each thread should have its own fr::SocketSelector, this will + //spread connections over multiple workers. auto listener = std::make_shared(); if(listener->listen("8080") != fr::Socket::Success) { @@ -20,77 +38,57 @@ int main() return EXIT_FAILURE; } - for(size_t a = 0; a < std::thread::hardware_concurrency(); ++a) - { - if(fork() <= 0) - break; - } + //Create a socket selector, and add the listener so we get notified on connection requests + fr::SocketSelector listen_loop_selector; + listen_loop_selector.add(listener, nullptr); - //Setup our socket selector and add the listener to it - fr::SocketSelector selector; - selector.add(listener, nullptr); - - //Enter main server loop - volatile bool running = true; + bool running = true; while(running) { - //Wait for a socket to be ready with no timeout - auto ready_sockets = selector.wait(); - - //Process each socket + //Pass a timeout here, so that we can periodically check 'running' to see if we should exit + auto ready_sockets = listen_loop_selector.wait(std::chrono::milliseconds(100)); for(auto &ready_socket : ready_sockets) { - //If this is the listener, then accept a new connection + //If it's the listener, accept a new connection if(ready_socket.first->get_socket_descriptor() == listener->get_socket_descriptor()) { - auto client = std::make_shared(); + auto client = std::make_shared(); //Or fr::SSLSocket if(listener->accept(*client) == fr::Socket::Success) { - //We've accepted the connection, so add it to the selector, associating a new fr::HttpRequest - //Object which will slowly be filled with the client's request. You could also pass a struct - //associated with the connection for easier state management. - selector.add(client, new fr::HttpRequest()); - client->set_blocking(false); + client->set_blocking(false); //This is important + listen_loop_selector.add(client, new SessionState()); //We assign a 'SessionState' object for each connection as opaque data } - break; + continue; } - //Else, a socket is sending data/has disconnected, accept it - auto client = std::static_pointer_cast(ready_socket.first); //fr::TcpSocket -> fr::Socket -> fr::SocketDescriptor - auto partial_request = static_cast(ready_socket.second); + //Else, we have new activity on a client socket + auto client = std::static_pointer_cast(ready_socket.first); + auto session = static_cast(ready_socket.second); + //Try and receive data char data[0x1000]; size_t received = 0; auto recv_status = client->receive_raw(data, sizeof(data), received); if(recv_status == fr::Socket::Success) { - //We receied data, so parse it using the partial HTTP request associated with this connection - auto parse_status = partial_request->parse(data, received); + //We received data, so parse it using the partial HTTP request associated with this connection + auto parse_status = session->partial_request.parse(data, received); if(parse_status == fr::Socket::Success) { - //The client has sent a full request, send them something back - //This could alternatively pass this connection/request off to a handler in another - //thread for further processing. - fr::HttpResponse response; - response.set_body("

Hello World!

"); - client->send(response); + //The client has sent a full request, queue it for processing + process_complete_request(client, std::move(session->partial_request)); + session->partial_request = fr::HttpRequest(); } - else if(parse_status == fr::Socket::NotEnoughData) + else if(parse_status != fr::Socket::NotEnoughData) { - //More data needed, wait for it - } - else - { - //HTTP error, disconnect the client - delete partial_request; - selector.remove(client); - client->disconnect(); + //HTTP error, disconnect the client. Remove from socket selector, and delete opaque data. + delete (SessionState*)listen_loop_selector.remove(client); } } - else if(recv_status == fr::Socket::Disconnected) + else if(recv_status != fr::Socket::WouldBlock) { - //Free the allocated fr::HttpRequest. The socket is auto-removed from the selector as it notified us. - delete partial_request; + //Error, disconnect it. Remove from socket selector, and delete opaque data. + delete (SessionState*)listen_loop_selector.remove(client); } } }