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