diff options
Diffstat (limited to '091socket.cc')
-rw-r--r-- | 091socket.cc | 99 |
1 files changed, 92 insertions, 7 deletions
diff --git a/091socket.cc b/091socket.cc index 97c90716..74626853 100644 --- a/091socket.cc +++ b/091socket.cc @@ -2,13 +2,78 @@ struct socket_t { int fd; sockaddr_in addr; + bool polled; socket_t() { fd = 0; + polled = false; bzero(&addr, sizeof(addr)); } }; :(before "End Primitive Recipe Declarations") +_OPEN_CLIENT_SOCKET, +:(before "End Primitive Recipe Numbers") +put(Recipe_ordinal, "$open-client-socket", _OPEN_CLIENT_SOCKET); +:(before "End Primitive Recipe Checks") +case _OPEN_CLIENT_SOCKET: { + if (SIZE(inst.ingredients) != 2) { + raise << maybe(get(Recipe, r).name) << "'$open-client-socket' requires exactly two ingredients, but got '" << inst.original_string << "'\n" << end(); + break; + } + if (!is_mu_text(inst.ingredients.at(0))) { + raise << maybe(get(Recipe, r).name) << "first ingredient of '$open-client-socket' should be text (the hostname), but got '" << to_string(inst.ingredients.at(0)) << "'\n" << end(); + break; + } + if (!is_mu_number(inst.ingredients.at(1))) { + raise << maybe(get(Recipe, r).name) << "second ingredient of '$open-client-socket' should be a number (the port of the hostname to connect to), but got '" << to_string(inst.ingredients.at(1)) << "'\n" << end(); + break; + } + if (SIZE(inst.products) != 1) { + raise << maybe(get(Recipe, r).name) << "'$open-client-socket' requires exactly one product, but got '" << inst.original_string << "'\n" << end(); + break; + } + if (!is_mu_number(inst.products.at(0))) { + raise << maybe(get(Recipe, r).name) << "first product of '$open-client-socket' should be a number (socket handle), but got '" << to_string(inst.products.at(0)) << "'\n" << end(); + break; + } + break; +} +:(before "End Primitive Recipe Implementations") +case _OPEN_CLIENT_SOCKET: { + string host = read_mu_text(ingredients.at(0).at(0)); + int port = ingredients.at(1).at(0); + socket_t* client = client_socket(host, port); + products.resize(1); + if (client->fd < 0) { // error + delete client; + products.at(0).push_back(0); + break; + } + long long int result = reinterpret_cast<long long int>(client); + products.at(0).push_back(static_cast<double>(result)); + break; +} +:(code) +socket_t* client_socket(const string& host, int port) { + socket_t* result = new socket_t; + result->fd = socket(AF_INET, SOCK_STREAM, 0); + if (result->fd < 0) { + raise << "Failed to create socket.\n" << end(); + return result; + } + result->addr.sin_family = AF_INET; + hostent* tmp = gethostbyname(host.c_str()); + bcopy(tmp->h_addr, reinterpret_cast<char*>(&result->addr.sin_addr.s_addr), tmp->h_length); + result->addr.sin_port = htons(port); + if (connect(result->fd, reinterpret_cast<sockaddr*>(&result->addr), sizeof(result->addr)) < 0) { + close(result->fd); + result->fd = -1; + raise << "Failed to connect to " << host << ':' << port << '\n' << end(); + } + return result; +} + +:(before "End Primitive Recipe Declarations") _OPEN_SERVER_SOCKET, :(before "End Primitive Recipe Numbers") put(Recipe_ordinal, "$open-server-socket", _OPEN_SERVER_SOCKET); @@ -154,17 +219,35 @@ case _READ_FROM_SOCKET: { :(before "End Primitive Recipe Implementations") case _READ_FROM_SOCKET: { long long int x = static_cast<long long int>(ingredients.at(0).at(0)); - int bytes = static_cast<int>(ingredients.at(1).at(0)); socket_t* socket = reinterpret_cast<socket_t*>(x); - int socket_fd = socket->fd; - char contents[bytes]; + // 1. we'd like to simply read() from the socket + // however read() on a socket never returns EOF, so we wouldn't know when to stop + // 2. recv() can signal EOF, but it also signals "no data yet" in the beginning + // so use poll() in the beginning to wait for data before calling recv() + // 3. but poll() will block on EOF, so only use poll() on the very first + // $read-from-socket on a socket + if (!socket->polled) { + socket->polled = true; + pollfd p; + bzero(&p, sizeof(p)); + p.fd = socket->fd; + p.events = POLLIN | POLLHUP; + if (poll(&p, /*num pollfds*/1, /*no timeout*/-1) <= 0) { + raise << maybe(current_recipe_name()) << "error in $read-from-socket\n" << end(); + products.resize(2); + products.at(0).push_back(0); + products.at(1).push_back(false); + break; + } + } + int bytes = static_cast<int>(ingredients.at(1).at(0)); + char* contents = new char[bytes]; bzero(contents, bytes); - int bytes_read = read(socket_fd, contents, bytes-/*terminal null*/1); -//? cerr << "Read:\n" << string(contents) << "\n"; -//? cerr << "bytes read: " << bytes_read << '\n'; + int bytes_read = recv(socket->fd, contents, bytes-/*terminal null*/1, MSG_DONTWAIT); products.resize(2); products.at(0).push_back(new_mu_text(contents)); - products.at(1).push_back(bytes_read < bytes-1); + products.at(1).push_back(bytes_read <= 0); + delete contents; break; } @@ -223,5 +306,7 @@ case _CLOSE_SOCKET: { :(before "End Includes") #include <netinet/in.h> +#include <netdb.h> +#include <poll.h> #include <sys/socket.h> #include <unistd.h> |