# frozen_string_literal: true# Released under the MIT License.# Copyright, 2023, by Samuel Williams.require'socket'moduleIO::EndpointclassWrapperinclude::Socket::ConstantsifIO.method_defined?(:timeout=)defset_timeout(io,timeout)io.timeout=timeoutendelsedefset_timeout(io,timeout)warn"IO#timeout= not supported on this platform."endenddefset_buffered(socket,buffered)casebufferedwhentruesocket.setsockopt(IPPROTO_TCP,TCP_NODELAY,0)whenfalsesocket.setsockopt(IPPROTO_TCP,TCP_NODELAY,1)endrescueErrno::EINVAL# On Darwin, sometimes occurs when the connection is not yet fully formed. Empirically, TCP_NODELAY is enabled despite this result.rescueErrno::EOPNOTSUPP# Some platforms may simply not support the operation.rescueErrno::ENOPROTOOPT# It may not be supported by the protocol (e.g. UDP). ¯\_(ツ)_/¯enddefasyncraiseNotImplementedErrorend# Establish a connection to a given `remote_address`.# @example# socket = Async::IO::Socket.connect(Async::IO::Address.tcp("8.8.8.8", 53))# @parameter remote_address [Address] The remote address to connect to.# @parameter linger [Boolean] Wait for data to be sent before closing the socket.# @parameter local_address [Address] The local address to bind to before connecting.defconnect(remote_address,local_address: nil,linger: nil,timeout: nil,buffered: false,**options)socket=nilbeginsocket=::Socket.new(remote_address.afamily,remote_address.socktype,remote_address.protocol)iflingersocket.setsockopt(SOL_SOCKET,SO_LINGER,1)endifbuffered==falseset_buffered(socket,buffered)endiftimeoutset_timeout(socket,timeout)endiflocal_addressifdefined?(IP_BIND_ADDRESS_NO_PORT)# Inform the kernel (Linux 4.2+) to not reserve an ephemeral port when using bind(2) with a port number of 0. The port will later be automatically chosen at connect(2) time, in a way that allows sharing a source port as long as the 4-tuple is unique.socket.setsockopt(SOL_IP,IP_BIND_ADDRESS_NO_PORT,1)endsocket.bind(local_address.to_sockaddr)endrescuesocket&.closeraiseendbeginsocket.connect(remote_address.to_sockaddr)rescueExceptionsocket.closeraiseendreturnsocketunlessblock_given?beginyieldsocketensuresocket.closeendend# JRuby requires ServerSocketifdefined?(::ServerSocket)ServerSocket=::ServerSocketelseServerSocket=::Socketend# Bind to a local address.# @example# socket = Async::IO::Socket.bind(Async::IO::Address.tcp("0.0.0.0", 9090))# @parameter local_address [Address] The local address to bind to.# @parameter reuse_port [Boolean] Allow this port to be bound in multiple processes.# @parameter reuse_address [Boolean] Allow this port to be bound in multiple processes.# @parameter linger [Boolean] Wait for data to be sent before closing the socket.# @parameter protocol [Integer] The socket protocol to use.defbind(local_address,protocol: 0,reuse_address: true,reuse_port: nil,linger: nil,bound_timeout: nil,**options,&block)socket=nilbeginsocket=ServerSocket.new(local_address.afamily,local_address.socktype,protocol)ifreuse_addresssocket.setsockopt(SOL_SOCKET,SO_REUSEADDR,1)endifreuse_portsocket.setsockopt(SOL_SOCKET,SO_REUSEPORT,1)endiflingersocket.setsockopt(SOL_SOCKET,SO_LINGER,1)end# Set the timeout:ifbound_timeoutset_timeout(socket,bound_timeout)endsocket.bind(local_address.to_sockaddr)rescuesocket&.closeraiseendreturnsocketunlessblock_given?asyncdobeginyieldsocketensuresocket.closeendendend# Bind to a local address and accept connections in a loop.defaccept(server,timeout: nil,linger: nil,**options,&block)whiletruesocket,address=server.acceptiflingersocket.setsockopt(SOL_SOCKET,SO_LINGER,1)endiftimeoutset_timeout(socket,timeout)endasyncdoyieldsocket,addressendendendendclassThreadWrapper<Wrapperdefasync(&block)::Thread.new(&block)endendclassFiberWrapper<Wrapperdefasync(&block)::Fiber.schedule(&block)endendifFiber.respond_to?(:scheduler)defWrapper.defaultifFiber.schedulerFiberWrapper.newelseThreadWrapper.newendendelsedefWrapper.defaultThreadWrapper.newendendend