about summary refs log tree commit diff stats
path: root/091socket.cc
diff options
context:
space:
mode:
Diffstat (limited to '091socket.cc')
-rw-r--r--091socket.cc99
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>