Class: TinyTCPService

Inherits:
Object
  • Object
show all
Defined in:
lib/tiny_tcp_service.rb

Overview

usage:

s = TinyTCPService.new(
  1234,
  ->(m) { puts m }
)

s.stop!      # gracefully shutdown the server

TinyTCPService implements a line-based, call and response protocol, where every incoming message must be a newline-terminated (“n”) String, and for every received message the service responds with a newline-terminated String. been set).

If you need more complex objects to be sent over the wire, consider something like JSON.

NOTE: if you’re running a TinyTCPService and a client of your system violates your communication protocol, you should raise an instance of TinyTCPService::BadClient, and the TinyTCPService instance will take care of safely removing the client.

Defined Under Namespace

Classes: BadClient

Instance Method Summary collapse

Constructor Details

#initialize(port) ⇒ TinyTCPService

Returns a new instance of TinyTCPService.



24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
# File 'lib/tiny_tcp_service.rb', line 24

def initialize(port)
  @port = port

  @server = TCPServer.new(port)
  @clients = []
  @running = true

  @msg_handler = nil
  @error_handlers = {}

  # client accept thread
  @thread = Thread.new do |t|
    loop do
      break unless running?
      @clients << @server.accept
    end

    @clients.each{|c| _remove_client!(c) if c && !c.closed? }
    @server.close
  end

  # service thread
  Thread.new do |t|
    loop do
      break unless running?

      readable, _, errored = IO.select(@clients, nil, @clients, 1)
      readable&.each do |c|
        begin
          m = c.gets&.chomp

          if m.is_a?(String)
            c.puts(@msg_handler&.call(m))
          else
            _remove_client!(c)
          end
        rescue TinyTCPService::BadClient, Errno::ECONNRESET => e
          _remove_client!(c)
        rescue => e
          handler = @error_handlers[e.class]

          if handler
            handler.call(e)
          else
            stop!
            raise e
          end
        end
      end

      errored&.each do |c|
        _remove_client!(c)
      end
    end
  end
end

Instance Method Details

#_remove_client!(c) ⇒ Object



114
115
116
117
# File 'lib/tiny_tcp_service.rb', line 114

def _remove_client!(c)
  @clients.delete(c)
  c.close if c && !c.closed?
end

#add_error_handler(klass, block) ⇒ Object

add the error handler and block for the specified class

you can assume that the local variable name of the error will be ‘e’



100
101
102
# File 'lib/tiny_tcp_service.rb', line 100

def add_error_handler(klass, block)
  @error_handlers[klass] = block
end

#joinObject

join the service Thread if you want to wait until it’s closed



82
83
84
# File 'lib/tiny_tcp_service.rb', line 82

def join
  @thread.join
end

#msg_handler=(h) ⇒ Object

h - some object that responds to #call



87
88
89
# File 'lib/tiny_tcp_service.rb', line 87

def msg_handler=(h)
  @msg_handler = h
end

#num_clientsObject

returns the number of connected clients



110
111
112
# File 'lib/tiny_tcp_service.rb', line 110

def num_clients
  @clients.length
end

#remove_error_handler(klass) ⇒ Object

remove the error handler associated with klass



105
106
107
# File 'lib/tiny_tcp_service.rb', line 105

def remove_error_handler(klass)
  @error_handlers.delete(klass)
end

#running?Boolean

returns true if the server is running false otherwise

Returns:

  • (Boolean)


93
94
95
# File 'lib/tiny_tcp_service.rb', line 93

def running?
  @running
end

#stop!Object

stops the server gracefully



120
121
122
# File 'lib/tiny_tcp_service.rb', line 120

def stop!
  @running = false
end