echo_client.c revision 9a690580
1/* Copyright (c) 2017 - 2020 LiteSpeed Technologies Inc. See LICENSE. */ 2/* 3 * echo_client.c -- This is really a "line client:" it connects to QUIC server 4 * and sends it stuff, line by line. It works in tandem with echo_server. 5 */ 6 7#include <assert.h> 8#include <errno.h> 9#include <stdio.h> 10#include <stdlib.h> 11#include <string.h> 12#include <unistd.h> 13#include <sys/queue.h> 14#include <sys/types.h> 15#include <sys/stat.h> 16#include <fcntl.h> 17 18#include <event2/event.h> 19 20#include "lsquic.h" 21#include "test_common.h" 22#include "prog.h" 23 24#include "../src/liblsquic/lsquic_logger.h" 25 26struct lsquic_conn_ctx; 27 28struct echo_client_ctx { 29 struct lsquic_conn_ctx *conn_h; 30 struct prog *prog; 31}; 32 33struct lsquic_conn_ctx { 34 lsquic_conn_t *conn; 35 struct echo_client_ctx *client_ctx; 36}; 37 38 39static lsquic_conn_ctx_t * 40echo_client_on_new_conn (void *stream_if_ctx, lsquic_conn_t *conn) 41{ 42 struct echo_client_ctx *client_ctx = stream_if_ctx; 43 lsquic_conn_ctx_t *conn_h = malloc(sizeof(*conn_h)); 44 conn_h->conn = conn; 45 conn_h->client_ctx = client_ctx; 46 client_ctx->conn_h = conn_h; 47 lsquic_conn_make_stream(conn); 48 return conn_h; 49} 50 51 52static void 53echo_client_on_conn_closed (lsquic_conn_t *conn) 54{ 55 lsquic_conn_ctx_t *conn_h = lsquic_conn_get_ctx(conn); 56 LSQ_NOTICE("Connection closed"); 57 prog_stop(conn_h->client_ctx->prog); 58 free(conn_h); 59} 60 61 62struct lsquic_stream_ctx { 63 lsquic_stream_t *stream; 64 struct echo_client_ctx *client_ctx; 65 struct event *read_stdin_ev; 66 char buf[0x100]; 67 size_t buf_off; 68}; 69 70 71static void 72read_stdin (int fd, short what, void *ctx) 73{ 74 ssize_t nr; 75 lsquic_stream_ctx_t *st_h = ctx; 76 77 nr = read(fd, st_h->buf + st_h->buf_off++, 1); 78 LSQ_DEBUG("read %zd bytes from stdin", nr); 79 if (0 == nr) 80 { 81 lsquic_stream_shutdown(st_h->stream, 2); 82 } 83 else if (-1 == nr) 84 { 85 perror("read"); 86 exit(1); 87 } 88 else if ('\n' == st_h->buf[ st_h->buf_off - 1 ]) 89 { 90 LSQ_DEBUG("read newline: wantwrite"); 91 lsquic_stream_wantwrite(st_h->stream, 1); 92 lsquic_engine_process_conns(st_h->client_ctx->prog->prog_engine); 93 } 94 else if (st_h->buf_off == sizeof(st_h->buf)) 95 { 96 LSQ_NOTICE("line too long"); 97 exit(2); 98 } 99 else 100 event_add(st_h->read_stdin_ev, NULL); 101} 102 103 104static lsquic_stream_ctx_t * 105echo_client_on_new_stream (void *stream_if_ctx, lsquic_stream_t *stream) 106{ 107 lsquic_stream_ctx_t *st_h = calloc(1, sizeof(*st_h)); 108 st_h->stream = stream; 109 st_h->client_ctx = stream_if_ctx; 110 st_h->buf_off = 0; 111 st_h->read_stdin_ev = event_new(prog_eb(st_h->client_ctx->prog), 112 STDIN_FILENO, EV_READ, read_stdin, st_h); 113 event_add(st_h->read_stdin_ev, NULL); 114 return st_h; 115} 116 117 118static void 119echo_client_on_read (lsquic_stream_t *stream, lsquic_stream_ctx_t *st_h) 120{ 121 char c; 122 size_t nr; 123 124 nr = lsquic_stream_read(stream, &c, 1); 125 if (0 == nr) 126 { 127 lsquic_stream_shutdown(stream, 2); 128 return; 129 } 130 printf("%c", c); 131 fflush(stdout); 132 if ('\n' == c) 133 { 134 event_add(st_h->read_stdin_ev, NULL); 135 lsquic_stream_wantread(stream, 0); 136 } 137} 138 139 140static void 141echo_client_on_write (lsquic_stream_t *stream, lsquic_stream_ctx_t *st_h) 142{ 143 /* Here we make an assumption that we can write the whole buffer. 144 * Don't do it in a real program. 145 */ 146 lsquic_stream_write(stream, st_h->buf, st_h->buf_off); 147 st_h->buf_off = 0; 148 149 lsquic_stream_flush(stream); 150 lsquic_stream_wantwrite(stream, 0); 151 lsquic_stream_wantread(stream, 1); 152} 153 154 155static void 156echo_client_on_close (lsquic_stream_t *stream, lsquic_stream_ctx_t *st_h) 157{ 158 LSQ_NOTICE("%s called", __func__); 159 if (st_h->read_stdin_ev) 160 { 161 event_del(st_h->read_stdin_ev); 162 event_free(st_h->read_stdin_ev); 163 } 164 free(st_h); 165 lsquic_conn_close(lsquic_stream_conn(stream)); 166} 167 168 169const struct lsquic_stream_if client_echo_stream_if = { 170 .on_new_conn = echo_client_on_new_conn, 171 .on_conn_closed = echo_client_on_conn_closed, 172 .on_new_stream = echo_client_on_new_stream, 173 .on_read = echo_client_on_read, 174 .on_write = echo_client_on_write, 175 .on_close = echo_client_on_close, 176}; 177 178 179static void 180usage (const char *prog) 181{ 182 const char *const slash = strrchr(prog, '/'); 183 if (slash) 184 prog = slash + 1; 185 LSQ_NOTICE( 186"Usage: %s [opts]\n" 187"\n" 188"Options:\n" 189 , prog); 190} 191 192 193int 194main (int argc, char **argv) 195{ 196 int opt, s; 197 struct sport_head sports; 198 struct prog prog; 199 struct echo_client_ctx client_ctx; 200 201 memset(&client_ctx, 0, sizeof(client_ctx)); 202 client_ctx.prog = &prog; 203 204 TAILQ_INIT(&sports); 205 prog_init(&prog, 0, &sports, &client_echo_stream_if, &client_ctx); 206 207 while (-1 != (opt = getopt(argc, argv, PROG_OPTS "h"))) 208 { 209 switch (opt) { 210 case 'h': 211 usage(argv[0]); 212 prog_print_common_options(&prog, stdout); 213 exit(0); 214 default: 215 if (0 != prog_set_opt(&prog, opt, optarg)) 216 exit(1); 217 } 218 } 219 220 int flags = fcntl(STDIN_FILENO, F_GETFL); 221 flags |= O_NONBLOCK; 222 if (0 != fcntl(STDIN_FILENO, F_SETFL, flags)) 223 { 224 perror("fcntl"); 225 exit(1); 226 } 227 228 if (0 != prog_prep(&prog)) 229 { 230 LSQ_ERROR("could not prep"); 231 exit(EXIT_FAILURE); 232 } 233 if (0 != prog_connect(&prog, NULL, 0)) 234 { 235 LSQ_ERROR("could not connect"); 236 exit(EXIT_FAILURE); 237 } 238 239 LSQ_DEBUG("entering event loop"); 240 241 s = prog_run(&prog); 242 prog_cleanup(&prog); 243 244 exit(0 == s ? EXIT_SUCCESS : EXIT_FAILURE); 245} 246