/* * Copyright 2013 The Emscripten Authors. All rights reserved. * Emscripten is available under two separate licenses, the MIT license and the * University of Illinois/NCSA Open Source License. Both these licenses can be * found in the LICENSE file. */ #include #include #include #include #include #include #include #include #include #include #include #include #ifdef __EMSCRIPTEN__ #include #endif #include "test_sockets_msg.h" // message to send to the server #ifndef MESSAGE #define MESSAGE "pingtothepong" #endif typedef enum { MSG_CONNECT, MSG_READ, MSG_WRITE } msg_state_t; typedef struct { int fd; msg_t msg; msg_state_t state; } server_t; server_t server; msg_t echo_msg; int echo_read; int echo_wrote; void finish(int result) { printf("finish: %d\n", result); if (server.fd) { close(server.fd); server.fd = 0; } #ifdef __EMSCRIPTEN__ #if TEST_ASYNC emscripten_force_exit(result); #else emscripten_cancel_main_loop(); #endif #endif exit(result); } void main_loop() { static char out[1024*2]; static int pos = 0; fd_set fdr; fd_set fdw; int res; // Timeout of zero means don't block. Emscripten doesn't support blocking select in the general case struct timeval tv = {0}; // make sure that server.fd is ready to read / write FD_ZERO(&fdr); FD_ZERO(&fdw); FD_SET(server.fd, &fdr); FD_SET(server.fd, &fdw); res = select(64, &fdr, &fdw, NULL, &tv); if (res == -1) { perror("select failed"); finish(EXIT_FAILURE); } if (server.state == MSG_READ) { if (!FD_ISSET(server.fd, &fdr)) { return; } #if !TEST_DGRAM // as a test, confirm with ioctl that we have data available // after selecting int available; res = ioctl(server.fd, FIONREAD, &available); assert(res != -1); assert(available); #endif res = do_msg_read(server.fd, &server.msg, echo_read, 0, NULL, NULL); if (res == -1) { return; } else if (res == 0) { perror("server closed"); finish(EXIT_FAILURE); } echo_read += res; // once we've read the entire message, validate it if (echo_read >= server.msg.length) { assert(!strcmp(server.msg.buffer, MESSAGE)); finish(EXIT_SUCCESS); } } if (server.state == MSG_CONNECT) { if (!FD_ISSET(server.fd, &fdw)) { return; } int error = 0; socklen_t len = sizeof(error); int ret = getsockopt(server.fd, SOL_SOCKET, SO_ERROR, &error, &len); if (error != 0) { printf("connect failed: %s\n", strerror(error)); finish(EXIT_FAILURE); } printf("connected\n"); server.state = MSG_WRITE; } if (server.state == MSG_WRITE) { if (!FD_ISSET(server.fd, &fdw)) { return; } res = do_msg_write(server.fd, &echo_msg, echo_wrote, 0, NULL, 0); if (res == -1) { return; } else if (res == 0) { perror("server closed"); finish(EXIT_FAILURE); } echo_wrote += res; // once we're done writing the message, read it back if (echo_wrote >= echo_msg.length) { server.state = MSG_READ; } } } // The callbacks for the async network events have a different signature than from // emscripten_set_main_loop (they get passed the fd of the socket triggering the event). // In this test application we want to try and keep as much in common as the timed loop // version but in a real application the fd can be used instead of needing to select(). void async_main_loop(int fd, void* userData) { printf("%s callback\n", (char*)userData); main_loop(); } void error_callback(int fd, int err, const char* msg, void* userData) { int error; socklen_t len = sizeof(error); int ret = getsockopt(fd, SOL_SOCKET, SO_ERROR, &error, &len); printf("%s callback\n", (char*)userData); printf("error message: %s\n", msg); if (err == error) { finish(EXIT_SUCCESS); } else { finish(EXIT_FAILURE); } } int main() { struct sockaddr_in addr; int res; memset(&server, 0, sizeof(server_t)); server.state = MSG_CONNECT; // setup the message we're going to echo memset(&echo_msg, 0, sizeof(msg_t)); echo_msg.length = strlen(MESSAGE) + 1; echo_msg.buffer = malloc(echo_msg.length); strncpy(echo_msg.buffer, MESSAGE, echo_msg.length); echo_read = 0; echo_wrote = 0; // create the socket and set to non-blocking #if !TEST_DGRAM server.fd = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP); #else server.fd = socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP); #endif if (server.fd == -1) { perror("cannot create socket"); finish(EXIT_FAILURE); } fcntl(server.fd, F_SETFL, O_NONBLOCK); // connect the socket memset(&addr, 0, sizeof(addr)); addr.sin_family = AF_INET; addr.sin_port = htons(SOCKK); if (inet_pton(AF_INET, "127.0.0.1", &addr.sin_addr) != 1) { perror("inet_pton failed"); finish(EXIT_FAILURE); } res = connect(server.fd, (struct sockaddr *)&addr, sizeof(addr)); if (res == -1 && errno != EINPROGRESS) { perror("connect failed"); finish(EXIT_FAILURE); } { int z; struct sockaddr_in adr_inet; socklen_t len_inet = sizeof adr_inet; z = getsockname(server.fd, (struct sockaddr *)&adr_inet, &len_inet); if (z != 0) { perror("getsockname"); finish(EXIT_FAILURE); } char buffer[1000]; sprintf(buffer, "%s:%u", inet_ntoa(adr_inet.sin_addr), (unsigned)ntohs(adr_inet.sin_port)); // TODO: This is not the correct result: We should have a auto-bound address char *correct = "0.0.0.0:0"; printf("got (expected) socket: %s (%s), size %lu (%lu)\n", buffer, correct, strlen(buffer), strlen(correct)); assert(strlen(buffer) == strlen(correct)); assert(strcmp(buffer, correct) == 0); } { int z; struct sockaddr_in adr_inet; socklen_t len_inet = sizeof adr_inet; z = getpeername(server.fd, (struct sockaddr *)&adr_inet, &len_inet); if (z != 0) { perror("getpeername"); finish(EXIT_FAILURE); } char buffer[1000]; sprintf(buffer, "%s:%u", inet_ntoa(adr_inet.sin_addr), (unsigned)ntohs(adr_inet.sin_port)); char correct[1000]; sprintf(correct, "127.0.0.1:%u", SOCKK); printf("got (expected) socket: %s (%s), size %lu (%lu)\n", buffer, correct, strlen(buffer), strlen(correct)); assert(strlen(buffer) == strlen(correct)); assert(strcmp(buffer, correct) == 0); } #ifdef __EMSCRIPTEN__ #if TEST_ASYNC // The first parameter being passed is actually an arbitrary userData pointer // for simplicity this test just passes a basic char* emscripten_set_socket_error_callback("error", error_callback); emscripten_set_socket_open_callback("open", async_main_loop); emscripten_set_socket_message_callback("message", async_main_loop); #else emscripten_set_main_loop(main_loop, 60, 0); #endif #else while (1) main_loop(); #endif return EXIT_SUCCESS; }