From: "Aleksi Niemelä" Date: 2000-09-19T20:56:34+09:00 Subject: [ruby-talk:5006] Working server I've tried to get some base for server framework in place, but I shouldn't have been in such hurry - I just ended up with chunk of code which does not work. Then I've tried few things, but I'm all the time recurring to the same problem. Here's a little bit of discussion about the problem using standard sample shipped with Ruby, and few surprises. I hope you can understand what's going on even while I try to confuse with my explanation :). Example: 1) Please fire up a sample/tsvr.rb server, and check the port it returns. (I copy the source here, so people wanting just read don't have to look up the file.) # socket example - server side using thread # usage: ruby tsvr.rb require "socket" gs = TCPserver.open(0) addr = gs.addr addr.shift printf("server is on %d\n", addr.join(":")) while TRUE Thread.start(gs.accept) do |s| print(s, " is accepted\n") while s.gets s.write($_) end print(s, " is gone\n") s.close end end 2) Run the following client and specify the port as a command line argument. 10.times {|i| s=TCPSocket.new("localhost", ARGV[0]) s.write("abcdefgh\n"*1) puts s.gets("\n") s.close } 3) For me the server for the above client does not work. But it worked with my home machine yesterday so I wonder what went wrong. (Here's a little bit of debugging.) Ah, it seems that the Ruby has changed underneath. The client opens up a socket fine (server says for example "# is accepted"), then it writes to the socket, and stays reading the socket and never gets any response. The server OTOH throws an exception (you can see it by adding Thread.abort_on_exception=true), as there's no Array#gets. What?! It should have been socket, what's this talk about Array. 4) Let's replace the client and socket with something more easily followable. The client gets few notification messages to tell where it's going. Let's gather all the established sockets too (so they won't finalize yet). sockets = [] 10.times {|i| s=TCPSocket.new("localhost", ARGV[0]) puts "#{i} writing" s.write("abcdefgh\n"*1) puts "#{i} reading" puts s.gets("\n") puts "#{i} closing" s.close sockets << s } And then the new server. The major change to the server is the "for socket in s" loop (as I don't know if the array s could hold more than one accepted connection at a time). require "socket" #Thread.abort_on_exception=true gs = TCPserver.open(0) addr = gs.addr addr.shift printf("server is on %d\n", addr.join(":")) while TRUE Thread.start(gs.accept) do |s| for socket in s print(socket, " is accepted\n") while socket.gets("\n") puts "got #{$_.length}" socket.write($_) puts "wrote #{$_.length}" end print(socket, " is gone\n") socket.close end end end 5) Let's run the things again. Now everything goes well (at least for me) and the dialogue is about like this: Server Client # is accepted 4 writing 4 reading got 9 wrote 9 abcdefgh 4 closing # is gone So it seems to work. Now we get to the real point, so far we have been clearing out odd behaviour of examples (possibly due to the 1.4->1.6 change). 6) Let's remove the socket closing code at the client side and check how things go. And things go well. The client gets through it's 10 connections, writes, gets response, and continues. The server doesn't report closed sockets before the client program exists (as then Ruby closes automatically). When the client dies normally, we get dozen of "gone" messages on the server side. It seems the server connections "go away" even when the client dies unexpectedly (dunno why, but it's good :). 7) Ok, the let's make two more test. Let's remove the reading part from client. sockets = [] 10.times {|i| s=TCPSocket.new("localhost", ARGV[0]) puts "#{i} writing" s.write("abcdefgh\n"*1) # puts "#{i} reading" # puts s.gets("\n") puts "#{i} closing" s.close sockets << s } Now the server reports connections, serve them, but *does not* print any "gone" messages, even after the client process have died. My machine is running Linux, and running netstat tells (only beginning, the first two connections) Active Internet connections (w/o servers) Proto Recv-Q Send-Q Local Address Foreign Address State tcp 0 9 localhost:4284 localhost:4294 CLOSE tcp 0 9 localhost:4284 localhost:4293 CLOSE tcp 0 9 localhost:4284 localhost:4292 CLOSE tcp 0 9 localhost:4284 localhost:4291 CLOSE So the sockets are closed, but Ruby doesn't know about it. One can get the same effect by having the reading (remove comments on earlier code), but writing more (changes "*1" to "*2" or something at s.write -line) to the echo server. ======= If you managed to get through here's my thoughts what's happening. Ruby "blocks" on writing to a closed socket. With this I don't mean kernel level blocking, but the writing Ruby thread gets blocked from execution. One can detect when socket goes down by trying to read from it. I've tried to create server classes where for each connection there's two threads - one reading and the other writing, and when one of these notes the connection is down, both are terminated. The problem is that if there's no reading going on on the socket, there's no way to know when the socket did go down. I'm not sure if my interpretation is correct, if there's a bug in current implementation nor if everything is just as it should. So I thought it might be better to discuss about the situation a little. - Aleksi