efl_net_{socket,dialer,server}_simple: easy to use, buffered network sockets.

The low level I/O primitives are powerful but adds some complexity to
use, for bi-directional streaming communication one ends creating two
Efl.Io.Queue and two Efl.Io.Copier to pipe data to socket when it can
operate.

Then encapsulate the socket using the new Efl.Io.Buffered_Stream, this
will allow the socket, be a dialer or a server client, to be operated
as a single handle that internally carries about the buffering for
you.

As one can see in the examples, compared to their "manual"
alternatives they are very easy to use, ressembling
Ecore_Con_Server/Ecore_Con_Client, but also offers line-based
delimiters and the possibility to let the socket to handle queueing
for you in case you received partial messages (just do not
read/clear/discard the received data).
This commit is contained in:
Gustavo Sverzut Barbieri 2016-11-25 17:18:34 -02:00
parent 46341b329d
commit 167ff29ea0
12 changed files with 1862 additions and 0 deletions

View File

@ -7,15 +7,18 @@ ecore_con_eolian_files = \
lib/ecore_con/efl_network_server.eo \
lib/ecore_con/efl_network_connector.eo \
lib/ecore_con/efl_net_socket.eo \
lib/ecore_con/efl_net_socket_simple.eo \
lib/ecore_con/efl_net_socket_fd.eo \
lib/ecore_con/efl_net_socket_tcp.eo \
lib/ecore_con/efl_net_socket_udp.eo \
lib/ecore_con/efl_net_dialer.eo \
lib/ecore_con/efl_net_dialer_simple.eo \
lib/ecore_con/efl_net_dialer_tcp.eo \
lib/ecore_con/efl_net_dialer_udp.eo \
lib/ecore_con/efl_net_dialer_http.eo \
lib/ecore_con/efl_net_dialer_websocket.eo \
lib/ecore_con/efl_net_server.eo \
lib/ecore_con/efl_net_server_simple.eo \
lib/ecore_con/efl_net_server_fd.eo \
lib/ecore_con/efl_net_server_tcp.eo \
lib/ecore_con/efl_net_server_udp.eo \
@ -92,15 +95,18 @@ static_libs/http-parser/http_parser.h \
lib/ecore_con/ecore_con_private.h \
lib/ecore_con/ecore_con_info.c \
lib/ecore_con/efl_net_socket.c \
lib/ecore_con/efl_net_socket_simple.c \
lib/ecore_con/efl_net_socket_fd.c \
lib/ecore_con/efl_net_socket_tcp.c \
lib/ecore_con/efl_net_socket_udp.c \
lib/ecore_con/efl_net_dialer.c \
lib/ecore_con/efl_net_dialer_simple.c \
lib/ecore_con/efl_net_dialer_tcp.c \
lib/ecore_con/efl_net_dialer_udp.c \
lib/ecore_con/efl_net_dialer_http.c \
lib/ecore_con/efl_net_dialer_websocket.c \
lib/ecore_con/efl_net_server.c \
lib/ecore_con/efl_net_server_simple.c \
lib/ecore_con/efl_net_server_fd.c \
lib/ecore_con/efl_net_server_tcp.c \
lib/ecore_con/efl_net_server_udp.c \

View File

@ -53,10 +53,12 @@
/efl_io_queue_example
/efl_io_buffered_stream_example
/efl_net_server_example
/efl_net_server_simple_example
/efl_net_dialer_http_example
/efl_net_dialer_websocket_example
/efl_net_dialer_websocket_autobahntestee
/efl_net_dialer_udp_example
/efl_net_dialer_simple_example
/efl_net_dialer_unix_example
/ecore_evas_vnc
/efl_net_socket_ssl_dialer_example

View File

@ -86,10 +86,12 @@ efl_io_copier_simple_example \
efl_io_queue_example \
efl_io_buffered_stream_example \
efl_net_server_example \
efl_net_server_simple_example \
efl_net_dialer_http_example \
efl_net_dialer_websocket_example \
efl_net_dialer_websocket_autobahntestee \
efl_net_dialer_udp_example \
efl_net_dialer_simple_example \
efl_net_socket_ssl_dialer_example \
efl_net_socket_ssl_server_example \
efl_net_session_example \
@ -324,6 +326,9 @@ efl_io_buffered_stream_example_LDADD = $(ECORE_CON_COMMON_LDADD)
efl_net_server_example_SOURCES = efl_net_server_example.c
efl_net_server_example_LDADD = $(ECORE_CON_COMMON_LDADD)
efl_net_server_simple_example_SOURCES = efl_net_server_simple_example.c
efl_net_server_simple_example_LDADD = $(ECORE_CON_COMMON_LDADD)
efl_net_dialer_http_example_SOURCES = efl_net_dialer_http_example.c
efl_net_dialer_http_example_LDADD = $(ECORE_CON_COMMON_LDADD)
@ -336,6 +341,9 @@ efl_net_dialer_websocket_autobahntestee_LDADD = $(ECORE_CON_COMMON_LDADD)
efl_net_dialer_udp_example_SOURCES = efl_net_dialer_udp_example.c
efl_net_dialer_udp_example_LDADD = $(ECORE_CON_COMMON_LDADD)
efl_net_dialer_simple_example_SOURCES = efl_net_dialer_simple_example.c
efl_net_dialer_simple_example_LDADD = $(ECORE_CON_COMMON_LDADD)
if ! HAVE_WINDOWS
EXTRA_PROGRAMS += efl_net_dialer_unix_example
efl_net_dialer_unix_example_SOURCES = efl_net_dialer_unix_example.c
@ -413,10 +421,12 @@ efl_io_copier_simple_example.c \
efl_io_queue_example.c \
efl_io_buffered_stream_example.c \
efl_net_server_example.c \
efl_net_server_simple_example.c \
efl_net_dialer_http_example.c \
efl_net_dialer_websocket_example.c \
efl_net_dialer_websocket_autobahntestee.c \
efl_net_dialer_udp_example.c \
efl_net_dialer_simple_example.c \
efl_net_socket_ssl_dialer_example.c \
efl_net_socket_ssl_server_example.c \
efl_net_session_example.c \

View File

@ -0,0 +1,451 @@
#define EFL_BETA_API_SUPPORT 1
#define EFL_EO_API_SUPPORT 1
#include <Ecore.h>
#include <Ecore_Con.h>
#include <Ecore_Getopt.h>
#include <fcntl.h>
#include <ctype.h>
static int retval = EXIT_SUCCESS;
static Eina_Bool do_read = EINA_FALSE;
static Eina_Bool do_discard = EINA_FALSE;
static Eina_Slice line_delm_slice;
static void
_connected(void *data EINA_UNUSED, const Efl_Event *event)
{
fprintf(stderr,
"INFO: connected to '%s' (%s)\n"
"INFO: - local address=%s\n"
"INFO: - do read=%u\n"
"INFO: - do discard=%u\n",
efl_net_dialer_address_dial_get(event->object),
efl_net_socket_address_remote_get(event->object),
efl_net_socket_address_local_get(event->object),
do_read, do_discard);
}
static void
_eos(void *data EINA_UNUSED, const Efl_Event *event)
{
Eina_Slice s;
fprintf(stderr, "INFO: end of stream.\n");
/* on _error() we close it, then do not read as it has nothing */
if (efl_io_closer_closed_get(event->object))
return;
if (efl_io_buffered_stream_slice_get(event->object, &s))
{
fprintf(stderr,
"-- BEGIN RECEIVED DATA --\n"
EINA_SLICE_STR_FMT
"-- END RECEIVED DATA--\n",
EINA_SLICE_STR_PRINT(s));
}
}
static void
_can_read(void *data EINA_UNUSED, const Efl_Event *event)
{
Eina_Bool can_read = efl_io_reader_can_read_get(event->object);
/*
* Since we have more high level events, such as "slice,changed"
* and "line", it's not that interesting to monitor
* "can_read,changed" anymore. We do and print out, but no actual
* reads as we print from _line() or _eos().
*
* But reads can be done as usual, see the '#if' block below.
*/
fprintf(stderr, "INFO: can read=%d\n", can_read);
#if 0
if ((can_read) && (do_read))
{
char buf[4];
Eina_Rw_Slice rw_slice = EINA_SLICE_ARRAY(buf);
Eina_Error err;
do
{
err = efl_io_reader_read(event->object, &rw_slice);
if (err)
{
if (err == EAGAIN)
{
fprintf(stderr, "ERROR: read all available data\n");
break;
}
fprintf(stderr, "ERROR: could not read: %s\n", eina_error_msg_get(err));
retval = EXIT_FAILURE;
ecore_main_loop_quit();
return;
}
fprintf(stderr, "INFO: read '" EINA_SLICE_STR_FMT "'\n", EINA_SLICE_STR_PRINT(rw_slice));
}
while (err == 0);
}
#endif
}
static void
_line(void *data EINA_UNUSED, const Efl_Event *event)
{
const Eina_Slice slice = *(const Eina_Slice *)event->info;
if (!eina_slice_endswith(slice, line_delm_slice))
{
fprintf(stderr, "WARNING: received without line-delimiter '"
EINA_SLICE_STR_FMT "'\n",
EINA_SLICE_STR_PRINT(slice));
}
else
{
Eina_Slice s = slice;
s.len -= line_delm_slice.len;
fprintf(stderr, "INFO: received '" EINA_SLICE_STR_FMT "'\n",
EINA_SLICE_STR_PRINT(s));
}
/*
* If you used the line and it's not interesting anymore, then you
* can discard it.
*
* It has the same effect as calling efl_io_reader_read() as per
* #if block below
*/
if (do_discard)
{
#if 1
efl_io_buffered_stream_discard(event->object, slice.len);
#else
{
/* efl_io_buffered_stream_discard() paired with
* efl_io_buffered_stream_slice_get() + 'slice,changed' or
* 'line' events is a faster alternative than reading,
* since it doesn't copy the data.
*/
char *buf = malloc(slice.len);
Eina_Rw_Slice rw_slice = {
.mem = buf,
.len = slice.len,
};
Eina_Error err = efl_io_reader_read(event->object, &rw_slice);
fprintf(stderr, "INFO: read error=%s '" EINA_SLICE_FMT "'\n", eina_error_msg_get(err) ? eina_error_msg_get(err) : "success", EINA_SLICE_PRINT(rw_slice));
free(buf);
}
#endif
}
}
static void
_resolved(void *data EINA_UNUSED, const Efl_Event *event)
{
fprintf(stderr, "INFO: resolved %s => %s\n",
efl_net_dialer_address_dial_get(event->object),
efl_net_socket_address_remote_get(event->object));
}
static void
_error(void *data EINA_UNUSED, const Efl_Event *event)
{
const Eina_Error *perr = event->info;
fprintf(stderr, "INFO: error: %d '%s'\n", *perr, eina_error_msg_get(*perr));
if (!efl_io_closer_closed_get(event->object))
efl_io_closer_close(event->object);
retval = EXIT_FAILURE;
}
static void
_done_sending(void *data EINA_UNUSED, const Efl_Event *event EINA_UNUSED)
{
fprintf(stderr, "INFO: done sending\n");
if (!do_read)
{
ecore_main_loop_quit();
return;
}
}
static void
_done_receiving(void *data EINA_UNUSED, const Efl_Event *event EINA_UNUSED)
{
fprintf(stderr, "INFO: done receiving\n");
}
static void
_done(void *data EINA_UNUSED, const Efl_Event *event EINA_UNUSED)
{
fprintf(stderr, "INFO: done sending and receiving\n");
ecore_main_loop_quit();
}
EFL_CALLBACKS_ARRAY_DEFINE(dialer_cbs,
{ EFL_NET_DIALER_EVENT_CONNECTED, _connected }, /* optional */
{ EFL_NET_DIALER_EVENT_RESOLVED, _resolved }, /* optional */
{ EFL_IO_READER_EVENT_CAN_READ_CHANGED, _can_read }, /* optional, can be used to read data, here just for monitoring */
{ EFL_IO_READER_EVENT_EOS, _eos }, /* recommended, notifies no more incoming data */
{ EFL_IO_BUFFERED_STREAM_EVENT_LINE, _line }, /* optional, could use 'slice,changed' or 'can_read,changed' instead */
{ EFL_IO_BUFFERED_STREAM_EVENT_ERROR, _error }, /* recommended */
{ EFL_IO_BUFFERED_STREAM_EVENT_WRITE_FINISHED, _done_sending }, /* optional */
{ EFL_IO_BUFFERED_STREAM_EVENT_READ_FINISHED, _done_receiving }, /* optional, same as 'eos' */
{ EFL_IO_BUFFERED_STREAM_EVENT_FINISHED, _done }); /* recommended, notifies both send and receive are finished */
static char *
_unescape(const char *str)
{
char *ret = strdup(str);
char *c, *w;
Eina_Bool escaped = EINA_FALSE;
for (c = ret, w = ret; *c != '\0'; c++)
{
if (escaped)
{
escaped = EINA_FALSE;
switch (*c)
{
case 'n': *w = '\n'; break;
case 'r': *w = '\r'; break;
case 't': *w = '\t'; break;
default: w++; /* no change */
}
w++;
}
else
{
if (*c == '\\')
escaped = EINA_TRUE;
else
w++;
}
}
*w = '\0';
return ret;
}
static const char * protocols[] = {
"tcp",
"udp",
"ssl",
#ifndef _WIN32
"unix",
#endif
NULL
};
static const Ecore_Getopt options = {
"efl_net_dialer_simple_example", /* program name */
NULL, /* usage line */
"1", /* version */
"(C) 2016 Enlightenment Project", /* copyright */
"BSD 2-Clause", /* license */
/* long description, may be multiline and contain \n */
"Example of Efl_Net_Dialer_Simple usage, sending a message and receiving a reply\n",
EINA_FALSE,
{
ECORE_GETOPT_STORE_TRUE('r', "read", "Wait for data to be read."),
ECORE_GETOPT_STORE_TRUE('D', "discard-lines", "Lines that are read are discarded from final output."),
ECORE_GETOPT_APPEND('s', "send", "send the given string to the server once connected.", ECORE_GETOPT_TYPE_STR),
ECORE_GETOPT_STORE_STR('d', "line-delimiter",
"If set will define a line delimiter for copy operation, instead of a fixed chunk size. This will trigger line events."),
ECORE_GETOPT_STORE_ULONG('l', "buffer-limit",
"If set will limit buffer size to this limit of bytes. If used alongside with --line-delimiter and that delimiter was not found but bffer limit was reached, the line event will be triggered without the delimiter at the end."),
ECORE_GETOPT_STORE_ULONG('c', "read-chunk-size",
"If set will change the base chunk size used while reading."),
ECORE_GETOPT_STORE_DOUBLE('i', "inactivity-timeout",
"If greater than zero, specifies the number of seconds without any reads or writes that the dialer will be timed out."),
ECORE_GETOPT_STORE_DOUBLE('t', "connect-timeout",
"If greater than zero, specifies the number of seconds without any reads or writes that the dialer will be timed out."),
ECORE_GETOPT_VERSION('V', "version"),
ECORE_GETOPT_COPYRIGHT('C', "copyright"),
ECORE_GETOPT_LICENSE('L', "license"),
ECORE_GETOPT_HELP('h', "help"),
ECORE_GETOPT_CHOICE_METAVAR(0, NULL, "The dialer protocol.", "protocol",
protocols),
ECORE_GETOPT_STORE_METAVAR_STR(0, NULL,
"The address (URL) to dial", "address"),
ECORE_GETOPT_SENTINEL
}
};
int
main(int argc, char **argv)
{
const Efl_Class *cls;
Eina_List *to_send = NULL;
char *str;
char *line_delimiter_str = NULL;
char *address = NULL;
char *protocol = NULL;
unsigned long buffer_limit = 0;
unsigned long read_chunk_size = 0;
double inactivity_timeout = 0.0;
double connect_timeout = 0.0;
Eina_Bool quit_option = EINA_FALSE;
Ecore_Getopt_Value values[] = {
ECORE_GETOPT_VALUE_BOOL(do_read),
ECORE_GETOPT_VALUE_BOOL(do_discard),
ECORE_GETOPT_VALUE_LIST(to_send),
ECORE_GETOPT_VALUE_STR(line_delimiter_str),
ECORE_GETOPT_VALUE_ULONG(buffer_limit),
ECORE_GETOPT_VALUE_ULONG(read_chunk_size),
ECORE_GETOPT_VALUE_DOUBLE(inactivity_timeout),
ECORE_GETOPT_VALUE_DOUBLE(connect_timeout),
/* standard block to provide version, copyright, license and help */
ECORE_GETOPT_VALUE_BOOL(quit_option), /* -V/--version quits */
ECORE_GETOPT_VALUE_BOOL(quit_option), /* -C/--copyright quits */
ECORE_GETOPT_VALUE_BOOL(quit_option), /* -L/--license quits */
ECORE_GETOPT_VALUE_BOOL(quit_option), /* -h/--help quits */
/* positional argument */
ECORE_GETOPT_VALUE_STR(protocol),
ECORE_GETOPT_VALUE_STR(address),
ECORE_GETOPT_VALUE_NONE /* sentinel */
};
int args;
Eo *dialer, *loop;
Eina_Error err;
ecore_init();
ecore_con_init();
args = ecore_getopt_parse(&options, values, argc, argv);
if (args < 0)
{
fputs("ERROR: Could not parse command line options.\n", stderr);
retval = EXIT_FAILURE;
goto end;
}
if (quit_option) goto end;
loop = ecore_main_loop_get();
args = ecore_getopt_parse_positional(&options, values, argc, argv, args);
if (args < 0)
{
fputs("ERROR: Could not parse positional arguments.\n", stderr);
retval = EXIT_FAILURE;
goto end;
}
if (!protocol)
{
fputs("ERROR: missing protocol.\n", stderr);
retval = EXIT_FAILURE;
goto end;
}
if (strcmp(protocol, "tcp") == 0) cls = EFL_NET_DIALER_TCP_CLASS;
else if (strcmp(protocol, "udp") == 0) cls = EFL_NET_DIALER_UDP_CLASS;
else if (strcmp(protocol, "ssl") == 0) cls = EFL_NET_DIALER_SSL_CLASS;
#ifndef _WIN32
else if (strcmp(protocol, "unix") == 0) cls = EFL_NET_DIALER_UNIX_CLASS;
#endif
else
{
fprintf(stderr, "ERROR: unsupported protocol: %s\n", protocol);
goto end;
}
/* A delimiter is optional, if empty or unset, buffered stream uses
* a copier that will execute writes based on read_chunk_size and
* only event "data" is emitted.
*
* If a line delimiter is set, it will hold writes until the
* delimiter is found, source reached End-of-Stream (eos) or the
* copier buffer limit is reached. The "line" event is emitted.
*/
line_delimiter_str = _unescape(line_delimiter_str ? line_delimiter_str : "\\r\\n");
if (line_delimiter_str)
line_delm_slice = (Eina_Slice)EINA_SLICE_STR(line_delimiter_str);
dialer = efl_add(EFL_NET_DIALER_SIMPLE_CLASS, loop,
efl_name_set(efl_added, "dialer"),
efl_net_dialer_simple_inner_class_set(efl_added, cls), /* alternatively you could create the inner dialer and set with efl_io_buffered_stream_inner_io_set() */
efl_io_buffered_stream_line_delimiter_set(efl_added, &line_delm_slice), /* optional */
efl_io_buffered_stream_max_queue_size_input_set(efl_added, buffer_limit), /* optional, defaults to unlimited */
efl_io_buffered_stream_max_queue_size_output_set(efl_added, buffer_limit), /* optional, defaults to unlimited */
efl_io_buffered_stream_read_chunk_size_set(efl_added, read_chunk_size), /* optional, defaults to 4096 */
efl_io_buffered_stream_inactivity_timeout_set(efl_added, inactivity_timeout), /* optional, defaults to 0.0 (disabled) */
efl_net_dialer_timeout_dial_set(efl_added, connect_timeout), /* optional, defaults to 0.0 (disabled) */
efl_event_callback_array_add(efl_added, dialer_cbs(), NULL));
err = efl_net_dialer_dial(dialer, address);
if (err != 0)
{
fprintf(stderr, "ERROR: could not dial %s '%s': %s",
protocol, address, eina_error_msg_get(err));
goto no_mainloop;
}
/* unlike low-level I/O that wouldn't write data until it's
* connected, the simple dialer will queue it in memory, sending when
* it's ready. Thus just write & forget.
*/
if (!to_send)
{
if (!do_read)
{
Eina_Slice s = EINA_SLICE_STR_LITERAL("Hello World!");
fprintf(stderr, "INFO: sending '" EINA_SLICE_STR_FMT "'\n", EINA_SLICE_STR_PRINT(s));
efl_io_writer_write(dialer, &s, NULL);
if (line_delm_slice.len)
{
Eina_Slice s = line_delm_slice;
efl_io_writer_write(dialer, &s, NULL);
}
}
else
fprintf(stderr, "INFO: nothing to send, just read...\n");
}
else
{
EINA_LIST_FREE(to_send, str)
{
/* ignore empty sends, but add line delimiter, so we can do HTTP's last line :-) */
if (str[0] != '\0')
{
Eina_Slice s = EINA_SLICE_STR(str);
fprintf(stderr, "INFO: sending '" EINA_SLICE_STR_FMT "'\n", EINA_SLICE_STR_PRINT(s));
efl_io_writer_write(dialer, &s, NULL);
}
free(str);
if (line_delm_slice.len)
{
Eina_Slice s = line_delm_slice;
efl_io_writer_write(dialer, &s, NULL);
}
}
}
efl_io_buffered_stream_eos_mark(dialer); /* we're done sending */
ecore_main_loop_begin();
fprintf(stderr, "INFO: main loop finished.\n");
no_mainloop:
efl_del(dialer);
end:
EINA_LIST_FREE(to_send, str) free(str);
ecore_con_shutdown();
ecore_shutdown();
return retval;
}

View File

@ -0,0 +1,559 @@
#define EFL_BETA_API_SUPPORT 1
#define EFL_EO_API_SUPPORT 1
#include <Ecore.h>
#include <Ecore_Con.h>
#include <Ecore_Getopt.h>
#include <fcntl.h>
static int retval = EXIT_SUCCESS;
static Eina_Bool echo = EINA_FALSE;
static double timeout = 10.0;
/* NOTE: client i/o events are only used as debug, you can omit these */
static void
_client_can_read_changed(void *data EINA_UNUSED, const Efl_Event *event)
{
fprintf(stderr, "INFO: client %s can_read=%d\n",
efl_net_socket_address_remote_get(event->object),
efl_io_reader_can_read_get(event->object));
}
static void
_client_can_write_changed(void *data EINA_UNUSED, const Efl_Event *event)
{
fprintf(stderr, "INFO: client %s can_write=%d\n",
efl_net_socket_address_remote_get(event->object),
efl_io_writer_can_write_get(event->object));
}
static void
_client_eos(void *data EINA_UNUSED, const Efl_Event *event)
{
fprintf(stderr, "INFO: client %s eos.\n",
efl_net_socket_address_remote_get(event->object));
}
static void
_client_read_finished(void *data EINA_UNUSED, const Efl_Event *event)
{
Eina_Slice s;
/* on _error() we close it, then do not read as it has nothing */
if (efl_io_closer_closed_get(event->object))
return;
if (echo) return;
if (efl_io_buffered_stream_slice_get(event->object, &s))
{
fprintf(stderr,
"-- BEGIN RECEIVED DATA --\n"
EINA_SLICE_STR_FMT
"-- END RECEIVED DATA--\n",
EINA_SLICE_STR_PRINT(s));
}
}
static void
_client_closed(void *data EINA_UNUSED, const Efl_Event *event)
{
fprintf(stderr, "INFO: client %s closed.\n",
efl_net_socket_address_remote_get(event->object));
}
/* this is the only event that matters, from here we remove our extra
* reference from the client and let it be deleted.
*/
static void
_client_finished(void *data EINA_UNUSED, const Efl_Event *event)
{
fprintf(stderr, "INFO: client %s finished sending and receiving, remove extra reference.\n",
efl_net_socket_address_remote_get(event->object));
if (!efl_io_closer_closed_get(event->object))
efl_io_closer_close(event->object);
efl_unref(event->object);
}
/*
* On errors, such as ETIMEDOUT, we want to close the client if not
* happened yet.
*/
static void
_client_error(void *data EINA_UNUSED, const Efl_Event *event)
{
Eina_Error *perr = event->info;
fprintf(stderr, "ERROR: client %s error: %s\n",
efl_net_socket_address_remote_get(event->object),
eina_error_msg_get(*perr));
if (!efl_io_closer_closed_get(event->object))
efl_io_closer_close(event->object);
}
EFL_CALLBACKS_ARRAY_DEFINE(client_cbs,
{ EFL_IO_READER_EVENT_CAN_READ_CHANGED, _client_can_read_changed },
{ EFL_IO_READER_EVENT_EOS, _client_eos },
{ EFL_IO_WRITER_EVENT_CAN_WRITE_CHANGED, _client_can_write_changed },
{ EFL_IO_CLOSER_EVENT_CLOSED, _client_closed },
{ EFL_IO_BUFFERED_STREAM_EVENT_READ_FINISHED, _client_read_finished },
{ EFL_IO_BUFFERED_STREAM_EVENT_FINISHED, _client_finished },
{ EFL_IO_BUFFERED_STREAM_EVENT_ERROR, _client_error });
/* copier events are of interest, you should hook to at least "done"
* and "error"
*/
/* echo copier is about the same socket, you can close it right away */
static void
_echo_copier_done(void *data EINA_UNUSED, const Efl_Event *event)
{
Eo *copier = event->object;
fprintf(stderr, "INFO: echo copier done, close and del %p\n", copier);
efl_del(copier); /* set to close_on_destructor, will auto close copier and client */
}
static void
_echo_copier_error(void *data EINA_UNUSED, const Efl_Event *event)
{
Eo *copier = event->object;
const Eina_Error *perr = event->info;
if (*perr == ETIMEDOUT)
{
Eo *client = efl_io_copier_source_get(copier);
fprintf(stderr, "INFO: client '%s' timed out, delete it.\n",
efl_net_socket_address_remote_get(client));
efl_del(copier);
return;
}
retval = EXIT_FAILURE;
fprintf(stderr, "ERROR: echo copier %p failed %d '%s', close and del.\n",
copier, *perr, eina_error_msg_get(*perr));
efl_del(copier);
}
EFL_CALLBACKS_ARRAY_DEFINE(echo_copier_cbs,
{ EFL_IO_COPIER_EVENT_DONE, _echo_copier_done },
{ EFL_IO_COPIER_EVENT_ERROR, _echo_copier_error});
/* server events are mandatory, afterall you need to define what's
* going to happen after a client socket is connected. This is the
* "client,add" event.
*
* if clients_limit and clients_reject_excess are set, then
* "client,rejected" is dispatched for rejected sockets, they contain
* the string with socket identification.
*/
static void
_server_client_add(void *data EINA_UNUSED, const Efl_Event *event)
{
Efl_Net_Socket *client = event->info;
fprintf(stderr, "INFO: accepted client %s\n",
efl_net_socket_address_remote_get(client));
/* to use a client, you must efl_ref() it. */
efl_ref(client);
/*
* monitor the client if it's done and for debug purposes
* (optional)
*/
efl_event_callback_array_add(client, client_cbs(), NULL);
efl_io_buffered_stream_inactivity_timeout_set(client, timeout);
/*
* Since sockets are reader/writer/closer objects, we can use the
* Efl_Io_Copier utility.
*/
if (echo)
{
/*
* An echo copier is pretty simple, use the socket as both
* source and destination.
*
* This is the same as efl_net_server_example.c
*/
Eo *echo_copier = efl_add(EFL_IO_COPIER_CLASS, efl_parent_get(client),
efl_io_copier_source_set(efl_added, client),
efl_io_copier_destination_set(efl_added, client),
efl_event_callback_array_add(efl_added, echo_copier_cbs(), client),
efl_io_closer_close_on_destructor_set(efl_added, EINA_TRUE) /* we want to auto-close as we have a single copier */
);
fprintf(stderr, "INFO: using an echo copier=%p for client %s\n",
echo_copier, efl_net_socket_address_remote_get(client));
return;
}
else
{
/*
* Here is where the "simple" kicks in, instead of all the
* complexity listed in efl_net_server_example.c, we just
* "write & forget" the "Hello World!" and wait for all data
* to be received with a simple "finished" event.
*/
Eina_Slice slice = EINA_SLICE_STR_LITERAL("Hello World!");
efl_io_writer_write(client, &slice, NULL);
efl_io_buffered_stream_eos_mark(client); /* say that's it */
}
}
static void
_server_client_rejected(void *data EINA_UNUSED, const Efl_Event *event)
{
const char *client_address = event->info;
fprintf(stderr, "INFO: rejected client %s\n", client_address);
}
static void
_server_error(void *data EINA_UNUSED, const Efl_Event *event)
{
const Eina_Error *perr = event->info;
fprintf(stderr, "ERROR: %d '%s'\n", *perr, eina_error_msg_get(*perr));
retval = EXIT_FAILURE;
ecore_main_loop_quit();
}
static void
_server_serving(void *data EINA_UNUSED, const Efl_Event *event)
{
fprintf(stderr, "INFO: serving at %s\n",
efl_net_server_address_get(event->object));
if (efl_class_get(event->object) == EFL_NET_SERVER_TCP_CLASS)
{
fprintf(stderr,
"TCP options:\n"
" - IPv6 only: %u\n",
efl_net_server_tcp_ipv6_only_get(event->object));
}
else if (efl_class_get(event->object) == EFL_NET_SERVER_UDP_CLASS)
{
Eina_Iterator *it;
const char *str;
fprintf(stderr,
"UDP options:\n"
" - IPv6 only: %u\n"
" - don't route: %u\n"
" - multicast TTL: %u\n"
" - multicast loopback: %u\n"
" - multicast groups:\n",
efl_net_server_udp_ipv6_only_get(event->object),
efl_net_server_udp_dont_route_get(event->object),
efl_net_server_udp_multicast_time_to_live_get(event->object),
efl_net_server_udp_multicast_loopback_get(event->object));
it = efl_net_server_udp_multicast_groups_get(event->object);
EINA_ITERATOR_FOREACH(it, str)
fprintf(stderr, " * %s\n", str);
eina_iterator_free(it);
}
}
EFL_CALLBACKS_ARRAY_DEFINE(server_cbs,
{ EFL_NET_SERVER_EVENT_CLIENT_ADD, _server_client_add },
{ EFL_NET_SERVER_EVENT_CLIENT_REJECTED, _server_client_rejected },
{ EFL_NET_SERVER_EVENT_ERROR, _server_error },
{ EFL_NET_SERVER_EVENT_SERVING, _server_serving });
static const char * protocols[] = {
"tcp",
"udp",
"ssl",
#ifndef _WIN32
"unix",
#endif
NULL
};
static const char *ciphers_strs[] = {
"auto",
"sslv3",
"tlsv1",
"tlsv1.1",
"tlsv1.2",
NULL
};
static const Ecore_Getopt options = {
"efl_net_server_example", /* program name */
NULL, /* usage line */
"1", /* version */
"(C) 2016 Enlightenment Project", /* copyright */
"BSD 2-Clause", /* license */
/* long description, may be multiline and contain \n */
"Example of Efl_Net_Server objects usage.\n"
"\n"
"This example spawns a server of the given protocol at the given address.",
EINA_FALSE,
{
ECORE_GETOPT_STORE_TRUE('e', "echo",
"Behave as 'echo' server, send back to client all the data receive"),
ECORE_GETOPT_STORE_TRUE(0, "socket-activated",
"Try to use $LISTEN_FDS from systemd, if not do a regular serve()"),
ECORE_GETOPT_STORE_UINT('l', "clients-limit",
"If set will limit number of clients to accept"),
ECORE_GETOPT_STORE_TRUE('r', "clients-reject-excess",
"Immediately reject excess clients (over limit)"),
ECORE_GETOPT_STORE_FALSE(0, "ipv4-on-ipv6",
"IPv4 clients will be automatically converted into IPv6 and handled transparently."),
ECORE_GETOPT_STORE_DOUBLE('t', "inactivity-timeout",
"The timeout in seconds to disconnect a client. The timeout is restarted for each client when there is some activity. It's particularly useful for UDP where there is no disconnection event."),
ECORE_GETOPT_VERSION('V', "version"),
ECORE_GETOPT_COPYRIGHT('C', "copyright"),
ECORE_GETOPT_LICENSE('L', "license"),
ECORE_GETOPT_HELP('h', "help"),
ECORE_GETOPT_CATEGORY("udp", "UDP options"),
ECORE_GETOPT_STORE_TRUE(0, "udp-dont-route",
"If true, datagrams won't be routed using a gateway, being restricted to the local network."),
ECORE_GETOPT_STORE_UINT(0, "udp-multicast-ttl",
"Multicast time to live in number of hops from 0-255. Defaults to 1 (only local network)."),
ECORE_GETOPT_STORE_FALSE(0, "udp-multicast-noloopback",
"Disable multicast loopback."),
ECORE_GETOPT_APPEND('M', "udp-multicast-group", "Join a multicast group in the form 'IP@INTERFACE', with optional '@INTERFACE', where INTERFACE is the IP address of the interface to join the multicast.", ECORE_GETOPT_TYPE_STR),
ECORE_GETOPT_CATEGORY("ssl", "SSL options"),
ECORE_GETOPT_CHOICE('c', "ssl-cipher", "Cipher to use, defaults to 'auto'", ciphers_strs),
ECORE_GETOPT_APPEND(0, "ssl-certificate", "certificate path to use.", ECORE_GETOPT_TYPE_STR),
ECORE_GETOPT_APPEND(0, "ssl-private-key", "private key path to use.", ECORE_GETOPT_TYPE_STR),
ECORE_GETOPT_APPEND(0, "ssl-crl", "certificate revogation list to use.", ECORE_GETOPT_TYPE_STR),
ECORE_GETOPT_APPEND(0, "ssl-ca", "certificate authorities path to use.", ECORE_GETOPT_TYPE_STR),
ECORE_GETOPT_CHOICE_METAVAR(0, NULL, "The server protocol.", "protocol",
protocols),
ECORE_GETOPT_STORE_METAVAR_STR(0, NULL,
"The server address to listen, such as "
"IPv4:PORT, [IPv6]:PORT, Unix socket path...",
"address"),
ECORE_GETOPT_SENTINEL
}
};
int
main(int argc, char **argv)
{
const Efl_Class *cls;
char *protocol = NULL;
char *address = NULL;
Eina_List *udp_mcast_groups = NULL;
char *str;
unsigned int clients_limit = 0;
unsigned udp_mcast_ttl = 1;
Eina_Bool clients_reject_excess = EINA_FALSE;
Eina_Bool ipv6_only = EINA_TRUE;
Eina_Bool udp_dont_route = EINA_FALSE;
Eina_Bool udp_mcast_loopback = EINA_TRUE;
Eina_List *certificates = NULL;
Eina_List *private_keys = NULL;
Eina_List *crls = NULL;
Eina_List *cas = NULL;
char *cipher_choice = NULL;
Eina_Bool socket_activated = EINA_FALSE;
Eina_Bool quit_option = EINA_FALSE;
Ecore_Getopt_Value values[] = {
ECORE_GETOPT_VALUE_BOOL(echo),
ECORE_GETOPT_VALUE_BOOL(socket_activated),
ECORE_GETOPT_VALUE_UINT(clients_limit),
ECORE_GETOPT_VALUE_BOOL(clients_reject_excess),
ECORE_GETOPT_VALUE_BOOL(ipv6_only),
ECORE_GETOPT_VALUE_DOUBLE(timeout),
/* standard block to provide version, copyright, license and help */
ECORE_GETOPT_VALUE_BOOL(quit_option), /* -V/--version quits */
ECORE_GETOPT_VALUE_BOOL(quit_option), /* -C/--copyright quits */
ECORE_GETOPT_VALUE_BOOL(quit_option), /* -L/--license quits */
ECORE_GETOPT_VALUE_BOOL(quit_option), /* -h/--help quits */
ECORE_GETOPT_VALUE_BOOL(quit_option), /* category: udp */
ECORE_GETOPT_VALUE_BOOL(udp_dont_route),
ECORE_GETOPT_VALUE_UINT(udp_mcast_ttl),
ECORE_GETOPT_VALUE_BOOL(udp_mcast_loopback),
ECORE_GETOPT_VALUE_LIST(udp_mcast_groups),
ECORE_GETOPT_VALUE_BOOL(quit_option), /* category: ssl */
ECORE_GETOPT_VALUE_STR(cipher_choice),
ECORE_GETOPT_VALUE_LIST(certificates),
ECORE_GETOPT_VALUE_LIST(private_keys),
ECORE_GETOPT_VALUE_LIST(crls),
ECORE_GETOPT_VALUE_LIST(cas),
/* positional argument */
ECORE_GETOPT_VALUE_STR(protocol),
ECORE_GETOPT_VALUE_STR(address),
ECORE_GETOPT_VALUE_NONE /* sentinel */
};
int args;
Eo *simple_server, *server;
Eina_Error err;
ecore_init();
ecore_con_init();
args = ecore_getopt_parse(&options, values, argc, argv);
if (args < 0)
{
fputs("ERROR: Could not parse command line options.\n", stderr);
retval = EXIT_FAILURE;
goto end;
}
if (quit_option) goto end;
args = ecore_getopt_parse_positional(&options, values, argc, argv, args);
if (args < 0)
{
fputs("ERROR: Could not parse positional arguments.\n", stderr);
retval = EXIT_FAILURE;
goto end;
}
if (!protocol)
{
fputs("ERROR: missing protocol.\n", stderr);
retval = EXIT_FAILURE;
goto end;
}
if (strcmp(protocol, "tcp") == 0) cls = EFL_NET_SERVER_TCP_CLASS;
else if (strcmp(protocol, "udp") == 0) cls = EFL_NET_SERVER_UDP_CLASS;
else if (strcmp(protocol, "ssl") == 0) cls = EFL_NET_SERVER_SSL_CLASS;
#ifndef _WIN32
else if (strcmp(protocol, "unix") == 0) cls = EFL_NET_SERVER_UNIX_CLASS;
#endif
else
{
fprintf(stderr, "ERROR: unsupported protocol: %s\n", protocol);
goto end;
}
simple_server = efl_add(EFL_NET_SERVER_SIMPLE_CLASS, ecore_main_loop_get(), /* it's mandatory to use a main loop provider as the server parent */
efl_net_server_simple_inner_class_set(efl_added, cls), /* alternatively you could create the inner server and set with efl_net_server_simple_inner_server_set() */
efl_net_server_clients_limit_set(efl_added,
clients_limit,
clients_reject_excess), /* optional */
efl_event_callback_array_add(efl_added, server_cbs(), NULL)); /* mandatory to have "client,add" in order to be useful */
if (!simple_server)
{
fprintf(stderr, "ERROR: could not create simple server for class %p (%s)\n",
cls, efl_class_name_get(cls));
goto end;
}
/* get the inner server so we can configure it for each protocol */
server = efl_net_server_simple_inner_server_get(simple_server);
if (cls == EFL_NET_SERVER_TCP_CLASS)
{
efl_net_server_tcp_ipv6_only_set(server, ipv6_only);
efl_net_server_fd_close_on_exec_set(server, EINA_TRUE); /* recommended */
efl_net_server_fd_reuse_address_set(server, EINA_TRUE); /* optional, but nice for testing */
efl_net_server_fd_reuse_port_set(server, EINA_TRUE); /* optional, but nice for testing... not secure unless you know what you're doing */
if (socket_activated) efl_net_server_fd_socket_activate(server, address);
}
else if (cls == EFL_NET_SERVER_UDP_CLASS)
{
const Eina_List *lst;
efl_net_server_udp_ipv6_only_set(server, ipv6_only);
efl_net_server_udp_dont_route_set(server, udp_dont_route);
efl_net_server_udp_multicast_time_to_live_set(server, udp_mcast_ttl);
efl_net_server_udp_multicast_loopback_set(server, udp_mcast_loopback);
EINA_LIST_FOREACH(udp_mcast_groups, lst, str)
efl_net_server_udp_multicast_join(server, str);
efl_net_server_fd_close_on_exec_set(server, EINA_TRUE); /* recommended */
efl_net_server_fd_reuse_address_set(server, EINA_TRUE); /* optional, but nice for testing */
efl_net_server_fd_reuse_port_set(server, EINA_TRUE); /* optional, but nice for testing... not secure unless you know what you're doing */
if (socket_activated) efl_net_server_fd_socket_activate(server, address);
}
else if (cls == EFL_NET_SERVER_SSL_CLASS)
{
Eo *ssl_ctx;
Efl_Net_Ssl_Cipher cipher = EFL_NET_SSL_CIPHER_AUTO;
if (cipher_choice)
{
if (strcmp(cipher_choice, "auto") == 0)
cipher = EFL_NET_SSL_CIPHER_AUTO;
else if (strcmp(cipher_choice, "sslv3") == 0)
cipher = EFL_NET_SSL_CIPHER_SSLV3;
else if (strcmp(cipher_choice, "tlsv1") == 0)
cipher = EFL_NET_SSL_CIPHER_TLSV1;
else if (strcmp(cipher_choice, "tlsv1.1") == 0)
cipher = EFL_NET_SSL_CIPHER_TLSV1_1;
else if (strcmp(cipher_choice, "tlsv1.2") == 0)
cipher = EFL_NET_SSL_CIPHER_TLSV1_2;
}
ssl_ctx = efl_add(EFL_NET_SSL_CONTEXT_CLASS, NULL,
efl_net_ssl_context_certificates_set(efl_added, eina_list_iterator_new(certificates)),
efl_net_ssl_context_private_keys_set(efl_added, eina_list_iterator_new(private_keys)),
efl_net_ssl_context_certificate_revogation_lists_set(efl_added, eina_list_iterator_new(crls)),
efl_net_ssl_context_certificate_authorities_set(efl_added, eina_list_iterator_new(cas)),
efl_net_ssl_context_setup(efl_added, cipher, EINA_FALSE /* a server! */));
efl_net_server_ssl_context_set(server, ssl_ctx);
efl_net_server_ssl_close_on_exec_set(server, EINA_TRUE); /* recommended */
efl_net_server_ssl_reuse_address_set(server, EINA_TRUE); /* optional, but nice for testing */
efl_net_server_ssl_reuse_port_set(server, EINA_TRUE); /* optional, but nice for testing... not secure unless you know what you're doing */
if (socket_activated) efl_net_server_ssl_socket_activate(server, address);
}
#ifndef _WIN32
else if (cls == EFL_NET_SERVER_UNIX_CLASS)
{
efl_net_server_unix_unlink_before_bind_set(server, EINA_TRUE); /* makes testing easier */
if (socket_activated) efl_net_server_fd_socket_activate(server, address);
}
#endif
/* an explicit call to efl_net_server_serve() after the object is
* constructed allows for more complex setup, such as interacting
* with the object to add more properties that couldn't be done
* during efl_add().
*/
if (!efl_net_server_serving_get(simple_server))
{
if (socket_activated)
fprintf(stderr, "WARNING: --socket-activated, but not able to use $LISTEN_FDS descriptors. Try to start the server...\n");
err = efl_net_server_serve(simple_server, address);
if (err)
{
fprintf(stderr, "ERROR: could not serve(%s): %s\n",
address, eina_error_msg_get(err));
goto end_server;
}
}
ecore_main_loop_begin();
end_server:
efl_del(simple_server);
simple_server = NULL;
end:
EINA_LIST_FREE(udp_mcast_groups, str)
free(str);
ecore_con_shutdown();
ecore_shutdown();
return retval;
}

View File

@ -8,6 +8,10 @@
#include "efl_net_dialer.eo.h"
#include "efl_net_server.eo.h"
#include "efl_net_socket_simple.eo.h"
#include "efl_net_dialer_simple.eo.h"
#include "efl_net_server_simple.eo.h"
#include "efl_net_socket_fd.eo.h"
#include "efl_net_server_fd.eo.h"

View File

@ -0,0 +1,367 @@
#ifdef HAVE_CONFIG_H
# include <config.h>
#endif
#include "Ecore.h"
#include "Ecore_Con.h"
#include "ecore_con_private.h"
typedef struct
{
const Efl_Class *inner_class;
Eina_Stringshare *proxy_url;
double dial_timeout;
double inactivity_timeout;
size_t max_queue_size_input;
size_t max_queue_size_output;
size_t read_chunk_size;
Eina_Slice line_delimiter;
struct {
Eina_Bool proxy_url;
Eina_Bool dial_timeout;
Eina_Bool inactivity_timeout;
Eina_Bool max_queue_size_input;
Eina_Bool max_queue_size_output;
Eina_Bool read_chunk_size;
Eina_Bool line_delimiter;
} pending;
} Efl_Net_Dialer_Simple_Data;
#define MY_CLASS EFL_NET_DIALER_SIMPLE_CLASS
static void
_efl_net_dialer_simple_inner_io_resolved(void *data, const Efl_Event *event)
{
Eo *o = data;
efl_event_callback_call(o, EFL_NET_DIALER_EVENT_RESOLVED, event->info);
}
static void
_efl_net_dialer_simple_inner_io_error(void *data, const Efl_Event *event)
{
Eo *o = data;
efl_event_callback_call(o, EFL_NET_DIALER_EVENT_ERROR, event->info);
efl_event_callback_call(o, EFL_IO_BUFFERED_STREAM_EVENT_ERROR, event->info);
}
static void
_efl_net_dialer_simple_inner_io_connected(void *data, const Efl_Event *event)
{
Eo *o = data;
efl_event_callback_call(o, EFL_NET_DIALER_EVENT_CONNECTED, event->info);
}
EFL_CALLBACKS_ARRAY_DEFINE(_efl_net_dialer_simple_inner_io_cbs,
{ EFL_NET_DIALER_EVENT_RESOLVED, _efl_net_dialer_simple_inner_io_resolved },
{ EFL_NET_DIALER_EVENT_ERROR, _efl_net_dialer_simple_inner_io_error },
{ EFL_NET_DIALER_EVENT_CONNECTED, _efl_net_dialer_simple_inner_io_connected });
EOLIAN static Efl_Object *
_efl_net_dialer_simple_efl_object_finalize(Eo *o, Efl_Net_Dialer_Simple_Data *pd)
{
if (efl_io_buffered_stream_inner_io_get(o)) goto end;
if (!pd->inner_class)
{
ERR("no valid dialer was set with efl_io_buffered_stream_inner_io_set() and no class set with efl_net_dialer_simple_inner_class_set()!");
return NULL;
}
else
{
Eo *dialer = efl_add(pd->inner_class, o);
EINA_SAFETY_ON_NULL_RETURN_VAL(dialer, NULL);
if (!efl_isa(dialer, EFL_NET_DIALER_INTERFACE))
{
ERR("class %s=%p doesn't implement Efl.Net.Dialer interface!", efl_class_name_get(pd->inner_class), pd->inner_class);
efl_del(dialer);
return NULL;
}
DBG("created new inner dialer %p (%s)", dialer, efl_class_name_get(efl_class_get(dialer)));
efl_io_buffered_stream_inner_io_set(o, dialer);
}
end:
return efl_finalize(efl_super(o, MY_CLASS));
}
EOLIAN static void
_efl_net_dialer_simple_efl_object_destructor(Eo *o, Efl_Net_Dialer_Simple_Data *pd)
{
Eo *inner_io;
if (pd->inner_class) pd->inner_class = NULL;
eina_stringshare_replace(&pd->proxy_url, NULL);
if (pd->line_delimiter.mem)
{
free((void *)pd->line_delimiter.mem);
pd->line_delimiter.mem = NULL;
}
inner_io = efl_io_buffered_stream_inner_io_get(o);
if (inner_io)
{
efl_event_callback_array_del(inner_io, _efl_net_dialer_simple_inner_io_cbs(), o);
if (efl_parent_get(inner_io) == o)
efl_parent_set(inner_io, NULL);
}
efl_destructor(efl_super(o, EFL_NET_DIALER_SIMPLE_CLASS));
}
EOLIAN static void
_efl_net_dialer_simple_efl_io_buffered_stream_inner_io_set(Eo *o, Efl_Net_Dialer_Simple_Data *pd, Efl_Object *io)
{
EINA_SAFETY_ON_FALSE_RETURN(efl_isa(io, EFL_NET_DIALER_INTERFACE));
efl_io_buffered_stream_inner_io_set(efl_super(o, MY_CLASS), io);
efl_event_callback_array_add(io, _efl_net_dialer_simple_inner_io_cbs(), o);
/* apply pending dialer values */
if (pd->pending.proxy_url)
{
pd->pending.proxy_url = EINA_FALSE;
efl_net_dialer_proxy_set(io, pd->proxy_url);
eina_stringshare_replace(&pd->proxy_url, NULL);
}
if (pd->pending.dial_timeout)
{
pd->pending.dial_timeout = EINA_FALSE;
efl_net_dialer_timeout_dial_set(io, pd->dial_timeout);
}
/* apply pending io buffered stream (own) values */
if (pd->pending.inactivity_timeout)
{
pd->pending.inactivity_timeout = EINA_FALSE;
efl_io_buffered_stream_inactivity_timeout_set(o, pd->inactivity_timeout);
}
if (pd->pending.max_queue_size_input)
{
pd->pending.max_queue_size_input = EINA_FALSE;
efl_io_buffered_stream_max_queue_size_input_set(o, pd->max_queue_size_input);
}
if (pd->pending.max_queue_size_output)
{
pd->pending.max_queue_size_output = EINA_FALSE;
efl_io_buffered_stream_max_queue_size_output_set(o, pd->max_queue_size_output);
}
if (pd->pending.read_chunk_size)
{
pd->pending.read_chunk_size = EINA_FALSE;
efl_io_buffered_stream_read_chunk_size_set(o, pd->read_chunk_size);
}
if (pd->pending.line_delimiter)
{
pd->pending.line_delimiter = EINA_FALSE;
efl_io_buffered_stream_line_delimiter_set(o, &pd->line_delimiter);
free((void *)pd->line_delimiter.mem);
pd->line_delimiter.mem = NULL;
}
}
EOLIAN static Eina_Error
_efl_net_dialer_simple_efl_net_dialer_dial(Eo *o, Efl_Net_Dialer_Simple_Data *pd EINA_UNUSED, const char *address)
{
return efl_net_dialer_dial(efl_io_buffered_stream_inner_io_get(o), address);
}
EOLIAN static const char *
_efl_net_dialer_simple_efl_net_dialer_address_dial_get(Eo *o, Efl_Net_Dialer_Simple_Data *pd EINA_UNUSED)
{
return efl_net_dialer_address_dial_get(efl_io_buffered_stream_inner_io_get(o));
}
EOLIAN static Eina_Bool
_efl_net_dialer_simple_efl_net_dialer_connected_get(Eo *o, Efl_Net_Dialer_Simple_Data *pd EINA_UNUSED)
{
return efl_net_dialer_connected_get(efl_io_buffered_stream_inner_io_get(o));
}
EOLIAN static void
_efl_net_dialer_simple_efl_net_dialer_proxy_set(Eo *o, Efl_Net_Dialer_Simple_Data *pd, const char *proxy_url)
{
Eo *inner_io = efl_io_buffered_stream_inner_io_get(o);
if (!inner_io)
{
eina_stringshare_replace(&pd->proxy_url, proxy_url);
pd->pending.proxy_url = EINA_TRUE;
return;
}
efl_net_dialer_proxy_set(inner_io, proxy_url);
}
EOLIAN static const char *
_efl_net_dialer_simple_efl_net_dialer_proxy_get(Eo *o, Efl_Net_Dialer_Simple_Data *pd)
{
Eo *inner_io = efl_io_buffered_stream_inner_io_get(o);
if (!inner_io) return pd->proxy_url;
return efl_net_dialer_proxy_get(inner_io);
}
EOLIAN static void
_efl_net_dialer_simple_efl_net_dialer_timeout_dial_set(Eo *o, Efl_Net_Dialer_Simple_Data *pd, double seconds)
{
Eo *inner_io = efl_io_buffered_stream_inner_io_get(o);
if (!inner_io)
{
pd->dial_timeout = seconds;
pd->pending.dial_timeout = EINA_TRUE;
return;
}
efl_net_dialer_timeout_dial_set(inner_io, seconds);
}
EOLIAN static double
_efl_net_dialer_simple_efl_net_dialer_timeout_dial_get(Eo *o, Efl_Net_Dialer_Simple_Data *pd)
{
Eo *inner_io = efl_io_buffered_stream_inner_io_get(o);
if (!inner_io) return pd->dial_timeout;
return efl_net_dialer_timeout_dial_get(inner_io);
}
EOLIAN static void
_efl_net_dialer_simple_efl_io_buffered_stream_inactivity_timeout_set(Eo *o, Efl_Net_Dialer_Simple_Data *pd, double seconds)
{
Eo *inner_io = efl_io_buffered_stream_inner_io_get(o);
if (!inner_io)
{
pd->inactivity_timeout = seconds;
pd->pending.inactivity_timeout = EINA_TRUE;
return;
}
efl_io_buffered_stream_inactivity_timeout_set(efl_super(o, MY_CLASS), seconds);
}
EOLIAN static double
_efl_net_dialer_simple_efl_io_buffered_stream_inactivity_timeout_get(Eo *o, Efl_Net_Dialer_Simple_Data *pd)
{
Eo *inner_io = efl_io_buffered_stream_inner_io_get(o);
if (!inner_io) return pd->inactivity_timeout;
return efl_io_buffered_stream_inactivity_timeout_get(efl_super(o, MY_CLASS));
}
EOLIAN static void
_efl_net_dialer_simple_efl_io_buffered_stream_max_queue_size_input_set(Eo *o, Efl_Net_Dialer_Simple_Data *pd, size_t size)
{
Eo *inner_io = efl_io_buffered_stream_inner_io_get(o);
if (!inner_io)
{
pd->max_queue_size_input = size;
pd->pending.max_queue_size_input = EINA_TRUE;
return;
}
efl_io_buffered_stream_max_queue_size_input_set(efl_super(o, MY_CLASS), size);
}
EOLIAN static size_t
_efl_net_dialer_simple_efl_io_buffered_stream_max_queue_size_input_get(Eo *o, Efl_Net_Dialer_Simple_Data *pd)
{
Eo *inner_io = efl_io_buffered_stream_inner_io_get(o);
if (!inner_io) return pd->max_queue_size_input;
return efl_io_buffered_stream_max_queue_size_input_get(efl_super(o, MY_CLASS));
}
EOLIAN static void
_efl_net_dialer_simple_efl_io_buffered_stream_max_queue_size_output_set(Eo *o, Efl_Net_Dialer_Simple_Data *pd, size_t size)
{
Eo *inner_io = efl_io_buffered_stream_inner_io_get(o);
if (!inner_io)
{
pd->max_queue_size_output = size;
pd->pending.max_queue_size_output = EINA_TRUE;
return;
}
efl_io_buffered_stream_max_queue_size_output_set(efl_super(o, MY_CLASS), size);
}
EOLIAN static size_t
_efl_net_dialer_simple_efl_io_buffered_stream_max_queue_size_output_get(Eo *o, Efl_Net_Dialer_Simple_Data *pd)
{
Eo *inner_io = efl_io_buffered_stream_inner_io_get(o);
if (!inner_io) return pd->max_queue_size_output;
return efl_io_buffered_stream_max_queue_size_output_get(efl_super(o, MY_CLASS));
}
EOLIAN static void
_efl_net_dialer_simple_efl_io_buffered_stream_read_chunk_size_set(Eo *o, Efl_Net_Dialer_Simple_Data *pd, size_t size)
{
Eo *inner_io = efl_io_buffered_stream_inner_io_get(o);
if (!inner_io)
{
pd->read_chunk_size = size;
pd->pending.read_chunk_size = EINA_TRUE;
return;
}
efl_io_buffered_stream_read_chunk_size_set(efl_super(o, MY_CLASS), size);
}
EOLIAN static size_t
_efl_net_dialer_simple_efl_io_buffered_stream_read_chunk_size_get(Eo *o, Efl_Net_Dialer_Simple_Data *pd)
{
Eo *inner_io = efl_io_buffered_stream_inner_io_get(o);
if (!inner_io) return pd->read_chunk_size;
return efl_io_buffered_stream_read_chunk_size_get(efl_super(o, MY_CLASS));
}
EOLIAN static void
_efl_net_dialer_simple_efl_io_buffered_stream_line_delimiter_set(Eo *o, Efl_Net_Dialer_Simple_Data *pd, const Eina_Slice *slice)
{
Eo *inner_io = efl_io_buffered_stream_inner_io_get(o);
if (!inner_io)
{
free((void *)pd->line_delimiter.mem);
if ((!slice) || (!slice->len))
{
pd->line_delimiter.mem = NULL;
pd->line_delimiter.len = 0;
}
else
{
char *mem;
pd->line_delimiter.mem = mem = malloc(slice->len + 1);
EINA_SAFETY_ON_NULL_RETURN(pd->line_delimiter.mem);
memcpy(mem, slice->mem, slice->len);
mem[slice->len] = '\0';
pd->line_delimiter.len = slice->len;
}
pd->pending.line_delimiter = EINA_TRUE;
return;
}
efl_io_buffered_stream_line_delimiter_set(efl_super(o, MY_CLASS), slice);
}
EOLIAN static const Eina_Slice *
_efl_net_dialer_simple_efl_io_buffered_stream_line_delimiter_get(Eo *o, Efl_Net_Dialer_Simple_Data *pd)
{
Eo *inner_io = efl_io_buffered_stream_inner_io_get(o);
if (!inner_io) return &pd->line_delimiter;
return efl_io_buffered_stream_line_delimiter_get(efl_super(o, MY_CLASS));
}
EOLIAN static void
_efl_net_dialer_simple_inner_class_set(Eo *o, Efl_Net_Dialer_Simple_Data *pd, const Efl_Class *klass)
{
EINA_SAFETY_ON_TRUE_RETURN(efl_finalized_get(o));
EINA_SAFETY_ON_NULL_RETURN(klass);
pd->inner_class = klass;
DBG("%p inner_class=%p %s", o, klass, efl_class_name_get(klass));
}
EOLIAN static const Efl_Class *
_efl_net_dialer_simple_inner_class_get(Eo *o EINA_UNUSED, Efl_Net_Dialer_Simple_Data *pd)
{
return pd->inner_class;
}
#include "efl_net_dialer_simple.eo.c"

View File

@ -0,0 +1,85 @@
class Efl.Net.Dialer.Simple (Efl.Net.Socket.Simple, Efl.Net.Dialer) {
[[Connects to a remote server offering an easy to use, buffered I/O.
The simple dialer is based on @Efl.Net.Socket.Simple, that
encapsulates an actual @Efl.Net.Socket, and uses it with an
@Efl.Io.Buffered_Stream, which creates an input @Efl.Io.Queue,
an output @Efl.Io.Queue and these are linked using a receiver
and a sender @Efl.Io.Copier.
The idea is that unlike traditional @Efl.Net.Socket that will
attempt to write directly to socket and thus may take less data
than requested, this one will keep the pending data in its own
buffer, feeding to the actual socket when it
@Efl.Io.Writer.can_write. That makes its operation much simpler
as @Efl.Io.Writer.write will always take the full data -- allows
"write and forget", if unlimited (see
@Efl.Io.Buffered_Stream.max_queue_size_output).
Reading is also much simpler since received data is kept in an
@Efl.Io.Queue, thus its size can be queried with
@Efl.Io.Buffered_Stream.pending_read and read with
@Efl.Io.Reader.read or peeked with
@Efl.Io.Buffered_Stream.slice_get, then discarded with
@Efl.Io.Buffered_Stream.discard or
@Efl.Io.Buffered_Stream.clear.
Then when waiting for a complete message, just peek at its
contents, if not complete do nothing, if complete then either
@Efl.Io.Reader.read to get a copy or manipulate a read-only
reference from @Efl.Io.Buffered_Stream.slice_get and then
@Efl.Io.Buffered_Stream.discard
The actual dialer is created using the class given as the
constructor property @.inner_class and can be retrieved with
@Efl.Io.Buffered_Stream.inner_io, which should be used with
care, like extra configuration before @Efl.Net.Dialer.dial is
called.
If your object class requires some constructor-only properties
to be set prior to @Efl.Object.finalize, then use
@Efl.Io.Buffered_Stream.inner_io directly providing an already
created dialer.
@since 1.19
]]
methods {
@property inner_class {
[[The class used to create @Efl.Io.Buffered_Stream.inner_io if none was provided.
This class could be set at construction time and will be
used to create the inner socket during
@Efl.Object.finalize.
It is a helper for users, removing the burden to
manually create and specify a dialer object.
]]
get {
[[The internal class used to create the inner dialer.]]
}
set {
[[Constructor-only property to define the class used to create the inner dialer.]]
}
values {
klass: const(Efl.Class); [[The class]]
}
}
}
implements {
Efl.Object.finalize;
Efl.Object.destructor;
Efl.Io.Buffered_Stream.inner_io.set;
Efl.Net.Dialer.dial;
Efl.Net.Dialer.address_dial.get;
Efl.Net.Dialer.connected.get;
Efl.Net.Dialer.proxy;
Efl.Net.Dialer.timeout_dial;
Efl.Io.Buffered_Stream.inactivity_timeout;
Efl.Io.Buffered_Stream.max_queue_size_input;
Efl.Io.Buffered_Stream.max_queue_size_output;
Efl.Io.Buffered_Stream.read_chunk_size;
Efl.Io.Buffered_Stream.line_delimiter;
}
}

View File

@ -0,0 +1,243 @@
#define EFL_NET_SERVER_PROTECTED 1
#ifdef HAVE_CONFIG_H
# include <config.h>
#endif
#include "Ecore.h"
#include "Ecore_Con.h"
#include "ecore_con_private.h"
typedef struct
{
const Efl_Class *inner_class;
Eo *inner_server;
} Efl_Net_Server_Simple_Data;
#define MY_CLASS EFL_NET_SERVER_SIMPLE_CLASS
static void
_efl_net_server_simple_client_event_closed(void *data, const Efl_Event *event)
{
Eo *server = data;
Eo *client = event->object;
efl_event_callback_del(client, EFL_IO_CLOSER_EVENT_CLOSED, _efl_net_server_simple_client_event_closed, server);
if (efl_parent_get(client) == server)
efl_parent_set(client, NULL);
/* do NOT change count as we're using the underlying server's count */
//efl_net_server_clients_count_set(server, efl_net_server_clients_count_get(server) - 1);
}
static Eina_Bool
_efl_net_server_simple_efl_net_server_client_announce(Eo *o, Efl_Net_Server_Simple_Data *pd EINA_UNUSED, Eo *client)
{
EINA_SAFETY_ON_NULL_RETURN_VAL(client, EINA_FALSE);
EINA_SAFETY_ON_FALSE_GOTO(efl_isa(client, EFL_NET_SOCKET_SIMPLE_CLASS), wrong_type);
EINA_SAFETY_ON_FALSE_GOTO(efl_parent_get(client) == o, wrong_parent);
efl_event_callback_call(o, EFL_NET_SERVER_EVENT_CLIENT_ADD, client);
if (efl_parent_get(client) != o)
{
DBG("client %s was reparented! Ignoring it...",
efl_net_socket_address_remote_get(client));
return EINA_TRUE;
}
if (efl_ref_get(client) == 1) /* users must take a reference themselves */
{
DBG("client %s was not handled, closing it...",
efl_net_socket_address_remote_get(client));
efl_del(client);
return EINA_FALSE;
}
else if (efl_io_closer_closed_get(client))
{
DBG("client %s was closed from 'client,add', delete it...",
efl_net_socket_address_remote_get(client));
efl_del(client);
return EINA_FALSE;
}
/* do NOT change count as we're using the underlying server's count */
//efl_net_server_clients_count_set(o, efl_net_server_clients_count_get(o) + 1);
efl_event_callback_add(client, EFL_IO_CLOSER_EVENT_CLOSED, _efl_net_server_simple_client_event_closed, o);
return EINA_TRUE;
wrong_type:
ERR("%p client %p (%s) doesn't implement Efl.Net.Socket.Simple class, deleting it.", o, client, efl_class_name_get(efl_class_get(client)));
efl_del(client);
return EINA_FALSE;
wrong_parent:
ERR("%p client %p (%s) parent=%p is not our child, deleting it.", o, client, efl_class_name_get(efl_class_get(client)), efl_parent_get(client));
efl_del(client);
return EINA_FALSE;
}
static void
_efl_net_server_simple_inner_server_client_add(void *data, const Efl_Event *event)
{
Eo *o = data;
Eo *client_inner = event->info;
Eo *client_simple;
const char *addr = efl_net_socket_address_remote_get(client_inner);
client_simple = efl_add(EFL_NET_SOCKET_SIMPLE_CLASS, o,
efl_io_buffered_stream_inner_io_set(efl_added, client_inner));
if (!client_simple)
{
ERR("simple server %p could not wrap client %p '%s'", o, client_inner, addr);
efl_event_callback_call(o, EFL_NET_SERVER_EVENT_CLIENT_REJECTED, (void *)addr);
return;
}
efl_net_server_client_announce(o, client_simple);
}
static void
_efl_net_server_simple_inner_server_client_rejected(void *data, const Efl_Event *event)
{
Eo *o = data;
efl_event_callback_call(o, EFL_NET_SERVER_EVENT_CLIENT_REJECTED, event->info);
}
static void
_efl_net_server_simple_inner_server_error(void *data, const Efl_Event *event)
{
Eo *o = data;
efl_event_callback_call(o, EFL_NET_SERVER_EVENT_ERROR, event->info);
}
static void
_efl_net_server_simple_inner_server_serving(void *data, const Efl_Event *event)
{
Eo *o = data;
efl_event_callback_call(o, EFL_NET_SERVER_EVENT_SERVING, event->info);
}
EFL_CALLBACKS_ARRAY_DEFINE(_efl_net_server_simple_inner_server_cbs,
{ EFL_NET_SERVER_EVENT_CLIENT_ADD, _efl_net_server_simple_inner_server_client_add },
{ EFL_NET_SERVER_EVENT_CLIENT_REJECTED, _efl_net_server_simple_inner_server_client_rejected },
{ EFL_NET_SERVER_EVENT_ERROR, _efl_net_server_simple_inner_server_error },
{ EFL_NET_SERVER_EVENT_SERVING, _efl_net_server_simple_inner_server_serving });
EOLIAN static Efl_Object *
_efl_net_server_simple_efl_object_finalize(Eo *o, Efl_Net_Server_Simple_Data *pd)
{
if (pd->inner_server) goto end;
if (!pd->inner_class)
{
ERR("no valid server was set with efl_net_server_simple_inner_server_set() and no class set with efl_net_server_simple_inner_class_set()!");
return NULL;
}
else
{
Eo *server = efl_add(pd->inner_class, o);
EINA_SAFETY_ON_NULL_RETURN_VAL(server, NULL);
if (!efl_isa(server, EFL_NET_SERVER_INTERFACE))
{
ERR("class %s=%p doesn't implement Efl.Net.Server interface!", efl_class_name_get(pd->inner_class), pd->inner_class);
efl_del(server);
return NULL;
}
DBG("created new inner server %p (%s)", server, efl_class_name_get(efl_class_get(server)));
efl_net_server_simple_inner_server_set(o, server);
}
end:
return efl_finalize(efl_super(o, MY_CLASS));
}
EOLIAN static void
_efl_net_server_simple_efl_object_destructor(Eo *o, Efl_Net_Server_Simple_Data *pd)
{
if (pd->inner_class) pd->inner_class = NULL;
if (pd->inner_server)
{
efl_event_callback_array_del(pd->inner_server, _efl_net_server_simple_inner_server_cbs(), o);
if (efl_parent_get(pd->inner_server) == o)
efl_parent_set(pd->inner_server, NULL);
}
efl_destructor(efl_super(o, MY_CLASS));
}
EOLIAN static Eina_Error
_efl_net_server_simple_efl_net_server_serve(Eo *o EINA_UNUSED, Efl_Net_Server_Simple_Data *pd, const char *address)
{
return efl_net_server_serve(pd->inner_server, address);
}
EOLIAN static const char *
_efl_net_server_simple_efl_net_server_address_get(Eo *o EINA_UNUSED, Efl_Net_Server_Simple_Data *pd)
{
return efl_net_server_address_get(pd->inner_server);
}
EOLIAN static unsigned int
_efl_net_server_simple_efl_net_server_clients_count_get(Eo *o EINA_UNUSED, Efl_Net_Server_Simple_Data *pd)
{
return efl_net_server_clients_count_get(pd->inner_server);
}
EOLIAN static void
_efl_net_server_simple_efl_net_server_clients_limit_set(Eo *o EINA_UNUSED, Efl_Net_Server_Simple_Data *pd, unsigned int limit, Eina_Bool reject_excess)
{
efl_net_server_clients_limit_set(pd->inner_server, limit, reject_excess);
}
EOLIAN static void
_efl_net_server_simple_efl_net_server_clients_limit_get(Eo *o EINA_UNUSED, Efl_Net_Server_Simple_Data *pd, unsigned int *limit, Eina_Bool *reject_excess)
{
efl_net_server_clients_limit_get(pd->inner_server, limit, reject_excess);
}
EOLIAN static Eina_Bool
_efl_net_server_simple_efl_net_server_serving_get(Eo *o EINA_UNUSED, Efl_Net_Server_Simple_Data *pd)
{
return efl_net_server_serving_get(pd->inner_server);
}
EOLIAN static void
_efl_net_server_simple_inner_class_set(Eo *o, Efl_Net_Server_Simple_Data *pd, const Efl_Class *klass)
{
EINA_SAFETY_ON_TRUE_RETURN(efl_finalized_get(o));
EINA_SAFETY_ON_NULL_RETURN(klass);
pd->inner_class = klass;
DBG("%p inner_class=%p %s", o, klass, efl_class_name_get(klass));
}
EOLIAN static const Efl_Class *
_efl_net_server_simple_inner_class_get(Eo *o EINA_UNUSED, Efl_Net_Server_Simple_Data *pd)
{
return pd->inner_class;
}
EOLIAN static void
_efl_net_server_simple_inner_server_set(Eo *o, Efl_Net_Server_Simple_Data *pd, Efl_Object *server)
{
EINA_SAFETY_ON_TRUE_RETURN(efl_finalized_get(o));
EINA_SAFETY_ON_NULL_RETURN(server);
EINA_SAFETY_ON_TRUE_RETURN(pd->inner_server != NULL);
EINA_SAFETY_ON_FALSE_RETURN(efl_isa(server, EFL_NET_SERVER_INTERFACE));
pd->inner_server = efl_ref(server);
efl_event_callback_array_add(server, _efl_net_server_simple_inner_server_cbs(), o);
DBG("%p inner_server=%p (%s)", o, server, efl_class_name_get(efl_class_get(server)));
}
EOLIAN static Efl_Object *
_efl_net_server_simple_inner_server_get(Eo *o EINA_UNUSED, Efl_Net_Server_Simple_Data *pd)
{
return pd->inner_server;
}
#include "efl_net_server_simple.eo.c"

View File

@ -0,0 +1,55 @@
class Efl.Net.Server.Simple (Efl.Loop_User, Efl.Net.Server) {
[[A network server wrapper that creates clients based on @Efl.Net.Socket.Simple.
This is just a wrapper server, it will take an actual server
using @.inner_server or create one using @.inner_class.
@since 1.19
]]
methods {
@property inner_class {
[[The class used to create @.inner_server if none was provided.
This class must be set at construction time and will be
used to create the inner socket during
@Efl.Object.finalize.
It is a helper for users, removing the burden to
manually create and specify a dialer object.
]]
get {
[[The internal class used to create the inner dialer.]]
}
set {
[[Constructor-only property to define the class used to create the inner dialer.]]
}
values {
klass: const(Efl.Class); [[The class]]
}
}
@property inner_server {
[[The inner @Efl.Net.Server this wrapper operates on.]]
get {
[[The internal server used for actual operations, use with care!]]
}
set {
[[Constructor-only property to set the inner_server.]]
}
values {
server: Efl.Object; [[The server instance]]
}
}
}
implements {
Efl.Object.finalize;
Efl.Object.destructor;
Efl.Net.Server.serve;
Efl.Net.Server.client_announce;
Efl.Net.Server.address.get;
Efl.Net.Server.clients_count.get;
Efl.Net.Server.clients_limit;
Efl.Net.Server.serving.get;
}
}

View File

@ -0,0 +1,35 @@
#ifdef HAVE_CONFIG_H
# include <config.h>
#endif
#include "Ecore.h"
#include "Ecore_Con.h"
#include "ecore_con_private.h"
typedef struct
{
} Efl_Net_Socket_Simple_Data;
#define MY_CLASS EFL_NET_SOCKET_SIMPLE_CLASS
EOLIAN static void
_efl_net_socket_simple_efl_io_buffered_stream_inner_io_set(Eo *o, Efl_Net_Socket_Simple_Data *pd EINA_UNUSED, Efl_Object *io)
{
EINA_SAFETY_ON_FALSE_RETURN(efl_isa(io, EFL_NET_SOCKET_INTERFACE));
efl_io_buffered_stream_inner_io_set(efl_super(o, MY_CLASS), io);
}
EOLIAN static const char *
_efl_net_socket_simple_efl_net_socket_address_local_get(Eo *o, Efl_Net_Socket_Simple_Data *pd EINA_UNUSED)
{
return efl_net_socket_address_local_get(efl_io_buffered_stream_inner_io_get(o));
}
EOLIAN static const char *
_efl_net_socket_simple_efl_net_socket_address_remote_get(Eo *o, Efl_Net_Socket_Simple_Data *pd EINA_UNUSED)
{
return efl_net_socket_address_remote_get(efl_io_buffered_stream_inner_io_get(o));
}
#include "efl_net_socket_simple.eo.c"

View File

@ -0,0 +1,45 @@
class Efl.Net.Socket.Simple (Efl.Io.Buffered_Stream, Efl.Net.Socket) {
[[A wrapper socket offering an easy to use, buffered I/O.
The simple socket encapsulates an actual @Efl.Net.Socket, and
uses it with an @Efl.Io.Buffered_Stream, which creates an input
@Efl.Io.Queue, an output @Efl.Io.Queue and these are linked
using a receiver and a sender @Efl.Io.Copier.
The idea is that unlike traditional @Efl.Net.Socket that will
attempt to write directly to socket and thus may take less data
than requested, this one will keep the pending data in its own
buffer, feeding to the actual socket when it
@Efl.Io.Writer.can_write. That makes its operation much simpler
as @Efl.Io.Writer.write will always take the full data -- allows
"write and forget", if unlimited (see
@Efl.Io.Buffered_Stream.max_queue_size_output).
Reading is also much simpler since received data is kept in an
@Efl.Io.Queue, thus its size can be queried with
@Efl.Io.Buffered_Stream.pending_read and read with
@Efl.Io.Reader.read or peeked with
@Efl.Io.Buffered_Stream.slice_get, then discarded with
@Efl.Io.Buffered_Stream.discard or
@Efl.Io.Buffered_Stream.clear.
Then when waiting for a complete message, just peek at its
contents, if not complete do nothing, if complete then either
@Efl.Io.Reader.read to get a copy or manipulate a read-only
reference from @Efl.Io.Buffered_Stream.slice_get and then
@Efl.Io.Buffered_Stream.discard
The actual socket is set with the constructor method
@Efl.Io.Buffered_Stream.inner_io.set and can be retrieved with
@Efl.Io.Buffered_Stream.inner_io.get, which should be used with
care.
@since 1.19
]]
implements {
Efl.Io.Buffered_Stream.inner_io.set;
Efl.Net.Socket.address_local.get;
Efl.Net.Socket.address_remote.get;
}
}