1 //
2 // process_per_connection.cpp
3 // ~~~~~~~~~~~~~~~~~~~~~~~~~~
4 //
5 // Copyright (c) 2003-2020 Christopher M. Kohlhoff (chris at kohlhoff dot com)
6 //
7 // Distributed under the Boost Software License, Version 1.0. (See accompanying
8 // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
9 //
10 
11 #include <boost/asio/io_context.hpp>
12 #include <boost/asio/ip/tcp.hpp>
13 #include <boost/asio/signal_set.hpp>
14 #include <boost/asio/write.hpp>
15 #include <boost/array.hpp>
16 #include <boost/bind/bind.hpp>
17 #include <cstdlib>
18 #include <iostream>
19 #include <sys/types.h>
20 #include <sys/wait.h>
21 #include <unistd.h>
22 
23 using boost::asio::ip::tcp;
24 
25 class server
26 {
27 public:
server(boost::asio::io_context & io_context,unsigned short port)28   server(boost::asio::io_context& io_context, unsigned short port)
29     : io_context_(io_context),
30       signal_(io_context, SIGCHLD),
31       acceptor_(io_context, tcp::endpoint(tcp::v4(), port)),
32       socket_(io_context)
33   {
34     start_signal_wait();
35     start_accept();
36   }
37 
38 private:
start_signal_wait()39   void start_signal_wait()
40   {
41     signal_.async_wait(boost::bind(&server::handle_signal_wait, this));
42   }
43 
handle_signal_wait()44   void handle_signal_wait()
45   {
46     // Only the parent process should check for this signal. We can determine
47     // whether we are in the parent by checking if the acceptor is still open.
48     if (acceptor_.is_open())
49     {
50       // Reap completed child processes so that we don't end up with zombies.
51       int status = 0;
52       while (waitpid(-1, &status, WNOHANG) > 0) {}
53 
54       start_signal_wait();
55     }
56   }
57 
start_accept()58   void start_accept()
59   {
60     acceptor_.async_accept(socket_,
61         boost::bind(&server::handle_accept, this, boost::placeholders::_1));
62   }
63 
handle_accept(const boost::system::error_code & ec)64   void handle_accept(const boost::system::error_code& ec)
65   {
66     if (!ec)
67     {
68       // Inform the io_context that we are about to fork. The io_context cleans
69       // up any internal resources, such as threads, that may interfere with
70       // forking.
71       io_context_.notify_fork(boost::asio::io_context::fork_prepare);
72 
73       if (fork() == 0)
74       {
75         // Inform the io_context that the fork is finished and that this is the
76         // child process. The io_context uses this opportunity to create any
77         // internal file descriptors that must be private to the new process.
78         io_context_.notify_fork(boost::asio::io_context::fork_child);
79 
80         // The child won't be accepting new connections, so we can close the
81         // acceptor. It remains open in the parent.
82         acceptor_.close();
83 
84         // The child process is not interested in processing the SIGCHLD signal.
85         signal_.cancel();
86 
87         start_read();
88       }
89       else
90       {
91         // Inform the io_context that the fork is finished (or failed) and that
92         // this is the parent process. The io_context uses this opportunity to
93         // recreate any internal resources that were cleaned up during
94         // preparation for the fork.
95         io_context_.notify_fork(boost::asio::io_context::fork_parent);
96 
97         socket_.close();
98         start_accept();
99       }
100     }
101     else
102     {
103       std::cerr << "Accept error: " << ec.message() << std::endl;
104       start_accept();
105     }
106   }
107 
start_read()108   void start_read()
109   {
110     socket_.async_read_some(boost::asio::buffer(data_),
111         boost::bind(&server::handle_read, this,
112           boost::placeholders::_1, boost::placeholders::_2));
113   }
114 
handle_read(const boost::system::error_code & ec,std::size_t length)115   void handle_read(const boost::system::error_code& ec, std::size_t length)
116   {
117     if (!ec)
118       start_write(length);
119   }
120 
start_write(std::size_t length)121   void start_write(std::size_t length)
122   {
123     boost::asio::async_write(socket_, boost::asio::buffer(data_, length),
124         boost::bind(&server::handle_write, this, boost::placeholders::_1));
125   }
126 
handle_write(const boost::system::error_code & ec)127   void handle_write(const boost::system::error_code& ec)
128   {
129     if (!ec)
130       start_read();
131   }
132 
133   boost::asio::io_context& io_context_;
134   boost::asio::signal_set signal_;
135   tcp::acceptor acceptor_;
136   tcp::socket socket_;
137   boost::array<char, 1024> data_;
138 };
139 
main(int argc,char * argv[])140 int main(int argc, char* argv[])
141 {
142   try
143   {
144     if (argc != 2)
145     {
146       std::cerr << "Usage: process_per_connection <port>\n";
147       return 1;
148     }
149 
150     boost::asio::io_context io_context;
151 
152     using namespace std; // For atoi.
153     server s(io_context, atoi(argv[1]));
154 
155     io_context.run();
156   }
157   catch (std::exception& e)
158   {
159     std::cerr << "Exception: " << e.what() << std::endl;
160   }
161 }
162