Skip to content

Commit

Permalink
Initial commit.
Browse files Browse the repository at this point in the history
  • Loading branch information
postmodern committed May 10, 2011
0 parents commit e5939d5
Show file tree
Hide file tree
Showing 13 changed files with 304 additions and 0 deletions.
3 changes: 3 additions & 0 deletions .document
@@ -0,0 +1,3 @@
-
ChangeLog.*
LICENSE.txt
2 changes: 2 additions & 0 deletions .gitignore
@@ -0,0 +1,2 @@
doc/
pkg/
1 change: 1 addition & 0 deletions .rspec
@@ -0,0 +1 @@
--colour --format documentation
1 change: 1 addition & 0 deletions .yardopts
@@ -0,0 +1 @@
--markup markdown --title "threaded_server Documentation" --protected
4 changes: 4 additions & 0 deletions ChangeLog.md
@@ -0,0 +1,4 @@
### 0.1.0 / 2011-05-09

* Initial release:

20 changes: 20 additions & 0 deletions LICENSE.txt
@@ -0,0 +1,20 @@
Copyright (c) 2011 Hal Brodigan

Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:

The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
27 changes: 27 additions & 0 deletions README.md
@@ -0,0 +1,27 @@
# threaded_server

* [Homepage](http://github.com/postmodern/threaded_server)
* [Documentation](http://rubydoc.info/gems/threaded_server/frames)
* [Email](mailto:postmodern.mod3 at gmail.com)

## Description

A generic TCP Server with a fixed-size Thread Pool.

## Examples

require 'threaded_server'

ThreadedServer.open('127.0.0.1',8080) do |socket|
socket.puts Time.now
end

## Install

$ gem install threaded_server

## Copyright

Copyright (c) 2011 Hal Brodigan

See {file:LICENSE.txt} for details.
38 changes: 38 additions & 0 deletions Rakefile
@@ -0,0 +1,38 @@
require 'rubygems'
require 'rake'

begin
gem 'ore-tasks', '~> 0.4'
require 'ore/tasks'

Ore::Tasks.new
rescue LoadError => e
STDERR.puts e.message
STDERR.puts "Run `gem install ore-tasks` to install 'ore/tasks'."
end

begin
gem 'rspec', '~> 2.4'
require 'rspec/core/rake_task'

RSpec::Core::RakeTask.new
rescue LoadError => e
task :spec do
abort "Please run `gem install rspec` to install RSpec."
end
end

task :test => :spec
task :default => :spec

begin
gem 'yard', '~> 0.6.0'
require 'yard'

YARD::Rake::YardocTask.new
rescue LoadError => e
task :yard do
abort "Please run `gem install yard` to install YARD."
end
end
task :doc => :yard
16 changes: 16 additions & 0 deletions gemspec.yml
@@ -0,0 +1,16 @@
name: threaded_server
version: 0.1.0
summary: Similar to GServer, but with a Thread Pool.
description:
A generic TCP Server with a fixed-size Thread Pool.

license: MIT
authors: Postmodern
email: postmodern.mod3@gmail.com
homepage: http://github.com/postmodern/threaded_server
has_yard: true

development_dependencies:
ore-tasks: ~> 0.4
rspec: ~> 2.4
yard: ~> 0.6.0
164 changes: 164 additions & 0 deletions lib/threaded_server.rb
@@ -0,0 +1,164 @@
require 'socket'
require 'thread'

#
# A generic TCP Server with a fixed-size Thread Pool.
#
class ThreadedServer

# Default maximum number of Connections
MAX_CONNECTIONS = 64

# The host the server will listen on
attr_reader :host

# The port the server will listen on
attr_reader :port

# The maximum number of active connections
attr_reader :max_connections

#
# Creates a new Threaded Server.
#
# @param [IPAddr, String] host
# The host to listen on.
#
# @param [Integer] port
# The port to listen on.
#
# @param [Hash] options
# Additional options.
#
# @option options [Integer] :max_connections (MAX_CONNECTIONS)
# The maximum number of active connections.
#
# @option options [#call] :handler
# The handler that will be passed new connections.
#
# @yield [connection]
# If a block is given, it will be passed the new connections.
#
# @yieldparam [TCPSocket] connection
# A new connection.
#
def initialize(host,port,options={},&block)
@host = host.to_s
@port = port

@max_connections = options.fetch(:max_connections,MAX_CONNECTIONS)
@queue = SizedQueue.new(@max_connections)
@handler = options.fetch(:handler,block)

@thread_pool = Array.new(@max_connections) do
Thread.new do
loop do
# receive a pending connection
client = @queue.pop

begin
# handle the connection
@handler.call(client)
rescue => error
on_error(client,error)
end

# close the connection
client.close
end
end
end
end

#
# Creates a new Threaded Server and begins listening for connections.
#
# @param [IPAddr, String] host
# The host to listen on.
#
# @param [Integer] port
# The port to listen on.
#
# @param [Hash] options
# Additional options.
#
# @option options [Integer] :max_connections (MAX_CONNECTIONS)
# The maximum number of active connections.
#
# @option options [#call] :handler
# The handler that will be passed new connections.
#
# @yield [connection]
# If a block is given, it will be passed the new connections.
#
# @yieldparam [TCPSocket] connection
# A new connection.
#
# @see #listen
#
def self.open(host,port,options={},&block)
server = new(host,port,options,&block)
server.listen
end

#
# Opens a listening TCP socket and begins accepting connections.
#
def listen
@server = TCPServer.new(@host,@port)

@server.listen(@max_connections)

loop do
client = begin
@server.accept
rescue
next
end

on_connect(client)
@queue.push(client)
end
end

#
# Closes the listening TCP socket.
#
def close
@server.close
end

protected

#
# Place holder method that will be passed newly connected sockets.
#
# @param [TCPSocket] socket
# The newly connected socket.
#
def on_connect(socket)
end

#
# Place holder method that will be passed soon to be closed sockets.
#
# @param [TCPSocket] socket
# The socket that will be closed.
#
def on_disconnect(socket)
end

#
# Place holder method that will be passed any exceptions that occurred.
#
# @param [TCPSocket] socket
# The socket that raised the exception.
#
# @param [Exception] error
# The exception.
#
def on_error(socket,error)
STDERR.puts "#{error.class}: #{error.message}"
end

end
5 changes: 5 additions & 0 deletions spec/spec_helper.rb
@@ -0,0 +1,5 @@
gem 'rspec', '~> 2.4'
require 'rspec'
require 'threaded_server/version'

include ThreadedServer
8 changes: 8 additions & 0 deletions spec/threaded_server_spec.rb
@@ -0,0 +1,8 @@
require 'spec_helper'
require 'threaded_server'

describe ThreadedServer do
it "should have a VERSION constant" do
subject.const_get('VERSION').should_not be_empty
end
end
15 changes: 15 additions & 0 deletions threaded_server.gemspec
@@ -0,0 +1,15 @@
# -*- encoding: utf-8 -*-

begin
Ore::Specification.new do |gemspec|
# custom logic here
end
rescue NameError
begin
require 'ore/specification'
retry
rescue LoadError
STDERR.puts "The '#{__FILE__}' file requires Ore."
STDERR.puts "Run `gem install ore-core` to install Ore."
end
end

0 comments on commit e5939d5

Please sign in to comment.