Class | Irc::Socket |
In: |
lib/rbot/ircsocket.rb
|
Parent: | Object |
wrapped TCPSocket for communication with the server. emulates a subset of TCPSocket functionality
MAX_IRC_SEND_PENALTY | = | 10 |
bytes_received | [R] | total number of bytes received from the irc server |
bytes_sent | [R] | total number of bytes sent to the irc server |
filter | [R] | an optional filter object. we call @filter.in(data) for all incoming data and @filter.out(data) for all outgoing data |
lines_received | [R] | total number of lines received from the irc server |
lines_sent | [R] | total number of lines sent to the irc server |
penalty_pct | [RW] | penalty multiplier (percent) |
server_uri | [R] | normalized uri of the current server |
throttle_bytes | [R] | accumulator for the throttle |
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 277: def initialize(server_list, host, opts={}) 278: @server_list = server_list.dup 279: @server_uri = nil 280: @conn_count = 0 281: @host = host 282: @sock = nil 283: @filter = IdentityFilter.new 284: @spooler = false 285: @lines_sent = 0 286: @lines_received = 0 287: @ssl = opts[:ssl] 288: @penalty_pct = opts[:penalty_pct] || 100 289: end
open a TCP connection to the server
# File lib/rbot/ircsocket.rb, line 296 296: def connect 297: if connected? 298: warning "reconnecting while connected" 299: return 300: end 301: srv_uri = @server_list[@conn_count % @server_list.size].dup 302: srv_uri = 'irc://' + srv_uri if !(srv_uri =~ /:\/\//) 303: @conn_count += 1 304: @server_uri = URI.parse(srv_uri) 305: @server_uri.port = 6667 if !@server_uri.port 306: debug "connection attempt \##{@conn_count} (#{@server_uri.host}:#{@server_uri.port})" 307: 308: if(@host) 309: begin 310: sock=TCPSocket.new(@server_uri.host, @server_uri.port, @host) 311: rescue ArgumentError => e 312: error "Your version of ruby does not support binding to a " 313: error "specific local address, please upgrade if you wish " 314: error "to use HOST = foo" 315: error "(this option has been disabled in order to continue)" 316: sock=TCPSocket.new(@server_uri.host, @server_uri.port) 317: end 318: else 319: sock=TCPSocket.new(@server_uri.host, @server_uri.port) 320: end 321: if(@ssl) 322: require 'openssl' 323: ssl_context = OpenSSL::SSL::SSLContext.new() 324: ssl_context.verify_mode = OpenSSL::SSL::VERIFY_NONE 325: sock = OpenSSL::SSL::SSLSocket.new(sock, ssl_context) 326: sock.sync_close = true 327: sock.connect 328: end 329: @sock = sock 330: @last_send = Time.new 331: @flood_send = Time.new 332: @burst = 0 333: @sock.extend(MonitorMixin) 334: @sendq = MessageQueue.new 335: @qthread = Thread.new { writer_loop } 336: 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 343: def emergency_puts(message, penalty = false) 344: @sock.synchronize do 345: # debug "In puts - got @sock" 346: puts_critical(message, penalty) 347: end 348: end
set filter to identity, not to nil
# File lib/rbot/ircsocket.rb, line 270 270: def filter=(f) 271: @filter = f || IdentityFilter.new 272: end
get the next line from the server (blocks)
# File lib/rbot/ircsocket.rb, line 359 359: def gets 360: if @sock.nil? 361: warning "socket get attempted while closed" 362: return nil 363: end 364: begin 365: reply = @filter.in(@sock.gets) 366: @lines_received += 1 367: reply.strip! if reply 368: debug "RECV: #{reply.inspect}" 369: return reply 370: rescue Exception => e 371: handle_socket_error(:RECV, e) 372: end 373: end
# File lib/rbot/ircsocket.rb, line 350 350: def handle_socket_error(string, e) 351: error "#{string} failed: #{e.pretty_inspect}" 352: # We assume that an error means that there are connection 353: # problems and that we should reconnect, so we 354: shutdown 355: raise SocketError.new(e.inspect) 356: end
# File lib/rbot/ircsocket.rb, line 375 375: def queue(msg, chan=nil, ring=0) 376: @sendq.push msg, chan, ring 377: end
Wraps Kernel.select on the socket
# File lib/rbot/ircsocket.rb, line 389 389: def select(timeout=nil) 390: Kernel.select([@sock], nil, nil, timeout) 391: end
shutdown the connection to the server
# File lib/rbot/ircsocket.rb, line 394 394: def shutdown(how=2) 395: return unless connected? 396: @qthread.kill 397: @qthread = nil 398: begin 399: @sock.close 400: rescue Exception => e 401: error "error while shutting down: #{e.pretty_inspect}" 402: end 403: @sock = nil 404: @sendq.clear 405: end
same as puts, but expects to be called with a lock held on @sock
# File lib/rbot/ircsocket.rb, line 431 431: def puts_critical(message, penalty=false) 432: # debug "in puts_critical" 433: begin 434: debug "SEND: #{message.inspect}" 435: if @sock.nil? 436: error "SEND attempted on closed socket" 437: else 438: # we use Socket#syswrite() instead of Socket#puts() because 439: # the latter is racy and can cause double message output in 440: # some circumstances 441: actual = @filter.out(message) + "\n" 442: now = Time.new 443: @sock.syswrite actual 444: @last_send = now 445: @flood_send = now if @flood_send < now 446: @flood_send += message.irc_send_penalty*@penalty_pct/100.0 if penalty 447: @lines_sent += 1 448: end 449: rescue Exception => e 450: handle_socket_error(:SEND, e) 451: end 452: end
# File lib/rbot/ircsocket.rb, line 409 409: def writer_loop 410: loop do 411: begin 412: now = Time.now 413: flood_delay = @flood_send - MAX_IRC_SEND_PENALTY - now 414: delay = [flood_delay, 0].max 415: if delay > 0 416: debug "sleep(#{delay}) # (f: #{flood_delay})" 417: sleep(delay) 418: end 419: msg = @sendq.shift 420: debug "got #{msg.inspect} from queue, sending" 421: emergency_puts(msg, true) 422: rescue Exception => e 423: error "Spooling failed: #{e.pretty_inspect}" 424: debug e.backtrace.join("\n") 425: raise e 426: end 427: end 428: end