Line data Source code
1 : // Copyright (c) 2012-2019 Made to Order Software Corp. All Rights Reserved
2 : //
3 : // This program is free software; you can redistribute it and/or modify
4 : // it under the terms of the GNU General Public License as published by
5 : // the Free Software Foundation; either version 2 of the License, or
6 : // (at your option) any later version.
7 : //
8 : // This program is distributed in the hope that it will be useful,
9 : // but WITHOUT ANY WARRANTY; without even the implied warranty of
10 : // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 : // GNU General Public License for more details.
12 : //
13 : // You should have received a copy of the GNU General Public License
14 : // along with this program; if not, write to the Free Software
15 : // Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
16 :
17 : /** \file
18 : * \brief Implementation of the Snap Communicator class.
19 : *
20 : * This class wraps the C poll() interface in a C++ object with many types
21 : * of objects:
22 : *
23 : * \li Server Connections; for software that want to offer a port to
24 : * which clients can connect to; the server will call accept()
25 : * once a new client connection is ready; this results in a
26 : * Server/Client connection object
27 : * \li Client Connections; for software that want to connect to
28 : * a server; these expect the IP address and port to connect to
29 : * \li Server/Client Connections; for the server when it accepts a new
30 : * connection; in this case the server gets a socket from accept()
31 : * and creates one of these objects to handle the connection
32 : *
33 : * Using the poll() function is the easiest and allows us to listen
34 : * on pretty much any number of sockets (on my server it is limited
35 : * at 16,768 and frankly over 1,000 we probably will start to have
36 : * real slowness issues on small VPN servers.)
37 : */
38 :
39 : // self
40 : //
41 : #include "eventdispatcher/tcp_server_client_buffer_connection.h"
42 :
43 : #include "eventdispatcher/utils.h"
44 :
45 :
46 : // snaplogger lib
47 : //
48 : #include "snaplogger/message.h"
49 :
50 :
51 : // C++ lib
52 : //
53 : #include <algorithm>
54 :
55 :
56 : // last include
57 : //
58 : #include <snapdev/poison.h>
59 :
60 :
61 :
62 : namespace ed
63 : {
64 :
65 :
66 :
67 : /** \brief Initialize a client socket.
68 : *
69 : * The client socket gets initialized with the specified 'socket'
70 : * parameter.
71 : *
72 : * If you are a pure client (opposed to a client that was just accepted)
73 : * you may want to consider using the snap_tcp_client_buffer_connection
74 : * instead. That gives you a way to open the socket from a set of address
75 : * and port definitions among other things.
76 : *
77 : * This initialization, so things work as expected in our environment,
78 : * the function marks the socket as non-blocking. This is important for
79 : * the reader and writer capabilities.
80 : *
81 : * \param[in] client The client to be used for reading and writing.
82 : */
83 0 : tcp_server_client_buffer_connection::tcp_server_client_buffer_connection(tcp_bio_client::pointer_t client)
84 0 : : tcp_server_client_connection(client)
85 : {
86 0 : non_blocking();
87 0 : }
88 :
89 :
90 : /** \brief Check whether this connection still has some input in its buffer.
91 : *
92 : * This function returns true if there is partial incoming data in this
93 : * object's buffer.
94 : *
95 : * \return true if some buffered input is waiting for completion.
96 : */
97 0 : bool tcp_server_client_buffer_connection::has_input() const
98 : {
99 0 : return !f_line.empty();
100 : }
101 :
102 :
103 :
104 : /** \brief Check whether this connection still has some output in its buffer.
105 : *
106 : * This function returns true if there is still some output in the client
107 : * buffer. Output is added by the write() function, which is called by
108 : * the send_message() function.
109 : *
110 : * \return true if some buffered output is waiting to be sent out.
111 : */
112 0 : bool tcp_server_client_buffer_connection::has_output() const
113 : {
114 0 : return !f_output.empty();
115 : }
116 :
117 :
118 :
119 : /** \brief Tells that this connection is a writer when we have data to write.
120 : *
121 : * This function checks to know whether there is data to be writen to
122 : * this connection socket. If so then the function returns true. Otherwise
123 : * it just returns false.
124 : *
125 : * This happens whenever you called the write() function and our cache
126 : * is not empty yet.
127 : *
128 : * \return true if there is data to write to the socket, false otherwise.
129 : */
130 0 : bool tcp_server_client_buffer_connection::is_writer() const
131 : {
132 0 : return get_socket() != -1 && !f_output.empty();
133 : }
134 :
135 :
136 : /** \brief Write data to the connection.
137 : *
138 : * This function can be used to send data to this TCP/IP connection.
139 : * The data is bufferized and as soon as the connection can WRITE
140 : * to the socket, it will wake up and send the data. In other words,
141 : * we cannot just sleep and wait for an answer. The transfer will
142 : * be asynchroneous.
143 : *
144 : * \todo
145 : * Determine whether we may end up with really large buffers that
146 : * grow for a long time. This function only inserts and the
147 : * process_signal() function only reads some of the bytes but it
148 : * does not reduce the size of the buffer until all the data was
149 : * sent.
150 : *
151 : * \param[in] data The pointer to the buffer of data to be sent.
152 : * \param[out] length The number of bytes to send.
153 : */
154 0 : ssize_t tcp_server_client_buffer_connection::write(void const * data, size_t const length)
155 : {
156 0 : if(get_socket() == -1)
157 : {
158 0 : errno = EBADF;
159 0 : return -1;
160 : }
161 :
162 0 : if(data != nullptr && length > 0)
163 : {
164 0 : char const * d(reinterpret_cast<char const *>(data));
165 0 : f_output.insert(f_output.end(), d, d + length);
166 0 : return length;
167 : }
168 :
169 0 : return 0;
170 : }
171 :
172 :
173 : /** \brief Read and process as much data as possible.
174 : *
175 : * This function reads as much incoming data as possible and processes
176 : * it.
177 : *
178 : * If the input includes a newline character ('\n') then this function
179 : * calls the process_line() callback which can further process that
180 : * line of data.
181 : *
182 : * \todo
183 : * Look into a way, if possible, to have a single instantiation since
184 : * as far as I know this code matches the one written in the
185 : * process_read() of the snap_tcp_client_buffer_connection and
186 : * the snap_pipe_buffer_connection classes.
187 : */
188 0 : void tcp_server_client_buffer_connection::process_read()
189 : {
190 : // we read one character at a time until we get a '\n'
191 : // since we have a non-blocking socket we can read as
192 : // much as possible and then check for a '\n' and keep
193 : // any extra data in a cache.
194 : //
195 0 : if(get_socket() != -1)
196 : {
197 0 : int count_lines(0);
198 0 : std::int64_t const date_limit(get_current_date() + get_processing_time_limit());
199 0 : std::vector<char> buffer;
200 0 : buffer.resize(1024);
201 0 : for(;;)
202 : {
203 0 : errno = 0;
204 0 : ssize_t const r(read(&buffer[0], buffer.size()));
205 0 : if(r > 0)
206 : {
207 0 : for(ssize_t position(0); position < r; )
208 : {
209 0 : std::vector<char>::const_iterator it(std::find(buffer.begin() + position, buffer.begin() + r, '\n'));
210 0 : if(it == buffer.begin() + r)
211 : {
212 : // no newline, just add the whole thing
213 0 : f_line += std::string(&buffer[position], r - position);
214 0 : break; // do not waste time, we know we are done
215 : }
216 :
217 : // retrieve the characters up to the newline
218 : // character and process the line
219 : //
220 0 : f_line += std::string(&buffer[position], it - buffer.begin() - position);
221 0 : process_line(f_line);
222 0 : ++count_lines;
223 :
224 : // done with that line
225 : //
226 0 : f_line.clear();
227 :
228 : // we had a newline, we may still have some data
229 : // in that buffer; (+1 to skip the '\n' itself)
230 : //
231 0 : position = it - buffer.begin() + 1;
232 : }
233 :
234 : // when we reach here all the data read in `buffer` is
235 : // now either fully processed or in f_line
236 : //
237 : // TODO: change the way this works so we can test the
238 : // limit after each process_line() call
239 : //
240 0 : if(count_lines >= get_event_limit()
241 0 : || get_current_date() >= date_limit)
242 : {
243 : // we reach one or both limits, stop processing so
244 : // the other events have a chance to run
245 : //
246 0 : break;
247 : }
248 : }
249 0 : else if(r == 0 || errno == 0 || errno == EAGAIN || errno == EWOULDBLOCK)
250 : {
251 : // no more data available at this time
252 : break;
253 : }
254 : else //if(r < 0)
255 : {
256 0 : int const e(errno);
257 : SNAP_LOG_WARNING
258 0 : << "an error occurred while reading from socket (errno: "
259 0 : << e
260 0 : << " -- "
261 0 : << strerror(e)
262 0 : << ").";
263 0 : process_error();
264 0 : return;
265 : }
266 : }
267 : }
268 :
269 : // process next level too
270 0 : tcp_server_client_connection::process_read();
271 : }
272 :
273 :
274 : /** \brief Write to the connection's socket.
275 : *
276 : * This function implementation writes as much data as possible to the
277 : * connection's socket.
278 : *
279 : * This function calls the process_empty_buffer() callback whenever the
280 : * output buffer goes empty.
281 : */
282 0 : void tcp_server_client_buffer_connection::process_write()
283 : {
284 0 : if(get_socket() != -1)
285 : {
286 0 : errno = 0;
287 0 : ssize_t const r(tcp_server_client_connection::write(&f_output[f_position], f_output.size() - f_position));
288 0 : if(r > 0)
289 : {
290 : // some data was written
291 0 : f_position += r;
292 0 : if(f_position >= f_output.size())
293 : {
294 0 : f_output.clear();
295 0 : f_position = 0;
296 0 : process_empty_buffer();
297 : }
298 : }
299 0 : else if(r != 0 && errno != 0 && errno != EAGAIN && errno != EWOULDBLOCK)
300 : {
301 : // connection is considered bad, get rid of it
302 : //
303 0 : int const e(errno);
304 : SNAP_LOG_ERROR
305 0 : << "an error occurred while writing to socket of \""
306 0 : << get_name()
307 0 : << "\" (errno: "
308 0 : << e
309 0 : << " -- "
310 0 : << strerror(e)
311 0 : << ").";
312 0 : process_error();
313 0 : return;
314 : }
315 : }
316 :
317 : // process next level too
318 0 : tcp_server_client_connection::process_write();
319 : }
320 :
321 :
322 : /** \brief The remote hanged up.
323 : *
324 : * This function makes sure that the local connection gets closed properly.
325 : */
326 0 : void tcp_server_client_buffer_connection::process_hup()
327 : {
328 : // this connection is dead...
329 : //
330 0 : close();
331 :
332 0 : tcp_server_client_connection::process_hup();
333 0 : }
334 :
335 :
336 :
337 6 : } // namespace ed
338 : // vim: ts=4 sw=4 et
|