Client and Server Version of Crossword

This example builds on echo_server03 and echo_client03 on the previous page and demonstrates how you can process data in the server and return a multi-line result. Much of the server code originates from our Oxygene for Java version of Adam Renak's Crossword program.

As with the other socket examples, it is convenient to put the server on a Raspberry Pi so that you do not need to circumvent the various security measures on port 1717. The main version loads the dictionary file from this website but we include an alternative load procedure for use when you do not have Internet access.

Code of Server

namespace crossword_server;
{
    Crossword search code copyright (c) 2010 Adam Renak

    Licensed under the Apache License, Version 2.0 (the "License"); you may
    not  use this file except in compliance with the License, as described at
    http://www.apache.org/licenses/ and http://www.pp4s.co.uk/licenses/

    Converted to Oxygene for Java by PPS. (2013)
}
interface

uses
  java.io.*, java.net.*;  
type
  ConnectionHandler = class(Runnable)
  private
    cs : Socket := nil;
    pw : PrintWriter := nil;
    br : BufferedReader := nil;
    isr : InputStreamReader := nil;
    CharPosn, writecount : Integer;
    result_string, input_string, lower_case_string, lower_case_word : string;    
  public
    constructor Create(s : Socket);
    method run;   
  end;

var
  Words, NarrowWords : Array[0 .. 100000] of string;
  word_length : Integer;
  Narrowfactor : Integer := -1;
const
  ListLength = 80368;

implementation

const
  MAX_CLIENTS = 5;
  SERVER_PORT = 1717;    
var
  serverSocket : ServerSocket := nil;
  clientSocket : Socket := nil;
  connectionHandler : ConnectionHandler;
  t : Thread; 

method ReportError(E : Exception);
begin
  writeln (E.toString);
  System.in.read;
  System.exit(0);
end; 
  
method LoadWordlist;
var
  isr : InputStreamReader;
  lnr : LineNumberReader;
  Count : Integer := -1;
  u : URL;
begin
  try 
    u := new URL('http://www.pp4s.co.uk/static/pp4s/resources/DictWordlist.txt'); 
    isr := new InputStreamReader(u.openStream);
    lnr   := new LineNumberReader(isr);
    repeat
      inc(Count);
      Words[Count] := lnr.readLine;   
    until Words[Count] = nil;
    lnr.close;
    isr.close;
  except  
    on E : Exception do
      ReportError(E);
  end; 
end;

//Narrows the wordlist down to words of similar length
method NarrowLength;                                     
var
  Count1, Count2 : Integer;
begin  
  Count2 := -1;
  for Count1 := 0 to ListLength - 1 do
    begin
      if word_length = length(words[Count1]) then
        begin
          inc(Count2);
          Narrowwords[Count2] := words[Count1];
          Narrowfactor := Count2; //Now Narrowfactor is zero based
        end;
    end;
end;

//Narrows list according to character position.
method Narrow(vWord : string; position : integer);                 
var
  NarrowPos, InsertCount : Integer;
begin
  InsertCount := -1; //Sets the pointer that the new narrowlist begins at.
  for NarrowPos := 0 to Narrowfactor  do //From 0 to last narrowed word
    if (vWord[position] = narrowwords[NarrowPos][position]) then
      begin
        Inc(InsertCount);
        NarrowWords[InsertCount] := Narrowwords[NarrowPos];
      end;
    Narrowfactor := InsertCount; //Reduces the Narrowfactor 
end;  
    
constructor ConnectionHandler.Create(s : Socket);
begin
  cs := s;
  try    
    isr := new InputStreamReader(cs.getInputStream);
    br := new BufferedReader(isr);
    pw := new PrintWriter(cs.getOutputStream, true);
  except  
    on E : Exception do
      ReportError(E);
    end;  
end;

method ConnectionHandler.run;
begin
  repeat
    input_string := br.readLine;
    if (input_string <> 'quit') and (input_string <> 'end')  then
      begin
        lower_case_string := input_string.toLowerCase; 
        lower_case_word := lower_case_string.trim; //Removes trailing spaces
        word_length := length(lower_case_word);
        Narrowlength;
        for CharPosn := 0 to word_length - 1 do  //Zero based chars in string  
        if lower_case_word[CharPosn] <> '?' then
          Narrow(lower_case_word, CharPosn);
        //Send a list of matching words:
        result_string := '--------------------------------------%n';
        for WriteCount := 0 to Narrowfactor do //Writes possible words    
          result_string := result_string + 'MATCH: ' + NarrowWords[WriteCount].toString + '%n'; 
        //If no match is found, it writes the five closest matches.
        if Narrowfactor = -1 then  
          begin
            result_string := result_string + 'No match found. Try these suggestions:%n';
            for WriteCount := (Narrowfactor + 1) to (Narrowfactor + 6) do 
              result_string := result_string + NarrowWords[WriteCount].toString + '%n';    
          end; 
        result_string := result_string + '--------------------------------------%n';
        pw.println(result_string);
        writeln ('Sending result');  
      end;
  until (input_string = 'end') or (input_string = 'quit');
  pw.close;
  isr.close;
  br.close;
  cs.close;
  if input_string = 'quit' then
    System.exit(0);
end;

begin
  Loadwordlist;
  try
    serverSocket := new ServerSocket(SERVER_PORT, MAX_CLIENTS);  
  except
    on E : Exception do
      ReportError(E);
  end;  
  writeln('Waiting');    
  repeat
    try
      clientSocket := serverSocket.accept;
    except  
      on E : Exception do
        ReportError(E);
    end;  
    connectionHandler := ConnectionHandler.Create(clientSocket);
    t := new Thread(connectionHandler);
    t.start;    
  until false;
end.

Code of LoadWordlist for offline use

Download data file DictWordlist.txt required by this version of the method.

method LoadWordlist;
var
  fis : FileInputStream;
  isr : InputStreamReader;
  lnr : LineNumberReader;
  Count : Integer := -1;
begin 
  fis := new FileInputStream('DictWordlist.txt');
  isr := new InputStreamReader(fis);
  lnr := new LineNumberReader(isr);
  repeat
    inc(Count);
    Words[Count] := lnr.readLine;   
  until Words[Count] = nil;
  fis.close;  
end;

Code of Client

namespace crossword_client;

interface

uses
java.io.*, java.net.*;

implementation

const
  SERVER_PORT = 1717;
var
  searchString, resultString, ip_string : String;
  pw : PrintWriter := nil;
  br : BufferedReader := nil;
  isr : InputStreamReader := nil;
  clientSocket : Socket := nil;
  ip : array[0 .. 20] of SByte;
  searchTerm : array[0 .. 20] of SByte;
  
begin  
  writeln('Please enter IP address of server (127.0.0.1 for same machine as client).');  
  System.in.read(ip);
  ip_string := new String(ip).trim;
  try
    clientSocket := new Socket(InetAddress.getByName(ip_string), SERVER_PORT);
    pw := new PrintWriter(clientSocket.getOutputStream, true);
    isr := new InputStreamReader(clientSocket.getInputStream);  
    br := new BufferedReader(isr);
  except
    on E : Exception do
      begin
        writeln(E.toString);
        System.in.read;
        System.exit(0);  
      end;  
  end;    
  repeat 
    writeln('Please enter search string.'); 
    writeln('Enter quit to quit the server and end to quit the client.');
    searchTerm := [32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32];
    System.in.read(searchTerm);
    searchString := new String(searchTerm).trim;
    pw.println(searchString);    
    if (searchString <> 'quit') and (SearchString <> 'end')  then
      begin
        resultString := br.readLine;     
        System.out.printf(resultString);
      end;    
  until  (searchString = 'quit') or (searchString = 'end');  
  pw.close;
  br.close;
  isr.close;
  clientSocket.close;
end.

Programming - a skill for life!

How to write client-server applications using TCP Sockets in Oxygene for Java