I'm trying to achieve a socket server on Ruby with multiply rooms, where people can send chat messages to each other, for example.
The code is pretty simple:
Main file:
require 'room'
require 'socket'
room = Room.new
### handle connections
Thread::abort_on_exception=true
server = TCPServer.open(2000) # Socket to listen on port 2000
puts 'Open socket at 2000 port'
loop { # Servers run forever
room.handle_player server.accept
}
room.rb
require 'player'
class Room
#players = []
attr_accessor :players
def initialize
end
def handle_player(connection)
puts ' New client connected!'
Thread.start(connection, self) do |client, room|
player = Player.new connection, room
#players.push player
end
end
def broadcast(message)
puts "Players is #{#players.class}" #NilClass !
end
end
player.rb
class Player
def initialize(connection, room)
#room = room
while line = connection.gets
puts 'got line ' + line
room.broadcast line
end
end
end
The problem is that when I call Room#broadcast from a player – it tells me that #players is nil, but it isn't! How that can be?
I'm not sure I'm doing everything correct (my feelings tell me that player should not have direct link to room), but it simplifies the example.
This has nothing to do with threads.
You should put that initialization #players = [] into the initialize of Room.
The way you have it, you do not initialize an instance variable of your object but an instance variable of the class Room.
You set #players as a Room's class instance variable.
Instead of this, you should put this in your initialize method:
def initialize
#players = []
end
Related
I have something similar in my project
class Raj
def execute
5.times do
Thread.new do
object = Gopal.new
object.db_connection
object.enter_tax_id
end
end
end
end
class Gopal
def db_connection
#db = "" # Created db connection here
#browser = Watir::Browser.new
end
def enter_tax_id
m = Mutex.new
m.synchronize do
data = #db_conn.select_one("select max(tax_id_no) from pcmp.tax_identifier")
#browser.text_field(id: 'something').set 'data'
end
end
end
The enter tax id method pulls information from the database and then enters a value into the text field. This thread has an issue since other threads are interacting with it; when multiple threads attempt to execute the same procedure, a 'executing in another thread' error is raised.
In order to resolve this issue, I used the following strategy:
class Gopal
#mutex = Mutex.new
def self.mutex_var
#mutex
end
def db_connection
#db = "" # Created db connection here
#browser = Watir::Browser.new
end
def enter_tax_id
Gopal.mutex_var.synchronize do
data = #db.select_one("select max(tax_id)+1 from table")
#browser.text_field(id: 'something').set 'data'
end
end
end
The mutex variable is now persistent as long as the class is loaded, thus even if another object is instantiated, the mutex variable will still be present and provide protection.
According to my test, client_1 should be a client class which I have used to see if I can output and put it. I need to use #output variable in my client class is STILL NIL, but I assign it with the capture_output method in my client class.
#war_server.client_keys(0).puts("Hello Frodo") #works
temp_holder = client_1.capture_output #store output from server to temp holder
puts "Here is the captured input from client_1! : #{temp_holder}"#works
puts #client_1.output #DOES NOT WORK. But it should because I assin it in my class and use a reader definition
Here's the code for my classes and test. Thanks!
require 'minitest/autorun'
require 'socket'
require_relative 'WarGame_Class.rb'
require_relative 'ModifiedPlayer_Class.rb'
require_relative 'DeckClass.rb'
class WarServer
def initialize(host, port)
#socket_server = TCPServer.new(host, port)
#players = [Player.new, Player.new]
#deck = CardDeck.new
#deck.deal_cards(#players[0].cards, #players[1].cards)
game = WarGame.new
#clients = {} # keys are sockets, values are players
end
def client_keys(key)
#clients.keys[key] # this should work
end
def input #input reader function
#input
end
def close
#socket_server.close
end
def capture_input ##input client to get what they wrote
#input = #clients.keys[0].read_nonblock(1000) # arbitrary max number of bytes
end
def accept_client
#Hash here to link client to player? (or game?)
client = #socket_server.accept
#clients[client] = #players[#clients.size]
# puts "clients key 0: #{#clients.keys[0]}"
puts
# puts "clients values: #{#clients.values}"
if #clients.size == 2
start_game#####################!!!! Starts game if two clients can put client messages in start game
end
end
def start_game ##############!!!
#clients.keys[0].puts "Welcome to War. Please press enter to play your card"
#clients.keys[1].puts "Welcome to War. Please press enter to play your card"
end
end
class MockWarClient
def initialize
#socket = TCPSocket.new('localhost', 2012)
end
def output
#output
end
def input
#input
end
def capture_output #need to add (socket)? How else read from specific socket?
#output = #socket.read_nonblock(1000) # arbitrary max number of bytes
rescue
#output = "capture_output error."
end
def write_input
#input = #war_server.client_keys.write_nonblock(1000)
end
end
class WarServerTest < MiniTest::Unit::TestCase
def setup #This would be like our INITIALIZE Function
#anything is available through out all tests (i.e., instance vars)
#war_server = WarServer.new('localhost', 2012)
end
def teardown
#war_server.close
end
def test_server_capture_output_from_client
client_1 = MockWarClient.new
#war_server.accept_client
client_2 = MockWarClient.new
#war_server.accept_client
#can output #war_server.client_keys, though, if I take out the argument to pass in.
#puts "Test_Server_output #client keys #{#war_server.client_keys(player)}" #cient_1?
puts "Test_Server_output #client keys 0 #{#war_server.client_keys(0)}"
puts "Test_Server_output #client keys 1 #{#war_server.client_keys(1)}"
#war_server.client_keys(0).puts("Hello Frodo")
temp_holder = client_1.capture_output
puts "Here is the captured input from client_1! : #{temp_holder}"
#puts #war_server.input
puts #client_1.output
end
end
client_1 is a local variable, distinct from #client_1. I don't see anywhere in this code where you assign #client_1. Unless there is some code you're not sharing which does this, then #client_1 will evaluate to nil.
#client_1 is an instance variable, which is a variable on the instance of a class and exists for the duration that the object is in existence. In order to assign a value to an instance variable, you must denote it by preappending the variable name with #.
In contrast, client_1 is a local variable, which exists only within a single method or block. In your snippet, you have assigned a value to the client_1 local variable, but not to the#client_1 instance variable, which is nil until assigned.
I'm trying to run some tests on my code, but I'm running into a problem. For a very simple line of code, I get a weird error message. This test is to make sure my server can receive info from one of my clients.
The tests and file run fine without this line:
client_1.#socket.puts("This gives an error.")
Including that bit of code gives this error:
macowner:WAR3 macowner$ ruby ServerTests1.rb
ServerTests1.rb:160: syntax error, unexpected tIVAR, expecting '('
client_1.#socket.puts("Output for Server to receive.") #Error
^
Help is greatly appreciated. I get this error fairly frequently, but I have no idea what it means (even with searching for it).
enter code here
require 'minitest/autorun'
require 'socket'
require_relative 'WarGame_Class.rb'
require_relative 'ModifiedPlayer_Class.rb'
require_relative 'DeckClass.rb'
class WarServer
def initialize(host, port)
#socket_server = TCPServer.new(host, port)
#players = [Player.new, Player.new]
#deck = CardDeck.new
#deck.deal_cards(#players[0].cards, #players[1].cards)
game = WarGame.new
#clients = {} # keys are sockets, values are players
end
def client_keys(key)
#clients.keys[key] # this should work
end
def input #input reader function
#input
end
def close
#socket_server.close
end
def capture_input ##input client to get what they wrote
#input = #clients.keys[0].read_nonblock(1000) # arbitrary max number of bytes
end
def accept_client
#Hash here to link client to player? (or game?)
client = #socket_server.accept
#clients[client] = #players[#clients.size]
# puts "clients key 0: #{#clients.keys[0]}"
puts
# puts "clients values: #{#clients.values}"
if #clients.size == 2
start_game#####################!!!! Starts game if two clients can put client messages in start game
end
end
def start_game ##############!!!
#clients.keys[0].puts "Welcome to War. Please press enter to play your card"
#clients.keys[1].puts "Welcome to War. Please press enter to play your card"
end
end
class MockWarClient
def initialize
#socket = TCPSocket.new('localhost', 2012)
end
def output
#output
end
def input
#input
end
def capture_output #need to add (socket)? How else read from specific socket?
#output = #socket.read_nonblock(1000) # arbitrary max number of bytes
rescue
#output = "capture_output error."
end
def write_input
#input = #war_server.client_keys.write_nonblock(1000)
end
end
class WarServerTest < MiniTest::Unit::TestCase
def setup #This would be like our INITIALIZE Function
#anything is available through out all tests (i.e., instance vars)
#war_server = WarServer.new('localhost', 2012)
end
def teardown
#war_server.close
end
def test_server_capture_output_from_client
client_1 = MockWarClient.new
#war_server.accept_client
client_2 = MockWarClient.new
#war_server.accept_client
client_1.#socket.puts("Output for Server to receive.") #Line I need to fix
#SOCKETforSERVER
#clien_n.SOCKETforSERVER.puts''
#Replace puts with write_nonblock, perhaps
end
end
If you're trying to access the #socket instance variable then you just need another accessor method in MockWarClient:
def socket
#socket
end
and the say client_1.socket.puts(...) to use it. You could also use attr_reader to simplify things:
class MockWarClient
attr_reader :socket, :input, :output
def initialize
#socket = TCPSocket.new('localhost', 2012)
end
def capture_output #need to add (socket)? How else read from specific socket?
#output = #socket.read_nonblock(1000) # arbitrary max number of bytes
rescue
#output = "capture_output error."
end
def write_input
#input = #war_server.client_keys.write_nonblock(1000)
end
end
That will create the three accessor methods for you.
You cannot call a method named #socket in a usual way as you are trying in client_1.#socket. That method name is very unusual because it is confusing with an instance variable, and it is doubtful that you actually have such method, but if you do, then the way to call such method is:
client_1.send(:#socket)
Okay, so I'm building a server to play the game of war as an assignment. My server needs to get input from two different sockets, and that's where I'm stuck. I've stored my sockets as keys and my players as values in a hash, but I can't get the sockets in my main program.
I can puts the hash in my main program, but I can't just get ONE socket so that I can tell my program to read from it. Here's my classes and the test I'm running:
require 'minitest/autorun'
require 'socket'
require_relative 'WarGame_Class.rb'
require_relative 'ModifiedPlayer_Class.rb'
require_relative 'DeckClass.rb'
class WarServer
def initialize(host, port)
#socket_server = TCPServer.new(host, port)
#players = [Player.new, Player.new]
#deck = CardDeck.new
#deck.deal_cards(#players[0].cards, #players[1].cards)
game = WarGame.new
#clients = {} # keys are sockets, values are players
end
def read_client_keys
#clients.key[0] #does not work
end
def close
#socket_server.close
end
def capture_input(player) ##input client to get what they wrote
#input = #clients.keys[0].read_nonblock(1000) # arbitrary max number of bytes
end
def accept_client
#Hash here to link client to player? (or game?)
client = #socket_server.accept
#clients[client] = #players[#clients.size]
# puts "clients key 0: #{#clients.keys[0]}"
puts
# puts "clients values: #{#clients.values}"
if #clients.size == 2
start_game#####################!!!! Starts game if two clients can put client messages in start game
end
end
def start_game ##############!!!
#clients.keys[0].puts "Welcome to War. Please press enter to play your card"
#clients.keys[1].puts "Welcome to War. Please press enter to play your card"
end
end
class MockWarClient
def initialize
#socket = TCPSocket.new('localhost', 2012)
end
def output
#output
end
def capture_output #need to add (socket)? How else read from specific socket?
#output = #socket.read_nonblock(1000) # arbitrary max number of bytes
rescue
#output = "capture_output error."
end
def capture_input
end
end
class WarServerTest < MiniTest::Unit::TestCase
def setup #This would be like our INITIALIZE Function
#anything is available through out all tests (i.e., instance vars)
#war_server = WarServer.new('localhost', 2012)
end
def teardown
#war_server.close
end
def test_server_capture_output_from_client
client_1 = MockWarClient.new
#war_server.accept_client
client_2 = MockWarClient.new
#war_server.accept_client
#can output #war_server.read_client_keys, though, if I take out the argument to pass in.
#puts "Test_Server_output #client keys #{#war_server.read_client_keys(player)}" #cient_1?
puts "Test_Server_output #client keys #{#war_server.read_client_keys}"
# I NEED TO JUST BE ABLE TO GET *ONE* KEY SO I CAN READ FROM THE SOCKET
#warserver.capture_input
refute(#war_server.input.empty)
end
end
You must use #keys[0] (or #keys.first) to select the first key of the Hash.
The function #key(val) does something different, returning the key corresponding to an occurrence of val in the hash.
http://www.ruby-doc.org/core-2.0.0/Hash.html#method-i-key-3F
def read_client_keys
#clients.keys[0] # this should work
end
I'm trying to follow a lesson with the ruby class below, I just don't understand how the results of the output statement is "John Smith" from calling the player function followed by new variable?
Is there any simpler way for this? the coder did it in a way that i got confused
Lastly, Can you tell me how to debug Ruby Class or any ruby code on TextMate? I mean debug just like debugging in Visual C++ shows me what first line gets called and excauted and then jumps to the next etc... to see how it works?
class Dungun
attr_accessor :player
def initialize(player_name)
#player = Player.new(player_name)
#rooms = []
end
class Player
attr_accessor :name, :location
def initialize(player_name)
#name = player_name
end
end
class Room
attr_accessor :reference, :name, :description, :connection
def initialize(reference,name,description,connection)
#reference = reference
#name = name
#description = description
#connection = connection
end
end
end
my_dungun = Dungun.new("John Smith")
puts my_dungun.player.name
Execution order
# 1. Called from my_dungun = Dungun.new("John Smith")
Dungun.new("John Smith")
# 2. Inside Dungun it will call the initialize from Dungun class
initialize("John Smith")
# 3. The initialize method, from Dungun class, will have this statement saying
# that instance variable #player will receive the
# result of Player.new("John Smith")
#player = Player.new("John Smith")
# 4. The Player's 'new' method will call the
# inner class Player's initialize method
initialize("John Smith")
# 5. The Player's initialize should assign "Jonh Smith" to #player's name
#name = "John Smith"
# 6. Then head back to where we stopped, and continue to the other
# statement at second line inside Dungun's 'new' method
#rooms = []
And read Mastering the Ruby Debugger for a ruby debug gem and some lessons!