home · contact · privacy
Re-wrote large parts of the server client architecture. No more fifo.
authorChristian Heller <c.heller@plomlompom.de>
Mon, 17 Mar 2014 03:23:23 +0000 (04:23 +0100)
committerChristian Heller <c.heller@plomlompom.de>
Mon, 17 Mar 2014 03:23:23 +0000 (04:23 +0100)
server/out in its old use moves to server/worldstate, while the new
server/out is used for specific server messages. server/in is now a
plain text file. Solved the problem of parallel server processes, too.

18 files changed:
README
TODO
roguelike
src/client/cleanup.c
src/client/cleanup.h
src/client/control.c
src/client/io.c
src/client/io.h
src/client/main.c
src/client/world.h
src/server/cleanup.c
src/server/cleanup.h
src/server/io.c
src/server/io.h
src/server/main.c
src/server/run.c
src/server/run.h
src/server/world.h

diff --git a/README b/README
index c44d7daaf0bb2e99b73a9468511dca1a2865ac1b..7963f621e5c7c1728d70283b73c80b2f13d2fd29 100644 (file)
--- a/README
+++ b/README
@@ -5,18 +5,17 @@ plomlompom tries to build his own roguelike. It doesn't do much yet (although
 plomlompom has insanely ambitious long-term plans).
 
 You can move around a player on an island and meet different enemies. You have 5
-hitpoints to lose before death; they start with different amounts of hitpoints,
-depending on their species. Dead enemies become dirt, skeletons or "magic meat"
--- such objects can be collected, and "magic meat" can be consumed to gain
-hitpoints. Note that different kinds of movements/actions take different numbers
-of turns to finish.
+hitpoints to lose before death. Enemies start with different amounts of
+hitpoints, depending on their species. Dead enemies become dirt, skeletons or
+"magic meat" -- such objects can be collected, and "magic meat" can be consumed
+to gain hitpoints. Note that different kinds of movements/actions take different
+numbers of turns to finish.
 
 Enemies' AI is very dumb so far: Each turn, they try to move towards their
-path-wise nearest enemy. If no enemy is found in their surroundings, they just
-wait.
+shortest-path-wise nearest enemy. If no enemy is found in their surroundings,
+they just wait.
 
-Note that diagonal movement is possible, but takes (40%) longer than orthogonal
-movement.
+Diagonal movement is possible, but takes (40%) longer than orthogonal movement.
 
 Once you start a new world, every move of yours is recorded in a file called
 "record". Once you re-start the game, all of your previous moves are replayed
@@ -36,10 +35,10 @@ $ ./roguelike
 
 (It may also work on other Unix-like systems with ncurses, who knows.)
 
-Note that make generates two executables ./roguelike-server and
-./roguelike-client. ./roguelike is a pre-existing shell script that merely
-executes both of them, with the server as a background job. You can also
-ignore the script and start the two by hand.
+Make generates two executables ./roguelike-server and ./roguelike-client.
+./roguelike is a pre-existing shell script that merely executes both of them,
+with the server as a background job. You can also ignore the script and start
+the two by hand.
 
 Client's keybindings and window management
 ------------------------------------------
@@ -103,7 +102,8 @@ All source files are thoroughly documented to explain more details of
 plomrogue's internals. The ./roguelike-server executable can be run with a -v
 option for helpful debugging info (mostly: what messages the client sends to the
 server). Server and client communicate via files in the ./server/ directory
-(generated when the server is first run). The ./server/in fifo receives commands
-for the server as null-terminated strings. The ./server/out file contains a
-serialized representation of the game world's data as it is to be available to
+(generated when the server is first run). The ./server/in file is read by the
+server for newline-delimited commands. The ./server/out file contains server
+messages to be read by clients. The ./server/worldstate file contains a
+serialized representation of the game world's data as it is to be visible to
 the player / the player's client.
diff --git a/TODO b/TODO
index 766b12dc001273b7065848299cc03209d7d3b30c..4c9b8948a0013f9221976b793170da45dd556d33 100644 (file)
--- a/TODO
+++ b/TODO
@@ -2,19 +2,15 @@ Next planned steps in plomrogue development:
 
 BOTH SERVER/CLIENT:
 
-- implement better server/client architecture, meeting these criteria:
-  - keep interaction of server and client in the filesystem (as currently
-    happens by use of plain text files and fifos) to allow for easy scripting
-  - allow for specific requests of the client for specific server data (such as:
-    "what path do you propose for the player character to get from A to B?")
-  - reduce hard disk i/o for server/client interaction (use fifos only?)
-  - further avoid exits that keep the server running and the lock file existing
-    when it shouldn't
+- make server and client communicate by specific world state info requests 
+  in server/out, replacing server/worldstate
 
 - check for return values of *printf()
 
 - make config files format more readable / self-explanatory
 
+- get rid of the useless "internal error code" in error messages
+
 SERVER:
 
 - implement field of view / line of sight and obstacles for those on the map
index ae3fd75d9d69d09130321b31fb4f1026bb8f62fc..3ea498d6478814146481e50f33bd3cc7428673d9 100755 (executable)
--- a/roguelike
+++ b/roguelike
@@ -10,20 +10,20 @@ set -e
 sleep 0.01
 
 # The client should not start if the server is not running. (If the server was
-# running in the foreround, any error exit of it so far would be caught by "set
+# running in the foreground, any error exit of it so far would be caught by "set
 # -e" above. But "set -e" is blind to error codes generated in the background.)
 kill -0 $! 2> /dev/null
 
-# Give server some time (max. 10 seconds) to generate its out file.
+# Give server some time (max. 10 seconds) to generate its worldstate file.
 i=0
-while [ ! -e server/out ] && [ $i -le 1000 ]
+while [ ! -e server/worldstate ] && [ $i -le 1000 ]
 do
     sleep 0.01
     i=`expr $i + 1`
 done
-if [ ! -e server/out ]
+if [ ! -e server/worldstate ]
 then
-    echo "Server failed generating outfile within given time limit."
+    echo "Server failed generating worldstate file within given time limit."
     false
 fi
 
index bcf52ac405e0a31ab41c5df4f85ea1e38d432d45..f060250a85a717f068284cc74e8381dfeac5df77 100644 (file)
@@ -4,6 +4,7 @@
 #include <ncurses.h> /* for endwin() */
 #include <stdint.h> /* uint32_t */
 #include <stdlib.h> /* free() */
+#include "../common/readwrite.h" /* try_fclose() */
 #include "command_db.h" /* free_command_db() */
 #include "misc.h" /* unload_interface_conf() */
 #include "world.h" /* world global */
@@ -17,6 +18,7 @@ static uint32_t cleanup_flags = 0x0000;
 
 extern void cleanup()
 {
+    char * f_name = "cleanup()";
     free(world.map.cells);
     free(world.log);
     free(world.player_inventory);
@@ -32,6 +34,14 @@ extern void cleanup()
     {
         free_command_db();
     }
+    if (cleanup_flags & CLEANUP_SERVER_IN)
+    {
+        try_fclose(world.file_server_in, f_name);
+    }
+    if (cleanup_flags & CLEANUP_SERVER_OUT)
+    {
+        try_fclose(world.file_server_out, f_name);
+    }
 }
 
 
index ce4866dc1648792dc7d32c84b5f4ced3291851b2..c234c1c34bf82343e41cec597c49b372895acd80 100644 (file)
@@ -16,7 +16,9 @@ enum cleanup_flag
 {
     CLEANUP_NCURSES    = 0x0001,
     CLEANUP_INTERFACE  = 0x0002,
-    CLEANUP_COMMANDS   = 0x0004
+    CLEANUP_COMMANDS   = 0x0004,
+    CLEANUP_SERVER_IN  = 0x0008,
+    CLEANUP_SERVER_OUT = 0x0008
 };
 
 extern void set_cleanup_flag(enum cleanup_flag flag);
index f236ecf9a8e808f79476e8d654e7568d4e631a02..745b2abe3a3e557ac9277f5f31caf9ab7db4cf25 100644 (file)
@@ -4,7 +4,7 @@
 #include <stdint.h> /* uint8_t, uint16_t */
 #include <stdio.h> /* sprintf() */
 #include <string.h> /* strlen() */
-#include "io.h" /* try_send() */
+#include "io.h" /* send() */
 #include "keybindings.h" /* get_command_to_keycode(), get_keycode_to_command(),
                           * mod_selected_keyb(), move_keyb_selection()
                           */
@@ -139,7 +139,7 @@ static uint8_t try_server_commands(struct Command * command)
         uint8_t arg_size = 3;
         char msg[command_size + 1 + arg_size + 1];
         sprintf(msg, "%s %d", command->server_msg, arg);
-        try_send(msg);
+        send(msg);
         return 1;
     }
     return 0;
index 27a0e0f46d623fc2b31be94c074a2c953d61becb..19dbb39478bdc7ee51129862c69c78e678c1fe4c 100644 (file)
@@ -1,21 +1,21 @@
 /* src/client/io.c */
 
 #include "io.h"
-#include <errno.h> /* global errno */
-#include <fcntl.h> /* open() */
 #include <limits.h> /* PIPE_BUF */
 #include <ncurses.h> /* halfdelay(), getch() */
 #include <stddef.h> /* NULL */
 #include <stdint.h> /* uint8_t, uint16_t, uint32_t */
-#include <stdio.h> /* FILE, sprintf(), fseek() */
+#include <stdio.h> /* FILE, sprintf(), fseek(), fflush() */
 #include <string.h> /* strcmp(), strlen(), memcpy() */
 #include <stdlib.h> /* free(), atoi() */
 #include <sys/stat.h> /* stat() */
-#include <unistd.h> /* access(), write() */
+#include <sys/types.h> /* time_t */
+#include <time.h> /* time() */
+#include <unistd.h> /* access() */
 #include "../common/try_malloc.h" /* try_malloc() */
 #include "../common/rexit.h" /* exit_trouble(), exit_err() */
 #include "../common/readwrite.h" /* try_fopen(), try_fclose(), try_fgets(),
-                                  * try_fgetc(), textfile_width()
+                                  * try_fgetc(), textfile_width(), try_fputc()
                                   */
 #include "control.h" /* try_key() */
 #include "map.h" /* map_center() */
@@ -47,8 +47,8 @@ static void read_log(char * read_buf, uint32_t linemax, FILE * file);
 static uint16_t read_value_from_line(char * read_buf, uint32_t linemax,
                                      FILE * file);
 
-/* If the server's out file has changed since the last read_world(), return a
- * pointer to its file descriptor; else, return NULL.
+/* If the server's worldstate file has changed since the last read_world(),
+ * return a pointer to its file descriptor; else, return NULL.
  *
  * Two tests are performed to check for a file change. The file's last data
  * modification time in seconds via stat() is compared against world.last_update
@@ -61,17 +61,28 @@ static uint16_t read_value_from_line(char * read_buf, uint32_t linemax,
  * the new world also starts in turn 1, not signifying any world change to the
  * turn check. The stat() check detects this change with at most 1 second delay.
  */
-static FILE * changed_server_out_file(char * path);
+static FILE * changed_worldstate_file(char * path);
 
-/* Attempt to read the server's out file as representation of the game world in
- * a hard-coded serialization format. Returns 1 on success and 0 if the out file
- * wasn't read for supposedly not having changed since a last read_world() call.
+/* Attempt to read the server's worldstate file as representation of the game
+ * world in a hard-coded serialization format. Returns 1 on success and 0 if the
+ * out file wasn't read for supposedly not having changed since a last
+ * read_world() call.
  *
  * map_center() is triggered by the first successful read_world() or on turn 1,
  * so the client focuses the map window on the player on client and world start.
  */
 static uint8_t read_world();
 
+/* If "last_server_answer_time" is too old, send a PING to the server; or, if a
+ * previous PING has not sparked any answer after a while, abort the client.
+ */
+static void test_ping_pong(time_t last_server_answer_time);
+
+/* Update "last_server_answer_time" if new stuff has been written to the
+ * server's out file.
+ */
+static void test_server_activity(time_t * last_server_answer_time);
+
 
 
 static void read_inventory(char * read_buf, uint32_t linemax, FILE * file)
@@ -157,9 +168,9 @@ static uint16_t read_value_from_line(char * read_buf, uint32_t linemax,
 
 
 
-static FILE * changed_server_out_file(char * path)
+static FILE * changed_worldstate_file(char * path)
 {
-    char * f_name = "changed_server_out_file()";
+    char * f_name = "changed_worldstate_file()";
     struct stat stat_buf;
     exit_trouble(stat(path, &stat_buf), f_name, "stat()");
     if (stat_buf.st_mtime != world.last_update)
@@ -184,11 +195,11 @@ static FILE * changed_server_out_file(char * path)
 static uint8_t read_world()
 {
     char * f_name = "read_world()";
-    char * path = "server/out";
-    char * quit_msg = "No server out file found to read. Server may be down.";
+    char * path = "server/worldstate";
+    char * quit_msg = "No worldstate file found to read. Server may be down.";
     static uint8_t first_read = 1;
     exit_err(access(path, F_OK), quit_msg);
-    FILE * file = changed_server_out_file(path);
+    FILE * file = changed_worldstate_file(path);
     if (!file)
     {
         return 0;
@@ -216,37 +227,52 @@ static uint8_t read_world()
 
 
 
-extern void try_send(char * msg)
+static void test_ping_pong(time_t last_server_answer_time)
 {
-    char * f_name = "try_send()";
-    uint32_t msg_size = strlen(msg) + 1;
-    char * err = "try_send() tries to send message larger than PIPE_BUF bytes.";
-    exit_err(msg_size > PIPE_BUF, err);
-    int fd_out;
-    uint16_t j = 1;
-    while (0 != j)
+    static uint8_t ping_sent = 0;
+    time_t now = time(0);
+    if (ping_sent && last_server_answer_time > now - 3)
     {
-        fd_out = open(world.path_server_in, O_WRONLY | O_NONBLOCK);
-        if (fd_out > 0)
-        {
-            break;
-        }
-        exit_err(-1 == fd_out && ENXIO != errno, "Server fifo not found.");
-        j++;
+        ping_sent = 0;
     }
-    exit_err(0 == j, "Failed to open server fifo for writing.");
-    j = 1;
-    while (0 != j)
+    if (!ping_sent && last_server_answer_time < now - 3)
     {
-        int test = write(fd_out, msg, msg_size);
-        if (test > 0)
-        {
-            break;
-        }
-        j++;
+        send("PING");
+        ping_sent = 1;
+        return;
+    }
+    exit_err(last_server_answer_time < now - 6, "Server not answering.");
+}
+
+
+
+static void test_server_activity(time_t * last_server_answer_time)
+{
+    char * f_name = "test_server_activity()";
+    int test = try_fgetc(world.file_server_out, f_name);
+    if (EOF == test)
+    {
+        return;
+    }
+    do
+    {
+        ;
     }
-    exit_err(0 == j, "Failed to write to server fifo.");
-    exit_trouble(-1 == close(fd_out), f_name, "close()");
+    while (EOF != (test = try_fgetc(world.file_server_out, f_name)));
+    * last_server_answer_time = time(0);
+}
+
+
+
+extern void send(char * msg)
+{
+    char * f_name = "send()";
+    uint32_t msg_size = strlen(msg) + 1;
+    char * err = "send() tries to send message larger than PIPE_BUF bytes.";
+    exit_err(msg_size > PIPE_BUF, err);
+    try_fwrite(msg, strlen(msg), 1, world.file_server_in, f_name);
+    try_fputc('\n', world.file_server_in, f_name);
+    fflush(world.file_server_in);
 }
 
 
@@ -256,8 +282,11 @@ extern char * io_loop()
     world.halfdelay = 1;            /* Ensures read_world() is only called 10 */
     halfdelay(world.halfdelay);     /* times a second during user inactivity. */
     uint8_t change_in_client = 0;
+    time_t last_server_answer_time = time(0);
     while (1)
     {
+        test_server_activity(&last_server_answer_time);
+        test_ping_pong(last_server_answer_time);
         if (world.winch)
         {
             reset_windows_on_winch();
@@ -279,6 +308,6 @@ extern char * io_loop()
             }
         }
     }
-    try_send("QUIT");
+    send("QUIT");
     return "Sent QUIT to server.";
 }
index 784c681e359ad27687db2bb3cf69e37b2b9cb79f..4db04cf2594753769161b6881ad082a0ca13c0d9 100644 (file)
@@ -9,19 +9,23 @@
 
 
 
-/* Try sending "msg" to the server by writing it to the file at
- * world.path_server_in. Try to open it 2^16 times before giving up. After
- * opening, try to write to it 2^16 times before giving up.
+/* Write "msg" plus newline to server input file at world.path_server_in.
+ *
+ * "msg"  must fit into size defined by PIPE_BUF so that no race conditiosn
+ * arise by many clients writing to the file in parallel.
  */
-extern void try_send(char * msg);
-
-/* Keep checking for user input and a changed server out file. Update client's
- * world representation on out file changes. Manipulate the client and send
- * commands to server based on the user input as interpreted by the control.h
- * library. On each change / activity, re-draw the windows with draw_all_wins().
- * When the loop ends regularly (due to the user sending a quit command), return
- * an appropriate quit message to write to stdout when the client winds down.
- * Also call reset_windows() on receiving a SIGWINCH.
+extern void send(char * msg);
+
+/* Keep checking for user input, a changed worldstate file and the server's
+ * wakefulness. Update client's world representation on worldstate file changes.
+ * Manipulate the client and send commands to server based on the user input as
+ * interpreted by the control.h library.
+ *
+ * On each change / activity, re-draw the windows with draw_all_wins(). When the
+ * loop ends regularly (due to the user sending a quit command), return an
+ * appropriate quit message to write to stdout when the client winds down. Call
+ * reset_windows() on receiving a SIGWINCH. Abort on assumed server death if the
+ * server's out file does not get updated, even on PING requests.
  */
 extern char * io_loop();
 
index c25c4f9b509ab91dd3789e78384c4f2c1e042628..c021ef8402df201ad881e8f7ad59d0b75ca02a7a 100644 (file)
@@ -7,10 +7,11 @@
 #include <string.h> /* memset() */
 #include <unistd.h> /* access() */
 #include "../common/err_try_fgets.h" /* set_err_try_fgets_delim() */
+#include "../common/readwrite.h" /* try_fopen() */
 #include "../common/rexit.h" /* set_cleanup_func(), exit_trouble(),exit_err() */
 #include "cleanup.h" /* cleanup(), set_cleanup_flag() */
 #include "command_db.h" /* init_command_db() */
-#include "io.h" /* io_loop(), try_send() */
+#include "io.h" /* io_loop() */
 #include "misc.h" /* load_interface_conf(), winch_called() */
 #include "windows.h" /* winch_called() */
 #include "world.h" /* struct World */
@@ -26,12 +27,13 @@ int main(int argc, char * argv[])
     char * f_name = "main()";
 
     /* Declare hard-coded paths and values here. */
-    world.path_server_in  = "server/in";
-    world.path_commands   = "confclient/commands";
-    world.path_interface  = "confclient/interface_conf";
-    world.winDB.legal_ids = "012ciklm";
-    world.delim           = "%\n";
+    world.path_commands    = "confclient/commands";
+    world.path_interface   = "confclient/interface_conf";
+    world.winDB.legal_ids  = "012ciklm";
+    world.delim            = "%\n";
     set_err_try_fgets_delim(world.delim);
+    char * path_server_in  = "server/in";
+    char * path_server_out = "server/out";
 
     /* Parse command line arguments. */
     obey_argv(argc, argv);
@@ -58,6 +60,13 @@ int main(int argc, char * argv[])
     act.sa_handler = &winch_called;
     exit_trouble(sigaction(SIGWINCH, &act, NULL), f_name, "sigaction()");
 
+    /* Open file streams for communicating with the server. */
+    exit_err(access(path_server_in, F_OK), "No server input file found.");
+    world.file_server_in = try_fopen(path_server_in, "a", f_name);
+    set_cleanup_flag(CLEANUP_SERVER_IN);
+    world.file_server_out = try_fopen(path_server_out, "r", f_name);
+    set_cleanup_flag(CLEANUP_SERVER_OUT);
+
     /* This is where most everything happens. */
     char * quit_msg = io_loop();
 
index 5b92f2e6ba046f19b0479264825cbd27811f2da3..d27a0a3ff363ed23eb5161e18103fbe7dc68980f 100644 (file)
@@ -7,6 +7,7 @@
 #define WORLD_H
 
 #include <stdint.h> /* uint8_t, uint16_t */
+#include <stdio.h> /* FILE */
 #include <sys/types.h> /* time_t */
 #include "map.h" /* struct Map */
 #include "../common/yx_uint8.h" /* struct yx_uint8 */
 
 struct World
 {
+    FILE * file_server_in; /* server input file to write commands to */
+    FILE * file_server_out; /* server output file to read messages from */
     struct WinDB winDB; /* data for window management and individual windows */
     struct CommandDB commandDB; /* data on commands from commands config file */
     struct KeyBindingDB kb_global; /* globally availabe keybindings */
     struct KeyBindingDB kb_wingeom; /* Win geometry config view keybindings */
     struct KeyBindingDB kb_winkeys; /* Win keybindings config view keybindings*/
     struct Map map; /* game map geometry and content */
-    time_t last_update; /* used for comparison with server outfile' mtime */
+    time_t last_update; /* used for comparison with worldstate file's mtime */
     char * log; /* log of player's activities */
-    char * path_server_in; /* path of server's input fifo */
     char * path_interface; /* path of interface configuration file */
     char * path_commands; /* path of commands config file */
     char * player_inventory; /* one-item-per-line string list of owned items */
index 4ad970ceaf44365e6b16202b00b4ce8ba3fa6ed2..08e3bb6057027637850b348380f2ad21fa99091b 100644 (file)
@@ -4,12 +4,14 @@
 #include <stdint.h> /* uint32_t */
 #include <stdlib.h> /* free() */
 #include <unistd.h> /* unlink() */
+#include "../common/readwrite.h" /* try_fclose() */
 #include "map_object_actions.h" /* free_map_object_actions() */
 #include "map_objects.h" /* free_map_objects(), free_map_object_defs() */
 #include "world.h" /* global world */
 
 
 
+
 /* The clean-up flags set by set_cleanup_flag(). */
 static uint32_t cleanup_flags = 0x0000;
 
@@ -17,12 +19,13 @@ static uint32_t cleanup_flags = 0x0000;
 
 extern void cleanup()
 {
+    char * f_name = "cleanup()";
     free(world.queue);
     free(world.log);
     free(world.map.cells);
-    if (cleanup_flags & CLEANUP_OUTFILE)
+    if (cleanup_flags & CLEANUP_WORLDSTATE)
     {
-        unlink(world.path_out);
+        unlink(world.path_worldstate);
     }
     if (cleanup_flags & CLEANUP_MAP_OBJECTS)
     {
@@ -36,10 +39,17 @@ extern void cleanup()
     {
         free_map_object_actions(world.map_obj_acts);
     }
-    if (cleanup_flags & CLEANUP_FIFO)    /* Fifo also serves as lockfile that */
-    {                                    /* affirms the running of a server   */
-        unlink(world.path_in);           /* instance. Therefore it should be  */
-    }                                    /* the last thing to be deleted.     */
+    if (cleanup_flags & CLEANUP_IN)
+    {
+        try_fclose(world.file_in, f_name);
+        unlink(world.path_in);
+    }
+    if (cleanup_flags & CLEANUP_OUT)
+    {
+        try_fclose(world.file_out, f_name);
+        free(world.server_test);
+        unlink(world.path_out);
+    }
 }
 
 
@@ -47,3 +57,10 @@ extern void set_cleanup_flag(enum cleanup_flag flag)
 {
     cleanup_flags = cleanup_flags | flag;
 }
+
+
+
+extern void unset_cleanup_flag(enum cleanup_flag flag)
+{
+    cleanup_flags = cleanup_flags ^ flag;
+}
index 61abd71f4b0a4e35089e27cd709da4e67bb0e1fc..16529414f90e6faafe7ee5d1871ddac5b8c853e7 100644 (file)
 enum cleanup_flag
 {
     CLEANUP_FIFO            = 0x0001,
-    CLEANUP_OUTFILE         = 0x0002,
+    CLEANUP_WORLDSTATE      = 0x0002,
     CLEANUP_MAP_OBJECT_DEFS = 0x0004,
     CLEANUP_MAP_OBJECTS     = 0x0008,
-    CLEANUP_MAP_OBJECT_ACTS = 0x0010
+    CLEANUP_MAP_OBJECT_ACTS = 0x0010,
+    CLEANUP_IN              = 0x0020,
+    CLEANUP_OUT             = 0x0040
 };
 
+/* In addition, unset_cleanup_flag() may be used to unset flags. */
 extern void set_cleanup_flag(enum cleanup_flag flag);
+extern void unset_cleanup_flag(enum cleanup_flag flag);
 
 /* Frees memory and unlinks some files. */
 extern void cleanup();
index 177c2952faca109802c355b36eb8c22d6a1607ef..035f4fe1d0759436f6dc5aed53710df00e82177b 100644 (file)
@@ -2,18 +2,17 @@
 
 #include "io.h"
 #include <errno.h> /* global errno */
-#include <fcntl.h> /* open(), O_RDONLY, O_NONBLOCK */
 #include <limits.h> /* PIPE_BUF */
 #include <stddef.h> /* size_t, NULL */
 #include <stdint.h> /* uint8_t, uint32_t */
-#include <stdio.h> /* define FILE, sprintf() */
+#include <stdio.h> /* defines EOF, FILE, sprintf() */
 #include <stdlib.h> /* free() */
-#include <string.h> /* strlen(), memset(), memcpy() */
-#include <unistd.h> /* read(), close() */
+#include <string.h> /* strlen(), memcpy() */
+#include <sys/types.h> /* time_t */
+#include <time.h> /* time() */
 #include "../common/readwrite.h" /* try_fopen(), try_fclose_unlink_rename(),
-                                  * try_fwrite(), try_fputc()
+                                  * try_fwrite(), try_fputc(), try_fgetc()
                                   */
-#include "../common/rexit.h" /* exit_trouble() */
 #include "../common/try_malloc.h" /* try_malloc() */
 #include "cleanup.h" /* set_cleanup_flag() */
 #include "map_objects.h" /* structs MapObj, MapObjDef, get_map_obj_def() */
 */
 static char * get_message_from_queue();
 
-/* Read fifo input to put into queue. Only stop reading when bytes were received
- * and the receiving has stopped. Read max. PIPE_BUF-sized chunks for atomicity.
+/* Read input file for input into world.queue. new queue input. Wait a few
+ * seconds until giving up. Translate '\n' chars in input file into '\0' chars.
  */
-static void read_fifo_into_queue();
+static void read_file_into_queue();
 
-/* Write to output file the world state as it is to be visible to clients. */
-static void update_out_file();
+/* Write world state as visible to clients to its file. Write single dot line to
+ * server output file to satisfy client ping mechanisms.
+ */
+static void update_worldstate_file();
 
 /* Write "value" to new \n-delimited line of "file". */
 static void write_value_as_line(uint32_t value, FILE * file);
@@ -53,76 +54,75 @@ static char * get_message_from_queue()
 {
     char * f_name = "get_message_from_queue()";
     char * message = NULL;
-    size_t cutout_len = strlen(world.queue);
-    if (0 < cutout_len)
-    {
-        cutout_len++;
-        message = try_malloc(cutout_len, f_name);
-        memcpy(message, world.queue, cutout_len);
-    }
-    for (;
-         cutout_len != world.queue_size && '\0' == world.queue[cutout_len];
-         cutout_len++);
-    world.queue_size = world.queue_size - cutout_len;
-    if (0 == world.queue_size)
+    if (world.queue_size)
     {
-        free(world.queue);  /* NULL so read_fifo_into_queue() may free() this */
-        world.queue = NULL; /* every time, even when it's un-allocated first. */
-    }
-    else
-    {
-        char * new_queue = try_malloc(world.queue_size, f_name);
-        memcpy(new_queue, &(world.queue[cutout_len]), world.queue_size);
-        free(world.queue);
-        world.queue = new_queue;
+        size_t cutout_len = strlen(world.queue);
+        if (0 < cutout_len)
+        {
+            cutout_len++;
+            message = try_malloc(cutout_len, f_name);
+            memcpy(message, world.queue, cutout_len);
+        }
+        for (;
+             cutout_len != world.queue_size && '\0' == world.queue[cutout_len];
+             cutout_len++);
+        world.queue_size = world.queue_size - cutout_len;
+        if (0 == world.queue_size)
+        {
+            free(world.queue);   /* NULL so read_file_into_queue() may free() */
+            world.queue = NULL;  /* this every time, even when it's           */
+        }                        /* un-allocated first. */
+        else
+        {
+            char * new_queue = try_malloc(world.queue_size, f_name);
+            memcpy(new_queue, &(world.queue[cutout_len]), world.queue_size);
+            free(world.queue);
+            world.queue = new_queue;
+        }
     }
     return message;
 }
 
 
 
-static void read_fifo_into_queue()
+static void read_file_into_queue()
 {
-    char * f_name = "read_fifo_into_queue()";
-    uint32_t buf_size = PIPE_BUF;
-    int fdesc_infile = open(world.path_in, O_RDONLY | O_NONBLOCK);
-    exit_trouble(-1 == fdesc_infile, "open()", f_name);
-    char buf[buf_size];
-    memset(buf, 0, buf_size);
-    int bytes_read;
-    uint8_t read_state = 0; /* 0: waiting for input. 1: started receiving it. */
-    while (1)
+    char * f_name = "read_file_into_queue()";
+    uint8_t wait_seconds = 5;
+    time_t now = time(0);
+    int test;
+    while (EOF == (test = try_fgetc(world.file_in, f_name)))
     {
-        bytes_read = read(fdesc_infile, buf, buf_size);
-        if (bytes_read > 0)
+        if (time(0) > now + wait_seconds)
         {
-            read_state = 1;
-            uint32_t old_queue_size = world.queue_size;
-            world.queue_size = world.queue_size + bytes_read;
-            char * new_queue = try_malloc(world.queue_size, f_name);
-            memcpy(new_queue, world.queue, old_queue_size);
-            memcpy(&(new_queue[old_queue_size]), buf, bytes_read);
-            free(world.queue);
-            world.queue = new_queue;
-            memset(buf, 0, buf_size);
-            continue;
+            return;
         }
-        exit_trouble(-1 == bytes_read && errno != EAGAIN, "read()", f_name);
-        if (1 == read_state)
+    }
+    do
+    {
+        char c = (char) test;
+        if ('\n' == c)
         {
-            break;
+            c = '\0';
         }
+        char * new_queue = try_malloc(world.queue_size + 1, f_name);
+        memcpy(new_queue, world.queue, world.queue_size);
+        char * new_pos = new_queue + world.queue_size;
+        * new_pos = c;
+        world.queue_size++;
+        free(world.queue);
+        world.queue = new_queue;
     }
-    exit_trouble(close(fdesc_infile), f_name, "close()");
+    while (EOF != (test = try_fgetc(world.file_in, f_name)));
 }
 
 
 
-static void update_out_file()
+static void update_worldstate_file()
 {
-    char * f_name = "update_out_file()";
-    char path_tmp[strlen(world.path_out) + strlen(world.tmp_suffix) + 1];
-    sprintf(path_tmp, "%s%s", world.path_out, world.tmp_suffix);
+    char * f_name = "update_worldstate_file()";
+    char path_tmp[strlen(world.path_worldstate) + strlen(world.tmp_suffix) + 1];
+    sprintf(path_tmp, "%s%s", world.path_worldstate, world.tmp_suffix);
     FILE * file = try_fopen(path_tmp, "w", f_name);
     struct MapObj * player = get_player();
     write_value_as_line(world.turn, file);
@@ -137,8 +137,11 @@ static void update_out_file()
     {
         try_fwrite(world.log, strlen(world.log), 1, file, f_name);
     }
-    try_fclose_unlink_rename(file, path_tmp, world.path_out, f_name);
-    set_cleanup_flag(CLEANUP_OUTFILE);
+    try_fclose_unlink_rename(file, path_tmp, world.path_worldstate, f_name);
+    set_cleanup_flag(CLEANUP_WORLDSTATE);
+    char * dot = ".\n";;
+    try_fwrite(dot, strlen(dot), 1, world.file_out, f_name);
+    fflush(world.file_out);
 }
 
 
@@ -224,11 +227,11 @@ extern char * io_round()
     }
     if (world.turn != world.last_update_turn)
     {
-        update_out_file();
+        update_worldstate_file();
         world.last_update_turn = world.turn;
     }
-    read_fifo_into_queue();
-    if ('\0' != world.queue[world.queue_size - 1])
+    read_file_into_queue();
+    if (world.queue_size && '\0' != world.queue[world.queue_size - 1])
     {
         char * new_queue = try_malloc(world.queue_size + 1, f_name);
         memcpy(new_queue, world.queue, world.queue_size);
index 78cb068279d4899490c5a1f1dcb267f7ac90eb57..02f8e9017d2dadc7c2bc6b6581f6b8a3f0d4193c 100644 (file)
@@ -1,7 +1,7 @@
 /* io.h:
  *
- * Communication of the server with the outside world via input fifo and output
- * file.
+ * Communication of the server with the outside world and its client via input,
+ * output and world state files.
  */
 
 #ifndef IO_H
@@ -11,8 +11,9 @@
 
 /* Return single \0-terminated string read from input queue (world.queue); or,
  * if queue is empty and world.turn is unequal world.last_update_turn, update
- * output file at world.path_out (and update world.last_update_turn), then read
- * file at world.path_in for the next load of bytes to put onto the input queue.
+ * world state file at world.path_worldstate (and update world.last_update_turn
+ * and write a single dot line to output file at world.path_out), then read file
+ * at world.path_in for the next load of bytes to put onto the input queue.
  *
  * Reading the file at world.path_in may put many \0-terminated strings on the
  * queue at once. Successive calls of io_round() will make these available one
index 64d4434943867fb7a36d7b60da2759dae675dd41..769d47c07f49c532be4016befc60087480f5686b 100644 (file)
@@ -1,12 +1,17 @@
 /* src/server/main.c */
 
 #include <errno.h> /* global errno */
-#include <stdio.h> /* printf() */
+#include <stdio.h> /* printf(), fflush() */
 #include <stdlib.h> /* exit() */
+#include <string.h> /* strlen() */
 #include <sys/stat.h> /* mkfifo(), mkdir() */
-#include <unistd.h> /* access() */
+#include <sys/types.h> /* defines pid_t, time_t */
+#include <time.h> /* #time() */
+#include <unistd.h> /* access(), getpid(), unlink() */
 #include "../common/err_try_fgets.h" /* set_err_try_fgets_delim() */
-#include "../common/rexit.h" /* exit_err, exit_trouble(), set_cleanup_func() */
+#include "../common/readwrite.h" /* try_fopen(), try_fwrite(), try_fclose() */
+#include "../common/rexit.h" /* exit_err, set_cleanup_func() */
+#include "../common/try_malloc.h" /* try_malloc() */
 #include "cleanup.h" /* set_cleanup_flag(), cleanup() */
 #include "init.h" /* run_game(), obey_argv() */
 #include "map_object_actions.h" /* init_map_object_actions() */
@@ -43,10 +48,11 @@ int main(int argc, char ** argv)
     }
     world.path_map_obj_defs = "confserver/defs";
     world.path_map_obj_acts = "confserver/map_object_actions";
-    world.path_in     = "server/in";
-    world.path_out    = "server/out";
-    world.path_record = "record";
-    world.tmp_suffix  = "_tmp";
+    world.path_worldstate   = "server/worldstate";
+    world.path_out          = "server/out";
+    world.path_in           = "server/in";
+    world.path_record       = "record";
+    world.tmp_suffix        = "_tmp";
     set_err_try_fgets_delim("%%\n");
 
     /* Set map geometry. */
@@ -61,19 +67,33 @@ int main(int argc, char ** argv)
     exit_err(access(world.path_map_obj_defs, F_OK), err_mod);
     exit_err(access(world.path_map_obj_acts, F_OK), err_moa);
 
-    /* Treat world.path_in file as server process lock file. */
-    char * err = "Found pre-existing input fifo file. This indicates another "
-                 "roguelike-server may be running. It should be killed first.";
-    exit_err(!access(world.path_in, F_OK), err);
-    int test = mkdir("server", 0700);
-    exit_trouble(test && EEXIST != errno, f_name, "mkdir()");
-    exit_trouble(mkfifo(world.path_in, 0600), f_name, "mkfifo()");
-    set_cleanup_flag(CLEANUP_FIFO);
-
     /* Init from config files map object (action) definitions. */
     init_map_object_defs();
     init_map_object_actions();
 
+    /* Create server directory if it does not exist yet. */
+    int test = mkdir("server", 0700);
+    exit_trouble(test && EEXIST != errno, f_name, "mkdir()");
+
+    /* Create server out file and start it with server process test string. */
+    world.file_out = try_fopen(world.path_out, "w", f_name);
+    world.server_test = try_malloc(10 + 1 + 10 + 1 + 1, f_name);
+    sprintf(world.server_test, "%d %d\n", getpid(), (int) time(0));
+    try_fwrite(world.server_test, strlen(world.server_test), 1,
+               world.file_out, f_name);
+    fflush(world.file_out);
+    set_cleanup_flag(CLEANUP_OUT);
+
+    /* Create server in file, switch to reading it. */
+    if (!access(world.path_in, F_OK))  /* This keeps out input from old input */
+    {                                  /* file streams of clients             */
+        unlink(world.path_in);         /* communicating with server processes */
+    }                                  /* superseded by this current one.     */
+    world.file_in = try_fopen(world.path_in, "w", f_name);
+    try_fclose(world.file_in, f_name);
+    world.file_in = try_fopen(world.path_in, "r", f_name);
+    set_cleanup_flag(CLEANUP_IN);
+
     /* Enter play or replay mode loops, then leave properly. */
     run_game();
     cleanup();
index 82e0aaefb866ce80fd3f5d4f63a7f281c598ac19..f5d0e170fd4fcbf237ca900bfa3b17f45af4d841 100644 (file)
@@ -3,16 +3,17 @@
 #include "run.h"
 #include <stddef.h> /* NULL */
 #include <stdint.h> /* uint8_t, uint16_t, uint32_t */
-#include <stdio.h> /* FILE, sprintf() */
+#include <stdio.h> /* FILE, sprintf(), fflush() */
 #include <stdlib.h> /* free() */
-#include <string.h> /* strlen(), strncmp(), atoi() */
+#include <string.h> /* strlen(), strcmp() strncmp(), atoi() */
 #include <unistd.h> /* access() */
 #include "../common/readwrite.h" /* try_fopen(), try_fcose(), try_fwrite(),
                                   * try_fgets(), try_fclose_unlink_rename(),
                                   * textfile_width(), try_fputc()
                                   */
-#include "../common/rexit.h" /* exit_trouble() */
+#include "../common/rexit.h" /* exit_trouble(), exit_err() */
 #include "ai.h" /* ai() */
+#include "cleanup.h" /* unset_cleanup_flag() */
 #include "init.h" /* remake_world() */
 #include "io.h" /* io_round() */
 #include "map_object_actions.h" /* get_moa_id_by_name() */
@@ -44,6 +45,13 @@ static uint8_t is_effort_finished(struct MapObjAct * moa,
  */
 static uint8_t apply_player_command(char * msg, char * command_name);
 
+/* Compares first line of file at world.path_out to world.server_test, aborts if
+ * they don't match, but not before unsetting the flags deleting files in the
+ * server directory, for in that case those must be assumed to belong to another
+ * server process.
+ */
+static void server_test();
+
 
 
 static void turn_over()
@@ -134,6 +142,27 @@ static uint8_t apply_player_command(char * msg, char * command_name)
 
 
 
+static void server_test()
+{
+    char * f_name = "server_test()";
+    char test[10 + 1 + 10 + 1 + 1];
+    FILE * file = try_fopen(world.path_out, "r", f_name);
+    try_fgets(test, 10 + 10 + 1 + 1, file, f_name);
+    try_fclose(file, f_name);
+    if (strcmp(test, world.server_test))
+    {
+        unset_cleanup_flag(CLEANUP_WORLDSTATE);
+        unset_cleanup_flag(CLEANUP_OUT);
+        unset_cleanup_flag(CLEANUP_IN);
+        char * msg = "Server test string in server output file does not match. "
+                     "This indicates that the current server process has been "
+                     "superseded by another one.";
+        exit_err(1, msg);
+    }
+}
+
+
+
 extern void obey_msg(char * msg, uint8_t do_record)
 {
     char * f_name = "obey_msg()";
@@ -179,6 +208,7 @@ extern uint8_t io_loop()
     char * f_name = "io_loop()";
     while (1)
     {
+        server_test();
         char * msg = io_round();
         if (NULL == msg)
         {
@@ -193,6 +223,14 @@ extern uint8_t io_loop()
             free(msg);
             return 1;
         }
+        if (!strcmp("PING", msg))
+        {
+            free(msg);
+            char * pong = "PONG\n";
+            try_fwrite(pong, strlen(pong), 1, world.file_out, f_name);
+            fflush(world.file_out);
+            continue;
+        }
         if (world.replay)
         {
             free(msg);
index 877d2fb2b263d2b28ff73aab121661104bc3e539..9fa526ff0121f2fea4e1352cec82f1f7b588e15e 100644 (file)
@@ -17,7 +17,10 @@ extern void obey_msg(char * msg, uint8_t do_record);
 
 /* Loop for receiving commands via io_round() and acting on them. Exits with 1
  * on "QUIT" command. In replay mode, exits with 0 on each non-"QUIT" command.
- * In play mode, processes incomming commands via obey_msg().
+ * Writes a "PONG" line to server output file on "PING" command. In play mode,
+ * processes further incomming commands via obey_msg(). Compares the first line
+ * of the file at world.path_out with world.server_test to ensure that the
+ * current server process has not been superseded by a new one.
  */
 extern uint8_t io_loop();
 
index 84235ce42d2622a3dab38577c8b0bc1a5d3d2bf5..47f7c65cb7670134fdc1c9cd939f44c5cd84746d 100644 (file)
@@ -7,6 +7,7 @@
 #define MAIN_H
 
 #include <stdint.h> /* uint8_t, uint16_t, uint32_t */
+#include <stdio.h> /* define FILE */
 #include "map.h" /* struct Map */
 struct MapObjDef;
 struct MapObjAct;
@@ -16,18 +17,22 @@ struct MapObj;
 
 struct World
 {
+    FILE * file_in; /* Input stream on file at .path_in. */
+    FILE * file_out; /* Output stream on file at .path_out. */
     struct Map map;
     struct MapObjDef * map_obj_defs; /* Map object definitions. */
     struct MapObjAct * map_obj_acts; /* Map object action definitions. */
     struct MapObj * map_objs; /* Map objects. */
     char * log; /* Logs the game events from the player's view. */
-    char * path_in; /* Fifo to receive command messages. */
-    char * path_out; /* File to write the game state as visible to clients.*/
+    char * server_test; /* String uniquely identifying server process. */
+    char * path_in; /* File to write client messages into. */
+    char * path_out; /* File to write server messages into. */
+    char * path_worldstate; /* File to represent world state  to clients.*/
     char * path_record; /* Record file from which to read the game history. */
     char * path_map_obj_defs; /* path for map object definitions config file */
     char * path_map_obj_acts; /* path for map object actions config file */
     char * tmp_suffix; /* Appended to paths of files for their tmp versions. */
-    char * queue; /* Stores un-processed messages received via input fifo. */
+    char * queue; /* Stores un-processed messages read from the input file. */
     uint32_t queue_size;/* Length of .queue sequence of \0-terminated strings.*/
     uint32_t seed; /* Randomness seed. */
     uint16_t replay; /* Turn up to which to replay game. No replay if zero. */