zactor 0.0.4
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/.document +5 -0
- data/.gitignore +42 -0
- data/.rake_tasks~ +19 -0
- data/.rspec +1 -0
- data/.yardopts +1 -0
- data/Gemfile +17 -0
- data/Gemfile.lock +94 -0
- data/Guardfile +12 -0
- data/LICENSE.txt +20 -0
- data/README.md +167 -0
- data/Rakefile +28 -0
- data/VERSION +1 -0
- data/examples/chat/client.rb +88 -0
- data/examples/chat/server.rb +59 -0
- data/examples/ping/inproc.rb +50 -0
- data/lib/zactor/actor_pub.rb +19 -0
- data/lib/zactor/actor_sub.rb +46 -0
- data/lib/zactor/broker.rb +71 -0
- data/lib/zactor/log_subscriber.rb +25 -0
- data/lib/zactor/message.rb +23 -0
- data/lib/zactor/version.rb +3 -0
- data/lib/zactor.rb +263 -0
- data/spec/lib/actor_spec.rb +136 -0
- data/spec/lib/exchange_spec.rb +81 -0
- data/spec/lib/link_spec.rb +67 -0
- data/spec/lib/zactor_spec.rb +40 -0
- data/spec/spec_helper.rb +13 -0
- data/zactor.gemspec +28 -0
- metadata +192 -0
data/.document
ADDED
data/.gitignore
ADDED
@@ -0,0 +1,42 @@
|
|
1
|
+
# rcov generated
|
2
|
+
coverage
|
3
|
+
|
4
|
+
# rdoc generated
|
5
|
+
rdoc
|
6
|
+
|
7
|
+
# yard generated
|
8
|
+
doc
|
9
|
+
.yardoc
|
10
|
+
|
11
|
+
# bundler
|
12
|
+
.bundle
|
13
|
+
|
14
|
+
# jeweler generated
|
15
|
+
pkg
|
16
|
+
|
17
|
+
# Have editor/IDE/OS specific files you need to ignore? Consider using a global gitignore:
|
18
|
+
#
|
19
|
+
# * Create a file at ~/.gitignore
|
20
|
+
# * Include files you want ignored
|
21
|
+
# * Run: git config --global core.excludesfile ~/.gitignore
|
22
|
+
#
|
23
|
+
# After doing this, these files will be ignored in all your git projects,
|
24
|
+
# saving you from having to 'pollute' every project you touch with them
|
25
|
+
#
|
26
|
+
# Not sure what to needs to be ignored for particular editors/OSes? Here's some ideas to get you started. (Remember, remove the leading # of the line)
|
27
|
+
#
|
28
|
+
# For MacOS:
|
29
|
+
#
|
30
|
+
#.DS_Store
|
31
|
+
#
|
32
|
+
# For TextMate
|
33
|
+
#*.tmproj
|
34
|
+
#tmtags
|
35
|
+
#
|
36
|
+
# For emacs:
|
37
|
+
#*~
|
38
|
+
#\#*
|
39
|
+
#.\#*
|
40
|
+
#
|
41
|
+
# For vim:
|
42
|
+
#*.swp
|
data/.rake_tasks~
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
build
|
2
|
+
console[script]
|
3
|
+
gemcutter:release
|
4
|
+
gemspec
|
5
|
+
gemspec:debug
|
6
|
+
gemspec:generate
|
7
|
+
gemspec:release
|
8
|
+
gemspec:validate
|
9
|
+
git:release
|
10
|
+
install
|
11
|
+
rcov
|
12
|
+
release
|
13
|
+
spec
|
14
|
+
version
|
15
|
+
version:bump:major
|
16
|
+
version:bump:minor
|
17
|
+
version:bump:patch
|
18
|
+
version:write
|
19
|
+
yard
|
data/.rspec
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
--color
|
data/.yardopts
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
-m markdown -e ruby-interface/yard
|
data/Gemfile
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
source "http://rubygems.org"
|
2
|
+
|
3
|
+
group :development do
|
4
|
+
gem "rspec", ">= 2"
|
5
|
+
gem "yard", "~> 0.6.0"
|
6
|
+
gem "bundler", "~> 1.0.0"
|
7
|
+
gem "rcov", ">= 0"
|
8
|
+
gem "bluecloth"
|
9
|
+
gem "rr"
|
10
|
+
gem "guard-rspec"
|
11
|
+
gem "growl"
|
12
|
+
gem "ruby-debug19"
|
13
|
+
gem "em-spec", :git => "https://github.com/mloughran/em-spec.git", :branch => 'rspec2'
|
14
|
+
end
|
15
|
+
|
16
|
+
|
17
|
+
gemspec
|
data/Gemfile.lock
ADDED
@@ -0,0 +1,94 @@
|
|
1
|
+
GIT
|
2
|
+
remote: https://github.com/mloughran/em-spec.git
|
3
|
+
revision: fc888b289a424fa93848ffdac3dc4b24fdd34950
|
4
|
+
branch: rspec2
|
5
|
+
specs:
|
6
|
+
em-spec (0.2.2)
|
7
|
+
|
8
|
+
PATH
|
9
|
+
remote: .
|
10
|
+
specs:
|
11
|
+
zactor (0.0.4)
|
12
|
+
activesupport (> 0.1)
|
13
|
+
bson (> 0.1)
|
14
|
+
bson_ext (> 0.1)
|
15
|
+
em-zeromq (> 0.1)
|
16
|
+
ffi (> 0.1)
|
17
|
+
ffi-rzmq (> 0.1)
|
18
|
+
ruby-interface (> 0)
|
19
|
+
|
20
|
+
GEM
|
21
|
+
remote: http://rubygems.org/
|
22
|
+
specs:
|
23
|
+
activesupport (3.0.7)
|
24
|
+
archive-tar-minitar (0.5.2)
|
25
|
+
bluecloth (2.1.0)
|
26
|
+
bson (1.2.4)
|
27
|
+
bson_ext (1.2.4)
|
28
|
+
columnize (0.3.2)
|
29
|
+
configuration (1.2.0)
|
30
|
+
diff-lcs (1.1.2)
|
31
|
+
em-zeromq (0.2.1)
|
32
|
+
eventmachine (>= 1.0.0.beta.3)
|
33
|
+
ffi-rzmq (>= 0.7.2)
|
34
|
+
eventmachine (1.0.0.beta.3)
|
35
|
+
ffi (1.0.7)
|
36
|
+
rake (>= 0.8.7)
|
37
|
+
ffi-rzmq (0.7.2)
|
38
|
+
growl (1.0.3)
|
39
|
+
guard (0.3.0)
|
40
|
+
open_gem (~> 1.4.2)
|
41
|
+
thor (~> 0.14.6)
|
42
|
+
guard-rspec (0.2.0)
|
43
|
+
guard (>= 0.2.2)
|
44
|
+
i18n (0.5.0)
|
45
|
+
launchy (0.3.7)
|
46
|
+
configuration (>= 0.0.5)
|
47
|
+
rake (>= 0.8.1)
|
48
|
+
linecache19 (0.5.11)
|
49
|
+
ruby_core_source (>= 0.1.4)
|
50
|
+
open_gem (1.4.2)
|
51
|
+
launchy (~> 0.3.5)
|
52
|
+
rake (0.8.7)
|
53
|
+
rcov (0.9.9)
|
54
|
+
rr (1.0.2)
|
55
|
+
rspec (2.5.0)
|
56
|
+
rspec-core (~> 2.5.0)
|
57
|
+
rspec-expectations (~> 2.5.0)
|
58
|
+
rspec-mocks (~> 2.5.0)
|
59
|
+
rspec-core (2.5.1)
|
60
|
+
rspec-expectations (2.5.0)
|
61
|
+
diff-lcs (~> 1.1.2)
|
62
|
+
rspec-mocks (2.5.0)
|
63
|
+
ruby-debug-base19 (0.11.24)
|
64
|
+
columnize (>= 0.3.1)
|
65
|
+
linecache19 (>= 0.5.11)
|
66
|
+
ruby_core_source (>= 0.1.4)
|
67
|
+
ruby-debug19 (0.11.6)
|
68
|
+
columnize (>= 0.3.1)
|
69
|
+
linecache19 (>= 0.5.11)
|
70
|
+
ruby-debug-base19 (>= 0.11.19)
|
71
|
+
ruby-interface (0.0.2)
|
72
|
+
activesupport (> 0.1)
|
73
|
+
i18n (> 0.1)
|
74
|
+
ruby-interface
|
75
|
+
ruby_core_source (0.1.4)
|
76
|
+
archive-tar-minitar (>= 0.5.2)
|
77
|
+
thor (0.14.6)
|
78
|
+
yard (0.6.5)
|
79
|
+
|
80
|
+
PLATFORMS
|
81
|
+
ruby
|
82
|
+
|
83
|
+
DEPENDENCIES
|
84
|
+
bluecloth
|
85
|
+
bundler (~> 1.0.0)
|
86
|
+
em-spec!
|
87
|
+
growl
|
88
|
+
guard-rspec
|
89
|
+
rcov
|
90
|
+
rr
|
91
|
+
rspec (>= 2)
|
92
|
+
ruby-debug19
|
93
|
+
yard (~> 0.6.0)
|
94
|
+
zactor!
|
data/Guardfile
ADDED
@@ -0,0 +1,12 @@
|
|
1
|
+
# A sample Guardfile
|
2
|
+
# More info at https://github.com/guard/guard#readme
|
3
|
+
|
4
|
+
guard 'rspec', :version => 2 do
|
5
|
+
watch(/^spec\/(.*)_spec.rb/)
|
6
|
+
watch(/^lib\/(.*)\.rb/) { |m| "spec/lib/#{m[1]}_spec.rb" }
|
7
|
+
watch(/^spec\/spec_helper.rb/) { "spec" }
|
8
|
+
|
9
|
+
# watch(/^lib\/zactor.rb/) { "spec/lib/actor_spec.rb" }
|
10
|
+
|
11
|
+
watch(/^app\/(.*)\.rb/) { |m| "spec/app/#{m[1]}_spec.rb" }
|
12
|
+
end
|
data/LICENSE.txt
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2011 Andrew Rudenko
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,167 @@
|
|
1
|
+
Что это
|
2
|
+
=======
|
3
|
+
Zactor позволяет любой руби-объект научить общаться с другими объектами посредством отправки и получения сообщений. При этом неважно где именно расположен объект, в том же процессе, в соседнем, или на другой физической машине.
|
4
|
+
|
5
|
+
Zactor использует zmq как бекенд для сообщений, eventmachine и em-zeromq для асинхронной работы с zmq, RubyInterface для описания себя, BSON для сериализации данных.
|
6
|
+
|
7
|
+
Каждый zactor-объект имеет свой identity, который генерируется автоматически, либо задается вручную и постоянен. Совокупность identity, host и port на которых был рожден этот объект, это достаточная информация для того что бы отправить этому объекту сообщение из другого объекта. Выглядит примерно так:
|
8
|
+
|
9
|
+
zactor.actor =>
|
10
|
+
{"identity"=>"actor.2154247120-0.0.0.0:8000", "host"=>"0.0.0.0:8000"}
|
11
|
+
|
12
|
+
Ипользование
|
13
|
+
============
|
14
|
+
|
15
|
+
Для начала нужно стартануть Zactor.
|
16
|
+
|
17
|
+
Zactor.start 8000
|
18
|
+
|
19
|
+
Процесс забиндится на 0.0.0.0:8000, через эту точку будет происходить общение между zactor-процессами. Стартоваться должен в запущенном EM-контексте.
|
20
|
+
|
21
|
+
В каждый zactor-активный класс нужно делать include Zactor, после чего у класса и его экземпляров для доступа к функциями Zactor появится метод zactor. После создания объекта нужно выполнить zactor.init
|
22
|
+
|
23
|
+
class A
|
24
|
+
include Zactor
|
25
|
+
|
26
|
+
def initialize
|
27
|
+
zactor.init
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
Для отправки сообщений другому объекту нам нужно знать его идентификатор. Идентификатор можно получить тремя способами:
|
32
|
+
|
33
|
+
* Непосрдественной передачей. При инициализации или в любом другом месте, это исключительно внутренняя логика приложения. Идентификатор объекта можно получить вызвав zactor.actor
|
34
|
+
* При получении сообщения. В сообщении всегда содержится информация об отправителе
|
35
|
+
* Если объект имеет заранее известный identity, то мы можем получить его полный идентификатор вызвав Zactor.get_actor с identity и хостом, на котором он запущен
|
36
|
+
|
37
|
+
actor = Zactor.get_actor "broker", :host => "0.0.0.0:8001"
|
38
|
+
|
39
|
+
Получив идентификатор можно отправлять ему сообщения
|
40
|
+
|
41
|
+
zactor.send_request actor, :show_me, :boobs
|
42
|
+
|
43
|
+
|
44
|
+
Каждый класс может определять какие именно ивенты он может получать и что с ними делать
|
45
|
+
|
46
|
+
include Zactor
|
47
|
+
|
48
|
+
zactor do
|
49
|
+
event(:show_me) do |o, msg, what|
|
50
|
+
case what
|
51
|
+
when :boobs
|
52
|
+
do_it
|
53
|
+
else
|
54
|
+
do_smth_else
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
Рассмотрим пример банального ping-pong
|
60
|
+
|
61
|
+
class A
|
62
|
+
include Zactor
|
63
|
+
|
64
|
+
def initialize
|
65
|
+
zactor.init
|
66
|
+
ping Zactor.get_actor("b")
|
67
|
+
end
|
68
|
+
|
69
|
+
def ping(actor)
|
70
|
+
puts "Ping!"
|
71
|
+
zactor.send_request actor, :ping do |res|
|
72
|
+
puts res
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
|
78
|
+
class B
|
79
|
+
include Zactor
|
80
|
+
|
81
|
+
zactor do
|
82
|
+
identity "b"
|
83
|
+
|
84
|
+
event(:ping) do |o, msg|
|
85
|
+
msg.reply "Pong!"
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
def initialize
|
90
|
+
zactor.init
|
91
|
+
end
|
92
|
+
|
93
|
+
end
|
94
|
+
|
95
|
+
EM.run do
|
96
|
+
Zactor.start 8000
|
97
|
+
|
98
|
+
a = A.new
|
99
|
+
b = B.new
|
100
|
+
end
|
101
|
+
|
102
|
+
A посылает сообщение :ping для B, а B отвечает "Pong!"
|
103
|
+
|
104
|
+
В коллбэк определенный в event передается объект получившый сообщение, объект сообщения ({Zactor::Message}) и далее переданные в запросе аргументы (если они есть). У {Zactor::Message} есть два основных метода: sender, возвращающий идентификатор отправителя и reply, который посылает ответ на запрос.
|
105
|
+
|
106
|
+
Важный момент, identity должно задаваться ДО zactor.init и после этого не может меняться.
|
107
|
+
|
108
|
+
ZMQ
|
109
|
+
===
|
110
|
+
|
111
|
+
При Zactor.start стартует брокер, по одному на каждый процесс, через него проходят все сообщения данного процесса, принимает сообщения через SUB-сокет, отправляет через PUB. SUB подписан на все сообщения. Каждый zactor-объект создает по паре сокетов, PUB подключается к SUB-брокера, а SUB к PUB-брокера. SUB подписывается на сообщения содержащие его identity.
|
112
|
+
|
113
|
+

|
114
|
+
|
115
|
+
Рассмотрим жизнь сообщения на примере с ping-ping. В случае с b в том же процессе:
|
116
|
+
|
117
|
+
<div class=wsd wsd_style="default"><pre>
|
118
|
+
A[PUB]->Broker[SUB]: Посылаем запрос :ping
|
119
|
+
Broker[SUB]->Broker[PUB]: Перебрасываем запрос в PUB сокет
|
120
|
+
Broker[PUB]->B[SUB]: Передаем получателю сообщение
|
121
|
+
B[PUB]->Broker[SUB]: Отправляем ответ "Pong!"
|
122
|
+
Broker[SUB]->Broker[PUB]: Перебрасываем запрос в PUB сокет
|
123
|
+
Broker[PUB]->A[SUB]: Отправитель получает ответ
|
124
|
+
</pre></div><script type="text/javascript" src="http://www.websequencediagrams.com/service.js"></script>
|
125
|
+
|
126
|
+
В случае с b в другом процессе:
|
127
|
+
|
128
|
+
<div class=wsd wsd_style="default"><pre>
|
129
|
+
A[PUB for App2]->App2 Broker[SUB]: Посылаем запрос :ping
|
130
|
+
App2 Broker[SUB]->App2 Broker[PUB]: Перебрасываем запрос в PUB сокет
|
131
|
+
App2 Broker[PUB]->B[SUB]: Передаем получателю сообщение
|
132
|
+
B[PUB for App1]->App1 Broker[SUB]: Отправляем ответ "Pong!"
|
133
|
+
App1 Broker[SUB]->App1 Broker[PUB]: Перебрасываем запрос в PUB сокет
|
134
|
+
App1 Broker[PUB]->A[SUB]: Отправитель получает ответ
|
135
|
+
</pre></div><script type="text/javascript" src="http://www.websequencediagrams.com/service.js"></script>
|
136
|
+
|
137
|
+
Балансировка
|
138
|
+
============
|
139
|
+
|
140
|
+
Так как это ZMQ, мы можем очень просто изменить тип получения сообщения. Например, добавив балансер. Теперь можно запускать процессы со ссылкой на этот балансер.
|
141
|
+
|
142
|
+
Zactor.start :balancer => "0.0.0.0:4000"
|
143
|
+
|
144
|
+
У нас получится примерно следующая схема:
|
145
|
+
|
146
|
+

|
147
|
+
|
148
|
+
Теперь наш ping можно отправлять в балансер, а отвечать будет один из подключенных воркеров.
|
149
|
+
|
150
|
+
ping Zactor.get_actor("b", :host => "0.0.0.0:4000")
|
151
|
+
|
152
|
+
|
153
|
+
Протокол обмена
|
154
|
+
===============
|
155
|
+
|
156
|
+
|
157
|
+
Perfomance
|
158
|
+
==========
|
159
|
+
|
160
|
+
А хрен его знает, толком не мерялось ничего :)
|
161
|
+
|
162
|
+
TODO
|
163
|
+
====
|
164
|
+
|
165
|
+
* Сделать событие отваливания объектов. Наверное, что-то вроде простого аналога link в эрланге.
|
166
|
+
* Добавить таймауты для запросов с коллбэками. Сейчас они будут висеть бесконечно и засрут память.
|
167
|
+
* Доступ к отправителю в колллбэке запроса. В случае с балансировкой он будет не тем же, кому мы посылали сообщение
|
data/Rakefile
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'bundler'
|
3
|
+
begin
|
4
|
+
Bundler.setup(:default, :development)
|
5
|
+
rescue Bundler::BundlerError => e
|
6
|
+
$stderr.puts e.message
|
7
|
+
$stderr.puts "Run `bundle install` to install missing gems"
|
8
|
+
exit e.status_code
|
9
|
+
end
|
10
|
+
require 'rake'
|
11
|
+
|
12
|
+
Bundler::GemHelper.install_tasks
|
13
|
+
|
14
|
+
require 'rspec/core'
|
15
|
+
require 'rspec/core/rake_task'
|
16
|
+
RSpec::Core::RakeTask.new(:spec) do |spec|
|
17
|
+
spec.pattern = FileList['spec/**/*_spec.rb']
|
18
|
+
end
|
19
|
+
|
20
|
+
RSpec::Core::RakeTask.new(:rcov) do |spec|
|
21
|
+
spec.pattern = 'spec/**/*_spec.rb'
|
22
|
+
spec.rcov = true
|
23
|
+
end
|
24
|
+
|
25
|
+
task :default => :spec
|
26
|
+
|
27
|
+
require 'yard'
|
28
|
+
YARD::Rake::YardocTask.new
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
0.0.3
|
@@ -0,0 +1,88 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
|
3
|
+
# ruby client.rb SELF_PORT SERVER LOGIN
|
4
|
+
# ruby client.rb 6001 0.0.0.0:8000 lolo
|
5
|
+
require 'bundler'
|
6
|
+
ENV['BUNDLE_GEMFILE'] = File.join(File.dirname(__FILE__), '..', '..', 'Gemfile')
|
7
|
+
|
8
|
+
Bundler.setup(:default)
|
9
|
+
|
10
|
+
require 'zactor'
|
11
|
+
class Client
|
12
|
+
include Zactor
|
13
|
+
|
14
|
+
zactor do
|
15
|
+
event(:message) do |o, msg, text|
|
16
|
+
puts text
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def initialize(login)
|
21
|
+
@login = login
|
22
|
+
@persons = {}
|
23
|
+
end
|
24
|
+
|
25
|
+
def start(server)
|
26
|
+
zactor.init
|
27
|
+
@server = Zactor.get_actor("server", :host => server)
|
28
|
+
connect
|
29
|
+
end
|
30
|
+
|
31
|
+
def connect
|
32
|
+
puts "Подключаемся"
|
33
|
+
zactor.send_request(@server, :new_client, @login) do
|
34
|
+
puts "Поключились!"
|
35
|
+
zactor.link(@server) { connect }
|
36
|
+
end.timeout(5) { puts "Проблемы с подключением..." }
|
37
|
+
end
|
38
|
+
|
39
|
+
def send_message(text)
|
40
|
+
if text =~ /(\w+) -> (.+)/
|
41
|
+
send_personal($1, $2)
|
42
|
+
else
|
43
|
+
zactor.send_request(@server, :message, text)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def send_personal(login, text)
|
48
|
+
if client = @persons[login]
|
49
|
+
zactor.send_request(client, :message, "(personally) #{@login}:" + "#{text}")
|
50
|
+
else
|
51
|
+
zactor.send_request(@server, :client_request, login) do |res, client|
|
52
|
+
case res
|
53
|
+
when :ok
|
54
|
+
@persons[login] = client
|
55
|
+
zactor.link(client) { @persons.delete login }
|
56
|
+
zactor.send_request(client, :message, "(personally) #{@login}:" + "#{text}")
|
57
|
+
else
|
58
|
+
puts "Ошибка отправки сообщения"
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
def stop
|
65
|
+
zactor.finish
|
66
|
+
EM.stop
|
67
|
+
end
|
68
|
+
|
69
|
+
module KeyboardInput
|
70
|
+
include EM::Protocols::LineText2
|
71
|
+
attr_accessor :client
|
72
|
+
def receive_line(data)
|
73
|
+
client.send_message data
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
end
|
78
|
+
|
79
|
+
client = Client.new ARGV[2]
|
80
|
+
|
81
|
+
Signal.trap('INT') { client.stop }
|
82
|
+
Signal.trap('TERM') { client.stop }
|
83
|
+
|
84
|
+
EM.run do
|
85
|
+
Zactor.start ARGV[0]#, :debug => true
|
86
|
+
client.start ARGV[1]
|
87
|
+
EM.open_keyboard(Client::KeyboardInput) { |c| c.client = client }
|
88
|
+
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
|
3
|
+
# ruby server.rb PORT
|
4
|
+
# ruby server.rb 8000
|
5
|
+
require 'bundler'
|
6
|
+
ENV['BUNDLE_GEMFILE'] = File.join(File.dirname(__FILE__), '..', '..', 'Gemfile')
|
7
|
+
Bundler.setup(:default)
|
8
|
+
|
9
|
+
require 'zactor'
|
10
|
+
|
11
|
+
class Server
|
12
|
+
include Zactor
|
13
|
+
|
14
|
+
zactor do
|
15
|
+
identity "server"
|
16
|
+
|
17
|
+
event(:new_client) do |o, msg, login|
|
18
|
+
o.new_client msg.sender, login
|
19
|
+
msg.reply :ok
|
20
|
+
end
|
21
|
+
|
22
|
+
event(:client_request) do |o, msg, login|
|
23
|
+
if client = o.clients.detect { |k, v| v == login }
|
24
|
+
msg.reply :ok, client.first
|
25
|
+
else
|
26
|
+
msg.reply :error
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
event(:message) do |o, msg, text|
|
31
|
+
o.send_message msg.sender, text
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
attr_accessor :clients
|
36
|
+
def initialize
|
37
|
+
zactor.init
|
38
|
+
@clients = {}
|
39
|
+
end
|
40
|
+
|
41
|
+
def new_client(client, login)
|
42
|
+
@clients[client] = login
|
43
|
+
send_message client, "присоединился"
|
44
|
+
zactor.link client do
|
45
|
+
send_message client, "отсоединился"
|
46
|
+
clients.delete client
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def send_message(from, message)
|
51
|
+
(clients.keys - [from]).each { |c| zactor.send_request c, :message, "#{clients[from]}: #{message}"}
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
EM.run do
|
56
|
+
Zactor.start ARGV[0]#, :debug => true
|
57
|
+
Server.new
|
58
|
+
puts "Server started"
|
59
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
require "rubygems"
|
2
|
+
require "bundler"
|
3
|
+
|
4
|
+
ENV['BUNDLE_GEMFILE'] = File.join(File.dirname(__FILE__), '..', '..', 'Gemfile')
|
5
|
+
|
6
|
+
Bundler.setup(:default, :development)
|
7
|
+
|
8
|
+
require 'zactor'
|
9
|
+
require 'eventmachine'
|
10
|
+
|
11
|
+
class A
|
12
|
+
include Zactor
|
13
|
+
|
14
|
+
def initialize
|
15
|
+
zactor.init
|
16
|
+
ping Zactor.get_actor("b")
|
17
|
+
end
|
18
|
+
|
19
|
+
def ping(actor)
|
20
|
+
puts "Ping!"
|
21
|
+
zactor.send_request actor, :ping do |res|
|
22
|
+
puts res
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
|
28
|
+
class B
|
29
|
+
include Zactor
|
30
|
+
|
31
|
+
zactor do
|
32
|
+
identity "b"
|
33
|
+
|
34
|
+
event(:ping) do |o, msg|
|
35
|
+
msg.reply "Pong!"
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def initialize
|
40
|
+
zactor.init
|
41
|
+
end
|
42
|
+
|
43
|
+
end
|
44
|
+
|
45
|
+
EM.run do
|
46
|
+
Zactor.start 8000
|
47
|
+
|
48
|
+
a = A.new
|
49
|
+
b = B.new
|
50
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module Zactor
|
2
|
+
class ActorPub
|
3
|
+
include ZMQMEssages
|
4
|
+
|
5
|
+
attr_accessor :actor
|
6
|
+
def initialize(actor, endpoint)
|
7
|
+
Zactor.logger.debug "ZactorPub (#{actor.actor}): starting"
|
8
|
+
@actor = actor
|
9
|
+
@connection = Zactor.zmq.connect ZMQ::PUB, endpoint, self
|
10
|
+
@socket = @connection.socket
|
11
|
+
end
|
12
|
+
|
13
|
+
def close
|
14
|
+
@connection.unbind
|
15
|
+
rescue
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
module Zactor
|
2
|
+
class ActorSub
|
3
|
+
attr_accessor :actor
|
4
|
+
def initialize(actor)
|
5
|
+
Zactor.logger.debug "ZactorSub (#{actor.actor}): starting"
|
6
|
+
@actor = actor
|
7
|
+
@connection = Zactor.zmq.connect ZMQ::SUB, "inproc://zactor_broker_pub", self
|
8
|
+
@connection.subscribe actor.actor['identity']
|
9
|
+
end
|
10
|
+
|
11
|
+
def on_readable(socket, messages)
|
12
|
+
@connection.notify_readable = true
|
13
|
+
Zactor.logger.debug "ZactorSub for #{actor.actor}: Messages!"
|
14
|
+
to = messages.shift
|
15
|
+
sender = messages.shift
|
16
|
+
case messages.shift.copy_out_string
|
17
|
+
when "reply"
|
18
|
+
reply messages
|
19
|
+
when "request"
|
20
|
+
request sender, messages
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def request(sender_mes, messages)
|
25
|
+
Zactor.logger.debug "ZactorSub for #{actor.actor}: request!"
|
26
|
+
sender = BSON.deserialize(sender_mes.copy_out_string)
|
27
|
+
callback_id = messages[0].copy_out_string
|
28
|
+
event = messages[1].copy_out_string
|
29
|
+
args = BSON.deserialize(messages[2].copy_out_string)['args']
|
30
|
+
actor.receive_request sender, event, callback_id, *args
|
31
|
+
end
|
32
|
+
|
33
|
+
def reply(messages)
|
34
|
+
Zactor.logger.debug "ZactorSub for #{actor.actor}: reply!"
|
35
|
+
callback_id = messages[0].copy_out_string
|
36
|
+
if callback_id != ''
|
37
|
+
actor.receive_reply callback_id, *BSON.deserialize(messages[1].copy_out_string)['args']
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def close
|
42
|
+
@connection.unbind
|
43
|
+
rescue
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|