Skip to content

Commit a05fe04

Browse files
committed
Add tinync as an example of coroutine
1 parent d335963 commit a05fe04

File tree

3 files changed

+212
-0
lines changed

3 files changed

+212
-0
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
## Project Listing
44
- [tpool](tpool/): A lightweight thread pool.
5+
- [tinync](tinync/): A tiny `nc` implementation using coroutine.
56
- [picosh](picosh/): A minimalist UNIX shell.
67
- [httpd](httpd/): A multi-threaded web server.
78
- [ringbuffer](ringbuffer/): A lock-less ring buffer.

tinync/Makefile

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
CFLAGS ?= -std=gnu99 -Wall -Wextra -Werror
2+
3+
all: tinync
4+
5+
tinync: tinync.o
6+
tinync.o : tinync.c
7+
8+
clean:
9+
rm -f tinync tinync
10+
11+
.PHONY: all clean

tinync/tinync.c

Lines changed: 200 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,200 @@
1+
#include <stddef.h>
2+
3+
/* coroutine status values */
4+
enum {
5+
CR_BLOCKED = 0,
6+
CR_FINISHED = 1,
7+
};
8+
9+
/* Helper macros to generate unique labels */
10+
#define __cr_line3(name, line) _cr_##name##line
11+
#define __cr_line2(name, line) __cr_line3(name, line)
12+
#define __cr_line(name) __cr_line2(name, __LINE__)
13+
14+
struct cr {
15+
void *label;
16+
int status;
17+
void *local; /* private local storage */
18+
};
19+
20+
#define cr_init() \
21+
{ \
22+
.label = NULL, .status = CR_BLOCKED \
23+
}
24+
#define cr_begin(o) \
25+
do { \
26+
if ((o)->status == CR_FINISHED) \
27+
return; \
28+
if ((o)->label) \
29+
goto *(o)->label; \
30+
} while (0)
31+
#define cr_label(o, stat) \
32+
do { \
33+
(o)->status = (stat); \
34+
__cr_line(label) : (o)->label = &&__cr_line(label); \
35+
} while (0)
36+
#define cr_end(o) cr_label(o, CR_FINISHED)
37+
38+
#define cr_status(o) (o)->status
39+
40+
#define cr_wait(o, cond) \
41+
do { \
42+
cr_label(o, CR_BLOCKED); \
43+
if (!(cond)) \
44+
return; \
45+
} while (0)
46+
47+
#define cr_exit(o, stat) \
48+
do { \
49+
cr_label(o, stat); \
50+
return; \
51+
} while (0)
52+
53+
#define cr_queue(T, size) \
54+
struct { \
55+
T buf[size]; \
56+
size_t r, w; \
57+
}
58+
#define cr_queue_init() \
59+
{ \
60+
.r = 0, .w = 0 \
61+
}
62+
#define cr_queue_len(q) (sizeof((q)->buf) / sizeof((q)->buf[0]))
63+
#define cr_queue_cap(q) ((q)->w - (q)->r)
64+
#define cr_queue_empty(q) ((q)->w == (q)->r)
65+
#define cr_queue_full(q) (cr_queue_cap(q) == cr_queue_len(q))
66+
67+
#define cr_queue_push(q, el) \
68+
(!cr_queue_full(q) && ((q)->buf[(q)->w++ % cr_queue_len(q)] = (el), 1))
69+
#define cr_queue_pop(q) \
70+
(cr_queue_empty(q) ? NULL : &(q)->buf[(q)->r++ % cr_queue_len(q)])
71+
72+
/* Wrap system calls and other functions that return -1 and set errno */
73+
#define cr_sys(o, call) \
74+
cr_wait(o, (errno = 0) || !(((call) == -1) && \
75+
(errno == EAGAIN || errno == EWOULDBLOCK || \
76+
errno == EINPROGRESS || errno == EINTR)))
77+
78+
#include <arpa/inet.h>
79+
#include <errno.h>
80+
#include <fcntl.h>
81+
#include <netinet/in.h>
82+
#include <stdio.h>
83+
#include <stdlib.h>
84+
#include <sys/select.h>
85+
#include <sys/socket.h>
86+
#include <unistd.h>
87+
88+
typedef cr_queue(uint8_t, 4096) byte_queue_t;
89+
90+
static void stdin_loop(struct cr *o, byte_queue_t *out)
91+
{
92+
static uint8_t b;
93+
static int r;
94+
cr_begin(o);
95+
for (;;) {
96+
cr_sys(o, r = read(STDIN_FILENO, &b, 1));
97+
if (r == 0) {
98+
cr_wait(o, cr_queue_empty(out));
99+
cr_exit(o, 1);
100+
}
101+
cr_wait(o, !cr_queue_full(out));
102+
cr_queue_push(out, b);
103+
}
104+
cr_end(o);
105+
}
106+
107+
static void socket_write_loop(struct cr *o, int fd, byte_queue_t *in)
108+
{
109+
static uint8_t *b;
110+
cr_begin(o);
111+
for (;;) {
112+
cr_wait(o, !cr_queue_empty(in));
113+
b = cr_queue_pop(in);
114+
cr_sys(o, send(fd, b, 1, 0));
115+
}
116+
cr_end(o);
117+
}
118+
119+
static void socket_read_loop(struct cr *o, int fd)
120+
{
121+
static uint8_t b;
122+
static int r;
123+
cr_begin(o);
124+
for (;;) {
125+
cr_sys(o, r = recv(fd, &b, 1, 0));
126+
if (r == 0)
127+
cr_exit(o, 1);
128+
cr_sys(o, write(STDOUT_FILENO, &b, 1));
129+
}
130+
cr_end(o);
131+
}
132+
133+
static int nonblock(int fd)
134+
{
135+
int flags = fcntl(fd, F_GETFL, 0);
136+
if (flags == -1)
137+
return -1;
138+
return fcntl(fd, F_SETFL, flags | O_NONBLOCK);
139+
}
140+
141+
int main(int argc, char *argv[])
142+
{
143+
if (argc != 3) {
144+
fprintf(stderr, "USAGE: %s <ip> <port>\n", argv[0]);
145+
return 1;
146+
}
147+
148+
char *host = argv[1];
149+
int port = atoi(argv[2]);
150+
151+
int fd = socket(AF_INET, SOCK_STREAM, 0);
152+
if (fd < 0) {
153+
perror("socket()");
154+
return 1;
155+
}
156+
if (nonblock(fd) < 0) {
157+
perror("nonblock() socket");
158+
return 1;
159+
}
160+
if (nonblock(STDIN_FILENO) < 0) {
161+
perror("nonblock() stdin");
162+
return 1;
163+
}
164+
if (nonblock(STDOUT_FILENO) < 0) {
165+
perror("nonblock() stdout");
166+
return 1;
167+
}
168+
169+
struct sockaddr_in addr = {
170+
.sin_family = AF_INET,
171+
.sin_addr =
172+
{
173+
.s_addr = inet_addr(host),
174+
},
175+
.sin_port = htons(port),
176+
};
177+
connect(fd, (struct sockaddr *) &addr, sizeof(struct sockaddr_in));
178+
179+
struct cr cr_stdin = cr_init();
180+
struct cr cr_socket_read = cr_init();
181+
struct cr cr_socket_write = cr_init();
182+
byte_queue_t queue = cr_queue_init();
183+
184+
while (cr_status(&cr_stdin) == CR_BLOCKED &&
185+
cr_status(&cr_socket_read) == CR_BLOCKED) {
186+
if (cr_queue_empty(&queue)) {
187+
fd_set fds;
188+
FD_ZERO(&fds);
189+
FD_SET(STDIN_FILENO, &fds);
190+
FD_SET(fd, &fds);
191+
select(fd + 1, &fds, NULL, NULL, NULL);
192+
}
193+
socket_read_loop(&cr_socket_read, fd);
194+
socket_write_loop(&cr_socket_write, fd, &queue);
195+
stdin_loop(&cr_stdin, &queue);
196+
}
197+
198+
close(fd);
199+
return 0;
200+
}

0 commit comments

Comments
 (0)