echo_server.c revision fb3e20e0
1/* Copyright (c) 2017 - 2020 LiteSpeed Technologies Inc. See LICENSE. */ 2/* 3 * echo_server.c -- QUIC server that echoes back input line by line 4 */ 5 6#include <assert.h> 7#include <signal.h> 8#include <stdio.h> 9#include <stdlib.h> 10#include <string.h> 11#include <sys/queue.h> 12#include <time.h> 13#ifndef WIN32 14#include <unistd.h> 15#include <netinet/in.h> 16#else 17#include "vc_compat.h" 18#include "getopt.h" 19#endif 20 21#include "lsquic.h" 22#include "test_common.h" 23#include "prog.h" 24 25#include "../src/liblsquic/lsquic_logger.h" 26 27 28struct lsquic_conn_ctx; 29 30struct echo_server_ctx { 31 TAILQ_HEAD(, lsquic_conn_ctx) conn_ctxs; 32 unsigned max_reqs; 33 int n_conn; 34 struct sport_head sports; 35 struct prog *prog; 36}; 37 38struct lsquic_conn_ctx { 39 TAILQ_ENTRY(lsquic_conn_ctx) next_connh; 40 lsquic_conn_t *conn; 41 struct echo_server_ctx *server_ctx; 42}; 43 44 45static lsquic_conn_ctx_t * 46echo_server_on_new_conn (void *stream_if_ctx, lsquic_conn_t *conn) 47{ 48 struct echo_server_ctx *server_ctx = stream_if_ctx; 49 lsquic_conn_ctx_t *conn_h = calloc(1, sizeof(*conn_h)); 50 conn_h->conn = conn; 51 conn_h->server_ctx = server_ctx; 52 TAILQ_INSERT_TAIL(&server_ctx->conn_ctxs, conn_h, next_connh); 53 LSQ_NOTICE("New connection!"); 54 print_conn_info(conn); 55 return conn_h; 56} 57 58 59static void 60echo_server_on_conn_closed (lsquic_conn_t *conn) 61{ 62 lsquic_conn_ctx_t *conn_h = lsquic_conn_get_ctx(conn); 63 if (conn_h->server_ctx->n_conn) 64 { 65 --conn_h->server_ctx->n_conn; 66 LSQ_NOTICE("Connection closed, remaining: %d", conn_h->server_ctx->n_conn); 67 if (0 == conn_h->server_ctx->n_conn) 68 prog_stop(conn_h->server_ctx->prog); 69 } 70 else 71 LSQ_NOTICE("Connection closed"); 72 TAILQ_REMOVE(&conn_h->server_ctx->conn_ctxs, conn_h, next_connh); 73 free(conn_h); 74} 75 76 77struct lsquic_stream_ctx { 78 lsquic_stream_t *stream; 79 struct echo_server_ctx *server_ctx; 80 char buf[0x100]; 81 size_t buf_off; 82}; 83 84 85static lsquic_stream_ctx_t * 86echo_server_on_new_stream (void *stream_if_ctx, lsquic_stream_t *stream) 87{ 88 lsquic_stream_ctx_t *st_h = malloc(sizeof(*st_h)); 89 st_h->stream = stream; 90 st_h->server_ctx = stream_if_ctx; 91 st_h->buf_off = 0; 92 lsquic_stream_wantread(stream, 1); 93 return st_h; 94} 95 96 97static struct lsquic_conn_ctx * 98find_conn_h (const struct echo_server_ctx *server_ctx, lsquic_stream_t *stream) 99{ 100 struct lsquic_conn_ctx *conn_h; 101 lsquic_conn_t *conn; 102 103 conn = lsquic_stream_conn(stream); 104 TAILQ_FOREACH(conn_h, &server_ctx->conn_ctxs, next_connh) 105 if (conn_h->conn == conn) 106 return conn_h; 107 return NULL; 108} 109 110 111static void 112echo_server_on_read (lsquic_stream_t *stream, lsquic_stream_ctx_t *st_h) 113{ 114 struct lsquic_conn_ctx *conn_h; 115 size_t nr; 116 117 nr = lsquic_stream_read(stream, st_h->buf + st_h->buf_off++, 1); 118 if (0 == nr) 119 { 120 LSQ_NOTICE("EOF: closing connection"); 121 lsquic_stream_shutdown(stream, 2); 122 conn_h = find_conn_h(st_h->server_ctx, stream); 123 lsquic_conn_close(conn_h->conn); 124 } 125 else if ('\n' == st_h->buf[ st_h->buf_off - 1 ]) 126 { 127 /* Found end of line: echo it back */ 128 lsquic_stream_wantwrite(stream, 1); 129 lsquic_stream_wantread(stream, 0); 130 } 131 else if (st_h->buf_off == sizeof(st_h->buf)) 132 { 133 /* Out of buffer space: line too long */ 134 LSQ_NOTICE("run out of buffer space"); 135 lsquic_stream_shutdown(stream, 2); 136 } 137 else 138 { 139 /* Keep reading */; 140 } 141} 142 143 144static void 145echo_server_on_write (lsquic_stream_t *stream, lsquic_stream_ctx_t *st_h) 146{ 147 lsquic_stream_write(stream, st_h->buf, st_h->buf_off); 148 st_h->buf_off = 0; 149 lsquic_stream_flush(stream); 150 lsquic_stream_wantwrite(stream, 0); 151 lsquic_stream_wantread(stream, 1); 152} 153 154 155static void 156echo_server_on_stream_close (lsquic_stream_t *stream, lsquic_stream_ctx_t *st_h) 157{ 158 struct lsquic_conn_ctx *conn_h; 159 LSQ_NOTICE("%s called", __func__); 160 conn_h = find_conn_h(st_h->server_ctx, stream); 161 LSQ_WARN("%s: TODO: free connection handler %p", __func__, conn_h); 162 free(st_h); 163} 164 165 166const struct lsquic_stream_if server_echo_stream_if = { 167 .on_new_conn = echo_server_on_new_conn, 168 .on_conn_closed = echo_server_on_conn_closed, 169 .on_new_stream = echo_server_on_new_stream, 170 .on_read = echo_server_on_read, 171 .on_write = echo_server_on_write, 172 .on_close = echo_server_on_stream_close, 173}; 174 175 176static void 177usage (const char *prog) 178{ 179 const char *const slash = strrchr(prog, '/'); 180 if (slash) 181 prog = slash + 1; 182 printf( 183"Usage: %s [opts]\n" 184"\n" 185"Options:\n" 186 , prog); 187} 188 189 190int 191main (int argc, char **argv) 192{ 193 int opt, s; 194 struct prog prog; 195 struct echo_server_ctx server_ctx; 196 197 memset(&server_ctx, 0, sizeof(server_ctx)); 198 server_ctx.prog = &prog; 199 TAILQ_INIT(&server_ctx.sports); 200 TAILQ_INIT(&server_ctx.conn_ctxs); 201 202 prog_init(&prog, LSENG_SERVER, &server_ctx.sports, 203 &server_echo_stream_if, &server_ctx); 204 205 while (-1 != (opt = getopt(argc, argv, PROG_OPTS "hn:"))) 206 { 207 switch (opt) { 208 case 'n': 209 server_ctx.n_conn = atoi(optarg); 210 break; 211 case 'h': 212 usage(argv[0]); 213 prog_print_common_options(&prog, stdout); 214 exit(0); 215 default: 216 if (0 != prog_set_opt(&prog, opt, optarg)) 217 exit(1); 218 } 219 } 220 221 if (0 != prog_prep(&prog)) 222 { 223 LSQ_ERROR("could not prep"); 224 exit(EXIT_FAILURE); 225 } 226 227 LSQ_DEBUG("entering event loop"); 228 229 s = prog_run(&prog); 230 prog_cleanup(&prog); 231 232 exit(0 == s ? EXIT_SUCCESS : EXIT_FAILURE); 233} 234