em-ruby-sockets 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/lib/em-ruby-sockets.rb +28 -0
- data/lib/em-ruby-sockets/errors.rb +3 -0
- data/lib/em-ruby-sockets/tcp_client.rb +223 -0
- data/lib/em-ruby-sockets/tcp_connection.rb +48 -0
- data/lib/em-ruby-sockets/version.rb +5 -0
- metadata +99 -0
@@ -0,0 +1,28 @@
|
|
1
|
+
require "eventmachine"
|
2
|
+
require "kgio"
|
3
|
+
require "iobuffer"
|
4
|
+
|
5
|
+
require "em-ruby-sockets/version"
|
6
|
+
require "em-ruby-sockets/errors"
|
7
|
+
require "em-ruby-sockets/tcp_connection"
|
8
|
+
require "em-ruby-sockets/tcp_client"
|
9
|
+
require "em-ruby-sockets/file_streamer"
|
10
|
+
|
11
|
+
|
12
|
+
|
13
|
+
module EventMachine::RubySockets
|
14
|
+
|
15
|
+
def self.tcp_connect ip, port, klass=EM::RubySockets::TcpClient
|
16
|
+
raise EM::RubySockets::Error, "EventMachine is not running" unless EM.reactor_running?
|
17
|
+
|
18
|
+
sock = ::Kgio::TCPSocket.start(ip, port)
|
19
|
+
|
20
|
+
conn = EM.watch sock, klass
|
21
|
+
conn.post_init
|
22
|
+
conn.send :init
|
23
|
+
|
24
|
+
block_given? and yield conn
|
25
|
+
conn
|
26
|
+
end
|
27
|
+
|
28
|
+
end
|
@@ -0,0 +1,223 @@
|
|
1
|
+
module EventMachine::RubySockets
|
2
|
+
|
3
|
+
class TcpClient < ::EM::Connection
|
4
|
+
|
5
|
+
include TcpConnection
|
6
|
+
|
7
|
+
def init
|
8
|
+
self.notify_writable = true
|
9
|
+
self.notify_readable = true
|
10
|
+
@buffer = ::IO::Buffer.new
|
11
|
+
@connected = false
|
12
|
+
@error = false
|
13
|
+
end
|
14
|
+
private :init
|
15
|
+
|
16
|
+
def notify_writable
|
17
|
+
return if @error
|
18
|
+
self.notify_writable = false
|
19
|
+
|
20
|
+
unless @connected
|
21
|
+
begin
|
22
|
+
case @io.kgio_trywrite("")
|
23
|
+
when nil
|
24
|
+
@connected = true
|
25
|
+
on_connected()
|
26
|
+
end
|
27
|
+
|
28
|
+
rescue => e
|
29
|
+
connection_terminated e
|
30
|
+
return
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
try_send_data nil unless @error or @buffer.empty?
|
35
|
+
end
|
36
|
+
private :notify_writable
|
37
|
+
|
38
|
+
def notify_readable
|
39
|
+
return if @error
|
40
|
+
|
41
|
+
case buf = @io.kgio_tryread(4096)
|
42
|
+
# Reads at most maxlen bytes from the stream socket.
|
43
|
+
when ::String
|
44
|
+
receive_data buf
|
45
|
+
# Returns nil on EOF.
|
46
|
+
when nil
|
47
|
+
connection_terminated :remote_close
|
48
|
+
end
|
49
|
+
|
50
|
+
rescue => e
|
51
|
+
connection_terminated e
|
52
|
+
end
|
53
|
+
private :notify_readable
|
54
|
+
|
55
|
+
def try_send_data data
|
56
|
+
if data
|
57
|
+
if @buffer.empty?
|
58
|
+
@buffer << data
|
59
|
+
# If the buffer is not empty then there is a pending EM tick to send it,
|
60
|
+
# so don't add a new one and just append new data to the buffer.
|
61
|
+
else
|
62
|
+
@buffer << data
|
63
|
+
return
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
# Don't send the data now but in next EM iteration.
|
68
|
+
EM.next_tick do
|
69
|
+
unless @error
|
70
|
+
begin
|
71
|
+
case buf = @io.kgio_trywrite(@buffer.read)
|
72
|
+
# Returns nil if the write was completed in full.
|
73
|
+
when nil
|
74
|
+
@buffer.clear
|
75
|
+
self.notify_writable = false if self.notify_writable?
|
76
|
+
# Returns :wait_writable if EAGAIN is encountered and nothing
|
77
|
+
# was written. It could occur *after* the connection is
|
78
|
+
# established.
|
79
|
+
when :wait_writable
|
80
|
+
self.notify_writable = true unless self.notify_writable?
|
81
|
+
# Returns a String containing the unwritten portion if EAGAIN was
|
82
|
+
# encountered, but some portion was successfully written.
|
83
|
+
when ::String
|
84
|
+
@buffer << buf
|
85
|
+
self.notify_writable = true unless self.notify_writable?
|
86
|
+
end
|
87
|
+
|
88
|
+
rescue => e
|
89
|
+
connection_terminated e
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
private :try_send_data
|
95
|
+
|
96
|
+
def connection_terminated cause
|
97
|
+
return if @error
|
98
|
+
|
99
|
+
@error = true
|
100
|
+
detach
|
101
|
+
@io.close unless @io.closed?
|
102
|
+
@buffer.clear
|
103
|
+
if @connected
|
104
|
+
on_disconnected cause
|
105
|
+
else
|
106
|
+
on_connection_error cause
|
107
|
+
end
|
108
|
+
end
|
109
|
+
private :connection_terminated
|
110
|
+
|
111
|
+
# Access to the Ruby socket itself. A good place for socket settings is
|
112
|
+
# the post_init method.
|
113
|
+
def io
|
114
|
+
@io
|
115
|
+
end
|
116
|
+
|
117
|
+
# Sends the given _data_ to the remote peer. It can be safely called
|
118
|
+
# before the connection is established (the data is then stored within
|
119
|
+
# an internal buffer).
|
120
|
+
# Returns true if the connection is established or trying to connect.
|
121
|
+
# False if the connection has been closed or if an unrecoverable error
|
122
|
+
# occurred while writting into the socket.
|
123
|
+
#
|
124
|
+
# Arguments:
|
125
|
+
# - [data] a String.
|
126
|
+
def send_data data
|
127
|
+
raise ::ArgumentError, "argument must be a String" unless data.is_a?(::String)
|
128
|
+
return false if @error
|
129
|
+
|
130
|
+
if @connected
|
131
|
+
try_send_data data
|
132
|
+
else
|
133
|
+
@buffer << data
|
134
|
+
end
|
135
|
+
true
|
136
|
+
end
|
137
|
+
|
138
|
+
# Terminates the connection regardless it's been sucessfuly connected
|
139
|
+
# or not. The callback on_disconnected will be called with argument
|
140
|
+
# _nil_.
|
141
|
+
def disconnect
|
142
|
+
return if @error
|
143
|
+
@error = true
|
144
|
+
detach
|
145
|
+
@io.close unless @io.closed?
|
146
|
+
@buffer.clear
|
147
|
+
on_disconnected nil
|
148
|
+
end
|
149
|
+
alias :close_connection :disconnect
|
150
|
+
alias :close_connection_after_writing :disconnect
|
151
|
+
|
152
|
+
# Sets the duration after which a TCP connection in a connecting state
|
153
|
+
# will fail. After the given duration on_connection_error() is called
|
154
|
+
# with argument :pending_connect_timeout.
|
155
|
+
#
|
156
|
+
# Arguments:
|
157
|
+
# - [duration] Value in seconds (float).
|
158
|
+
def pending_connect_timeout= duration
|
159
|
+
return false if @pending_connect_timeout
|
160
|
+
|
161
|
+
EM.add_timer(@pending_connect_timeout = duration.to_f) do
|
162
|
+
unless @error or @connected
|
163
|
+
connection_terminated :pending_connect_timeout
|
164
|
+
end
|
165
|
+
end
|
166
|
+
@pending_connect_timeout
|
167
|
+
end
|
168
|
+
alias :set_pending_connect_timeout :pending_connect_timeout=
|
169
|
+
|
170
|
+
# Gets the duration after which a TCP connection in a connecting state
|
171
|
+
# will fail. Returns nil if not set.
|
172
|
+
def pending_connect_timeout
|
173
|
+
@pending_connect_timeout
|
174
|
+
end
|
175
|
+
|
176
|
+
# Returns the number of bytes to be sent by the socket.
|
177
|
+
def get_outbound_data_size
|
178
|
+
@buffer.size
|
179
|
+
end
|
180
|
+
|
181
|
+
# Executed upon instance initialization and before the connection
|
182
|
+
# is attempted. Good place for defining custom attributes.
|
183
|
+
#
|
184
|
+
# This method must be defined in the user's inherinted class.
|
185
|
+
def post_init
|
186
|
+
end
|
187
|
+
|
188
|
+
# Called upon connection is established.
|
189
|
+
#
|
190
|
+
# This method must be defined in the user's inherinted class.
|
191
|
+
def on_connected
|
192
|
+
end
|
193
|
+
|
194
|
+
# Called when the connection attemp failed. _cause_ can be:
|
195
|
+
# - [:pending_connect_timeout] Connection not established within pending_connect_timeout value.
|
196
|
+
# - [exception] An exception.
|
197
|
+
#
|
198
|
+
# This method must be defined in the user's inherinted class.
|
199
|
+
def on_connection_error cause
|
200
|
+
end
|
201
|
+
|
202
|
+
# Called when TCP data is received in the socket.
|
203
|
+
#
|
204
|
+
# This method must be defined in the user's inherinted class.
|
205
|
+
def receive_data data
|
206
|
+
end
|
207
|
+
|
208
|
+
# Called when the connection is terminated by the peer (and it was
|
209
|
+
# already established) or when it is terminated locally (by calling
|
210
|
+
# disconnect so cause becomes _nil_).
|
211
|
+
#
|
212
|
+
# _cause_ can be:
|
213
|
+
# - [nil] Local disconnection via disconnect method.
|
214
|
+
# - [:remote_close] Remote normal close.
|
215
|
+
# - [exception] Some other cause.
|
216
|
+
#
|
217
|
+
# This method must be defined in the user's inherinted class.
|
218
|
+
def on_disconnected cause
|
219
|
+
end
|
220
|
+
|
221
|
+
end
|
222
|
+
|
223
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
#$debug = true
|
2
|
+
|
3
|
+
module EventMachine::RubySockets
|
4
|
+
|
5
|
+
module TcpConnection
|
6
|
+
|
7
|
+
# Access to the Ruby socket itself. A good place for socket settings is
|
8
|
+
# the post_init() method.
|
9
|
+
def io
|
10
|
+
@io
|
11
|
+
end
|
12
|
+
|
13
|
+
# Returns true if the socket has been closed or failed to connect.
|
14
|
+
def error?
|
15
|
+
@error
|
16
|
+
end
|
17
|
+
|
18
|
+
# Returns the number of bytes to be sent by the socket.
|
19
|
+
def get_outbound_data_size
|
20
|
+
@buffer.size
|
21
|
+
end
|
22
|
+
|
23
|
+
# Returns an array of [ port, ip ] with the local address used
|
24
|
+
# for this socket.
|
25
|
+
# It returns nil in case the socket has been closed or failed to connect.
|
26
|
+
def local_address
|
27
|
+
return nil if @error
|
28
|
+
::Socket.unpack_sockaddr_in @io.getsockname
|
29
|
+
end
|
30
|
+
|
31
|
+
# Returns an array of [ port, ip ] with the remote connected address.
|
32
|
+
# It returns nil in case the socket is not connected.
|
33
|
+
def remote_address
|
34
|
+
return nil unless @connected
|
35
|
+
::Socket.unpack_sockaddr_in @io.getpeername
|
36
|
+
end
|
37
|
+
|
38
|
+
# Streams the given _filename_ through this connection. It does not set callback or
|
39
|
+
# errback. For a more powerful usage create a EventMachine::RubySockets::FileStreamer
|
40
|
+
# instance.
|
41
|
+
def stream_file_data filename, args={}
|
42
|
+
EM::RubySockets::FileStreamer.new(self, filename, args).run
|
43
|
+
end
|
44
|
+
alias :send_file_data :stream_file_data
|
45
|
+
|
46
|
+
end
|
47
|
+
|
48
|
+
end
|
metadata
ADDED
@@ -0,0 +1,99 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: em-ruby-sockets
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
prerelease: false
|
5
|
+
segments:
|
6
|
+
- 0
|
7
|
+
- 0
|
8
|
+
- 1
|
9
|
+
version: 0.0.1
|
10
|
+
platform: ruby
|
11
|
+
authors:
|
12
|
+
- "I\xC3\xB1aki Baz Castillo"
|
13
|
+
autorequire:
|
14
|
+
bindir: bin
|
15
|
+
cert_chain: []
|
16
|
+
|
17
|
+
date: 2012-04-14 00:00:00 +02:00
|
18
|
+
default_executable:
|
19
|
+
dependencies:
|
20
|
+
- !ruby/object:Gem::Dependency
|
21
|
+
name: kgio
|
22
|
+
prerelease: false
|
23
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
24
|
+
none: false
|
25
|
+
requirements:
|
26
|
+
- - ">="
|
27
|
+
- !ruby/object:Gem::Version
|
28
|
+
segments:
|
29
|
+
- 2
|
30
|
+
- 7
|
31
|
+
- 4
|
32
|
+
version: 2.7.4
|
33
|
+
type: :runtime
|
34
|
+
version_requirements: *id001
|
35
|
+
- !ruby/object:Gem::Dependency
|
36
|
+
name: iobuffer
|
37
|
+
prerelease: false
|
38
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
39
|
+
none: false
|
40
|
+
requirements:
|
41
|
+
- - ">="
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
segments:
|
44
|
+
- 1
|
45
|
+
- 1
|
46
|
+
- 2
|
47
|
+
version: 1.1.2
|
48
|
+
type: :runtime
|
49
|
+
version_requirements: *id002
|
50
|
+
description: em-ruby-sockets uses Ruby sockets within EventMachine::watch()
|
51
|
+
email: ibc@aliax.net
|
52
|
+
executables: []
|
53
|
+
|
54
|
+
extensions: []
|
55
|
+
|
56
|
+
extra_rdoc_files: []
|
57
|
+
|
58
|
+
files:
|
59
|
+
- lib/em-ruby-sockets.rb
|
60
|
+
- lib/em-ruby-sockets/version.rb
|
61
|
+
- lib/em-ruby-sockets/errors.rb
|
62
|
+
- lib/em-ruby-sockets/tcp_connection.rb
|
63
|
+
- lib/em-ruby-sockets/tcp_client.rb
|
64
|
+
has_rdoc: true
|
65
|
+
homepage: https://github.com/ibc/em-ruby-sockets
|
66
|
+
licenses: []
|
67
|
+
|
68
|
+
post_install_message:
|
69
|
+
rdoc_options: []
|
70
|
+
|
71
|
+
require_paths:
|
72
|
+
- lib
|
73
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
74
|
+
none: false
|
75
|
+
requirements:
|
76
|
+
- - ">="
|
77
|
+
- !ruby/object:Gem::Version
|
78
|
+
segments:
|
79
|
+
- 1
|
80
|
+
- 8
|
81
|
+
- 7
|
82
|
+
version: 1.8.7
|
83
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
84
|
+
none: false
|
85
|
+
requirements:
|
86
|
+
- - ">="
|
87
|
+
- !ruby/object:Gem::Version
|
88
|
+
segments:
|
89
|
+
- 0
|
90
|
+
version: "0"
|
91
|
+
requirements: []
|
92
|
+
|
93
|
+
rubyforge_project:
|
94
|
+
rubygems_version: 1.3.7
|
95
|
+
signing_key:
|
96
|
+
specification_version: 3
|
97
|
+
summary: Ruby sockets integrated within EventMachine
|
98
|
+
test_files: []
|
99
|
+
|