require "./tcp_socket" # A Transmission Control Protocol (TCP/IP) server. # # NOTE: To use `TCPServer`, you must explicitly import it with `require "socket"` # # Usage example: # ``` # require "socket" # # def handle_client(client) # message = client.gets # client.puts message # end # # server = TCPServer.new("localhost", 1234) # while client = server.accept? # spawn handle_client(client) # end # ``` # # Options: # - *host* local interface to bind on, or `::` to bind on all local interfaces. # - *port* specific port to bind on, or `0` to receive an "ephemeral" (free, assigned by kernel) port. # - *backlog* to specify how many pending connections are allowed. # - *reuse_port* to enable multiple processes to bind to the same port (`SO_REUSEPORT`). class TCPServer < TCPSocket include Socket::Server # Creates a new `TCPServer`, waiting to be bound. def self.new(family : Family = Family::INET) super(family) end # Binds a socket to the *host* and *port* combination. def initialize(host : String, port : Int, backlog : Int = SOMAXCONN, dns_timeout = nil, reuse_port : Bool = false) Addrinfo.tcp(host, port, timeout: dns_timeout) do |addrinfo| super(addrinfo.family, addrinfo.type, addrinfo.protocol) self.reuse_address = true self.reuse_port = true if reuse_port if errno = @fd_lock.reference { system_bind(addrinfo, "#{host}:#{port}") } close next errno end if errno = listen(backlog) { |errno| errno } close next errno end end end # Creates a TCPServer from an existing system file descriptor or socket # handle. # # This adopts *fd* into the IO system that will reconfigure it as per the # event loop runtime requirements. # # NOTE: On Windows, the handle must have been created with # `WSA_FLAG_OVERLAPPED`. def initialize(*, fd : Handle, family : Family = Family::INET) super(fd: fd, family: family) end # Creates a new TCP server, listening on all local interfaces (`::`). def self.new(port : Int, backlog = SOMAXCONN, reuse_port = false) new("::", port, backlog, reuse_port: reuse_port) end # Creates a new TCP server and yields it to the block. Eventually closes the # server socket when the block returns. # # Returns the value of the block. def self.open(host, port, backlog = SOMAXCONN, reuse_port = false, &) server = new(host, port, backlog, reuse_port: reuse_port) begin yield server ensure server.close end end # Creates a new TCP server, listening on all interfaces, and yields it to the # block. Eventually closes the server socket when the block returns. # # Returns the value of the block. def self.open(port : Int, backlog = SOMAXCONN, reuse_port = false, &) server = new(port, backlog, reuse_port: reuse_port) begin yield server ensure server.close end end # Accepts an incoming connection. # # Returns the client `TCPSocket` or `nil` if the server is closed after invoking # this method. # # ``` # require "socket" # # server = TCPServer.new(2022) # loop do # if socket = server.accept? # # handle the client in a fiber # spawn handle_connection(socket) # else # # another fiber closed the server # break # end # end # ``` def accept? : TCPSocket? return if closed? if rs = @fd_lock.read { system_accept } sock = TCPSocket.new(handle: rs[0], family: family, type: type, protocol: protocol, blocking: rs[1]) sock.sync = sync? sock end end end