I've got a Ruby TCPSocket client that works great except when I'm trying to close it. When I call the disconnect method in my code below, I get this error:
./smartlinc.rb:70:in `start_listen': stream closed (IOError)
from ./smartlinc.rb:132:in `initialize'
from ./smartlinc.rb:132:in `new'
from ./smartlinc.rb:132:in `start_listen'
from bot.rb:45:in `initialize'
from bot.rb:223:in `new'
from bot.rb:223
Here's the (simplified) code:
class Smartlinc
def initialize
#socket = TCPSocket.new(HOST, PORT)
end
def disconnect
#socket.close
end
def start_listen
# Listen on a background thread
th = Thread.new do
Thread.current.abort_on_exception = true
# Listen for Ctrl-C and disconnect socket gracefully.
Kernel.trap('INT') do
self.disconnect
exit
end
while true
ready = IO.select([#socket])
readable = ready[0]
readable.each do |soc|
if soc == #socket
buf = #socket.recv_nonblock(1024)
if buf.length == 0
puts "The socket connection is dead. Exiting."
exit
else
puts "Received Message"
end
end
end # end each
end # end while
end # end thread
end # end message callback
end
Is there a way I can prevent or catch this error? I'm no expert in socket programming (obviously!), so all help is appreciated.
Your thread is sitting in IO.select() while the trap code happily slams the door in its face with #socket.close, hence you get some complaining.
Don't set abort_on_exception to true, or then handle the exception properly in your code:
Something along these lines...
Kernel.trap('INT') do
#interrupted = true
disconnect
exit
end
...
ready = nil
begin
ready = IO.select(...)
rescue IOError
if #interrupted
puts "Interrupted, we're outta here..."
exit
end
# Else it was a genuine IOError caused by something else, so propagate it up..
raise
end
...
Related
I have a module that should send GPS location to my server. Below I describe how communication happens between module and server:
When the module connects to the server, module sends its IMEI.
If server accepts data, then should reply to module 01. Note that confirmation should be sent as binary packet. I.e. 1 byte 0x01.
I'm struggling at second step. Have tried various combination, but neither worked.
client.puts('\x01')
client.send([0x01].pack("C"), 0)
client.write["01"].pack('H*')
Below is a full code example:
require 'socket'
class ClientThread
def initialize(port)
#server = TCPServer.open(port)
end
def run
puts "Started TCP Server"
loop do
Thread.start(#server.accept) do |client|
2.times do |index|
data = client.recv(8192)
if index == 0
client.send('\x01'.encode('utf-8'), 0) # RESPONSE TO DEVISE THAT SERVER IS READY TO ACCEPT DATA
elsif index == 1
puts self.log("Done! Closing Connection")
client.close
else
client.send('\x00'.encode('utf-8'), 0) # RESPONSE TO DEVISE THAT SERVER IS NOT READY TO ACCEPT DATA
end
end # end of loop twice
end # end of Thread
end # end of infinite loop
end # run method
end # end of class
new_thread = ClientThread.new(65432)
p new_thread.run
Device does not understand that server is ready to accept data. Most likely, because binary packet is formed incorrectly. How do you form response, so device would understand to send GSP data?
Ask me please if any questions. Thanks.
I have managed to make it work.
Instead of client.send('\x01'.encode('utf-8'), 0) I have used client.puts [0x01].pack("C") and module started to send data.
Below is a full working example for Teltonika FMT100.
Module sends its IMEI to the server.
Server accepts data and replies as binary packet 0x01.
Module send GPS data to the server. At this point you will need to decode data.
require 'socket'
class ClientThread
def initialize(port)
#server = TCPServer.open(port)
#imei = "unknown"
end
def log(msg)
"#{Time.now.utc} #{msg}"
end
def run
puts self.log("Started TCP Server")
loop do
Thread.start(#server.accept) do |client|
if client
2.times do |index|
begin
data = client.recv(8192)
if index == 0
#imei = data
p self.log("Device Authenticated | IMEI: #{#imei}")
client.puts [0x01].pack("C")
elsif index == 1
p data.unpack('H*').first
p self.log("Done! Closing Connection")
client.close
else
client.puts [0x00].pack("C")
end
rescue IOError
p self.log("Stream is already closed")
end
end # end of loop twice
else
p self.log('Socket is null')
end # if conditional
end # end of Thread
end # end of infinite loop
end # run method
end # end of class
new_thread = ClientThread.new(65432)
p new_thread.run
You should get something similar to:
"2021-12-30 11:12:22 UTC Device Authenticated | IMEI: \u0000\u000F357544374597827"
"00000000000004d608130000017dfcc4f8f8000f1753862097342800000000000000f00c05ef00f0011505c800450205b50000b60000422fbb430fc944000002f100006019100009d77a000000017dfcc5f2f8000f1753862097342800000000000000f00c05ef00f0001504c800450205b50000b60000422fbe430fcb44000002f100006019100009d77a000000017dfcce8a30000f1753862097342800000000000000000c05ef00f0001505c800450205b50000b60000422f6a430fbd44000002f100006019100009d77a000000017dfcce8e18000f1753862097342800000000000000f00c05ef00f0011505c800450205b50000b60000422f78430fc244000002f100006019100009d77a000000017dfccf8430000f1753862097342800000000000000f00c05ef00f0001504c800450205b50000b60000422fbd430fcb44000002f100006019100009d77a000000017dfcdbd488000f1753862097342800000000000000000c05ef00f0001504c800450205b50000b60000422fc0430fcb44000002f100006019100009d77a000000017dfcdbd870000f1753862097342800000000000000f00c05ef00f0011504c800450205b50000b60000422fac430fc744000002f100006019100009d77a000000017dfcdcbee8000f1753862097342800000000000000f00c05ef00f0001504c800450205b50000b60000422fb8430fcb44000002f100006019100009d77a000000017dfcddb500000f1753862097342800000000000000f00c05ef00f0011504c800450205b50000b60000422fb5430fcb44000002f100006019100009d77a000000017dfcdebab8000f1753862097342800000000000000f00c05ef00f0001504c800450205b50000b60000422fbe430fca44000002f100006019100009d77a000000017dfced4948000f1753862097342800000000000000000c05ef00f0001504c800450205b50000b60000422fbe430fc644000002f100006019100009d77a000000017dfced4d30000f1753862097342800000000000000f00c05ef00f0011504c800450205b50000b60000422fbf430fc644000002f100006019100009d77a000000017dfcee3790000f1753862097342800000000000000f00c05ef00f0001504c800450205b50000b60000422fc2430fc644000002f100006019100009d77a000000017dfcefd5a0000f1753862097342800000000000000f00c05ef00f0011504c800450205b50000b60000422fbe430fc644000002f100006019100009d77a000000017dfcf13918000f1753862097342800000000000000f00c05ef00f0001504c800450205b50000b60000422fc1430fc644000002f100006019100009d77a000000017dfcf2b7e8000f1753862097342800000000000000f00c05ef00f0011504c800450205b50000b60000422fb7430fc644000002f100006019100009d77a000000017dfcf3a630000f1753862097342800000000000000f00c05ef00f0001504c800450205b50000b60000422fb8430fc644000002f100006019100009d77a000000017dfd2aac20000f1753862097342800000000000000000c05ef00f0001504c800450205b50000b60000422fa0430fcb44000002f100006019100009d77a000000017dfd61b210000f1753862097342800000000000000000c05ef00f0001504c800450205b50000b60000422f9b430fcb44000002f100006019100009d77a00130000b8e0"
"2021-12-30 11:12:24 UTC Done! Closing Connection"
I am fairly new to programming in general and I am using EventMachine on both the client and server side to open a websocket connection between them.
My issue is with the Client side, and when the connection is lost due to network connectivity issues.
def websocket_connection
EM.run do
begin
puts "Connecting"
ws = WebSocket::EventMachine::Client.connect(:uri => "Server Info")
puts "Success WS: #{ws}"
rescue
puts "I've been rescued"
puts "Rescue WS: #{ws}"
ws = nil
sleep 10
websocket_connection
end
ws.onopen do
puts "Connected!"
end
ws.onping do
put "Ping!"
end
ws.onmessage do |msg|
puts msg
end
ws.onclose do |code|
puts "Connection Closed!"
puts "Code: #{code}"
ws = nil
sleep 10
websocket_connection
end
end
end
This connects to the server just fine, but if I pull the network connection and plug it back in I am stuck in an infinite loop of it trying to reconnect with code 1002 (WebSocket protocol error.).
I have tried to call EM.reconnect(server, port, ws) on close, but it crashes and throws this error `connect_server': unable to resolve address: The requested name is valid, but no data of the requested type was found. Which makes sense because it can't contact DNS. Even if wrap the EM.reconnect in a begin rescue it just tries once and never tries again.
I have tried stopping EventMachine and close (EM.stop) but that gets stuck in an infinite loop trying to reconnect.
I am not really sure how to get this client to reconnect to the server after a network lose.
EDIT:
Updated the code above a little bit.
CMD Line:
Success WS: #WebSocket::EventMachine::Client:0x00000002909ac8
Pulled Ethernet Cable
Rescue WS:
Connected Ethernet Cable
Success WS: #WebSocket::EventMachine::Client:0x000000031c42a8
Success WS: #WebSocket::EventMachine::Client:0x000000031a3d50
Success WS: #WebSocket::EventMachine::Client:0x00000003198a90
CTRL + C
block in websocket_connection': undefined methodonopen' for nil:NilClass (NoMethodError)
So it looks like it thinks its connecting, I don't see any connections on the server side.
Well, I couldn't find a way to do a proper reconnect using EventMachine. It looks like weird things happen in EventMachine when you drop your network connection. I ended up relaunching the ruby app under a new Process then killing the current script, not the best way to do this but after a week of trying to get the reconnect working through EventMachine I have just given up. This code works below.
def websocket_restart
exec "ruby script"
exit 0
end
def websocket_connection
EM.run do
begin
puts "Connecting"
ws = WebSocket::EventMachine::Client.connect(:uri => "Server Info")
rescue
websocket_restart
end
ws.onopen do
puts "Connected!"
end
ws.onping do
put "Ping!"
end
ws.onmessage do |msg|
puts msg
end
ws.onclose do |code|
puts "Connection Closed!"
puts "Code: #{code}"
websocket_restart
end
end
end
Intro
I have a client that makes numerous SSL connections to a 3rd party service. In certain cases, the 3rd party stops responding during the socket and ssl negotiation process. When this occurs, my current implementation "sits" for hours on end before timing out.
To combat this, I'm trying to implement the following process:
require 'socket'
require 'openssl'
# variables
host = '....'
port = ...
p12 = #OpenSSL::PKCS12 object
# set up socket
addr = Socket.getaddrinfo(host, nil)
sockaddr = Socket.pack_sockaddr_in(port, addr[0][3])
socket = Socket.new(Socket.const_get(addr[0][0]), Socket::SOCK_STREAM, 0)
socket.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1)
begin
socket.connect_nonblock(sockaddr)
rescue IO::WaitWritable
if IO.select(nil, [socket], nil, timeout)
begin
socket.connect_nonblock(sockaddr)
rescue Errno::EISCONN
puts "socket connected"
rescue
puts "socket error"
socket.close
raise
end
else
socket.close
raise "Connection timeout"
end
end
# negotiate ssl
context = OpenSSL::SSL::SSLContext.new
context.cert = p12.certificate
context.key = p12.key
ssl_socket = OpenSSL::SSL::SSLSocket.new(socket, context)
ssl_socket.sync_close = true
puts "ssl connecting"
ssl_socket.connect_nonblock
puts "ssl connected"
# cleanup
ssl_socket.close
puts "socket closed"
ssl_socket.connect_nonblock will eventually be wrapped in a similar structure as socket.connect_nonblock is.
The Problem
The issue I'm running into is that ssl_socket.connect_nonblock raises the following when run:
`connect_nonblock': read would block (OpenSSL::SSL::SSLError)
Instead, I'd expect it to raise an IO::WaitWritable as socket.connect_nonblock does.
I've scoured the internet for information on this particular error but can't find anything of particular use. From what I gather, others have had success using this method, so I'm not sure what I'm missing. For the sake of completeness, I've found the same results with both ruby 2.2.0 and 1.9.3.
Any suggestions are greatly appreciated!
Have same problem, I tried below, it seems works right for my situation.
ssl_socket = OpenSSL::SSL::SSLSocket.new socket, context
ssl_socket.sync = true
begin
ssl_socket.connect_nonblock
rescue IO::WaitReadable
if IO.select([ssl_socket], nil, nil, timeout)
retry
else
# timeout
end
rescue IO::WaitWritable
if IO.select(nil, [ssl_socket], nil, timeout)
retry
else
# timeout
end
end
I have been searching the whole day for socket accept non blocking. I found recv non blocking but that wouldn't benefit me in anyway. My script first starts a new socket class. It binds to the client with ip 127.0.0.1 and port 6112. Then it starts multi threading. Multi threading takes #sock.accept. << That is blocking. I have then used accept_nonblock. Though, that would throw me the following error:
IO::EWOULDBLOCKWaitReadable : A non-blocking socket operation could not be completed immediately. - accept(2) would block
I am using Ruby 2.2.
NOTE: I do not intend to use Rails to solve my problem, or give me a shortcut. I am sticking with pure Ruby 2.2.
Here is my script:
require 'socket'
include Socket::Constants
#sock = Socket.new(AF_INET, SOCK_STREAM, 0)
#sockaddr = Socket.sockaddr_in(6112, '127.0.0.1')
#sock.bind(#sockaddr)
#sock.listen(5)
Thread.new(#sock.accept_nonblock) do |connection|
#client = Client.new(ip, connection, self)
#clients.push(#client)
begin
while connection
packet = connection.recv(55555)
if packet == nil
DeleteClient(connection)
else
#toput = "[RECV]: #{packet}"
puts #toput
end
end
rescue Exception => e
if e.class != IOError
line1 = e.backtrace[0].split(".rb").last
line = line1.split(":")[1]
#Log.Error(e, e.class, e.backtrace[0].split(".rb").first + ".rb",line)
puts "#{ e } (#{ e.class })"
end
end
def DeleteClient(connection)
#clients.delete(#client)
connection.close
end
http://docs.ruby-lang.org/en/2.2.0/Socket.html#method-i-accept_nonblock
accept_nonblock raises an exception when it can't immediately accept a connection. You are expected to rescue this exception and then IO.select the socket.
begin # emulate blocking accept
client_socket, client_addrinfo = socket.accept_nonblock
rescue IO::WaitReadable, Errno::EINTR
IO.select([socket])
retry
end
A patch has recently been accepted which will add an exception: false option to accept_nonblock, which will allow you to use it without using exceptions for flow control. I don't know that it's shipped yet, though.
I'm going on a limb here, and posting a large chunk of code.
I hope it will answer both your question and the any related questions others reading this answer might raise.
I'm sorry if I went overboard, I just thought it was almost all relevant.
Issues like looping through an event stack, using IO.select to push events in a non-block manner and other performance issues are all related (in my opinion) to the nonblocking concept of socket programming.
So i'm posting a ruby module which acts as a server with a reactor, using a limited number of threads, rather than thousands of threads, each per connection (12 threads will give you better performance than a hundred). The reactor utilizes the IO.select method with a timeout once all it's active events are handled.
The module can set up multiple listening sockets which use #accept_nonblock, and they all currently act as an echo server.
It's basically the same code I used for the Plezi framework's core... with some stripped down functionality.
The following is a thread-pool with 12 working threads + the main thread (which will sleep and wait for the "TERM" signal)...
...And it's an example of an accept_nonblock with exception handling and a thread pool.
It's a simple socket echo server, test it as a remote client using telnet:
> telnet localhost 3000
Hi!
# => Hi!
bye
#=> will disconnect
here's the code - Good Luck!!!
require 'socket'
module SmallServer
module_function
####
# Replace this method with your actual server logic.
#
# this code will be called when a socket recieves data.
#
# For now, we will just echo.
def got_data io, io_params
begin
got = io.recv_nonblock( 1048576 ) # with maximum number of bytes to read at a time...
puts "echoing: #{got}"
if got.match /^(exit|bye|q)\R/
puts 'closing connection.'
io.puts "bye bye!"
remove_connection io
else
io.puts "echoing: #{got}"
end
rescue => e
# should also log error
remove_connection io
end
end
#########
# main loop and activation code
#
# This will create a thread pool and set them running.
def start
# prepare threads
exit_flag = false
max_threads = 12
threads = []
thread_cycle = Proc.new do
io_review rescue false
true while fire_event
end
(max_threads).times { Thread.new { thread_cycle.call until exit_flag } }
# set signal tarps
trap('INT'){ exit_flag = true; raise "close!" }
trap('TERM'){ exit_flag = true; raise "close!" }
puts "Services running. Press ^C to stop"
# sleep until trap raises exception (cycling might cause the main thread to loose signals that might be caught inside rescue clauses)
(sleep unless SERVICES.empty?) rescue true
# start shutdown.
exit_flag = true
# set fallback tarps
trap('INT'){ puts 'Forced exit.'; Kernel.exit }
trap('TERM'){ puts 'Forced exit.'; Kernel.exit }
puts 'Started shutdown process. Press ^C to force quit.'
# shut down listening sockets
stop_services
# disconnect active connections
stop_connections
# cycle down threads
puts "Waiting for workers to cycle down"
threads.each {|t| t.join if t.alive?}
# rundown any active events
thread_cycle.call
end
#######################
## Events (Callbacks) / Multi-tasking Platform
EVENTS = []
E_LOCKER = Mutex.new
# returns true if there are any unhandled events
def events?
E_LOCKER.synchronize {!EVENTS.empty?}
end
# pushes an event to the event's stack
# if a block is passed along, it will be used as a callback: the block will be called with the values returned by the handler's `call` method.
def push_event handler, *args, &block
if block
E_LOCKER.synchronize {EVENTS << [(Proc.new {|a| push_event block, handler.call(*a)} ), args]}
else
E_LOCKER.synchronize {EVENTS << [handler, args]}
end
end
# Runs the block asynchronously by pushing it as an event to the event's stack
#
def run_async *args, &block
E_LOCKER.synchronize {EVENTS << [ block, args ]} if block
!block.nil?
end
# creates an asynchronous call to a method, with an optional callback (shortcut)
def callback object, method, *args, &block
push_event object.method(method), *args, &block
end
# event handling FIFO
def fire_event
event = E_LOCKER.synchronize {EVENTS.shift}
return false unless event
begin
event[0].call(*event[1])
rescue OpenSSL::SSL::SSLError => e
puts "SSL Bump - SSL Certificate refused?"
rescue Exception => e
raise if e.is_a?(SignalException) || e.is_a?(SystemExit)
error e
end
true
end
#####
# Reactor
#
# IO review code will review the connections and sockets
# it will accept new connections and react to socket input
IO_LOCKER = Mutex.new
def io_review
IO_LOCKER.synchronize do
return false unless EVENTS.empty?
united = SERVICES.keys + IO_CONNECTION_DIC.keys
return false if united.empty?
io_r = (IO.select(united, nil, united, 0.1) )
if io_r
io_r[0].each do |io|
if SERVICES[io]
begin
callback self, :add_connection, io.accept_nonblock, SERVICES[io]
rescue Errno::EWOULDBLOCK => e
rescue => e
# log
end
elsif IO_CONNECTION_DIC[io]
callback(self, :got_data, io, IO_CONNECTION_DIC[io] )
else
puts "what?!"
remove_connection(io)
SERVICES.delete(io)
end
end
io_r[2].each { |io| (remove_connection(io) || SERVICES.delete(io)).close rescue true }
end
end
callback self, :clear_connections
true
end
#######################
# IO - listening sockets (services)
SERVICES = {}
S_LOCKER = Mutex.new
def add_service port = 3000, parameters = {}
parameters[:port] ||= port
parameters.update port if port.is_a?(Hash)
service = TCPServer.new(parameters[:port])
S_LOCKER.synchronize {SERVICES[service] = parameters}
callback Kernel, :puts, "Started listening on port #{port}."
true
end
def stop_services
puts 'Stopping services'
S_LOCKER.synchronize {SERVICES.each {|s, p| (s.close rescue true); puts "Stoped listening on port #{p[:port]}"}; SERVICES.clear }
end
#####################
# IO - Active connections handling
IO_CONNECTION_DIC = {}
C_LOCKER = Mutex.new
def stop_connections
C_LOCKER.synchronize {IO_CONNECTION_DIC.each {|io, params| io.close rescue true} ; IO_CONNECTION_DIC.clear}
end
def add_connection io, more_data
C_LOCKER.synchronize {IO_CONNECTION_DIC[io] = more_data} if io
end
def remove_connection io
C_LOCKER.synchronize { IO_CONNECTION_DIC.delete io; io.close rescue true }
end
# clears closed connections from the stack
def clear_connections
C_LOCKER.synchronize { IO_CONNECTION_DIC.delete_if {|c| c.closed? } }
end
end
start the echo server in irb with:
SmallServer.add_service(3000) ; SmallServer.start
I am attempting to create a better port scanner by implementing more error handling, and I've run into a bit of trouble.
def pscan(host_name, port)
begin
sock = Socket.new(:INET, :STREAM)
raw = Socket.sockaddr_in(port, host_name)
if sock.connect(raw)
puts "Port #{port} is up!"
rescue (Errno::ECONNREFUSED)
false
rescue (Errno::ETIMEDOUT)
puts "Port #{port} timed out!"
end
end
def run
port = 1
begin
while port <= ARGV[1].to_i do
popen(ARGV[0], port)
port += 1
end
rescue (Errno::EHOSTUNREACH)
puts "No path to host"
rescue(Interrupt)
puts " Process interrupted"
end
end
run
The trouble is that for each port scanned, it will print "No path to host", instead of printing that once and then closing the socket. I'm clearly doing it wrong but everywhere online I find simple code closes a socket connection this way.
I think that you forgot to close the if statement. Also, use 'ensure' to close it.
Try this:
def pscan(host_name, port)
begin
sock = Socket.new(:INET, :STREAM)
raw = Socket.sockaddr_in(port, host_name)
if sock.connect(raw)
puts "Port #{port} is up!"
end
rescue (Errno::ECONNREFUSED)
false
rescue (Errno::ETIMEDOUT)
puts "Port #{port} timed out!"
rescue (Errno::EHOSTUNREACH)
puts "No path to host"
ensure
sock.close
end
end
Like you mentioned in your comments and your code changes, you would have to move the socket closing into the block that's calling your code rather than inside of that code itself, since the block of code will continuously call the method and the socket will never actually be closed.
You'll basically have to do what you posted in your code:
require 'socket'
def pscan(host_name, port)
begin
sock = Socket.new(:INET, :STREAM)
raw = Socket.sockaddr_in(port, host_name)
if sock.connect(raw)
puts "Port #{port} is up!"
end
rescue (Errno::ECONNREFUSED)
puts "Connection refused to port #{port}"
false
rescue (Errno::ETIMEDOUT)
puts "Port #{port} timed out!"
false
end
end
def run
puts "Attempting to scan host #{ARGV[0]}"
port = 1
begin
while port <= ARGV[1].to_i do
pscan(ARGV[0], port)
port += 1
end
rescue (Errno::EHOSTUNREACH)
puts "No path to host #{ARVG[0]}"
rescue(Interrupt)
puts "Process interrupted"
end
end
run
Some thoughts: you may not always get "No path to host" errors when running your code; when I tested it in my network, I never got a "No path to host" error but always got a "Port ... timed out!" error even when I tested it against known unreachable hosts such as 10.0.0.1. You'll have to be wary of that when you run your code in other networks as you might get different results.