gnugo-devel
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

Re: [gnugo-devel] TCP/IP connection built into GNU Go?


From: Paul Pogonyshev
Subject: Re: [gnugo-devel] TCP/IP connection built into GNU Go?
Date: Sat, 9 Oct 2004 18:18:56 -0200
User-agent: KMail/1.4.3

I wrote:

> > You can easily test it with tcplisten. An alternative is to test it
> > with gnugoclient 3.0.
>
> Yes, I'll try `tcputils'.

Here is the next version of the patch.  Now both `--gtp-connect' and
`--gtp-listen' seem to work fine.  Special thanks go to Ray who in
a private message suggested to wait for the client to disconnect first
when listening.  I also learned on `tcputils' source, so another bunch
of thanks go to its author(s).

For anyone who wants to read the patch: only changes are in `main.c',
the rest is just like in the first version of the patch.

Remaining is a proper `configure' stuff.  Any volunteers?

I also hope to receive a Windows version of this stuff from Vadim
Lyakhovsky (he once posted here with suggestions on porting GNU Go
to Pocket PC.)  However, it is not yet even known if it is possible to
treat a socket as a stream (FILE *) on Windows.

Paul


Index: interface/gtp.c
===================================================================
RCS file: /cvsroot/gnugo/gnugo/interface/gtp.c,v
retrieving revision 1.19
diff -u -p -r1.19 gtp.c
--- interface/gtp.c     25 May 2004 03:13:47 -0000      1.19
+++ interface/gtp.c     9 Oct 2004 15:06:33 -0000
@@ -69,10 +69,17 @@ static gtp_transform_ptr vertex_transfor
  */
 static int current_id;
 
+/* The file all GTP output goes to.  This is made global for the user
+ * of this file may want to use functions other than gtp_printf() etc.
+ * Set by gtp_main_loop().
+ */
+FILE *gtp_output_file = NULL;
+
+
 /* Read filehandle gtp_input linewise and interpret as GTP commands. */
 void
-gtp_main_loop(struct gtp_command commands[], FILE *gtp_input,
-             FILE *gtp_dump_commands)
+gtp_main_loop(struct gtp_command commands[],
+             FILE *gtp_input, FILE *gtp_output, FILE *gtp_dump_commands)
 {
   char line[GTP_BUFSIZE];
   char command[GTP_BUFSIZE];
@@ -80,7 +87,9 @@ gtp_main_loop(struct gtp_command command
   int i;
   int n;
   int status = GTP_OK;
-  
+
+  gtp_output_file = gtp_output;
+
   while (status == GTP_OK) {
     /* Read a line from gtp_input. */
     if (!fgets(line, GTP_BUFSIZE, gtp_input))
@@ -180,25 +189,25 @@ gtp_mprintf(const char *fmt, ...)
       {
        /* rules of promotion => passed as int, not char */
        int c = va_arg(ap, int);
-       putc(c, stdout);
+       putc(c, gtp_output_file);
        break;
       }
       case 'd':
       {
        int d = va_arg(ap, int);
-       fprintf(stdout, "%d", d);
+       fprintf(gtp_output_file, "%d", d);
        break;
       }
       case 'f':
       {
        double f = va_arg(ap, double); /* passed as double, not float */
-       fprintf(stdout, "%f", f);
+       fprintf(gtp_output_file, "%f", f);
        break;
       }
       case 's':
       {
        char *s = va_arg(ap, char *);
-       fputs(s, stdout);
+       fputs(s, gtp_output_file);
        break;
       }
       case 'm':
@@ -212,20 +221,21 @@ gtp_mprintf(const char *fmt, ...)
       {
        int color = va_arg(ap, int);
        if (color == WHITE)
-         fputs("white", stdout);
+         fputs("white", gtp_output_file);
        else if (color == BLACK)
-         fputs("black", stdout);
+         fputs("black", gtp_output_file);
        else
-         fputs("empty", stdout);
+         fputs("empty", gtp_output_file);
        break;
       }
       default:
-       fprintf(stdout, "\n\nUnknown format character '%c'\n", *fmt);
+       /* FIXME: Should go to `stderr' instead? */
+       fprintf(gtp_output_file, "\n\nUnknown format character '%c'\n", *fmt);
        break;
       }
     }
     else
-      putc(*fmt, stdout);
+      putc(*fmt, gtp_output_file);
   }
   va_end(ap);
 }
@@ -237,7 +247,7 @@ gtp_printf(const char *format, ...)
 {
   va_list ap;
   va_start(ap, format);
-  vfprintf(stdout, format, ap);
+  vfprintf(gtp_output_file, format, ap);
   va_end(ap);
 }
 
@@ -278,7 +288,7 @@ gtp_success(const char *format, ...)
   va_list ap;
   gtp_start_response(GTP_SUCCESS);
   va_start(ap, format);
-  vfprintf(stdout, format, ap);
+  vfprintf(gtp_output_file, format, ap);
   va_end(ap);
   return gtp_finish_response();
 }
@@ -291,7 +301,7 @@ gtp_failure(const char *format, ...)
   va_list ap;
   gtp_start_response(GTP_FAILURE);
   va_start(ap, format);
-  vfprintf(stdout, format, ap);
+  vfprintf(gtp_output_file, format, ap);
   va_end(ap);
   return gtp_finish_response();
 }
Index: interface/gtp.h
===================================================================
RCS file: /cvsroot/gnugo/gnugo/interface/gtp.h,v
retrieving revision 1.15
diff -u -p -r1.15 gtp.h
--- interface/gtp.h     24 Jan 2004 04:04:56 -0000      1.15
+++ interface/gtp.h     9 Oct 2004 15:06:33 -0000
@@ -65,8 +65,8 @@ struct gtp_command {
   gtp_fn_ptr function;
 };
 
-void gtp_main_loop(struct gtp_command commands[], FILE *gtp_input,
-                  FILE *gtp_dump_commands);
+void gtp_main_loop(struct gtp_command commands[],
+                  FILE *gtp_input, FILE *gtp_output, FILE *gtp_dump_commands);
 void gtp_internal_set_boardsize(int size);
 void gtp_set_vertex_transform_hooks(gtp_transform_ptr in,
                                    gtp_transform_ptr out);
@@ -83,6 +83,8 @@ int gtp_decode_move(char *s, int *color,
 void gtp_print_vertices(int n, int movei[], int movej[]);
 void gtp_print_vertex(int i, int j);
 
+extern FILE *gtp_output_file;
+
 /*
  * Local Variables:
  * tab-width: 8
Index: interface/interface.h
===================================================================
RCS file: /cvsroot/gnugo/gnugo/interface/interface.h,v
retrieving revision 1.15
diff -u -p -r1.15 interface.h
--- interface/interface.h       24 Jan 2004 04:04:56 -0000      1.15
+++ interface/interface.h       9 Oct 2004 15:06:33 -0000
@@ -35,7 +35,7 @@ void play_ascii(SGFTree *tree, Gameinfo 
                char *filename, char *until);
 void play_ascii_emacs(SGFTree *tree, Gameinfo *gameinfo, 
                      char *filename, char *until);
-void play_gtp(FILE *gtp_input, FILE *gtp_dump_commands,
+void play_gtp(FILE *gtp_input, FILE *gtp_output, FILE *gtp_dump_commands,
              int gtp_initial_orientation);
 void play_gmp(Gameinfo *gameinfo, int simplified);
 void play_solo(Gameinfo *gameinfo, int benchmark);
Index: interface/main.c
===================================================================
RCS file: /cvsroot/gnugo/gnugo/interface/main.c,v
retrieving revision 1.103
diff -u -p -r1.103 main.c
--- interface/main.c    8 Sep 2004 17:03:42 -0000       1.103
+++ interface/main.c    9 Oct 2004 15:06:38 -0000
@@ -60,6 +60,14 @@ static void show_help(void);
 static void show_debug_help(void);
 static void show_debug_flags(void);
 
+static void socket_connect_to(char *address_string,
+                             FILE **input_file, FILE **output_file);
+static void socket_listen_at(char *address_string,
+                            FILE **input_file, FILE **output_file);
+static void socket_close_connection(FILE *input_file, FILE *output_file);
+static void socket_stop_listening(FILE *input_file, FILE *output_file);
+
+
 /* long options which have no short form */
 enum {OPT_BOARDSIZE = 127,
       OPT_HANDICAPSTONES,
@@ -74,6 +82,8 @@ enum {OPT_BOARDSIZE = 127,
       OPT_OUTFILE, 
       OPT_QUIET,
       OPT_GTP_INPUT,
+      OPT_GTP_CONNECT,
+      OPT_GTP_LISTEN,
       OPT_GTP_DUMP_COMMANDS,
       OPT_GTP_INITIAL_ORIENTATION,
       OPT_GTP_VERSION,
@@ -189,6 +199,8 @@ static struct gg_option const long_optio
   {"quiet",          no_argument,       0, OPT_QUIET},
   {"silent",         no_argument,       0, OPT_QUIET},
   {"gtp-input",      required_argument, 0, OPT_GTP_INPUT},
+  {"gtp-connect",    required_argument, 0, OPT_GTP_CONNECT},
+  {"gtp-listen",     required_argument, 0, OPT_GTP_LISTEN},
   {"gtp-dump-commands", required_argument, 0, OPT_GTP_DUMP_COMMANDS},
   {"orientation",    required_argument, 0, OPT_GTP_INITIAL_ORIENTATION},
   {"gtp-initial-orientation",
@@ -311,6 +323,8 @@ main(int argc, char *argv[])
   char *outflags = NULL;
   char *gtpfile = NULL;
   char *gtp_dump_commands_file = NULL;
+  int gtp_tcp_ip_mode = 0;
+  char *gtp_tcp_ip_address = NULL;
   
   char *printsgffile = NULL;
   
@@ -319,8 +333,6 @@ main(int argc, char *argv[])
   char debuginfluence_move[4] = "\0";
   
   int benchmark = 0;  /* benchmarking mode (-b) */
-  FILE *gtp_input_FILE;
-  FILE *gtp_dump_commands_FILE = NULL;
   FILE *output_check;
   int orientation = 0;
 
@@ -443,11 +455,25 @@ main(int argc, char *argv[])
       case OPT_QUIET:
        quiet = 1;
        break;
-       
+
       case OPT_GTP_INPUT:
-       gtpfile = gg_optarg;
+      case OPT_GTP_CONNECT:
+      case OPT_GTP_LISTEN:
+       if (gtp_tcp_ip_mode != 0 || gtpfile != NULL) {
+         fprintf(stderr, ("Options `--gtp-input', `--gtp-connect' and 
`--gtp-listen' "
+                          "are mutually-exclusive\n"));
+         exit(EXIT_FAILURE);
+       }
+
+       if (i == OPT_GTP_INPUT)
+         gtpfile = gg_optarg;
+       else {
+         gtp_tcp_ip_mode = i;
+         gtp_tcp_ip_address = gg_optarg;
+       }
+
        break;
-       
+
       case OPT_GTP_DUMP_COMMANDS:
        gtp_dump_commands_file = gg_optarg;
        break;
@@ -1340,29 +1366,49 @@ main(int argc, char *argv[])
     }
 #endif
 
-  case MODE_GTP:  
-    if (gtpfile != NULL) {
-      gtp_input_FILE = fopen(gtpfile, "r");
-      if (gtp_input_FILE == NULL) {
-       fprintf(stderr, "gnugo: Cannot open file %s\n", gtpfile);
-       return EXIT_FAILURE;
-      } 
-    }
-    else
-      gtp_input_FILE = stdin;
+  case MODE_GTP:
+    {
+      FILE *gtp_input_FILE = stdin;
+      FILE *gtp_output_FILE = stdout;
+      FILE *gtp_dump_commands_FILE = NULL;
+
+      if (gtpfile != NULL) {
+       gtp_input_FILE = fopen(gtpfile, "r");
+       if (gtp_input_FILE == NULL) {
+         fprintf(stderr, "gnugo: Cannot open file %s\n", gtpfile);
+         return EXIT_FAILURE;
+       }
+      }
+      else if (gtp_tcp_ip_mode == OPT_GTP_CONNECT) {
+       socket_connect_to(gtp_tcp_ip_address,
+                         &gtp_input_FILE, &gtp_output_FILE);
+      }
+      else if (gtp_tcp_ip_mode == OPT_GTP_LISTEN) {
+       socket_listen_at(gtp_tcp_ip_address,
+                        &gtp_input_FILE, &gtp_output_FILE);
+      }
 
-    if (gtp_dump_commands_file != NULL) {
-      gtp_dump_commands_FILE = fopen(gtp_dump_commands_file, "w");
-      if (gtp_dump_commands_FILE == NULL) {
-       fprintf(stderr, "gnugo: Cannot open file %s\n",
-               gtp_dump_commands_file);
-       return EXIT_FAILURE;
-      } 
+      if (gtp_dump_commands_file != NULL) {
+       gtp_dump_commands_FILE = fopen(gtp_dump_commands_file, "w");
+       if (gtp_dump_commands_FILE == NULL) {
+         fprintf(stderr, "gnugo: Cannot open file %s\n",
+                 gtp_dump_commands_file);
+         return EXIT_FAILURE;
+       }
+      }
+
+      play_gtp(gtp_input_FILE, gtp_output_FILE, gtp_dump_commands_FILE,
+              orientation);
+
+      if (gtp_dump_commands_FILE)
+       fclose(gtp_dump_commands_FILE);
+
+      if (gtp_tcp_ip_mode == OPT_GTP_CONNECT)
+       socket_close_connection(gtp_input_FILE, gtp_output_FILE);
+      else if (gtp_tcp_ip_mode == OPT_GTP_LISTEN)
+       socket_stop_listening(gtp_input_FILE, gtp_output_FILE);
     }
 
-    play_gtp(gtp_input_FILE, gtp_dump_commands_FILE, orientation);
-    if (gtp_dump_commands_FILE)
-      fclose(gtp_dump_commands_FILE);
     break;
 
   case MODE_ASCII_EMACS:  
@@ -1437,6 +1483,12 @@ Main Options:\n\
        --never-resign    Forbid GNU Go to resign\n\
        --resign-allowed         Allow resignation (default)\n\
        --gtp-input <file>Read gtp commands from file instead of stdin\n\
+       --gtp-connect [HOST:]PORT
+                         Connect to given host (127.0.0.1 if omitted) and 
port\n\
+                         and receive GTP commands on the established 
connection\n\
+       --gtp-listen [HOST:]PORT
+                         Wait for the first TCP/IP connection on the given 
port\n\
+                         (if HOST is specified, only to that host)\n\
    -l, --infile <file>   Load name sgf file\n\
    -L, --until <move>    Stop loading just before move is played. <move>\n\
                          can be the move number or location (eg L10).\n\
@@ -1628,6 +1680,166 @@ show_copyright(void)
 }
 
 
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <netdb.h>
+
+
+static void
+socket_connect_to(char *address_string, FILE **input_file, FILE **output_file)
+{
+  static char loopback_ip[] = "127.0.0.1";
+
+  struct sockaddr_in address;
+  char *port_string;
+  unsigned int port;
+  int connection_socket;
+  struct hostent *host_data;
+  char **address_pointer;
+
+  port_string = strchr(address_string, ':');
+  if (port_string)
+    *port_string++ = 0;
+  else {
+    port_string = address_string;
+    address_string = loopback_ip;
+  }
+
+  host_data = gethostbyname(address_string);
+  if (!host_data
+      || host_data->h_addrtype != AF_INET
+      || host_data->h_length != sizeof address.sin_addr) {
+    fprintf(stderr, "Failed to resolve host name `%s'\n", address_string);
+    exit(EXIT_FAILURE);
+  }
+
+  if (sscanf(port_string, "%u", &port) == 0 || port > 65535) {
+    fprintf(stderr, "A valid TCP/IP port number expected\n");
+    exit(EXIT_FAILURE);
+  }
+
+  connection_socket = socket(PF_INET, SOCK_STREAM, 0);
+  if (connection_socket == -1) {
+    fprintf(stderr, "Unexpected error: failed to create a socket\n");
+    exit(EXIT_FAILURE);
+  }
+
+  address.sin_family = AF_INET;
+  address.sin_port = htons(port);
+
+  for (address_pointer = host_data->h_addr_list; *address_pointer;
+       address_pointer++) {
+    memcpy(&address.sin_addr, *address_pointer, sizeof address.sin_addr);
+    if (connect(connection_socket, (struct sockaddr *) &address,
+               sizeof address) != -1)
+      break;
+  }
+
+  if (! *address_pointer) {
+    fprintf(stderr, "Failed to connect to %s:%d\n", host_data->h_name, port);
+    close(connection_socket);
+    exit(EXIT_FAILURE);
+  }
+
+  *input_file  = fdopen(connection_socket, "r");
+  *output_file = fdopen(dup(connection_socket), "w");
+}
+
+
+static void
+socket_listen_at(char *address_string, FILE **input_file, FILE **output_file)
+{
+  struct sockaddr_in address;
+  char *port_string;
+  unsigned int port;
+  int listening_socket;
+  int connection_socket;
+
+  port_string = strchr(address_string, ':');
+  if (port_string)
+    *port_string++ = 0;
+  else {
+    port_string = address_string;
+    address.sin_addr.s_addr = htonl(INADDR_ANY);
+  }
+
+  if (sscanf(port_string, "%u", &port) == 0 || port > 65535) {
+    fprintf(stderr, "A valid TCP/IP port number expected\n");
+    exit(EXIT_FAILURE);
+  }
+
+  if (port_string != address_string) {
+    struct hostent *host_data;
+
+    host_data = gethostbyname(address_string);
+    if (!host_data
+       || host_data->h_addrtype != AF_INET
+       || host_data->h_length != sizeof address.sin_addr) {
+      fprintf(stderr, "Failed to resolve host name `%s'\n", address_string);
+      exit(EXIT_FAILURE);
+    }
+
+    address_string = host_data->h_name;
+    memcpy(&address.sin_addr, host_data->h_addr_list[0],
+          sizeof address.sin_addr);
+  }
+
+  listening_socket = socket(PF_INET, SOCK_STREAM, 0);
+  if (listening_socket == -1) {
+    fprintf(stderr, "Unexpected error: failed to create a socket\n");
+    exit(EXIT_FAILURE);
+  }
+
+  address.sin_family = AF_INET;
+  address.sin_port = htons(port);
+
+  if (bind(listening_socket,
+          (struct sockaddr *) &address, sizeof address) == -1
+      || listen(listening_socket, 0) == -1
+      || (connection_socket = accept(listening_socket, NULL, NULL)) == -1) {
+    if (address_string != port_string)
+      fprintf(stderr, "Failed to listen on %s:%d\n", address_string, port);
+    else
+      fprintf(stderr, "Failed to listen on port %d\n", port);
+
+    close(listening_socket);
+    exit(EXIT_FAILURE);
+  }
+
+  close(listening_socket);
+
+  *input_file  = fdopen(connection_socket, "r");
+  *output_file = fdopen(dup(connection_socket), "w");
+}
+
+
+static void
+socket_close_connection(FILE *input_file, FILE *output_file)
+{
+  /* When connecting, we close the socket first. */
+  fclose(input_file);
+  fclose(output_file);
+}
+
+
+static void
+socket_stop_listening(FILE *input_file, FILE *output_file)
+{
+  int buffer[0x1000];
+
+  /* When listening, we wait for the client to disconnect first.
+   * Otherwise, socket doesn't get released properly.
+   */
+  do
+    fread(buffer, sizeof buffer, 1, input_file);
+  while (!feof(input_file));
+
+  fclose(input_file);
+  fclose(output_file);
+}
+
+
 /*
  * Local Variables:
  * tab-width: 8
Index: interface/play_gtp.c
===================================================================
RCS file: /cvsroot/gnugo/gnugo/interface/play_gtp.c,v
retrieving revision 1.153
diff -u -p -r1.153 play_gtp.c
--- interface/play_gtp.c        19 Jul 2004 12:23:09 -0000      1.153
+++ interface/play_gtp.c        9 Oct 2004 15:08:55 -0000
@@ -337,12 +337,15 @@ static struct gtp_command commands[] = {
 
 /* Start playing using the Go Text Protocol. */
 void
-play_gtp(FILE *gtp_input, FILE *gtp_dump_commands, int gtp_initial_orientation)
+play_gtp(FILE *gtp_input, FILE *gtp_output, FILE *gtp_dump_commands,
+        int gtp_initial_orientation)
 {
-  /* Make sure stdout is unbuffered. (Line buffering is also okay but
-   * not necessary. Block buffering breaks GTP mode.)
+  /* Make sure `gtp_output' is unbuffered. (Line buffering is also
+   * okay but not necessary. Block buffering breaks GTP mode.)
+   *
+   * FIXME: Maybe should go to `gtp.c'?
    */
-  setbuf(stdout, NULL);
+  setbuf(gtp_output, NULL);
 
   /* Inform the GTP utility functions about the board size. */
   gtp_internal_set_boardsize(board_size);
@@ -355,7 +358,7 @@ play_gtp(FILE *gtp_input, FILE *gtp_dump
   /* Prepare pattern matcher and reading code. */
   reset_engine();
   clearstats();
-  gtp_main_loop(commands, gtp_input, gtp_dump_commands);
+  gtp_main_loop(commands, gtp_input, gtp_output, gtp_dump_commands);
   if (showstatistics)
     showstats();
 }
@@ -2633,7 +2636,7 @@ gtp_move_reasons(char *s)
     return gtp_failure("vertex must not be occupied");
 
   gtp_start_response(GTP_SUCCESS);
-  if (list_move_reasons(stdout, POS(i, j)) == 0)
+  if (list_move_reasons(gtp_output_file, POS(i, j)) == 0)
     gtp_printf("\n");
   gtp_printf("\n");
   return GTP_OK;
@@ -3562,7 +3565,7 @@ gtp_showboard(char *s)
   
   gtp_start_response(GTP_SUCCESS);
   gtp_printf("\n");
-  simple_showboard(stdout);
+  simple_showboard(gtp_output_file);
   return gtp_finish_response();
 }
 
@@ -4015,7 +4018,7 @@ gtp_dragon_data(char *s)
                && dragon[POS(m, n)].origin == POS(m, n))) {
          gtp_print_vertex(m, n);
          gtp_printf(":\n");
-         report_dragon(stdout, POS(m, n));
+         report_dragon(gtp_output_file, POS(m, n));
        }
   }
   gtp_printf("\n");
@@ -4281,7 +4284,7 @@ static int
 gtp_echo_err(char *s)
 {
   fprintf(stderr, "%s", s);
-  fflush(stdout);
+  fflush(gtp_output_file);
   fflush(stderr);
   return gtp_success("%s", s);
 }





reply via email to

[Prev in Thread] Current Thread [Next in Thread]