diff --git a/tinync/Makefile b/tinync/Makefile index bfe7704..605ad50 100644 --- a/tinync/Makefile +++ b/tinync/Makefile @@ -6,6 +6,6 @@ tinync: tinync.o tinync.o : tinync.c clean: - rm -f tinync tinync + rm -f tinync.o tinync .PHONY: all clean diff --git a/tinync/README.md b/tinync/README.md new file mode 100644 index 0000000..4250342 --- /dev/null +++ b/tinync/README.md @@ -0,0 +1,85 @@ +# Tinync +Tinync is a simplified implementation of [nc](https://en.wikipedia.org/wiki/Netcat), which aims to demonstrate the usage of [coroutine](https://en.wikipedia.org/wiki/Coroutine) macros and their effects. + +## Internals +### Declaring a Coroutine Function +At the very beginning, a coroutine function should be declared first, coroutine function is part of program that will be executed simultaneously with other coroutines. All coroutines should be specified with the macro `cr_proto`, following shows the example: +```cpp +static void cr_proto(coroutine_name, parameter_declarations) +{ + /* coroutine body */ +} +``` + +### Controlling Macros +With in the coroutine, there are several macros for controlling the behavior of coroutine, following are list of them: +* `cr_begin` aims to initiate the context of coroutine when the coroutine is first invoked, or resume previous execution for the rest of invocations. This macro should be placed before other cotrolling macros except `cr_local`. +* `cr_end` ends up a coroutine that will mark the status of coroutine as finished, which could be detected outside to ensure whether a coroutine finisihed its job or not. +* `cr_wait` is used for waiting a condition happened. Once the condition it is waiting for has not happened yet, it will pause the current coroutine and switch to another. +* `cr_exit` not only yields a coroutine but also updates its state with given state. +* `cr_sys` is a wrapper of `cr_wait` which performs waiting on system calls and other functions that return -1 and set `errno`. +* `cr_local` is a marker for programmers to recognize a variable related to coroutine easily. + +In `tinync.c`, we can see the combinations of these macros: +```cpp +static void cr_proto(stdin_loop, byte_queue_t *out) +{ + /* b and r are variables used in coroutine whose + * value will be preserved across pauses. + */ + cr_local uint8_t b; + cr_local int r; + + /* Initiates the context of this coroutine. */ + cr_begin(); + for (;;) { + /* Wait for read system call to become successful. */ + cr_sys(r = read(STDIN_FILENO, &b, 1)); + if (r == 0) { + /* Wait until queue out is flushed. */ + cr_wait(cr_queue_empty(out)); + + /* Exit the coroutine with status update as finished. */ + cr_exit(1); + } + /* Wait until there is place in queue out. */ + cr_wait(!cr_queue_full(out)); + cr_queue_push(out, b); + } + /* End up this coroutine, status will be updated as finished. */ + cr_end(); +} +``` + +### Coroutine Context +Context is an important part for coroutine, which preserves the execution point of a coroutine that could be resumed later. To define a context for a coroutine, use `cr_context` macro and initiates it with macro `cr_context_init`. It is important to **assign an identical name to context and its corresponding function**. With example presented at the beginning, its corresponding context should be specified as follows: +```cpp +cr_context(coroutine_name) = cr_context_init(); +``` + +### Launching and Monitoring +Now, all required preparations are done, programmers may launch a coroutine via `cr_run` macro and monitor it with `cr_status` macro. To execute several coroutines simultaneously, place `cr_run`s that launch coroutines in a loop and keep tracking their status until all of them are finished. Following shows a simple example: +```cpp +while (cr_status(coroutine_1) != CR_FINISHED && + cr_status(coroutine_2) != CR_FINISHED && + cr_status(coroutine_3) != CR_FINISHED) { + cr_run(coroutine_1); + cr_run(coroutine_2); + cr_run(coroutine_3); +} +``` + +## Run the Sample Program +Tinync is a sample program that handles several coroutines to maintain communication with remote while accept user input simultaneously. To compile it, use `make`: +```shell +$ make +``` +This sample requires two terminals, let's say `T1` and `T2`. Before launching `tinync`, start `nc` in `T1` first: +```shell +$ nc -l 127.0.0.1 9000 +``` +Then launch `tinync` in `T2`: +```shell +$ tinync 127.0.0.1 9000 +``` +Now, any words typed in `T1` will be recived and presented in `T2`, and vice versa. \ No newline at end of file diff --git a/tinync/tinync.c b/tinync/tinync.c index 74ee73b..71220d3 100644 --- a/tinync/tinync.c +++ b/tinync/tinync.c @@ -14,40 +14,50 @@ enum { struct cr { void *label; int status; - void *local; /* private local storage */ }; -#define cr_init() \ +#define cr_context_name(name) __cr_context_##name +#define cr_context(name) struct cr cr_context_name(name) +#define cr_context_init() \ { \ .label = NULL, .status = CR_BLOCKED \ } -#define cr_begin(o) \ - do { \ - if ((o)->status == CR_FINISHED) \ - return; \ - if ((o)->label) \ - goto *(o)->label; \ + +#define cr_func_name(name) __cr_func_##name +#define cr_proto(name, ...) cr_func_name(name)(struct cr * ctx, ##__VA_ARGS__) + +#define cr_run(name, ...) \ + cr_func_name(name)(&cr_context_name(name), ##__VA_ARGS__) + +#define cr_local static + +#define cr_begin() \ + do { \ + if ((ctx)->status == CR_FINISHED) \ + return; \ + if ((ctx)->label) \ + goto *(ctx)->label; \ } while (0) #define cr_label(o, stat) \ do { \ (o)->status = (stat); \ __cr_line(label) : (o)->label = &&__cr_line(label); \ } while (0) -#define cr_end(o) cr_label(o, CR_FINISHED) +#define cr_end() cr_label(ctx, CR_FINISHED) -#define cr_status(o) (o)->status +#define cr_status(name) cr_context_name(name).status -#define cr_wait(o, cond) \ - do { \ - cr_label(o, CR_BLOCKED); \ - if (!(cond)) \ - return; \ +#define cr_wait(cond) \ + do { \ + cr_label(ctx, CR_BLOCKED); \ + if (!(cond)) \ + return; \ } while (0) -#define cr_exit(o, stat) \ - do { \ - cr_label(o, stat); \ - return; \ +#define cr_exit(stat) \ + do { \ + cr_label(ctx, stat); \ + return; \ } while (0) #define cr_queue(T, size) \ @@ -70,9 +80,9 @@ struct cr { (cr_queue_empty(q) ? NULL : &(q)->buf[(q)->r++ % cr_queue_len(q)]) /* Wrap system calls and other functions that return -1 and set errno */ -#define cr_sys(o, call) \ - cr_wait(o, (errno = 0) || !(((call) == -1) && \ - (errno == EAGAIN || errno == EWOULDBLOCK || \ +#define cr_sys(call) \ + cr_wait((errno = 0) || \ + !(((call) == -1) && (errno == EAGAIN || errno == EWOULDBLOCK || \ errno == EINPROGRESS || errno == EINTR))) #include @@ -87,47 +97,47 @@ struct cr { typedef cr_queue(uint8_t, 4096) byte_queue_t; -static void stdin_loop(struct cr *o, byte_queue_t *out) +static void cr_proto(stdin_loop, byte_queue_t *out) { - static uint8_t b; - static int r; - cr_begin(o); + cr_local uint8_t b; + cr_local int r; + cr_begin(); for (;;) { - cr_sys(o, r = read(STDIN_FILENO, &b, 1)); + cr_sys(r = read(STDIN_FILENO, &b, 1)); if (r == 0) { - cr_wait(o, cr_queue_empty(out)); - cr_exit(o, 1); + cr_wait(cr_queue_empty(out)); + cr_exit(1); } - cr_wait(o, !cr_queue_full(out)); + cr_wait(!cr_queue_full(out)); cr_queue_push(out, b); } - cr_end(o); + cr_end(); } -static void socket_write_loop(struct cr *o, int fd, byte_queue_t *in) +static void cr_proto(socket_write_loop, byte_queue_t *in, int fd) { - static uint8_t *b; - cr_begin(o); + cr_local uint8_t *b; + cr_begin(); for (;;) { - cr_wait(o, !cr_queue_empty(in)); + cr_wait(!cr_queue_empty(in)); b = cr_queue_pop(in); - cr_sys(o, send(fd, b, 1, 0)); + cr_sys(send(fd, b, 1, 0)); } - cr_end(o); + cr_end(); } -static void socket_read_loop(struct cr *o, int fd) +static void cr_proto(socket_read_loop, int fd) { - static uint8_t b; - static int r; - cr_begin(o); + cr_local uint8_t b; + cr_local int r; + cr_begin(); for (;;) { - cr_sys(o, r = recv(fd, &b, 1, 0)); + cr_sys(r = recv(fd, &b, 1, 0)); if (r == 0) - cr_exit(o, 1); - cr_sys(o, write(STDOUT_FILENO, &b, 1)); + cr_exit(1); + cr_sys(write(STDOUT_FILENO, &b, 1)); } - cr_end(o); + cr_end(); } static int nonblock(int fd) @@ -176,13 +186,14 @@ int main(int argc, char *argv[]) }; connect(fd, (struct sockaddr *) &addr, sizeof(struct sockaddr_in)); - struct cr cr_stdin = cr_init(); - struct cr cr_socket_read = cr_init(); - struct cr cr_socket_write = cr_init(); byte_queue_t queue = cr_queue_init(); - while (cr_status(&cr_stdin) == CR_BLOCKED && - cr_status(&cr_socket_read) == CR_BLOCKED) { + cr_context(stdin_loop) = cr_context_init(); + cr_context(socket_read_loop) = cr_context_init(); + cr_context(socket_write_loop) = cr_context_init(); + + while (cr_status(stdin_loop) == CR_BLOCKED && + cr_status(socket_read_loop) == CR_BLOCKED) { if (cr_queue_empty(&queue)) { fd_set fds; FD_ZERO(&fds); @@ -190,9 +201,9 @@ int main(int argc, char *argv[]) FD_SET(fd, &fds); select(fd + 1, &fds, NULL, NULL, NULL); } - socket_read_loop(&cr_socket_read, fd); - socket_write_loop(&cr_socket_write, fd, &queue); - stdin_loop(&cr_stdin, &queue); + cr_run(socket_read_loop, fd); + cr_run(socket_write_loop, &queue, fd); + cr_run(stdin_loop, &queue); } close(fd);