Serene Runtime 1.0.0-dev
C runtime for the Serene programming language
Loading...
Searching...
No Matches
11_tcp_ping_pong.c
Go to the documentation of this file.
1/* -*- C -*-
2 * Serene programming language
3 * Copyright (C) 2019-2026 Sameer Rahmani <[email protected]>
4 *
5 * This program is free software: you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation, either version 3 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program. If not, see <https://www.gnu.org/licenses/>.
17 */
18
19// Example 11 -- TCP ping-pong over loopback.
20//
21// Where example 09 is a single echo, this is a sustained request/response loop:
22// the client sends "ping" and waits for "pong" before sending the next one, for
23// several rounds. Because each side waits for the other's reply, the two fibers
24// alternate through the reactor -- one is always parked in an IO call while the
25// other runs -- and the stream never coalesces messages, so every server read
26// sees exactly one "ping".
27//
28// It is the canonical shape of a connection handler: accept (or connect), then
29// loop reading a request and writing a response until the peer hangs up (a read
30// of 0 bytes) or the exchange is done.
31
32#include <arpa/inet.h>
33#include <fcntl.h>
34#include <netinet/in.h>
35#include <stdint.h>
36#include <stdio.h>
37#include <sys/socket.h>
38#include <unistd.h>
39
40#include <serene/rt/fiber/io.h>
41#include <serene/runtime.h>
42
43#define ROUNDS 4000
44
45typedef struct {
46 int listener;
48
49typedef struct {
50 uint16_t port; // network byte order
52
53static void set_nonblock(int fd) {
54 int fl = fcntl(fd, F_GETFL, 0);
55 (void)fcntl(fd, F_SETFL, fl | O_NONBLOCK);
56}
57
58static srn_fiber_result_t server(srn_context_t *ctx, void *arg) {
59 (void)ctx;
60 server_args_t *s = arg;
62 if (HAS_ERROR(a)) {
63 printf("server: accept failed: %s\n", a.maybe_error->msg);
64 return nullptr;
65 }
66 int conn = a.fd;
67
68 // Read a request, write a response, repeat until the peer closes.
69 for (int i = 0; i < ROUNDS; i++) {
70 char buf[16] = {0};
71 srn_io_size_result_t r = srn_fiber_read(conn, buf, sizeof(buf) - 1, -1);
72 if (HAS_ERROR(r) || r.count == 0) {
73 printf("server: peer closed after %d rounds\n", i);
74 break;
75 }
76 buf[r.count] = '\0';
77 printf("server: round %d got \"%s\", replying \"pong\"\n", i, buf);
78 (void)srn_fiber_write(conn, "pong", 4, -1);
79 }
80
81 close(conn);
82 return nullptr;
83}
84
85static srn_fiber_result_t client(srn_context_t *ctx, void *arg) {
86 (void)ctx;
87 client_args_t *c = arg;
88 int fd = socket(AF_INET, SOCK_STREAM, 0);
89 if (fd < 0) {
90 perror("socket");
91 return nullptr;
92 }
93 set_nonblock(fd);
94
95 struct sockaddr_in to = {.sin_family = AF_INET, .sin_port = c->port};
96 to.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
97
98 srn_error_t *err = srn_fiber_connect(fd, (struct sockaddr *)&to, sizeof(to));
99 if (err != nullptr) {
100 printf("client: connect failed: %s\n", err->msg);
101 close(fd);
102 return nullptr;
103 }
104
105 // Send a ping, wait for the pong, then send the next one.
106 for (int i = 0; i < ROUNDS; i++) {
107 (void)srn_fiber_write(fd, "ping", 4, -1);
108 char buf[16] = {0};
109 srn_io_size_result_t r = srn_fiber_read(fd, buf, sizeof(buf) - 1, -1);
110 if (HAS_ERROR(r) || r.count == 0) {
111 printf("client: peer closed after %d rounds\n", i);
112 break;
113 }
114 buf[r.count] = '\0';
115 printf("client: round %d sent \"ping\", got \"%s\"\n", i, buf);
116 }
117
118 close(fd);
119 return nullptr;
120}
121
122int main(void) {
123 SERENE_RUNTIME_INIT_WITH_SCHED(engine, sched, nullptr);
124 srn_context_t *ctx = srn_context_make(engine);
125
126 int lst = socket(AF_INET, SOCK_STREAM, 0);
127 struct sockaddr_in a = {.sin_family = AF_INET};
128 a.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
129 if (lst < 0 || bind(lst, (struct sockaddr *)&a, sizeof(a)) != 0 || listen(lst, 4) != 0) {
130 perror("listener setup");
131 return 1;
132 }
133 set_nonblock(lst);
134 socklen_t al = sizeof(a);
135 if (getsockname(lst, (struct sockaddr *)&a, &al) != 0) {
136 perror("getsockname");
137 return 1;
138 }
139
140 server_args_t s = {lst};
141 client_args_t c = {a.sin_port};
142 (void)srn_fiber_spawn(ctx, server, &s);
143 (void)srn_fiber_spawn(ctx, client, &c);
144
145 srn_sched_run(sched, 1);
146 close(lst);
147
150 return 0;
151}
static srn_fiber_result_t client(srn_context_t *ctx, void *arg)
Definition 09_tcp_echo.c:77
static srn_fiber_result_t server(srn_context_t *ctx, void *arg)
Definition 09_tcp_echo.c:56
static void set_nonblock(int fd)
#define ROUNDS
int main(void)
static srn_fiber_result_t client(srn_context_t *ctx, void *arg)
static srn_fiber_result_t server(srn_context_t *ctx, void *arg)
srn_context_t * srn_context_make(srn_engine_t *engine)
Make an empty context, by allocating a new memory block.
Definition context.c:39
int srn_context_release(srn_context_t *ctx)
Definition context.c:64
#define HAS_ERROR(x)
True when x – a value carrying an ERROR_HEADER – has an error attached.
Definition errors.h:63
srn_fiber_t * srn_fiber_spawn(srn_context_t *ctx, srn_fiber_entry_t entry, void *arg)
Make and schedule a fiber with every default, the engine's scheduler, the configured stack size,...
Definition fiber.c:247
void * srn_fiber_result_t
What a fiber's entry produces, type-erased.
Definition fiber.h:157
srn_io_size_result_t srn_fiber_write(int fd, const void *buf, size_t len, int64_t offset)
Write len bytes from buf to fd, suspending the calling fiber until the operation completes rather tha...
Definition io.c:296
srn_error_t * srn_fiber_connect(int fd, const struct sockaddr *addr, socklen_t addrlen)
Connect the socket fd to addr/addrlen, suspending the calling fiber until the connection completes ra...
Definition io.c:330
srn_io_accept_result_t srn_fiber_accept(int fd, struct sockaddr *addr, socklen_t addr_cap)
Accept a pending connection on the listening socket fd, suspending the calling fiber until one arrive...
Definition io.c:308
srn_io_size_result_t srn_fiber_read(int fd, void *buf, size_t len, int64_t offset)
Read up to len bytes from fd into buf, suspending the calling fiber until the operation completes rat...
Definition io.c:284
The fiber facing IO API: blocking looking calls a fiber uses to do IO.
#define SERENE_RUNTIME_SHUTDOWN(engine)
Tear down what SERENE_RUNTIME_INIT brought up, in reverse order, the engine (reactor,...
Definition runtime.h:58
#define SERENE_RUNTIME_INIT_WITH_SCHED(engine, sched, config)
SERENE_RUNTIME_INIT plus a sched variable bound to the engine's scheduler, which every run and fiber ...
Definition runtime.h:46
void srn_sched_run(srn_scheduler_t *sched, size_t nworkers)
Run the scheduler with nworkers os threads draining it, returning once the pool goes quiescent (every...
Definition scheduler.c:843
uint16_t port
Definition 09_tcp_echo.c:48
A runtime error, a tag classifying the failure and a human-readable message.
Definition errors.h:125
char * msg
Definition errors.h:127
The result of srn_fiber_accept.
Definition io.h:86
The result of a byte transfer op (read/write/sendto/recvmsg/sendmsg).
Definition io.h:76
size_t count
Definition io.h:78