# 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.# Console.logger.warn(self) {"Unable to set sync=#{value}!"}rescueErrno::ENOPROTOOPT# It may not be supported by the protocol (e.g. UDP). ¯\_(ツ)_/¯enddefasyncraiseNotImplementedErrorend# Build and wrap the underlying io.# @option reuse_port [Boolean] Allow this port to be bound in multiple processes.# @option reuse_address [Boolean] Allow this port to be bound in multiple processes.# @option linger [Boolean] Wait for data to be sent before closing the socket.# @option buffered [Boolean] Enable or disable Nagle's algorithm for TCP sockets.defbuild(*arguments,timeout: nil,reuse_address: true,reuse_port: nil,linger: nil,buffered: false)socket=::Socket.new(*arguments)# Set the timeout:iftimeoutset_timeout(socket,timeout)endifreuse_addresssocket.setsockopt(SOL_SOCKET,SO_REUSEADDR,1)endifreuse_portsocket.setsockopt(SOL_SOCKET,SO_REUSEPORT,1)endiflingersocket.setsockopt(SOL_SOCKET,SO_LINGER,1)endifbuffered==falseset_buffered(socket,buffered)endyieldsocketifblock_given?returnsocketrescuesocket&.closeraiseend# Establish a connection to a given `remote_address`.# @example# socket = Async::IO::Socket.connect(Async::IO::Address.tcp("8.8.8.8", 53))# @param remote_address [Address] The remote address to connect to.# @option local_address [Address] The local address to bind to before connecting.defconnect(remote_address,local_address: nil,**options)socket=build(remote_address.afamily,remote_address.socktype,remote_address.protocol,**options)do|socket|iflocal_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)endendbeginsocket.connect(remote_address.to_sockaddr)rescueExceptionsocket.closeraiseendreturnsocketunlessblock_given?beginyieldsocketensuresocket.closeendend# Bind to a local address.# @example# socket = Async::IO::Socket.bind(Async::IO::Address.tcp("0.0.0.0", 9090))# @param local_address [Address] The local address to bind to.# @option protocol [Integer] The socket protocol to use.defbind(local_address,protocol: 0,**options,&block)socket=build(local_address.afamily,local_address.socktype,protocol,**options)do|socket|socket.bind(local_address.to_sockaddr)endreturnsocketunlessblock_given?asyncdobeginyieldsocketensuresocket.closeendendend# Bind to a local address and accept connections in a loop.defaccept(*arguments,backlog: SOMAXCONN,**options,&block)bind(*arguments,**options)do|server|server.listen(backlog)ifbacklogasyncdowhiletrueserver.accept(&block)endendendendendclassThreadWrapper<Wrapperdefasync(&block)Thread.new(&block)endendclassFiberWrapper<Wrapperdefasync(&block)Fiber.schedule(&block)endenddefWrapper.defaultifFiber.schedulerFiberWrapper.newelseThreadWrapper.newendendend