cwgem-selectserver 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,4 @@
1
+ *.gem
2
+ .bundle
3
+ Gemfile.lock
4
+ pkg/*
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source "http://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in cwgem-selectserver.gemspec
4
+ gemspec
data/README.markdown ADDED
@@ -0,0 +1,77 @@
1
+ ## Getting Started ##
2
+
3
+ A basic select multiplexing based TCP server. Client connections are handled by a method handler or a block handler. The basic format for handlers is:
4
+
5
+ ```ruby
6
+ def handler(server, client_socket)
7
+ end
8
+
9
+ server = Cwgem::SelectServer.new("localhost", 3555)
10
+ server.setup_method_handler(method(:handler))
11
+ server.start
12
+ ```
13
+
14
+ Where server is the server object itself, and client socket is the socket of the client. The server is passed in so you can do things like shutding down the server based on a client command:
15
+
16
+ ```ruby
17
+ def handler(server,client_socket)
18
+ command = client_socket.gets
19
+ server.shutdown if command.chomp! == "exit"
20
+ end
21
+
22
+ server = Cwgem::SelectServer.new("localhost", 3555)
23
+ server.setup_method_handler(method(:handler))
24
+ server.start
25
+ ```
26
+
27
+ Blocks can also be used as client connection handlers:
28
+
29
+ ```ruby
30
+ server = Cwgem::SelectServer.new("localhost", 3555)
31
+ server.start do | server, client_socket |
32
+ end
33
+ ```
34
+
35
+ Note that either a method handler must be setup before calling server.start, or a block passed into it. Failure to do so will result in an ArgumentError:
36
+
37
+ ```ruby
38
+ server = Cwgem::SelectServer.new("localhost", 3555)
39
+ server.start # this will throw ArgumentError since no handler is setup
40
+ ```
41
+
42
+ If you need to carry around extra metadata for client connections, the best method is to override the accept method and extend the socket through a module. In this example the client socket has a username attribute added to it by extending a ChatClient module.
43
+
44
+ ```ruby
45
+ module ChatClient
46
+ attr_accessor :username
47
+ end
48
+
49
+ server = Cwgem::SelectServer.new("localhost", 3555)
50
+ def server.accept
51
+ client = self.accept
52
+ client.extend(ChatClient)
53
+ @sockets << client
54
+ end
55
+
56
+ server.start do | server, socket |
57
+ 1
58
+ end
59
+ ```
60
+
61
+ ## Example ##
62
+
63
+ This example is an embedded ruby script that can be run using `ruby -x Readme.markdown`. It binds to localhost on port 3555, and acts as a simple echo server. The simple server can be shutdown by sending the USR1 signal `kill -USR1 [processid]`.
64
+
65
+ ```ruby
66
+ #! ruby
67
+
68
+ require 'cwgem-selectserver'
69
+
70
+ server = Cwgem::SelectServer.new("localhost", 3555)
71
+ Signal.trap("USR1") { server.shutdown; server.shutdown_handler; exit }
72
+ server.start do | server, socket |
73
+ data = socket.gets
74
+ socket.write data
75
+ end
76
+ __END__
77
+ ```
data/Rakefile ADDED
@@ -0,0 +1 @@
1
+ require 'bundler/gem_tasks'
@@ -0,0 +1,20 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+ require "cwgem-selectserver/version"
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = "cwgem-selectserver"
7
+ s.version = Cwgem::SelectServer::VERSION
8
+ s.authors = ["Chris White"]
9
+ s.email = ["cwprogram@live.com"]
10
+ s.homepage = ""
11
+ s.summary = %q{A basic TCP server that uses IO.select multiplexing}
12
+ s.description = %q{A basic TCP server that uses IO.select multiplexing}
13
+
14
+ s.rubyforge_project = "cwgem-selectserver"
15
+
16
+ s.files = `git ls-files`.split("\n")
17
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
18
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
19
+ s.require_paths = ["lib"]
20
+ end
@@ -0,0 +1,5 @@
1
+ module Cwgem
2
+ class SelectServer
3
+ VERSION = "0.2.0"
4
+ end
5
+ end
@@ -0,0 +1,97 @@
1
+ require 'socket'
2
+
3
+ module Cwgem
4
+ # @author Chris White
5
+ class SelectServer < ::TCPServer
6
+
7
+ # Setups up the server by binding to the host and port, adding the server
8
+ # socket to the list of sockets to check for, and initializing the
9
+ # client socket handler and server_active (used as a main loop check) variables
10
+ #
11
+ # @param [String] The host to bind to
12
+ # @param [Integer] The port to bind to
13
+ def initialize(host,port)
14
+ super
15
+ @sockets = [self]
16
+ @handler = nil
17
+ @server_active = true
18
+ end
19
+
20
+ # Sets a method based handler for the main server loop
21
+ #
22
+ # @param [Object] Any object that can respond to .call
23
+ def setup_method_handler(method_handler)
24
+ @handler = method_handler
25
+ end
26
+
27
+ # The main server loop. ArgumentError is raised if a handler is not set.
28
+ #
29
+ # @param [Object] If a block is given, it will be called with the server and client socket as arguments
30
+ def start(&block)
31
+ raise ArgumentError, "method handler must be set or block must be given" if
32
+ (!@handler or !@handler.respond_to?(:call)) and !block_given?
33
+
34
+ @handler = block if block_given?
35
+
36
+ while @server_active == true
37
+ readable_sockets,writable_sockets,exception_sockets = IO.select(@sockets,[], @sockets)
38
+
39
+ readable_sockets.each do | read_socket |
40
+ if read_socket == self
41
+ accept_client
42
+ else
43
+ begin
44
+ # Remove closed connections. An exception is raised
45
+ # if this is a dead socket, which we handle below
46
+ # by simply removing the socket.
47
+ if read_socket.closed? or read_socket.eof?
48
+ remove_client read_socket
49
+ else
50
+ @handler.call(self,read_socket)
51
+ end
52
+ rescue
53
+ remove_client read_socket
54
+ end
55
+ end
56
+ end
57
+
58
+ exception_sockets.each do | err_socket |
59
+ remove_client err_socket
60
+ end
61
+
62
+ end
63
+
64
+ shutdown_handler
65
+ end
66
+
67
+ # Accept a client socket and add it to the list of active
68
+ # sockets
69
+ def accept_client
70
+ client = self.accept
71
+ @sockets << client
72
+ end
73
+
74
+ # Remove a client socket from the list of active sockets
75
+ #
76
+ # @param [TCPSocket] The client socket to remove
77
+ def remove_client(client_socket)
78
+ @sockets.reject! { | socket | socket == client_socket }
79
+ end
80
+
81
+ # This sets a flag which will halt the main loop. The purpose of this method
82
+ # is to allow the main loop to finish its tasks before shutting everything
83
+ # down
84
+ def shutdown
85
+ @server_active = false
86
+ end
87
+
88
+ # Now that the server's main loop has finished processing, shutdown
89
+ # the server
90
+ def shutdown_handler
91
+ self.close unless self.closed?
92
+ @sockets = []
93
+ end
94
+
95
+ end #end class SelectServer
96
+
97
+ end
@@ -0,0 +1,12 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'cwgem-selectserver'
4
+
5
+ port = ARGV[0].to_i
6
+ server = Cwgem::SelectServer.new("localhost", port)
7
+ Signal.trap("USR1") { server.shutdown; server.shutdown_handler; exit }
8
+ server.start do | server, socket |
9
+ data = socket.gets
10
+ socket.write data
11
+ server.shutdown
12
+ end
@@ -0,0 +1,15 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'cwgem-selectserver'
4
+
5
+ def handle_client(server,socket)
6
+ data = socket.gets
7
+ socket.write data
8
+ server.shutdown
9
+ end
10
+
11
+ port = ARGV[0].to_i
12
+ server = Cwgem::SelectServer.new("localhost", port)
13
+ Signal.trap("USR1") { server.shutdown; server.shutdown_handler; exit }
14
+ server.setup_method_handler(method(:handle_client))
15
+ server.start
@@ -0,0 +1,17 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'cwgem-selectserver'
4
+
5
+ class ClientHandler
6
+ def handle_client(server,socket)
7
+ data = socket.gets
8
+ socket.write data
9
+ server.shutdown
10
+ end
11
+ end
12
+
13
+ port = ARGV[0].to_i
14
+ server = Cwgem::SelectServer.new("localhost", port)
15
+ Signal.trap("USR1") { server.shutdown; server.shutdown_handler; exit }
16
+ server.setup_method_handler(ClientHandler.new.method(:handle_client))
17
+ server.start
@@ -0,0 +1,53 @@
1
+ require 'test/unit'
2
+ require 'socket'
3
+ require 'cwgem-selectserver'
4
+
5
+ class TestSelectServer < Test::Unit::TestCase
6
+
7
+ def test_bind_to_port
8
+ server = Cwgem::SelectServer.new("127.0.0.1", 8555)
9
+ assert_raise(Errno::EADDRINUSE) do
10
+ other_server = TCPServer.new "127.0.0.1", 8555
11
+ end
12
+ server.shutdown_handler
13
+ end
14
+
15
+ def test_no_handler
16
+ server = Cwgem::SelectServer.new("127.0.0.1", 8555)
17
+ assert_raise(ArgumentError) do
18
+ server.start
19
+ end
20
+ server.shutdown_handler
21
+ end
22
+
23
+ def test_instance_method_handler
24
+ check_server "select_server_instance_method_handler.rb", 8555
25
+ end
26
+
27
+ def test_global_method_handler
28
+ check_server "select_server_global_method_handler.rb", 8555
29
+ end
30
+
31
+ def test_block_handler
32
+ check_server "select_server_block_handler.rb", 8555
33
+ end
34
+
35
+ def check_server(server_filename, port)
36
+ server_cmd = File.dirname(File.expand_path(__FILE__)) << "/#{server_filename} #{port}"
37
+ pipe = IO.popen(server_cmd)
38
+ # Sleep for 2 seconds to let the server bind
39
+ sleep 2
40
+
41
+ client = TCPSocket.new("localhost", port)
42
+ client.puts "Client Data"
43
+ data = client.gets
44
+ assert_equal(data, "Client Data\n")
45
+ client.close
46
+
47
+ ensure
48
+ # Be safe and send the server a shutdown signal
49
+ # in case the socket communication fails
50
+ Process.kill("USR1", pipe.pid)
51
+ end
52
+
53
+ end
metadata ADDED
@@ -0,0 +1,60 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: cwgem-selectserver
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.2.0
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Chris White
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2011-09-15 00:00:00.000000000 Z
13
+ dependencies: []
14
+ description: A basic TCP server that uses IO.select multiplexing
15
+ email:
16
+ - cwprogram@live.com
17
+ executables: []
18
+ extensions: []
19
+ extra_rdoc_files: []
20
+ files:
21
+ - .gitignore
22
+ - Gemfile
23
+ - README.markdown
24
+ - Rakefile
25
+ - cwgem-selectserver.gemspec
26
+ - lib/cwgem-selectserver.rb
27
+ - lib/cwgem-selectserver/version.rb
28
+ - test/select_server_block_handler.rb
29
+ - test/select_server_global_method_handler.rb
30
+ - test/select_server_instance_method_handler.rb
31
+ - test/select_server_test.rb
32
+ homepage: ''
33
+ licenses: []
34
+ post_install_message:
35
+ rdoc_options: []
36
+ require_paths:
37
+ - lib
38
+ required_ruby_version: !ruby/object:Gem::Requirement
39
+ none: false
40
+ requirements:
41
+ - - ! '>='
42
+ - !ruby/object:Gem::Version
43
+ version: '0'
44
+ required_rubygems_version: !ruby/object:Gem::Requirement
45
+ none: false
46
+ requirements:
47
+ - - ! '>='
48
+ - !ruby/object:Gem::Version
49
+ version: '0'
50
+ requirements: []
51
+ rubyforge_project: cwgem-selectserver
52
+ rubygems_version: 1.8.10
53
+ signing_key:
54
+ specification_version: 3
55
+ summary: A basic TCP server that uses IO.select multiplexing
56
+ test_files:
57
+ - test/select_server_block_handler.rb
58
+ - test/select_server_global_method_handler.rb
59
+ - test/select_server_instance_method_handler.rb
60
+ - test/select_server_test.rb