class Irc::Socket
wrapped TCPSocket for communication with the server. emulates a subset of TCPSocket functionality
Constants
- MAX_IRC_SEND_PENALTY
Attributes
total number of bytes received from the irc server
total number of bytes sent to the irc server
an optional filter object. we call @filter.in(data) for all incoming data and @filter.out(data) for all outgoing data
total number of lines received from the irc server
total number of lines sent to the irc server
penalty multiplier (percent)
normalized uri of the current server
accumulator for the throttle
Public Class Methods
- server_list
-
list of servers to connect to
- host
-
optional local host to bind to (ruby 1.7+ required)
create a new Irc::Socket
# File lib/rbot/ircsocket.rb, line 277 def initialize(server_list, host, opts={}) @server_list = server_list.dup @server_uri = nil @conn_count = 0 @host = host @sock = nil @filter = IdentityFilter.new @spooler = false @lines_sent = 0 @lines_received = 0 @ssl = opts[:ssl] @penalty_pct = opts[:penalty_pct] || 100 end
Public Instance Methods
# File lib/rbot/ircsocket.rb, line 379 def clearq @sendq.clear end
open a TCP connection to the server
# File lib/rbot/ircsocket.rb, line 296 def connect if connected? warning "reconnecting while connected" return end srv_uri = @server_list[@conn_count % @server_list.size].dup srv_uri = 'irc://' + srv_uri if !(srv_uri =~ /:\/\//) @conn_count += 1 @server_uri = URI.parse(srv_uri) @server_uri.port = 6667 if !@server_uri.port debug "connection attempt \##{@conn_count} (#{@server_uri.host}:#{@server_uri.port})" if(@host) begin sock=TCPSocket.new(@server_uri.host, @server_uri.port, @host) rescue ArgumentError => e error "Your version of ruby does not support binding to a " error "specific local address, please upgrade if you wish " error "to use HOST = foo" error "(this option has been disabled in order to continue)" sock=TCPSocket.new(@server_uri.host, @server_uri.port) end else sock=TCPSocket.new(@server_uri.host, @server_uri.port) end if(@ssl) require 'openssl' ssl_context = OpenSSL::SSL::SSLContext.new() ssl_context.verify_mode = OpenSSL::SSL::VERIFY_NONE sock = OpenSSL::SSL::SSLSocket.new(sock, ssl_context) sock.sync_close = true sock.connect end @sock = sock @last_send = Time.new @flood_send = Time.new @burst = 0 @sock.extend(MonitorMixin) @sendq = MessageQueue.new @qthread = Thread.new { writer_loop } end
# File lib/rbot/ircsocket.rb, line 291 def connected? !@sock.nil? end
used to send lines to the remote IRCd by skipping the queue message: IRC message to send it should only be used for stuff that *must not* be queued, i.e. the initial PASS, NICK and USER command or the final QUIT message
# File lib/rbot/ircsocket.rb, line 343 def emergency_puts(message, penalty = false) @sock.synchronize do # debug "In puts - got @sock" puts_critical(message, penalty) end end
set filter to identity, not to nil
# File lib/rbot/ircsocket.rb, line 270 def filter=(f) @filter = f || IdentityFilter.new end
flush the TCPSocket
# File lib/rbot/ircsocket.rb, line 384 def flush @sock.flush end
get the next line from the server (blocks)
# File lib/rbot/ircsocket.rb, line 359 def gets if @sock.nil? warning "socket get attempted while closed" return nil end begin reply = @filter.in(@sock.gets) @lines_received += 1 reply.strip! if reply debug "RECV: #{reply.inspect}" return reply rescue Exception => e handle_socket_error(:RECV, e) end end
# File lib/rbot/ircsocket.rb, line 350 def handle_socket_error(string, e) error "#{string} failed: #{e.pretty_inspect}" # We assume that an error means that there are connection # problems and that we should reconnect, so we shutdown raise SocketError.new(e.inspect) end
# File lib/rbot/ircsocket.rb, line 375 def queue(msg, chan=nil, ring=0) @sendq.push msg, chan, ring end
Wraps Kernel.select on the socket
# File lib/rbot/ircsocket.rb, line 389 def select(timeout=nil) Kernel.select([@sock], nil, nil, timeout) end
shutdown the connection to the server
# File lib/rbot/ircsocket.rb, line 394 def shutdown(how=2) return unless connected? @qthread.kill @qthread = nil begin @sock.close rescue Exception => e error "error while shutting down: #{e.pretty_inspect}" end @sock = nil @sendq.clear end
Private Instance Methods
same as puts, but expects to be called with a lock held on @sock
# File lib/rbot/ircsocket.rb, line 431 def puts_critical(message, penalty=false) # debug "in puts_critical" begin debug "SEND: #{message.inspect}" if @sock.nil? error "SEND attempted on closed socket" else # we use Socket#syswrite() instead of Socket#puts() because # the latter is racy and can cause double message output in # some circumstances actual = @filter.out(message) + "\n" now = Time.new @sock.syswrite actual @last_send = now @flood_send = now if @flood_send < now @flood_send += message.irc_send_penalty*@penalty_pct/100.0 if penalty @lines_sent += 1 end rescue Exception => e handle_socket_error(:SEND, e) end end
# File lib/rbot/ircsocket.rb, line 409 def writer_loop loop do begin now = Time.now flood_delay = @flood_send - MAX_IRC_SEND_PENALTY - now delay = [flood_delay, 0].max if delay > 0 debug "sleep(#{delay}) # (f: #{flood_delay})" sleep(delay) end msg = @sendq.shift debug "got #{msg.inspect} from queue, sending" emergency_puts(msg, true) rescue Exception => e error "Spooling failed: #{e.pretty_inspect}" debug e.backtrace.join("\n") raise e end end end