Line data Source code
1 : // Copyright (c) 2012-2022 Made to Order Software Corp. All Rights Reserved
2 : //
3 : // https://snapwebsites.org/project/eventdispatcher
4 : // contact@m2osw.com
5 : //
6 : // This program is free software; you can redistribute it and/or modify
7 : // it under the terms of the GNU General Public License as published by
8 : // the Free Software Foundation; either version 2 of the License, or
9 : // (at your option) any later version.
10 : //
11 : // This program is distributed in the hope that it will be useful,
12 : // but WITHOUT ANY WARRANTY; without even the implied warranty of
13 : // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 : // GNU General Public License for more details.
15 : //
16 : // You should have received a copy of the GNU General Public License
17 : // along with this program; if not, write to the Free Software
18 : // Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
19 :
20 : /** \file
21 : * \brief Event dispatch class.
22 : *
23 : * Class used to handle events.
24 : */
25 :
26 :
27 : // self
28 : //
29 : #include "eventdispatcher/tcp_client.h"
30 :
31 : #include "eventdispatcher/exception.h"
32 : #include "eventdispatcher/utils.h"
33 :
34 :
35 : // snaplogger lib
36 : //
37 : #include <snaplogger/message.h>
38 :
39 :
40 : //// C lib
41 : ////
42 : #include <netdb.h>
43 : #include <arpa/inet.h>
44 : #include <string.h>
45 :
46 :
47 : // last include
48 : //
49 : #include <snapdev/poison.h>
50 :
51 :
52 :
53 : namespace ed
54 : {
55 :
56 :
57 :
58 : /** \class tcp_client
59 : * \brief Create a client socket and connect to a server.
60 : *
61 : * This class is a client socket implementation used to connect to a server.
62 : * The server is expected to be running at the time the client is created
63 : * otherwise it fails connecting.
64 : *
65 : * This class is not appropriate to connect to a server that may come and go
66 : * over time.
67 : */
68 :
69 : /** \brief Construct a tcp_client object.
70 : *
71 : * The tcp_client constructor initializes a TCP client object by connecting
72 : * to the specified server. The server is defined with the \p addr and
73 : * \p port specified as parameters.
74 : *
75 : * \exception tcp_client_server_parameter_error
76 : * This exception is raised if the \p port parameter is out of range or the
77 : * IP address is an empty string or otherwise an invalid address.
78 : *
79 : * \exception tcp_client_server_runtime_error
80 : * This exception is raised if the client cannot create the socket or it
81 : * cannot connect to the server.
82 : *
83 : * \param[in] addr The address of the server to connect to. It must be valid.
84 : * \param[in] port The port the server is listening on.
85 : */
86 0 : tcp_client::tcp_client(std::string const & addr, int port)
87 : : f_port(port)
88 0 : , f_addr(addr)
89 : {
90 0 : if(f_port < 0 || f_port >= 65536)
91 : {
92 0 : throw event_dispatcher_invalid_parameter("invalid port for a client socket");
93 : }
94 0 : if(f_addr.empty())
95 : {
96 0 : throw event_dispatcher_invalid_parameter("an empty address is not valid for a client socket");
97 : }
98 :
99 0 : addrinfo hints;
100 0 : memset(&hints, 0, sizeof(hints));
101 0 : hints.ai_family = AF_UNSPEC;
102 0 : hints.ai_socktype = SOCK_STREAM;
103 0 : hints.ai_protocol = IPPROTO_TCP;
104 0 : std::string const port_str(std::to_string(f_port));
105 0 : addrinfo * addrinfo(nullptr);
106 0 : int const r(getaddrinfo(addr.c_str(), port_str.c_str(), &hints, &addrinfo));
107 0 : raii_addrinfo_t addr_info(addrinfo);
108 0 : if(r != 0
109 0 : || addrinfo == nullptr)
110 : {
111 0 : int const e(errno);
112 0 : std::string err("getaddrinfo() failed to parse the address or port \"");
113 0 : err += addr;
114 0 : err += ":";
115 0 : err += port_str;
116 0 : err += "\" strings (errno: ";
117 0 : err += std::to_string(e);
118 0 : err += " -- ";
119 0 : err += strerror(e);
120 0 : err += ")";
121 0 : SNAP_LOG_FATAL << err << SNAP_LOG_SEND;
122 0 : throw event_dispatcher_runtime_error(err);
123 : }
124 :
125 0 : f_socket.reset(socket(addr_info.get()->ai_family, SOCK_STREAM, IPPROTO_TCP));
126 0 : if(f_socket < 0)
127 : {
128 0 : int const e(errno);
129 0 : SNAP_LOG_FATAL
130 0 : << "socket() failed to create a socket descriptor (errno: "
131 : << e
132 : << " -- "
133 0 : << strerror(e)
134 : << ")"
135 : << SNAP_LOG_SEND;
136 0 : throw event_dispatcher_runtime_error("could not create socket for client");
137 : }
138 :
139 0 : if(connect(f_socket.get(), addr_info.get()->ai_addr, addr_info.get()->ai_addrlen) < 0)
140 : {
141 0 : int const e(errno);
142 0 : SNAP_LOG_FATAL
143 0 : << "connect() failed to connect a socket (errno: "
144 : << e
145 : << " -- "
146 0 : << strerror(e)
147 : << ")"
148 : << SNAP_LOG_SEND;
149 0 : throw event_dispatcher_runtime_error("could not connect client socket to \"" + f_addr + "\"");
150 : }
151 0 : }
152 :
153 : /** \brief Clean up the TCP client object.
154 : *
155 : * This function cleans up the TCP client object by closing the attached socket.
156 : *
157 : * \note
158 : * DO NOT use the shutdown() call since we may end up forking and using
159 : * that connection in the child.
160 : */
161 0 : tcp_client::~tcp_client()
162 : {
163 0 : }
164 :
165 : /** \brief Get the socket descriptor.
166 : *
167 : * This function returns the TCP client socket descriptor. This can be
168 : * used to change the descriptor behavior (i.e. make it non-blocking for
169 : * example.)
170 : *
171 : * \return The socket descriptor.
172 : */
173 0 : int tcp_client::get_socket() const
174 : {
175 0 : return f_socket.get();
176 : }
177 :
178 : /** \brief Get the TCP client port.
179 : *
180 : * This function returns the port used when creating the TCP client.
181 : * Note that this is the port the server is listening to and not the port
182 : * the TCP client is currently connected to.
183 : *
184 : * \return The TCP client port.
185 : */
186 0 : int tcp_client::get_port() const
187 : {
188 0 : return f_port;
189 : }
190 :
191 : /** \brief Get the TCP server address.
192 : *
193 : * This function returns the address used when creating the TCP address as is.
194 : * Note that this is the address of the server where the client is connected
195 : * and not the address where the client is running (although it may be the
196 : * same.)
197 : *
198 : * Use the get_client_addr() function to retrieve the client's TCP address.
199 : *
200 : * \return The TCP client address.
201 : */
202 0 : std::string tcp_client::get_addr() const
203 : {
204 0 : return f_addr;
205 : }
206 :
207 : /** \brief Get the TCP client port.
208 : *
209 : * This function retrieve the port of the client (used on your computer).
210 : * This is retrieved from the socket using the getsockname() function.
211 : *
212 : * \return The port or -1 if it cannot be determined.
213 : */
214 0 : int tcp_client::get_client_port() const
215 : {
216 0 : struct sockaddr_in6 addr;
217 0 : struct sockaddr *a(reinterpret_cast<struct sockaddr *>(&addr));
218 0 : socklen_t len(sizeof(addr));
219 0 : int const r(getsockname(f_socket.get(), a, &len));
220 0 : if(r != 0)
221 : {
222 0 : return -1;
223 : }
224 :
225 : // Note: I know the port is at the exact same location in both
226 : // structures in Linux but it could change on other Unices
227 : //
228 0 : if(a->sa_family == AF_INET
229 0 : && len >= sizeof(sockaddr_in))
230 : {
231 : // IPv4
232 0 : return reinterpret_cast<sockaddr_in const *>(a)->sin_port;
233 : }
234 :
235 0 : if(a->sa_family == AF_INET6
236 0 : && len >= sizeof(sockaddr_in6))
237 : {
238 : // IPv6
239 0 : return addr.sin6_port;
240 : }
241 :
242 0 : return -1;
243 : }
244 :
245 : /** \brief Get the TCP client address.
246 : *
247 : * This function retrieve the IP address of the client (your computer).
248 : * This is retrieved from the socket using the getsockname() function.
249 : *
250 : * \exception event_dispatcher_runtime_error
251 : * The function raises this exception if the address we retrieve doesn't
252 : * match its type properly;
253 : *
254 : * \return The IP address as a string.
255 : */
256 0 : std::string tcp_client::get_client_addr() const
257 : {
258 0 : sockaddr_in6 addr;
259 0 : sockaddr *a(reinterpret_cast<sockaddr *>(&addr));
260 0 : socklen_t len(sizeof(addr));
261 0 : int const r(getsockname(f_socket.get(), a, &len));
262 0 : if(r != 0)
263 : {
264 0 : throw event_dispatcher_runtime_error("address not available");
265 : }
266 :
267 0 : char buf[BUFSIZ];
268 0 : switch(a->sa_family)
269 : {
270 0 : case AF_INET:
271 0 : if(len < sizeof(sockaddr_in))
272 : {
273 0 : throw event_dispatcher_runtime_error("address size incompatible (AF_INET)");
274 : }
275 0 : inet_ntop(
276 : AF_INET
277 0 : , &reinterpret_cast<sockaddr_in *>(a)->sin_addr
278 : , buf
279 : , sizeof(buf));
280 0 : break;
281 :
282 0 : case AF_INET6:
283 0 : if(len < sizeof(sockaddr_in6))
284 : {
285 0 : throw event_dispatcher_runtime_error("address size incompatible (AF_INET6)");
286 : }
287 0 : inet_ntop(AF_INET6, &addr.sin6_addr, buf, sizeof(buf));
288 0 : break;
289 :
290 0 : default:
291 0 : throw event_dispatcher_runtime_error("unknown address family");
292 :
293 : }
294 :
295 0 : return buf;
296 : }
297 :
298 : /** \brief Read data from the socket.
299 : *
300 : * A TCP socket is a stream type of socket and one can read data from it
301 : * as if it were a regular file. This function reads \p size bytes and
302 : * returns. The function returns early if the server closes the connection.
303 : *
304 : * If your socket is blocking, \p size should be exactly what you are
305 : * expecting or this function will block forever or until the server
306 : * closes the connection.
307 : *
308 : * The function returns -1 if an error occurs. The error is available in
309 : * errno as expected in the POSIX interface.
310 : *
311 : * \param[in,out] buf The buffer where the data is read.
312 : * \param[in] size The size of the buffer.
313 : *
314 : * \return The number of bytes read from the socket, or -1 on errors.
315 : */
316 0 : int tcp_client::read(char *buf, size_t size)
317 : {
318 0 : return static_cast<int>(::read(f_socket.get(), buf, size));
319 : }
320 :
321 :
322 : /** \brief Read one line.
323 : *
324 : * This function reads one line from the current location up to the next
325 : * '\\n' character. We do not have any special handling of the '\\r'
326 : * character.
327 : *
328 : * The function may return 0 in which case the server closed the connection.
329 : *
330 : * \param[out] line The resulting line read from the server.
331 : *
332 : * \return The number of bytes read from the socket, or -1 on errors.
333 : * If the function returns 0 or more, then the \p line parameter
334 : * represents the characters read on the network.
335 : */
336 0 : int tcp_client::read_line(std::string& line)
337 : {
338 0 : line.clear();
339 0 : int len(0);
340 : for(;;)
341 : {
342 0 : char c;
343 0 : int r(read(&c, sizeof(c)));
344 0 : if(r <= 0)
345 : {
346 0 : return len == 0 && r < 0 ? -1 : len;
347 : }
348 0 : if(c == '\n')
349 : {
350 0 : return len;
351 : }
352 0 : ++len;
353 0 : line += c;
354 0 : }
355 : }
356 :
357 :
358 : /** \brief Write data to the socket.
359 : *
360 : * A TCP socket is a stream type of socket and one can write data to it
361 : * as if it were a regular file. This function writes \p size bytes to
362 : * the socket and then returns. This function returns early if the server
363 : * closes the connection.
364 : *
365 : * If your socket is not blocking, less than \p size bytes may be written
366 : * to the socket. In that case you are responsible for calling the function
367 : * again to write the remainder of the buffer until the function returns
368 : * a number of bytes written equal to \p size.
369 : *
370 : * The function returns -1 if an error occurs. The error is available in
371 : * errno as expected in the POSIX interface.
372 : *
373 : * \param[in] buf The buffer with the data to send over the socket.
374 : * \param[in] size The number of bytes in buffer to send over the socket.
375 : *
376 : * \return The number of bytes that were actually accepted by the socket
377 : * or -1 if an error occurs.
378 : */
379 0 : int tcp_client::write(const char *buf, size_t size)
380 : {
381 0 : return static_cast<int>(::write(f_socket.get(), buf, size));
382 : }
383 :
384 :
385 :
386 6 : } // namespace ed
387 : // vim: ts=4 sw=4 et
|