Problem with sockets

Hello there !

I was trying to code a scriptable MUD client (following the RubyQuiz
challenge). For the moment, I implemented a scriptable telnet which
works fine with my mail server and also with the test server provided by
James Edward Gray II on the RubyQuiz website. However, when I tried it
on a real MUD server, it just stopped receiving data after a few lines
(I tested it on imperian.com:23). The connection is not lost however, as
I can send commands (simple: if you can send "3", it will exit your
session :slight_smile: ).

I join my code here, all the connection stuff are in the two methods
"connect" and "idle" of the MUD class.

Thanks,

Pierre

Code :

======8<============8<===========8<===========8<============8<=========
#!/usr/bin/ruby -w

require "socket"

class MUD

  attr_reader :url, :port, :prefix, :io

  attr_reader :commands, :answers

  def initialize(url, port)
    @url = url
    @port = port
    @answers = MUDFilters.new
    @commands = MUDFilters.new
    @prefix = "!"
    @io = MUD::Io.new
    register_defaults
  end

  def connect
    @io.send ||= lambda { |line| @io.server_socket.write line }
    @io.report ||= lambda { |line| $stdout.write line }
    @socket = TCPSocket.new(@url, @port)
    @io.report["Connected to #{@url}:#{@port}\n"]
    @io.server_socket = @socket
    @io.freeze
    @url.freeze
    @prefix.freeze
    @thread = Thread.new do
      idle
    end
  end

  def disconnect
    @io.server_socket.close unless @io.server_socket.closed?
    @thread.join
    puts "Disconnected ..."
  end

  def idle
    f = File.new("log_server", "wb")
    begin
      while line = @io.server_socket.gets
        f.write line
        process_answer line
      end
    rescue
    end
    @io.server_socket.close unless @io.server_socket.closed?
    return 0
  end

  def process_command(line)
    process(@commands, line)
  end

  alias_method :send, :process_command

  def process_answer(line)
    process(@answers, line)
  end

  def add_command(regexp=//, priority=0, &block)
    register(@commands, Rule.new(regexp, priority, &block))
  end

  def remove_command(id)
    unregister(@commands, id)
  end

  def add_answer(regexp=//, priority=0, &block)
    register(@answers, Rule.new(regexp, priority, &block))
  end

  def remove_answer(id)
    unregister(@answers, id)
  end

protected

  def register_defaults
    # Echo the server commands if nothing else
    add_answer do |io,line|
      io.report[line]
    end
    # Send the commands to the server
    add_command do |io,line|
      io.send[line]
    end
    # Load a new definition file
    add_command(/^#{@prefix}load/i, 10000) do |io,line|
      if line =~ /^#{@prefix}load (.*)$/i
        config = $1
        if config and FileTest.exists? config and FileTest.readable?(config)
          io.report["Loading file #{config} ...\n"]
          load(config,true)
        end
      end
      nil
    end
  end

  def process(filters,line)
    selected = []
    filters.each do |regexp, rules|
      if line =~ regexp
        selected += rules
      end
    end
    selected.sort!
    selected.each do |rule|
      #puts "Launching rule #{rule.regexp.inspect} with priority
#{rule.priority}"
      line = rule.block[@io,line]
      if line == nil
        return
      end
    end
  end

  def register(filters, rule)
    filters[rule.regexp] << rule
    filters.ids[rule.object_id] = rule.regexp
    rule.object_id
  end

  def unregister(filters, id)
    regexp = filters.ids[id]
    filters[regexp].delete_if do |rule|
      rule.object_id == id
    end
  end

  class Rule
    attr_accessor :priority, :block
    attr_reader :regexp

    def initialize(regexp, priority, &block)
      @regexp = regexp
      @priority = priority
      @block = block
    end

    def <=>(other)
      other.priority <=> @priority
    end

  end
  class Io
    attr_accessor :send, :report
    attr_accessor :server_socket
  end

  class MUDFilters
    attr_accessor :ids

    include Enumerable

    def initialize
      @filters = Hash.new { |h,k| h[k] = [] }
      @ids = Hash.new
    end

    def [](regexp)
      @filters[regexp]
    end

    def []=(regexp, value)
      @filters[regexp] = value
    end

    def each(&block)
      @filters.each(&block)
    end

  end

end

if $0 == __FILE__
  require "getoptlong"

  opts = GetoptLong.new(
    [ "--port", "-p", GetoptLong::OPTIONAL_ARGUMENT ],
    [ "--config", "-c", GetoptLong::OPTIONAL_ARGUMENT ],
    [ "--prefix", GetoptLong::OPTIONAL_ARGUMENT],
    [ "--prompt", GetoptLong::OPTIONAL_ARGUMENT]
  )

  config = nil
  port = 23
  prefix = "!"

  opts.each do |opt, arg|
    case opt
      when "--port"
        port = arg.to_i
      when "--config"
        config = arg
      when "--prefix"
        prefix = Regexp.escape arg
    end
  end

  if ARGV.length != 1
    $stderr.write <<-EOF
    Usage: #{$0} [--port PORT] [--config FILE] [--prefix PREFIX] [--] HOST
        PORT port of the host to connect (default: #{port.to_s})
        FILE ruby file imported with a main function called with the
MUD object (default:none)
        HOST host to connect
        PREFIX prefix for local commands (default:#{prefix})
    EOF
    exit 2
  end

  host = ARGV[0]

  $mud = MUD.new(host, port)
  $mud.prefix.replace(prefix)

  if config and FileTest.exists? config and FileTest.readable? config
    load(config,true)
  end

  $mud.connect

  begin
    loop do
      line = $stdin.readline
      $mud.send line
    end
  rescue EOFError
    $mud.disconnect
  end
end
======>8============>8===========>8===========>8============>8=========

Pierre Barbier de Reuille wrote:

Hello there !

I was trying to code a scriptable MUD client (following the RubyQuiz
challenge). For the moment, I implemented a scriptable telnet which
works fine with my mail server and also with the test server provided
by James Edward Gray II on the RubyQuiz website. However, when I
tried it
on a real MUD server, it just stopped receiving data after a few lines
(I tested it on imperian.com:23). The connection is not lost however,
as
I can send commands (simple: if you can send "3", it will exit your
session :slight_smile: ).

I join my code here, all the connection stuff are in the two methods
"connect" and "idle" of the MUD class.

The first changes I'd do are to use the block form of File.open, use
synchronized IO during testing and print out any exceptions you catch in
#idle. At the moment the script silently terminates if there is an
exception with socket io and you never learn what went wrong (timeout for
example).

Regards

    robert

Robert Klemme a écrit :

Pierre Barbier de Reuille wrote:

Hello there !

I was trying to code a scriptable MUD client (following the RubyQuiz
challenge). For the moment, I implemented a scriptable telnet which
works fine with my mail server and also with the test server provided
by James Edward Gray II on the RubyQuiz website. However, when I
tried it
on a real MUD server, it just stopped receiving data after a few lines
(I tested it on imperian.com:23). The connection is not lost however,
as
I can send commands (simple: if you can send "3", it will exit your
session :slight_smile: ).

I join my code here, all the connection stuff are in the two methods
"connect" and "idle" of the MUD class.

The first changes I'd do are to use the block form of File.open, use
synchronized IO during testing and print out any exceptions you catch in
#idle. At the moment the script silently terminates if there is an
exception with socket io and you never learn what went wrong (timeout for
example).

Regards

    robert

Well, thanks a lot, I found the error :slight_smile: And, of course it was not were
I thought it was, and it was hidden by this bad exception handling :wink:

Pierre