cwgem-selectserver 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +4 -0
- data/Gemfile +4 -0
- data/README.markdown +77 -0
- data/Rakefile +1 -0
- data/cwgem-selectserver.gemspec +20 -0
- data/lib/cwgem-selectserver/version.rb +5 -0
- data/lib/cwgem-selectserver.rb +97 -0
- data/test/select_server_block_handler.rb +12 -0
- data/test/select_server_global_method_handler.rb +15 -0
- data/test/select_server_instance_method_handler.rb +17 -0
- data/test/select_server_test.rb +53 -0
- metadata +60 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
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,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
|