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] @ssl_verify = opts[:ssl_verify] @ssl_cert = opts[:ssl_cert] @ssl_ca_file = opts[:ssl_ca_file] @ssl_ca_path = opts[:ssl_ca_path] @penalty_pct = opts[:penalty_pct] || 100 end
Public Instance Methods
# File lib/rbot/ircsocket.rb, line 404 def clearq @sendq.clear end
open a TCP connection to the server
# File lib/rbot/ircsocket.rb, line 300 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 the host is a bracketed (IPv6) address, strip the brackets # since Ruby doesn't like them in the Socket host parameter # FIXME it would be safer to have it check for a valid # IPv6 bracketed address rather than just stripping the brackets srv_host = @server_uri.host if srv_host.match(/\A\[(.*)\]\z/) srv_host = $1 end if(@host) begin sock=TCPSocket.new(srv_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(srv_host, @server_uri.port) end else sock=TCPSocket.new(srv_host, @server_uri.port) end if(@ssl) require 'openssl' ssl_context = OpenSSL::SSL::SSLContext.new() if @ssl_verify ssl_context.ca_file = @ssl_ca_file if @ssl_ca_file and not @ssl_ca_file.empty? ssl_context.ca_path = @ssl_ca_path if @ssl_ca_path and not @ssl_ca_path.empty? ssl_context.verify_mode = OpenSSL::SSL::VERIFY_PEER else ssl_context.verify_mode = OpenSSL::SSL::VERIFY_NONE end if @ssl_cert and not @ssl_cert.empty? client_cert = OpenSSL::X509::Certificate.new(open(File.expand_path(@ssl_cert))) client_key = OpenSSL::PKey.read(open(File.expand_path(@ssl_cert))) ssl_context.add_certificate(client_cert, client_key) end 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 295 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 368 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 409 def flush @sock.flush end
get the next line from the server (blocks)
# File lib/rbot/ircsocket.rb, line 384 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 375 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 400 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 414 def select(timeout=nil) Kernel.select([@sock], nil, nil, timeout) end
shutdown the connection to the server
# File lib/rbot/ircsocket.rb, line 419 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 @server_uri = 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 457 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 435 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