I am new in ruby,and follow the book "The Ruby Programming Language", I am trying to learn some Socket in ruby,and the following is my simple server/client:
## server
require 'socket'
server= UDPSocket.new
server.bind('localhost', 3000)
loop do
data,address=server.recvfrom(1024)
server.send(data.reverse,0,address[3],address[1]) ############ My problem #########
puts "get #{data} from #{address[3]}"
end
##client
require 'socket'
ds = UDPSocket.new
#ds.connect('localhost', 3000)
while line=gets
ds.send(line.chomp, 0,'localhost', 3000)
response,address = ds.recvfrom(1024)
puts response
end
Note the line
server.send(data.reverse,0,address[3],address[1])
If I comment this line,it seems that the server will hold on ,and do not response to the client anymore.
I wonder why?
Does it mean that the UDPSocket must do some response to the client to continue?
Because you write "recvfrom" in the server side, if you comment this, it will not block, and it will continue to send data to client side. However, in real situation, the peers of communication should exchange information.
Related
While running a TCPServer in ruby 2.7.0, I want to see when my client has closed the connection (or is unable to continue reading). However, when I check on the server, I never see that the connection has closed.
I've tried using a bunch of the different ruby socket primitives but nothing seems to work here. I've tried writing to the socket as well in hopes of forcing an error but that doesn't seem to help.
I'm including an example here:
# main.rb
require_relative 'server'
PORT = 9000
server_thread = Server.thread
socket = TCPSocket.open("localhost", PORT)
socket.puts '5'
server_thread.join(1)
socket.close
puts socket.closed?
server_thread.join(2)
# server.rb
require 'socket'
class Server
def self.thread
Thread.new do
server = TCPServer.open(PORT)
while true
server.accept do |socket|
while true
socket.puts '1'
# why doesn't this ever happen?
puts 'closed' if socket.closed?
end
end
end
end
end
end
When running ruby main.rb, this code outputs
true
Whereas I expect it to output:
true
closed
The block that you pass to server.accept is ignored because Socket#accept does not accept a block argument.
When you call socket.close in main.rb, you close the client side of the connection. The server side of the connection will remain open.
You could call IO#read to wait until the client closes the connection.
Thread.new do
server = TCPServer.open(PORT)
loop do
socket = server.accept
socket.read
puts 'client closed connection'
socket.eof? #=> true
socket.close
end
end
The only response provided did not work. Attempting to read from the closed socket did not error like I expected it to.
I have come to the conclusion that what I was asking is simply not possible. You must either:
Send a keep-alive from the client and close when you do not receive it or
Face the consequences of not knowing whether or not your writes have
succeeded.
Personally, I was able to live with 2 since this was for a prototype.
I am working on an assignment where I have to develop a web server in Ruby using the socket library. I was able to get a simple web server up and running as seen in this thread here .
I am currently working on getting and storing the body of an HTTP request into a variable in my web server. The problem I am running into is trying to define a while loop that gets the entire body of a HTTP request.
I am attempting to get the body of a HTTP request by using the gets method. I could not find any documentation on this method (I saw it being used here)
and was wondering if there were more documentation online.
In my first post here, someone suggested that I use the Content-Length header to determine the size of the body and how much data to read from the socket. I don't really understand how I would go about implementing this because I am unsure how the gets method functions.
Since this is for an assignment, I don't think posting code would be a good idea. I am looking for more information on the gets method and any tips to point me towards the right direction.
You shouldn't be using gets. gets tries to read complete lines (ie it reads up to a line separator), but there is no guarantee that an http request body ends with a line separator.
Instead you should be using read - this allows you to read an arbitrary amount of data (as you mentioned you can use the content length header to know how much to read)
Your ultimate problem isn't related to gets, or even really anything in your code. But before we get to that, let's answer this question & explore sockets a little bit.
If you follow the chain up, you find that Ruby's TCPSocket class inherits from its IO class. It's IO that provides gets. gets will read, line-by-line, until there's nothing more to read. Let's create a simple client that connects to a port, spits out 4 lines of poetry, and then quits:
# poetry_sender.rb
require 'socket'
poem = ["'God save thee, ancient Mariner!",
"From the fiends, that plague thee thus!—",
"Why look'st thou so?'—With my cross-bow",
"I shot the ALBATROSS."]
puts "Client establishing connection..."
s = TCPSocket.new 'localhost', 2000
puts "Client sending poetry..."
poem.each { |line| s.puts line } # Print each line out on the socket
s.close # Close our socket
puts "All done."
And a simple server, that displays what the client sends us:
# poetry_receiver.rb
require 'socket'
server = TCPServer.new 2000 # Server bind to port 2000
loop do
puts "Server now awaiting some poetry..."
socket = server.accept # Wait for a client to connect
while line = socket.gets
puts "A client sent us this beautiful line: #{line}"
end
puts "They had nothing more to say; let's disconnect them."
socket.close
end
If you run the server (poetry_receiver.rb) first, and then the client, you'll see some output like this:
Server now awaiting a connection...
A client sent us this beautiful line: 'God save thee, ancient Mariner!
A client sent us this beautiful line: From the fiends, that plague thee thus!—
A client sent us this beautiful line: Why look'st thou so?'—With my cross-bow
A client sent us this beautiful line: I shot the ALBATROSS.
They had nothing more to say; let's disconnect them.
Server now awaiting a connection...
The last two lines are the important ones; they indicate that socket.gets returned nil and we exited the while loop.
So, how can we modify our poetry_sender.rb so the server doesn't detect the end of the poem? You might think it's got something to do with blank lines, but if you set poem = [] or poem = ["", "", ""] then you'll find that it still gets disconnected OK. But what if we added a delay before closing the socket in poetry_sender.rb?
sleep 60
s.close # Close our socket
puts "All done."
Now you'll see a big delay in the server output. The TCP server doesn't break out of its while loop until the TCP client closes its socket.
Now we can turn to your broader problem: you're trying to implement a simple HTTP server, but your server is getting hung up in a while loop when you try to connect via your web browser. It's because your web browser is keeping that socket open; but it has to, otherwise it has no way to send you back a response. So, how do we know when a client has finished sending us a response? The HTTP 1.1 spec says:
A client sends an HTTP request to a server in the form of a request message... followed by header fields... an empty line to indicate the end of the header section, and finally a message body containing the payload body (if any).
Let's not worry about the message body; how could we write a while loop that terminates if it has no more impact, or if it receives a blank line? Here's one way, in a simple HTTP server that just sends back "Hello world" no matter what request it receives:
require 'socket'
server = TCPServer.new('localhost', 2345)
http_request = [] # We'll store the lines of our incoming request here.
loop do
socket = server.accept
while (line = socket.gets) && line.chomp != '' # While the client is connected, and hasn't sent us a blank line yet...
http_request << line
end
# Send response headers
socket.print "HTTP/1.1 200 OK\r\n" +
"Content-Type: text/plain\r\n" +
"Connection: close\r\n" +
"\r\n"
# Send response body
socket.print "Hello world!"
socket.close
end
Quite late to the party, but I'm currently implementing my own rack app server (for fun).
Here you can see how I do it: https://github.com/tak1n/reifier/blob/master/lib/reifier/request.rb
The first line of a HTTP request is always the request line, which is basically something like GET /test HTTP/1.1
After the request line until \r\n you get the headers.
After that you are able to read the body (if PUT / POST request) with just using the CONTENT_LENGTH you parsed from the headers.
I'm trying to see the exact request incl. potential headers like IP, mac address etc.
Server side code:
require 'socket'
server = TCPServer.new 2000
loop do
client = server.accept
puts client.inspect # This should show IP and all transmitted information
client.close
end
Output is #<TCPSocket:fd 8>, but should show the exact request
You can read what client send to the server like this:
require 'socket'
server = TCPServer.new 2000
loop do
client = server.accept
# Print whatever client sends to server
while line = client.gets
puts line
end
client.close
end
The request header is not stored inside client, it works slightly differently: when your TCP server gets client request, you must accept it and then read whatever client is sending to server.
So, I'm trying to simulate some basic HTTP persistent connections using sockets and Ruby - for a college class.
The point is to build a server - able to handle multiple clients - that receives a file path and gives back the file content - just like an HTTP GET.
The current server implementation loops listening for clients, fires a new thread when there's an incoming connection and reads the file paths from this socket. It's very dumb, but it works fine when working with non-presistent connections - one request per connection.
But they should be persistent.
Which means the client shouldn't worry about closing the connection. In the non-persistent version the servers echoes the response and close the connection - goodbye client, farewell.
But being persistent means the server thread should loop and wait for more incoming requests until... well until there's no more requests. How does the server knows that? It doesn't! Some sort of timeout is needed. I tried to do that with Ruby's Timeout, but it didn't work.
Googling for some solutions - besides being thoroughly advised to avoid using Timeout module - I've seen a lot of posts about the IO.select method, that should handle this socket waiting issue way better than using threads and stuff (which really sounds cool, considering how Ruby threads (don't) work). I'm trying to understand here how IO.select works, but still wasn't able to make it work in the current scenario.
So I aske basically two things:
how can I efficiently work this timeout issue on the server-side, either using some thread based solution, low-level socket options or some IO.select magic?
how can the client side know that the server has closed its side of the connection?
Here's the current code for the server:
require 'date'
module Sockettp
class Server
def initialize(dir, port = Sockettp::DEFAULT_PORT)
#dir = dir
#port = port
end
def start
puts "Starting Sockettp server..."
puts "Serving #{#dir.yellow} on port #{#port.to_s.green}"
Socket.tcp_server_loop(#port) do |socket, client_addrinfo|
handle socket, client_addrinfo
end
end
private
def handle(socket, addrinfo)
Thread.new(socket) do |client|
log "New client connected"
begin
loop do
if client.eof?
puts "#{'-' * 100} end connection"
break
end
input = client.gets.chomp
body = content_for(input)
response = {}
if body
response.merge!({
status: 200,
body: body
})
else
response.merge!({
status: 404,
body: Sockettp::STATUSES[404]
})
end
log "#{addrinfo.ip_address} #{input} -- #{response[:status]} #{Sockettp::STATUSES[response[:status]]}".send(response[:status] == 200 ? :green : :red)
client.puts(response.to_json)
end
ensure
socket.close
end
end
end
def content_for(path)
path = File.join(#dir, path)
return File.read(path) if File.file?(path)
return Dir["#{path}/*"] if File.directory?(path)
end
def log(msg)
puts "#{Thread.current} -- #{DateTime.now.to_s} -- #{msg}"
end
end
end
Update
I was able to simulate the timeout behaviour using the IO.select method, but the implementation doesn't feel good when combining with a couple of threads for accepting new connections and another couple for handling requests. The concurrency makes the situation mad and unstable, and I'm probably not sticking with it unless I can figure out a better way of using this solution.
Update 2
Seems like Timeout is still the best way to handle this. I'm sticking with it till find a better option.
I still don't know how to deal with zombie client connections.
Solution
I endend up using IO.select (got inspired when looking at the webrick code). You cha check the final version here (lib/http/server/client_handler.rb)
You should implement something like heartbeat packets.Client side should send special packets to after few secs/mins to ensure that server doesn't time out the connection on the client end.You just avoid doing anything in this call.
I'm making a TCP socket server(ruby). A thread is created for each connected client. At some point I try to send data to all connected clients. The thread aborts on exception while trying.(ruby 1.8.7)
require 'socket'
# I test it home right now
server = TCPServer.new('localhost', 12345);
while(session = server.accept)
#Here is the thread being created
Thread.new(session) do |s|
while(msg = s.gets)
#Here is the part that causes the error
Thread.list.each { |aThread|
if aThread != Thread.current
#So what I want it to do is to echo the message from one client to all others
#But for some reason it doesn't, and aborts on the following string
aThread.print "#{msg}\0"
end
}
end
end
Thread.abort_on_exception = true
end
What am I doing wrong?
The following PDF discusses socket programming in ruby in detail. It includes asynchronous handling and goes through a functioning demonstration of a small chat program.
IBM guide to ruby socket programming