ws_client.c 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409
  1. #include <time.h>
  2. #include <stdio.h>
  3. #include <stdlib.h>
  4. #include <string.h>
  5. #include "civetweb.h"
  6. /* Get an OS independent definition for sleep() */
  7. #ifdef _WIN32
  8. #include <windows.h>
  9. #define sleep(x) Sleep((x)*1000)
  10. #else
  11. #include <unistd.h>
  12. #endif
  13. /* User defined client data structure */
  14. struct tclient_data {
  15. time_t started;
  16. time_t closed;
  17. struct tmsg_list_elem *msgs;
  18. };
  19. struct tmsg_list_elem {
  20. time_t timestamp;
  21. void *data;
  22. size_t len;
  23. struct tmsg_list_elem *next;
  24. };
  25. /* Helper function to get a printable name for websocket opcodes */
  26. static const char *
  27. msgtypename(int flags)
  28. {
  29. unsigned f = (unsigned)flags & 0xFu;
  30. switch (f) {
  31. case MG_WEBSOCKET_OPCODE_CONTINUATION:
  32. return "continuation";
  33. case MG_WEBSOCKET_OPCODE_TEXT:
  34. return "text";
  35. case MG_WEBSOCKET_OPCODE_BINARY:
  36. return "binary";
  37. case MG_WEBSOCKET_OPCODE_CONNECTION_CLOSE:
  38. return "clonnection close";
  39. case MG_WEBSOCKET_OPCODE_PING:
  40. return "PING";
  41. case MG_WEBSOCKET_OPCODE_PONG:
  42. return "PING";
  43. }
  44. return "unknown";
  45. }
  46. /* Callback for handling data received from the server */
  47. static int
  48. websocket_client_data_handler(struct mg_connection *conn,
  49. int flags,
  50. char *data,
  51. size_t data_len,
  52. void *user_data)
  53. {
  54. struct tclient_data *pclient_data = (struct tclient_data *)user_data;
  55. time_t now = time(NULL);
  56. /* We may get some different message types (websocket opcodes).
  57. * We will handle these messages differently. */
  58. int is_text = ((flags & 0xf) == MG_WEBSOCKET_OPCODE_TEXT);
  59. int is_bin = ((flags & 0xf) == MG_WEBSOCKET_OPCODE_BINARY);
  60. int is_ping = ((flags & 0xf) == MG_WEBSOCKET_OPCODE_PING);
  61. int is_pong = ((flags & 0xf) == MG_WEBSOCKET_OPCODE_PONG);
  62. int is_close = ((flags & 0xf) == MG_WEBSOCKET_OPCODE_CONNECTION_CLOSE);
  63. /* Log output: We got some data */
  64. printf("%10.0f - Client received %lu bytes of %s data from server%s",
  65. difftime(now, pclient_data->started),
  66. (long unsigned)data_len,
  67. msgtypename(flags),
  68. (is_text ? ": " : ".\n"));
  69. /* Check if we got a websocket PING request */
  70. if (is_ping) {
  71. /* PING requests are to check if the connection is broken.
  72. * They should be replied with a PONG with the same data.
  73. */
  74. mg_websocket_client_write(conn,
  75. MG_WEBSOCKET_OPCODE_PONG,
  76. data,
  77. data_len);
  78. return 1;
  79. }
  80. /* Check if we got a websocket PONG message */
  81. if (is_pong) {
  82. /* A PONG message may be a response to our PING, but
  83. * it is also allowed to send unsolicited PONG messages
  84. * send by the server to check some lower level TCP
  85. * connections. Just ignore all kinds of PONGs. */
  86. return 1;
  87. }
  88. /* It we got a websocket TEXT message, handle it ... */
  89. if (is_text) {
  90. struct tmsg_list_elem *p;
  91. struct tmsg_list_elem **where = &(pclient_data->msgs);
  92. /* ... by printing it to the log ... */
  93. fwrite(data, 1, data_len, stdout);
  94. printf("\n");
  95. /* ... and storing it (OOM ignored for simplicity). */
  96. p = (struct tmsg_list_elem *)malloc(sizeof(struct tmsg_list_elem));
  97. p->timestamp = now;
  98. p->data = malloc(data_len);
  99. memcpy(p->data, data, data_len);
  100. p->len = data_len;
  101. p->next = NULL;
  102. while (*where != NULL) {
  103. where = &((*where)->next);
  104. }
  105. *where = p;
  106. }
  107. /* Another option would be BINARY data. */
  108. if (is_bin) {
  109. /* In this example, we just ignore binary data.
  110. * According to some blogs, discriminating TEXT and
  111. * BINARY may be some remains from earlier drafts
  112. * of the WebSocket protocol.
  113. * Anyway, a real application will usually use
  114. * either TEXT or BINARY. */
  115. }
  116. /* It could be a CLOSE message as well. */
  117. if (is_close) {
  118. printf("%10.0f - Goodbye\n", difftime(now, pclient_data->started));
  119. return 0;
  120. }
  121. /* Return 1 to keep the connection open */
  122. return 1;
  123. }
  124. /* Callback for handling a close message received from the server */
  125. static void
  126. websocket_client_close_handler(const struct mg_connection *conn,
  127. void *user_data)
  128. {
  129. struct tclient_data *pclient_data = (struct tclient_data *)user_data;
  130. pclient_data->closed = time(NULL);
  131. printf("%10.0f - Client: Close handler\n",
  132. difftime(pclient_data->closed, pclient_data->started));
  133. }
  134. /* Websocket client test function */
  135. void
  136. run_websocket_client(const char *host,
  137. int port,
  138. int secure,
  139. const char *path,
  140. const char *greetings)
  141. {
  142. char err_buf[100] = {0};
  143. struct mg_connection *client_conn;
  144. struct tclient_data *pclient_data;
  145. int i;
  146. /* Allocate some memory for callback specific data.
  147. * For simplicity, we ignore OOM handling in this example. */
  148. pclient_data = (struct tclient_data *)malloc(sizeof(struct tclient_data));
  149. /* Store start time in the private structure */
  150. pclient_data->started = time(NULL);
  151. pclient_data->closed = 0;
  152. pclient_data->msgs = NULL;
  153. /* Log first action (time = 0.0) */
  154. printf("%10.0f - Connecting to %s:%i\n", 0.0, host, port);
  155. /* Connect to the given WS or WSS (WS secure) server */
  156. client_conn = mg_connect_websocket_client(host,
  157. port,
  158. secure,
  159. err_buf,
  160. sizeof(err_buf),
  161. path,
  162. NULL,
  163. websocket_client_data_handler,
  164. websocket_client_close_handler,
  165. pclient_data);
  166. /* Check if connection is possible */
  167. if (client_conn == NULL) {
  168. printf("mg_connect_websocket_client error: %s\n", err_buf);
  169. return;
  170. }
  171. /* Connection established */
  172. printf("%10.0f - Connected\n", difftime(time(NULL), pclient_data->started));
  173. /* If there are greetings to send, do it now */
  174. if (greetings) {
  175. printf("%10.0f - Sending greetings\n",
  176. difftime(time(NULL), pclient_data->started));
  177. mg_websocket_client_write(client_conn,
  178. MG_WEBSOCKET_OPCODE_TEXT,
  179. greetings,
  180. strlen(greetings));
  181. }
  182. /* Wait for some seconds */
  183. sleep(5);
  184. /* Does the server play "ping pong" ? */
  185. for (i = 0; i < 5; i++) {
  186. /* Send a PING message every 5 seconds. */
  187. printf("%10.0f - Sending PING\n",
  188. difftime(time(NULL), pclient_data->started));
  189. mg_websocket_client_write(client_conn,
  190. MG_WEBSOCKET_OPCODE_PING,
  191. (const char *)&i,
  192. sizeof(int));
  193. sleep(5);
  194. }
  195. /* Wait a while */
  196. /* If we do not use "ping pong", the server will probably
  197. * close the connection with a timeout earlier. */
  198. sleep(150);
  199. /* Send greetings again */
  200. if (greetings) {
  201. printf("%10.0f - Sending greetings again\n",
  202. difftime(time(NULL), pclient_data->started));
  203. mg_websocket_client_write(client_conn,
  204. MG_WEBSOCKET_OPCODE_TEXT,
  205. greetings,
  206. strlen(greetings));
  207. }
  208. /* Wait for some seconds */
  209. sleep(5);
  210. /* Send some "song text": http://www.99-bottles-of-beer.net/ */
  211. {
  212. char txt[128];
  213. int b = 99; /* start with 99 bottles */
  214. while (b > 0) {
  215. /* Send "b bottles" text line. */
  216. sprintf(txt,
  217. "%i bottle%s of beer on the wall, "
  218. "%i bottle%s of beer.",
  219. b,
  220. ((b != 1) ? "s" : ""),
  221. b,
  222. ((b != 1) ? "s" : ""));
  223. mg_websocket_client_write(client_conn,
  224. MG_WEBSOCKET_OPCODE_TEXT,
  225. txt,
  226. strlen(txt));
  227. /* Take a breath. */
  228. sleep(1);
  229. /* Drink a bottle */
  230. b--;
  231. /* Send "remaining bottles" text line. */
  232. if (b) {
  233. sprintf(txt,
  234. "Take one down and pass it around, "
  235. "%i bottle%s of beer on the wall.",
  236. b,
  237. ((b != 1) ? "s" : ""));
  238. } else {
  239. strcpy(txt,
  240. "Take one down and pass it around, "
  241. "no more bottles of beer on the wall.");
  242. }
  243. mg_websocket_client_write(client_conn,
  244. MG_WEBSOCKET_OPCODE_TEXT,
  245. txt,
  246. strlen(txt));
  247. /* Take a breath. */
  248. sleep(2);
  249. }
  250. /* Send "no more bottles" text line. */
  251. strcpy(txt,
  252. "No more bottles of beer on the wall, "
  253. "no more bottles of beer.");
  254. mg_websocket_client_write(client_conn,
  255. MG_WEBSOCKET_OPCODE_TEXT,
  256. txt,
  257. strlen(txt));
  258. /* Take a breath. */
  259. sleep(1);
  260. /* Buy new bottles. */
  261. b = 99;
  262. /* Send "buy some more" text line. */
  263. sprintf(txt,
  264. "Go to the store and buy some more, "
  265. "%i bottle%s of beer on the wall.",
  266. b,
  267. ((b != 1) ? "s" : ""));
  268. mg_websocket_client_write(client_conn,
  269. MG_WEBSOCKET_OPCODE_TEXT,
  270. txt,
  271. strlen(txt));
  272. }
  273. /* Wait for some seconds */
  274. sleep(5);
  275. /* Somewhat boring conversation, isn't it?
  276. * Tell the server we have to leave. */
  277. printf("%10.0f - Sending close message\n",
  278. difftime(time(NULL), pclient_data->started));
  279. mg_websocket_client_write(client_conn,
  280. MG_WEBSOCKET_OPCODE_CONNECTION_CLOSE,
  281. NULL,
  282. 0);
  283. /* We don't need to wait, this is just to have the log timestamp
  284. * a second later, and to not log from the handlers and from
  285. * here the same time (printf to stdout is not thread-safe, but
  286. * adding flock or mutex or an explicit logging function makes
  287. * this example unnecessarily complex). */
  288. sleep(5);
  289. /* Connection should be closed by now. */
  290. printf("%10.0f - Connection state: %s\n",
  291. difftime(time(NULL), pclient_data->started),
  292. ((pclient_data->closed == 0) ? "open" : "closed"));
  293. /* Close client connection */
  294. mg_close_connection(client_conn);
  295. printf("%10.0f - End of test\n",
  296. difftime(time(NULL), pclient_data->started));
  297. /* Print collected data */
  298. printf("\n\nPrint all text messages from server again:\n");
  299. {
  300. struct tmsg_list_elem **where = &(pclient_data->msgs);
  301. while (*where != NULL) {
  302. printf("%10.0f - [%5lu] ",
  303. difftime((*where)->timestamp, pclient_data->started),
  304. (unsigned long)(*where)->len);
  305. fwrite((const char *)(*where)->data, 1, (*where)->len, stdout);
  306. printf("\n");
  307. where = &((*where)->next);
  308. }
  309. }
  310. /* Free collected data */
  311. {
  312. struct tmsg_list_elem **where = &(pclient_data->msgs);
  313. void *p1 = 0;
  314. void *p2 = 0;
  315. while (*where != NULL) {
  316. free((*where)->data);
  317. free(p2);
  318. p2 = p1;
  319. p1 = *where;
  320. where = &((*where)->next);
  321. }
  322. free(p2);
  323. free(p1);
  324. }
  325. free(pclient_data);
  326. }
  327. /* main will initialize the CivetWeb library
  328. * and start the WebSocket client test function */
  329. int
  330. main(int argc, char *argv[])
  331. {
  332. const char *greetings = "Hello World!";
  333. const char *host = "echo.websocket.org";
  334. const char *path = "/";
  335. #if defined(NO_SSL)
  336. const int port = 80;
  337. const int secure = 0;
  338. mg_init_library(0);
  339. #else
  340. const int port = 443;
  341. const int secure = 1;
  342. mg_init_library(MG_FEATURES_SSL);
  343. #endif
  344. run_websocket_client(host, port, secure, path, greetings);
  345. }