class ServerEngine::SocketManager::Server

Attributes

path[R]
tcp_sockets[R]
udp_sockets[R]

Public Class Methods

generate_path() click to toggle source
# File lib/serverengine/socket_manager.rb, line 75
def self.generate_path
  if ServerEngine.windows?
    port = ENV['SERVERENGINE_SOCKETMANAGER_PORT']
    return port.to_i if port

    excluded_port_ranges = get_excluded_port_ranges
    get_dynamic_port_range
      .reject { |port| excluded_port_ranges.any? { |range| range.cover?(port) } }
      .find { |port| `netstat -na | findstr "#{port}"`.length == 0 }
  else
    base_dir = (ENV['SERVERENGINE_SOCKETMANAGER_SOCK_DIR'] || '/tmp')
    File.join(base_dir, 'SERVERENGINE_SOCKETMANAGER_' + Time.now.utc.iso8601 + '_' + Process.pid.to_s)
  end
end
get_dynamic_port_range() click to toggle source
# File lib/serverengine/socket_manager.rb, line 194
def self.get_dynamic_port_range
  numbers = []
  # Example output of netsh (actual output is localized):
  #
  # Protocol tcp Dynamic Port Range
  # ---------------------------------
  # Start Port      : 49152
  # Number of Ports : 16384
  #
  str = `netsh int ipv4 show dynamicport tcp`.force_encoding("ASCII-8BIT")
  str.each_line { |line| numbers << $1.to_i if line.match(/.*: (\d+)/) }

  start_port, n_ports = numbers[0], numbers[1]
  end_port = start_port + n_ports - 1

  if valid_dynamic_port_range(start_port, end_port)
    return start_port..end_port
  else
    # The default dynamic port range is 49152 - 65535 as of Windows Vista
    # and Windows Server 2008.
    # https://docs.microsoft.com/en-us/troubleshoot/windows-server/networking/default-dynamic-port-range-tcpip-chang
    return 49152..65535
  end
end
get_excluded_port_ranges() click to toggle source
# File lib/serverengine/socket_manager.rb, line 219
def self.get_excluded_port_ranges
  # Example output of netsh:
  #
  # Protocol tcp Port Exclusion Ranges
  #
  # Start Port    End Port
  # ----------    --------
  #       2869        2869
  #      49152       49251
  #      50000       50059     *
  #      57095       57194
  #
  # * - Administered port exclusions.
  #
  `netsh int ipv4 show excludedportrange tcp`
    .force_encoding("ASCII-8BIT")
    .lines
    .map { |line| line.match(/\s*(\d+)\s*(\d+)[\s\*]*/) ? $1.to_i..$2.to_i : nil }
    .compact
end
new(path, start: true) click to toggle source
# File lib/serverengine/socket_manager.rb, line 106
def initialize(path, start: true)
  @tcp_sockets = {}
  @udp_sockets = {}
  @mutex = Mutex.new
  @path = start ? start_server(path) : path
end
open(path = nil) click to toggle source
# File lib/serverengine/socket_manager.rb, line 90
def self.open(path = nil)
  return new(path) unless path.nil?
  if ServerEngine.windows?
    new(0)
  else
    new(self.generate_path)
  end
end
share_sockets_with_another_server(path) click to toggle source
# File lib/serverengine/socket_manager.rb, line 99
def self.share_sockets_with_another_server(path)
  raise NotImplementedError, "Not supported on Windows." if ServerEngine.windows?
  server = new(path, start: false)
  server.share_sockets_with_another_server
  server
end
valid_dynamic_port_range(start_port, end_port) click to toggle source
# File lib/serverengine/socket_manager.rb, line 187
def self.valid_dynamic_port_range(start_port, end_port)
  return false if start_port < 1025 or start_port > 65535
  return false if end_port < 1025 or end_port > 65535
  return false if start_port > end_port
  true
end

Public Instance Methods

close() click to toggle source
# File lib/serverengine/socket_manager.rb, line 125
def close
  stop_server
  nil
end
new_client() click to toggle source
# File lib/serverengine/socket_manager.rb, line 116
def new_client
  Client.new(@path)
end
start() click to toggle source
# File lib/serverengine/socket_manager.rb, line 120
def start
  start_server(path)
  nil
end

Private Instance Methods

listen(proto, bind, port) click to toggle source
# File lib/serverengine/socket_manager.rb, line 132
def listen(proto, bind, port)
  sockets, new_method = case proto
                        when :tcp then [@tcp_sockets, :listen_tcp_new]
                        when :udp then [@udp_sockets, :listen_udp_new]
                        else
                          raise ArgumentError, "invalid protocol: #{proto}"
                        end
  key, bind_ip = resolve_bind_key(bind, port)

  @mutex.synchronize do
    unless sockets.has_key?(key)
      sockets[key] = send(new_method, bind_ip, port)
    end
    return sockets[key]
  end
end
listen_tcp(bind, port) click to toggle source
# File lib/serverengine/socket_manager.rb, line 149
def listen_tcp(bind, port)
  listen(:tcp, bind, port)
end
listen_udp(bind, port) click to toggle source
# File lib/serverengine/socket_manager.rb, line 153
def listen_udp(bind, port)
  listen(:udp, bind, port)
end
process_peer(peer) click to toggle source
# File lib/serverengine/socket_manager.rb, line 170
def process_peer(peer)
  while true
    res = SocketManager.recv_peer(peer)
    return if res.nil?

    pid, method, *opts = res
    begin
      send_socket(peer, pid, method, *opts)
    rescue => e
      SocketManager.send_peer(peer, e)
    end
  end
ensure
  peer.close
end
resolve_bind_key(bind, port) click to toggle source
# File lib/serverengine/socket_manager.rb, line 157
def resolve_bind_key(bind, port)
  bind_ip = IPAddr.new(IPSocket.getaddress(bind))
  if bind_ip.ipv6?
    return "[#{bind_ip}]:#{port}", bind_ip
  else
    # assuming ipv4
    if bind_ip == "127.0.0.1" or bind_ip == "0.0.0.0"
      return "localhost:#{port}", bind_ip
    end
    return "#{bind_ip}:#{port}", bind_ip
  end
end