class Socket

Socket 类提供了对底层操作系统套接字实现的访问。它可以用于提供比协议特定的套接字类更多的操作系统特定功能。

Socket::Constants 下定义的常量也在 Socket 下定义。 例如,Socket::AF_INET 和 Socket::Constants::AF_INET 都可以使用。有关常量列表,请参见 Socket::Constants

什么是套接字?

套接字是双向通信通道的端点。套接字可以在进程内、同一机器上的进程之间或不同机器之间进行通信。有多种类型的套接字:例如,TCPSocketUDPSocketUNIXSocket

套接字有其自己的词汇

域:协议族

类型:两个端点之间的通信类型,通常是

协议:通常为。这可用于标识协议的变体。

主机名:网络接口的标识符

  • 字符串(主机名、IPv4 或 IPv6 地址或 broadcast,指定广播地址)

  • 指定 INADDR_ANY 的零长度字符串

  • 整数(解释为主机字节顺序的二进制地址)。

快速入门

与等效的 C 编程接口相比,许多类(例如 TCPSocketUDPSocketUNIXSocket)简化了套接字的使用。

让我们以类似于 C 的方式使用 IPv4 协议创建一个 Internet 套接字

require 'socket'

s = Socket.new Socket::AF_INET, Socket::SOCK_STREAM
s.connect Socket.pack_sockaddr_in(80, 'example.com')

您也可以使用 TCPSocket

s = TCPSocket.new 'example.com', 80

一个简单的服务器可能如下所示

require 'socket'

server = TCPServer.new 2000 # Server bound to port 2000

loop do
  client = server.accept    # Wait for a client to connect
  client.puts "Hello !"
  client.puts "Time is #{Time.now}"
  client.close
end

一个简单的客户端可能如下所示

require 'socket'

s = TCPSocket.new 'localhost', 2000

while line = s.gets # Read lines from socket
  puts line         # and print them
end

s.close             # close socket when done

异常处理

Ruby 的 Socket 实现会根据系统相关实现生成的错误引发异常。 这就是为什么方法的文档化方式将基于 Unix 的系统异常与基于 Windows 的异常隔离开来。如果需要有关特定异常的更多信息,请参阅 Unix 手册页或 Windows WinSock 参考。

便捷方法

虽然创建套接字的一般方法是 Socket.new,但在大多数情况下,有几种创建套接字的方法。

TCP 客户端套接字

Socket.tcp、TCPSocket.open

TCP 服务器套接字

Socket.tcp_server_loop、TCPServer.open

UNIX 客户端套接字

Socket.unix、UNIXSocket.open

UNIX 服务器套接字

Socket.unix_server_loop、UNIXServer.open

文档作者

  • Zach Dennis

  • Sam Roberts

  • The Pragmatic Bookshelf 的Programming Ruby

本文档中的许多材料经许可取自 The Pragmatic Bookshelf 的Programming Ruby

常量

ADDRESS_FAMILIES
AF_ALG

内核加密 API 的接口

AF_APPLETALK

AppleTalk 协议

AF_ATM

异步传输模式

AF_AX25

AX.25 协议

AF_BLUETOOTH

蓝牙低级套接字协议

AF_CAN

控制器局域网汽车总线协议

AF_CCITT

CCITT(现在为 ITU-T)协议

AF_CHAOS

麻省理工学院 CHAOS 协议

AF_CNT

计算机网络技术

AF_COIP

面向连接的 IP

AF_DATAKIT

Datakit 协议

AF_DEC

DECnet 协议

AF_DECnet

DECnet 协议

AF_DLI

DEC 直接数据链路接口协议

AF_E164

CCITT (ITU-T) E.164 建议

AF_ECMA

欧洲计算机制造商协议

NSC Hyperchannel 协议

AF_IB

InfiniBand 本机寻址

ARPANET IMP 协议

AF_INET

IPv4 协议

AF_INET6

IPv6 协议

AF_IPX

IPX 协议

AF_ISDN

综合业务数字网络

AF_ISO

ISO 开放系统互连协议

AF_KCM

KCM(内核连接多路复用器)接口

AF_KEY

密钥管理协议,最初为与 IPsec 一起使用而开发

AF_LAT

局域传输协议

链路层接口

AF_LLC

逻辑链路控制 (IEEE 802.2 LLC) 协议

AF_LOCAL

主机内部协议

AF_MAX

此平台的最大地址族

AF_MPLS

多协议标签交换

AF_NATM

本机 ATM 访问

AF_NDRV

网络驱动程序原始访问

AF_NETBIOS

NetBIOS

AF_NETGRAPH

Netgraph 套接字

内核用户界面设备

AF_NS

XEROX NS 协议

AF_OSI

ISO 开放系统互连协议

AF_PACKET

直接链路层访问

AF_PPP

点对点协议

AF_PPPOX

通用 PPP 传输层,用于设置 L2 隧道(L2TP 和 PPPoE)

AF_PUP

PARC 通用数据包协议

AF_RDS

可靠数据报套接字 (RDS) 协议

AF_ROUTE

内部路由协议

AF_SIP

简单互联网协议

AF_SNA

IBM SNA 协议

AF_SYSTEM

内核事件消息

AF_TIPC

TIPC,“集群域套接字”协议

AF_UNIX

UNIX 套接字

AF_UNSPEC

未指定的协议,任何受支持的地址族

AF_VSOCK

用于管理程序-客户机通信的 VSOCK(最初为“VMWare VSockets”)协议

AF_XDP

XDP(快速数据路径)接口

AI_ADDRCONFIG

仅在分配任何地址时接受

AI_ALL

允许所有地址

AI_CANONNAME

填写规范名称

AI_DEFAULT

getaddrinfo 的默认标志

AI_MASK

getaddrinfo 的有效标志掩码(不适用于应用程序)

AI_NUMERICHOST

阻止主机名解析

AI_NUMERICSERV

阻止服务名称解析

AI_PASSIVE

获取用于 bind() 的地址

AI_V4MAPPED

接受 IPv4 映射的 IPv6 地址

AI_V4MAPPED_CFG

如果内核支持,则接受 IPv4 映射的地址

CONNECTION_ATTEMPT_DELAY
EAI_ADDRFAMILY

不支持主机名的地址族

EAI_AGAIN

名称解析中的临时故障

EAI_BADFLAGS

无效标志

EAI_BADHINTS

提示的无效值

EAI_FAIL

名称解析中不可恢复的失败

EAI_FAMILY

不支持的地址族

EAI_MAX

getaddrinfo 的最大错误代码

EAI_MEMORY

内存分配失败

EAI_NODATA

没有与主机名关联的地址

EAI_NONAME

主机名或 servname,或者未知

EAI_OVERFLOW

参数缓冲区溢出

EAI_PROTOCOL

已解析的协议未知

EAI_SERVICE

套接字类型不支持 servname

EAI_SOCKTYPE

不支持的套接字类型

EAI_SYSTEM

在 errno 中返回的系统错误

HOSTNAME_RESOLUTION_QUEUE_UPDATED
IFF_802_1Q_VLAN

802.1Q VLAN 设备

IFF_ALLMULTI

接收所有多播数据包

IFF_ALTPHYS

使用备用物理连接

IFF_AUTOMEDIA

自动媒体选择处于活动状态

IFF_BONDING

绑定主设备或从设备

IFF_BRIDGE_PORT

用作网桥端口的设备

IFF_BROADCAST

广播地址有效

IFF_CANTCHANGE

标志不可更改

IFF_CANTCONFIG

无法使用 ioctl(2) 配置

IFF_DEBUG

打开调试

IFF_DISABLE_NETPOLL

在运行时禁用 netpoll

IFF_DONT_BRIDGE

不允许桥接此以太网设备

IFF_DORMANT

驱动程序发出休眠信号

IFF_DRV_OACTIVE

tx 硬件队列已满

IFF_DRV_RUNNING

已分配资源

IFF_DYING

接口正在关闭

IFF_DYNAMIC

具有更改地址的拨号设备

IFF_EBRIDGE

以太网桥接设备

IFF_ECHO

回显发送的数据包

IFF_ISATAP

ISATAP 接口 (RFC4214)

IFF_LINK0

每个链路层定义的位 0

IFF_LINK1

每个链路层定义的位 1

IFF_LINK2

每个链路层定义的位 2

IFF_LIVE_ADDR_CHANGE

硬件地址在运行时更改

IFF_LOOPBACK

环回网络

IFF_LOWER_UP

驱动程序发出 L1 up 信号

IFF_MACVLAN_PORT

用作 macvlan 端口的设备

IFF_MASTER

负载均衡器的主设备

IFF_MASTER_8023AD

绑定主设备,802.3ad。

IFF_MASTER_ALB

绑定主设备,balance-alb。

IFF_MASTER_ARPMON

绑定主设备,正在使用 ARP 监控

IFF_MONITOR

用户请求的监控模式

IFF_MULTICAST

支持多播

IFF_NOARP

无地址解析协议

IFF_NOTRAILERS

避免使用拖车

IFF_OACTIVE

正在传输

IFF_OVS_DATAPATH

用作 Open vSwitch 数据路径端口的设备

IFF_POINTOPOINT

点对点链接

IFF_PORTSEL

可以设置媒体类型

IFF_PPROMISC

用户请求的混杂模式

IFF_PROMISC

接收所有数据包

IFF_RENAMING

接口正在重命名

IFF_ROUTE

已安装路由条目

IFF_RUNNING

已分配资源

IFF_SIMPLEX

无法听到自己的传输

IFF_SLAVE

负载均衡器的从设备

IFF_SLAVE_INACTIVE

绑定从设备不是当前活动设备

IFF_SLAVE_NEEDARP

需要 ARP 进行验证

IFF_SMART

接口管理自己的路由

IFF_STATICARP

静态 ARP

IFF_SUPP_NOFCS

发送自定义 FCS

IFF_TEAM_PORT

用作团队端口

IFF_TX_SKB_SHARING

在传输时共享 skb

IFF_UNICAST_FLT

单播筛选

IFF_UP

接口已启动

IFF_VOLATILE

易失性标志

IFF_WAN_HDLC

WAN HDLC 设备

IFF_XMIT_DST_RELEASE

dev_hard_start_xmit() 允许释放 skb->dst

IFNAMSIZ

最大接口名称大小

IF_NAMESIZE

最大接口名称大小

INADDR_ALLHOSTS_GROUP

此子网上所有系统的多播组

INADDR_ANY

绑定到 INADDR_ANY 的套接字接收来自所有接口的数据包,并从默认 IP 地址发送

INADDR_BROADCAST

网络广播地址

INADDR_LOOPBACK

环回地址

INADDR_MAX_LOCAL_GROUP

最后一个本地网络多播组

INADDR_NONE

用于匹配无效 IP 地址的位掩码

INADDR_UNSPEC_GROUP

保留的多播组

INET6_ADDRSTRLEN

IPv6 地址字符串的最大长度

INET_ADDRSTRLEN

IPv4 地址字符串的最大长度

IPPORT_RESERVED

绑定或连接的默认最小地址

IPPORT_USERRESERVED

绑定或连接的默认最大地址

IPPROTO_AH

IP6 认证头

IPPROTO_BIP

IPPROTO_BIP

IPPROTO_DSTOPTS

IP6 目标选项

IPPROTO_EGP

外部网关协议

IPPROTO_EON

ISO cnlp

IPPROTO_ESP

IP6 封装安全负载

IPPROTO_FRAGMENT

IP6 分段头

IPPROTO_GGP

网关到网关协议

IPPROTO_HELLO

“hello”路由协议

IPPROTO_HOPOPTS

IP6 逐跳选项

IPPROTO_ICMP

控制消息协议

IPPROTO_ICMPV6

ICMP6

IPPROTO_IDP

XNS IDP

IPPROTO_IGMP

组管理协议

IPPROTO_IP

用于 IP 的虚拟协议

IPPROTO_IPV6

IP6 标头

IPPROTO_MAX

最大 IPPROTO 常量

IPPROTO_ND

Sun 网络磁盘协议

IPPROTO_NONE

IP6 无下一标头

IPPROTO_PUP

PARC 通用数据包协议

IPPROTO_RAW

原始 IP 数据包

IPPROTO_ROUTING

IP6 路由标头

IPPROTO_TCP

TCP

IPPROTO_TP

ISO 传输协议类 4

IPPROTO_UDP

UDP

IPPROTO_XTP

Xpress 传输协议

IPV6_ADRESS_FORMAT
IPV6_CHECKSUM

原始套接字的校验和偏移量

IPV6_DONTFRAG

不分段数据包

IPV6_DSTOPTS

目标选项

IPV6_HOPLIMIT

跳数限制

IPV6_HOPOPTS

逐跳选项

IPV6_JOIN_GROUP

加入组播组成员

IPV6_LEAVE_GROUP

离开组播组成员

IPV6_MTU_DISCOVER

路径 MTU 发现

IPV6_MULTICAST_HOPS

IP6 组播跳数

IPV6_MULTICAST_IF

IP6 组播接口

IPV6_MULTICAST_LOOP

IP6 组播环回

IPV6_NEXTHOP

下一跳地址

IPV6_PATHMTU

检索当前路径 MTU

IPV6_PKTINFO

接收带数据报的数据包信息

IPV6_RECVDSTOPTS

接收所有用于响应的 IP6 选项

IPV6_RECVERR

启用扩展的可靠错误消息传递

IPV6_RECVHOPLIMIT

接收带数据报的跳数限制

IPV6_RECVHOPOPTS

接收逐跳选项

IPV6_RECVPATHMTU

接收带数据报的当前路径 MTU

IPV6_RECVPKTINFO

接收目标 IP 地址和传入接口

IPV6_RECVRTHDR

接收路由标头

IPV6_RECVTCLASS

接收流量类别

IPV6_RTHDR

允许删除粘性路由标头

IPV6_RTHDRDSTOPTS

允许删除粘性目标选项标头

IPV6_RTHDR_TYPE_0

路由标头类型 0

IPV6_TCLASS

指定流量类别

IPV6_UNICAST_HOPS

IP6 单播跳数

IPV6_USE_MIN_MTU

使用最小 MTU 大小

IPV6_V6ONLY

仅将 IPv6 与通配符绑定绑定

IPX_TYPE

IPX_TYPE

IP_ADD_MEMBERSHIP

添加组播组成员

IP_ADD_SOURCE_MEMBERSHIP

添加组播组成员

IP_BLOCK_SOURCE

阻止具有给定源地址的 IPv4 组播数据包

IP_DEFAULT_MULTICAST_LOOP

默认组播环回

IP_DEFAULT_MULTICAST_TTL

默认组播 TTL

IP_DONTFRAG

不分段数据包

IP_DROP_MEMBERSHIP

删除组播组成员

IP_DROP_SOURCE_MEMBERSHIP

删除组播组成员

IP_FREEBIND

允许绑定到不存在的 IP 地址

IP_HDRINCL

标头包含在数据中

IP_IPSEC_POLICY

IPsec 安全策略

IP_MAX_MEMBERSHIPS

套接字可以加入的最大组播组数

IP_MINTTL

允许接收数据包的最小 TTL

IP_MSFILTER

组播源筛选

IP_MTU

套接字的最大传输单元

IP_MTU_DISCOVER

路径 MTU 发现

IP_MULTICAST_IF

IP 组播接口

IP_MULTICAST_LOOP

IP 组播环回

IP_MULTICAST_TTL

IP 组播 TTL

IP_ONESBCAST

强制传出的广播数据报具有未定向的广播地址

IP_OPTIONS

要包含在数据包中的 IP 选项

IP_PASSSEC

检索带数据报的安全上下文

IP_PKTINFO

接收带数据报的数据包信息

IP_PKTOPTIONS

接收带数据报的数据包选项

IP_PMTUDISC_DO

始终发送 DF 帧

IP_PMTUDISC_DONT

从不发送 DF 帧

IP_PMTUDISC_WANT

使用按路由提示

IP_PORTRANGE

为具有未指定端口号的套接字设置端口范围

IP_RECVDSTADDR

接收带数据报的 IP 目标地址

IP_RECVERR

启用扩展的可靠错误消息传递

IP_RECVIF

接收带数据报的接口信息

IP_RECVOPTS

接收带数据报的所有 IP 选项

IP_RECVRETOPTS

接收所有用于响应的 IP 选项

IP_RECVSLLA

接收带数据报的链路层地址

IP_RECVTOS

接收传入数据包的 TOS

IP_RECVTTL

接收带数据报的 IP TTL

IP_RETOPTS

要包含在数据报中的 IP 选项

IP_ROUTER_ALERT

通知传输路由器更仔细地检查 IP 数据包的内容

IP_SENDSRCADDR

传出 UDP 数据报的源地址

IP_TOS

IP 服务类型

IP_TRANSPARENT

透明代理

IP_TTL

IP 生存时间

IP_UNBLOCK_SOURCE

取消阻止具有给定源地址的 IPv4 组播数据包

IP_XFRM_POLICY

IP_XFRM_POLICY

LOCAL_CONNWAIT

连接阻塞直到被接受

LOCAL_CREDS

将凭据传递给接收者

LOCAL_PEERCRED

检索对等凭据

MCAST_BLOCK_SOURCE

阻止来自此源的组播数据包

MCAST_EXCLUDE

独占组播源筛选器

MCAST_INCLUDE

包含组播源筛选器

MCAST_JOIN_GROUP

加入组播组

MCAST_JOIN_SOURCE_GROUP

加入组播源组

MCAST_LEAVE_GROUP

离开组播组

MCAST_LEAVE_SOURCE_GROUP

离开组播源组

MCAST_MSFILTER

组播源筛选

MCAST_UNBLOCK_SOURCE

取消阻止来自此源的组播数据包

MSG_COMPAT

记录结束

MSG_CONFIRM

确认路径有效性

MSG_CTRUNC

控制数据在交付前丢失

MSG_DONTROUTE

发送时不使用路由表

MSG_DONTWAIT

此消息应为非阻塞

MSG_EOF

数据完成连接

MSG_EOR

数据完成记录

MSG_ERRQUEUE

从错误队列中获取消息

MSG_FASTOPEN

减少握手过程的步骤

MSG_FIN

MSG_FIN

MSG_FLUSH

保持序列的开始。转储到 so_temp

MSG_HAVEMORE

数据已准备好读取

MSG_HOLD

在 so_temp 中保留片段

MSG_MORE

发送者将发送更多

MSG_NOSIGNAL

不生成 SIGPIPE

MSG_OOB

处理带外数据

MSG_PEEK

查看传入消息

MSG_PROXY

等待完整请求

MSG_RCVMORE

数据保留在当前数据包中

MSG_RST

MSG_RST

MSG_SEND

在 so_temp 中发送数据包

MSG_SYN

MSG_SYN

MSG_TRUNC

数据在交付前被丢弃

MSG_WAITALL

等待完整请求或错误

NI_DGRAM

指定的服务是数据报服务(查找 UDP 端口)

NI_MAXHOST

主机名的最大长度

NI_MAXSERV

服务名称的最大长度

NI_NAMEREQD

需要名称

NI_NOFQDN

本地主机不需要 FQDN,仅返回本地部分

NI_NUMERICHOST

返回数字地址

NI_NUMERICSERV

将服务名称作为数字字符串返回

PF_ALG

内核加密 API 的接口

PF_APPLETALK

AppleTalk 协议

PF_ATM

异步传输模式

PF_AX25

AX.25 协议

PF_BLUETOOTH

蓝牙低级套接字协议

PF_CAN

控制器局域网汽车总线协议

PF_CCITT

CCITT(现在为 ITU-T)协议

PF_CHAOS

麻省理工学院 CHAOS 协议

PF_CNT

计算机网络技术

PF_COIP

面向连接的 IP

PF_DATAKIT

Datakit 协议

PF_DEC

DECnet 协议

PF_DECnet

DECnet 协议

PF_DLI

DEC 直接数据链路接口协议

PF_ECMA

欧洲计算机制造商协议

NSC Hyperchannel 协议

PF_IB

InfiniBand 本机寻址

ARPANET IMP 协议

PF_INET

IPv4 协议

PF_INET6

IPv6 协议

PF_IPX

IPX 协议

PF_ISDN

综合业务数字网络

PF_ISO

ISO 开放系统互连协议

PF_KCM

KCM(内核连接多路复用器)接口

PF_KEY

密钥管理协议,最初为与 IPsec 一起使用而开发

PF_LAT

局域传输协议

链路层接口

PF_LLC

逻辑链路控制 (IEEE 802.2 LLC) 协议

PF_LOCAL

主机内部协议

PF_MAX

此平台的最大地址族

PF_MPLS

多协议标签交换

PF_NATM

本机 ATM 访问

PF_NDRV

网络驱动程序原始访问

PF_NETBIOS

NetBIOS

PF_NETGRAPH

Netgraph 套接字

内核用户界面设备

PF_NS

XEROX NS 协议

PF_OSI

ISO 开放系统互连协议

PF_PACKET

直接链路层访问

PF_PIP

帮助识别 PIP 数据包

PF_PPP

点对点协议

PF_PPPOX

通用 PPP 传输层,用于设置 L2 隧道(L2TP 和 PPPoE)

PF_PUP

PARC 通用数据包协议

PF_RDS

可靠数据报套接字 (RDS) 协议

PF_ROUTE

内部路由协议

PF_RTIP

帮助识别 RTIP 数据包

PF_SIP

简单互联网协议

PF_SNA

IBM SNA 协议

PF_SYSTEM

内核事件消息

PF_TIPC

TIPC,“集群域套接字”协议

PF_UNIX

UNIX 套接字

PF_UNSPEC

未指定的协议,任何受支持的地址族

PF_VSOCK

用于管理程序-客户机通信的 VSOCK(最初为“VMWare VSockets”)协议

PF_XDP

XDP(快速数据路径)接口

PF_XTP

eXpress 传输协议

RESOLUTION_DELAY
SCM_BINTIME

时间戳 (bintime)

SCM_CREDENTIALS

发送者的凭据

SCM_CREDS

处理凭据

SCM_RIGHTS

访问权限

SCM_TIMESTAMP

时间戳 (timeval)

SCM_TIMESTAMPING

时间戳 (timespec 列表) (Linux 2.6.30)

SCM_TIMESTAMPNS

时间规范 (timespec)

SCM_UCRED

用户凭据

SCM_WIFI_STATUS

Wifi 状态 (Linux 3.3)

SHUT_RD

关闭套接字的读取端

SHUT_RDWR

关闭套接字的两端

SHUT_WR

关闭套接字的写入端

SOCK_CLOEXEC

在新文件描述符上设置 close-on-exec (FD_CLOEXEC) 标志。

SOCK_DGRAM

数据报套接字提供无连接、不可靠的消息传递

SOCK_NONBLOCK

在新文件描述符引用的打开文件描述上设置 O_NONBLOCK 文件状态标志 (参见 open(2))。

SOCK_PACKET

设备级数据包访问

SOCK_RAW

原始套接字提供低级访问,用于直接访问或实现网络协议

SOCK_RDM

可靠的数据报套接字提供可靠的消息传递

SOCK_SEQPACKET

顺序数据包套接字为数据报提供排序的、可靠的双向连接

SOCK_STREAM

流式套接字为字节流提供排序的、可靠的双向连接

SOL_ATALK

AppleTalk 套接字选项

SOL_AX25

AX.25 套接字选项

SOL_IP

IP 套接字选项

SOL_IPX

IPX 套接字选项

SOL_SOCKET

套接字级别选项

SOL_TCP

TCP 套接字选项

SOL_UDP

UDP 套接字选项

SOMAXCONN

套接字可以排队的最大连接请求数

SOPRI_BACKGROUND

后台套接字优先级

SOPRI_INTERACTIVE

交互式套接字优先级

SOPRI_NORMAL

普通套接字优先级

SO_ACCEPTCONN

套接字已调用 listen()

SO_ACCEPTFILTER

存在接受过滤器

SO_ALLZONES

绕过区域边界

SO_ATTACH_FILTER

附加接受过滤器

SO_BINDTODEVICE

仅从给定的接口发送数据包

SO_BINTIME

接收带有数据报的时间戳 (bintime)

SO_BPF_EXTENSIONS

查询支持的 BPF 扩展 (Linux 3.14)

SO_BROADCAST

允许发送广播消息

SO_BUSY_POLL

设置低延迟轮询的微秒阈值 (Linux 3.11)

SO_CONNECT_TIME

返回套接字已连接的秒数。此选项仅对面向连接的协议有效 (Windows)

SO_DEBUG

调试信息记录

SO_DETACH_FILTER

分离接受过滤器

SO_DOMAIN

为 socket() 给定的域 (Linux 2.6.32)

SO_DONTROUTE

使用接口地址

SO_DONTTRUNC

保留未读取的数据

SO_ERROR

获取并清除错误状态

SO_GET_FILTER

获取由 SO_ATTACH_FILTER 设置的过滤器 (Linux 3.8)

SO_INCOMING_CPU

接收附加到套接字的 CPU (Linux 3.19)

SO_INCOMING_NAPI_ID

接收附加到 RX 队列的 napi ID (Linux 4.12)

SO_KEEPALIVE

保持连接活动

SO_LINGER

如果存在数据,则在关闭时延迟

SO_LOCK_FILTER

锁定附加到套接字的过滤器 (Linux 3.9)

SO_MAC_EXEMPT

对未标记对等方的强制访问控制豁免

SO_MARK

设置基于标记路由的标记 (Linux 2.6.25)

SO_MAX_PACING_RATE

限制传输层计算的速率。[字节/秒] (Linux 3.13)

SO_NKE

安装套接字级网络内核扩展

SO_NOFCS

设置套接字的 netns (Linux 3.4)

SO_NOSIGPIPE

不要在 EPIPE 上发送 SIGPIPE

SO_NO_CHECK

禁用校验和

SO_NREAD

获取第一个数据包字节计数

SO_OOBINLINE

将接收到的带外数据保留在内联

SO_PASSCRED

接收 SCM_CREDENTIALS 消息

SO_PASSSEC

切换安全上下文传递 (Linux 2.6.18)

SO_PEEK_OFF

设置窥视偏移量 (Linux 3.4)

SO_PEERCRED

连接到此套接字的外部进程的凭据

SO_PEERNAME

连接用户的名称

SO_PEERSEC

获取安全凭据 (Linux 2.6.2)

SO_PRIORITY

此套接字上所有数据包的协议定义的优先级

SO_PROTOCOL

为 socket() 给定的协议 (Linux 2.6.32)

SO_RCVBUF

接收缓冲区大小

SO_RCVBUFFORCE

接收缓冲区大小,没有 rmem_max 限制 (Linux 2.6.14)

SO_RCVLOWAT

接收低水位标记

SO_RCVTIMEO

接收超时

SO_RECVUCRED

接收带有数据报的用户凭据

SO_REUSEADDR

允许本地地址重用

SO_REUSEPORT

允许本地地址和端口重用

SO_RTABLE

为此套接字设置路由表 (OpenBSD)

SO_RXQ_OVFL

切换 cmsg 以获取丢弃的数据包数 (Linux 2.6.33)

SO_SECURITY_AUTHENTICATION

SO_SECURITY_AUTHENTICATION

SO_SECURITY_ENCRYPTION_NETWORK

SO_SECURITY_ENCRYPTION_NETWORK

SO_SECURITY_ENCRYPTION_TRANSPORT

SO_SECURITY_ENCRYPTION_TRANSPORT

SO_SELECT_ERR_QUEUE

使 select() 使用 errorfds 检测套接字错误队列 (Linux 3.10)

SO_SETFIB

为套接字设置关联的路由表 (FreeBSD)

SO_SNDBUF

发送缓冲区大小

SO_SNDBUFFORCE

发送缓冲区大小,没有 wmem_max 限制 (Linux 2.6.14)

SO_SNDLOWAT

发送低水位标记

SO_SNDTIMEO

发送超时

SO_TIMESTAMP

接收带有数据报的时间戳 (timeval)

SO_TIMESTAMPING

传入和传出数据包的时间戳 (Linux 2.6.30)

SO_TIMESTAMPNS

接收带有数据报的纳秒时间戳 (timespec)

SO_TYPE

获取套接字类型

SO_USELOOPBACK

尽可能绕过硬件

主要为 ipfw 目的设置标识符

SO_WANTMORE

在更多数据准备就绪时给出提示

SO_WANTOOBFLAG

希望在接收时,OOB 数据位于 MSG_FLAG 中

SO_WIFI_STATUS

切换 cmsg 以获取 wifi 状态 (Linux 3.3)

TCP_CONGESTION

TCP 拥塞控制算法 (Linux 2.6.13, glibc 2.6)

TCP_CONNECTION_INFO

检索有关此套接字的信息 (macOS)

TCP Cookie 事务 (Linux 2.6.33, glibc 2.18)

TCP_CORK

不发送部分帧 (Linux 2.2, glibc 2.2)

TCP_DEFER_ACCEPT

在数据准备就绪之前,不通知侦听套接字 (Linux 2.4, glibc 2.2)

TCP_FASTOPEN

减少握手过程的步骤 (Linux 3.7, glibc 2.18)

TCP_INFO

检索有关此套接字的信息 (Linux 2.4, glibc 2.2)

TCP_KEEPALIVE

发送保持活动探测之前,等待的空闲时间 (macOS)

TCP_KEEPCNT

在断开连接之前允许的最大保持活动探测数 (Linux 2.4, glibc 2.2)

TCP_KEEPIDLE

发送保持活动探测之前,等待的空闲时间 (Linux 2.4, glibc 2.2)

TCP_KEEPINTVL

保持活动探测之间的时间 (Linux 2.4, glibc 2.2)

TCP_LINGER2

孤立的 FIN_WAIT2 套接字的生命周期 (Linux 2.4, glibc 2.2)

TCP_MAXSEG

设置最大段大小

TCP_MD5SIG

使用 MD5 摘要 (RFC2385, Linux 2.6.20, glibc 2.7)

TCP_NODELAY

不要延迟发送以合并数据包

TCP_NOOPT

不使用 TCP 选项

TCP_NOPUSH

不推送最后一个写入块

TCP_QUEUE_SEQ

修复模式下的队列序列 (Linux 3.5, glibc 2.18)

TCP_QUICKACK

启用快速确认模式 (Linux 2.4.4, glibc 2.3)

TCP_REPAIR

修复模式 (Linux 3.5, glibc 2.18)

TCP_REPAIR_OPTIONS

修复模式的选项 (Linux 3.5, glibc 2.18)

TCP_REPAIR_QUEUE

修复模式的队列 (Linux 3.5, glibc 2.18)

TCP_SYNCNT

在断开连接之前,SYN 重传次数 (Linux 2.4, glibc 2.2)

TCP_THIN_DUPACK

精简流的重复确认处理 (Linux 2.6.34, glibc 2.18)

TCP_THIN_LINEAR_TIMEOUTS

精简流的线性超时 (Linux 2.6.34, glibc 2.18)

TCP_TIMESTAMP

TCP 时间戳 (Linux 3.9, glibc 2.18)

TCP_USER_TIMEOUT

在中止 TCP 连接之前的最大超时时间 (Linux 2.6.37, glibc 2.18)

TCP_WINDOW_CLAMP

限制通告窗口的大小 (Linux 2.4, glibc 2.2)

UDP_CORK

不发送部分帧 (Linux 2.5.44, glibc 2.11)

公共类方法

accept_loop(*sockets) { |socket, client_addrinfo| ... } 单击以切换源代码

为每个通过给定套接字接受的连接,生成套接字和客户端地址。

参数是套接字列表。单个参数应为一个套接字或一个套接字数组。

此方法按顺序生成块。这意味着在块返回之前,不会接受下一个连接。因此,应使用并发机制(例如线程)来同时为多个客户端提供服务。

# File socket/lib/socket.rb, line 1216
def self.accept_loop(*sockets) # :yield: socket, client_addrinfo
  sockets.flatten!(1)
  if sockets.empty?
    raise ArgumentError, "no sockets"
  end
  loop {
    readable, _, _ = IO.select(sockets)
    readable.each {|r|
      sock, addr = r.accept_nonblock(exception: false)
      next if sock == :wait_readable
      yield sock, addr
    }
  }
end
getaddrinfo(nodename, servname[, family[, socktype[, protocol[, flags[, reverse_lookup]]]]]) → array 单击以切换源代码

获取 nodename:servname 的地址信息。

请注意,Addrinfo.getaddrinfo 以面向对象的方式提供相同的功能。

family 应为地址族,例如::INET, :INET6 等。

socktype 应为套接字类型,例如::STREAM, :DGRAM, :RAW 等。

protocol 应为族中定义的协议,默认为该族的 0。

flags 应为 Socket::AI_* 常量的按位或。

Socket.getaddrinfo("www.ruby-lang.org", "http", nil, :STREAM)
#=> [["AF_INET", 80, "carbon.ruby-lang.org", "221.186.184.68", 2, 1, 6]] # PF_INET/SOCK_STREAM/IPPROTO_TCP

Socket.getaddrinfo("localhost", nil)
#=> [["AF_INET", 0, "localhost", "127.0.0.1", 2, 1, 6],  # PF_INET/SOCK_STREAM/IPPROTO_TCP
#    ["AF_INET", 0, "localhost", "127.0.0.1", 2, 2, 17], # PF_INET/SOCK_DGRAM/IPPROTO_UDP
#    ["AF_INET", 0, "localhost", "127.0.0.1", 2, 3, 0]]  # PF_INET/SOCK_RAW/IPPROTO_IP

reverse_lookup 指示第三个元素的形式,并且必须是以下之一。如果省略 reverse_lookup,则默认值为 nil

+true+, +:hostname+:  hostname is obtained from numeric address using reverse lookup, which may take a time.
+false+, +:numeric+:  hostname is the same as numeric address.
+nil+:              obey to the current +do_not_reverse_lookup+ flag.

如果首选 Addrinfo 对象,请使用 Addrinfo.getaddrinfo

static VALUE
sock_s_getaddrinfo(int argc, VALUE *argv, VALUE _)
{
    VALUE host, port, family, socktype, protocol, flags, ret, revlookup;
    struct addrinfo hints;
    struct rb_addrinfo *res;
    int norevlookup;

    rb_scan_args(argc, argv, "25", &host, &port, &family, &socktype, &protocol, &flags, &revlookup);

    MEMZERO(&hints, struct addrinfo, 1);
    hints.ai_family = NIL_P(family) ? PF_UNSPEC : rsock_family_arg(family);

    if (!NIL_P(socktype)) {
        hints.ai_socktype = rsock_socktype_arg(socktype);
    }
    if (!NIL_P(protocol)) {
        hints.ai_protocol = NUM2INT(protocol);
    }
    if (!NIL_P(flags)) {
        hints.ai_flags = NUM2INT(flags);
    }
    if (NIL_P(revlookup) || !rsock_revlookup_flag(revlookup, &norevlookup)) {
        norevlookup = rsock_do_not_reverse_lookup;
    }

    res = rsock_getaddrinfo(host, port, &hints, 0);

    ret = make_addrinfo(res, norevlookup);
    rb_freeaddrinfo(res);
    return ret;
}
gethostbyaddr(address_string [, address_family]) → hostent 单击以切换源代码

请改用 Addrinfo#getnameinfo。由于以下原因,此方法已弃用

  • 不常见的地址表示形式:用于表示 IPv4/IPv6 地址的 4/16 字节二进制字符串。

  • gethostbyaddr() 可能需要很长时间,并且可能会阻止其他线程。(由于 gethostbyname() 不是线程安全的,因此无法释放 GVL。)

  • 此方法使用已从 POSIX 中删除的 gethostbyname() 函数。

此方法获取 address 的主机信息。

p Socket.gethostbyaddr([221,186,184,68].pack("CCCC"))
#=> ["carbon.ruby-lang.org", [], 2, "\xDD\xBA\xB8D"]

p Socket.gethostbyaddr([127,0,0,1].pack("CCCC"))
["localhost", [], 2, "\x7F\x00\x00\x01"]
p Socket.gethostbyaddr(([0]*15+[1]).pack("C"*16))
#=> ["localhost", ["ip6-localhost", "ip6-loopback"], 10,
     "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01"]
static VALUE
sock_s_gethostbyaddr(int argc, VALUE *argv, VALUE _)
{
    VALUE addr, family;
    struct hostent *h;
    char **pch;
    VALUE ary, names;
    int t = AF_INET;

    rb_warn("Socket.gethostbyaddr is deprecated; use Addrinfo#getnameinfo instead.");

    rb_scan_args(argc, argv, "11", &addr, &family);
    StringValue(addr);
    if (!NIL_P(family)) {
        t = rsock_family_arg(family);
    }
#ifdef AF_INET6
    else if (RSTRING_LEN(addr) == 16) {
        t = AF_INET6;
    }
#endif
    h = gethostbyaddr(RSTRING_PTR(addr), RSTRING_SOCKLEN(addr), t);
    if (h == NULL) {
#ifdef HAVE_HSTRERROR
        extern int h_errno;
        rb_raise(rb_eSocket, "%s", (char*)hstrerror(h_errno));
#else
        rb_raise(rb_eSocket, "host not found");
#endif
    }
    ary = rb_ary_new();
    rb_ary_push(ary, rb_str_new2(h->h_name));
    names = rb_ary_new();
    rb_ary_push(ary, names);
    if (h->h_aliases != NULL) {
        for (pch = h->h_aliases; *pch; pch++) {
            rb_ary_push(names, rb_str_new2(*pch));
        }
    }
    rb_ary_push(ary, INT2NUM(h->h_addrtype));
#ifdef h_addr
    for (pch = h->h_addr_list; *pch; pch++) {
        rb_ary_push(ary, rb_str_new(*pch, h->h_length));
    }
#else
    rb_ary_push(ary, rb_str_new(h->h_addr, h->h_length));
#endif

    return ary;
}
gethostbyname(hostname) → [official_hostname, alias_hostnames, address_family, *address_list] 单击以切换源代码

请改用 Addrinfo.getaddrinfo。由于以下原因,此方法已弃用

  • 结果的第三个元素是第一个地址的地址族。不会返回其余地址的地址族。

  • 不常见的地址表示形式:用于表示 IPv4/IPv6 地址的 4/16 字节二进制字符串。

  • gethostbyname() 可能需要很长时间,并且可能会阻止其他线程。(由于 gethostbyname() 不是线程安全的,因此无法释放 GVL。)

  • 此方法使用已从 POSIX 中删除的 gethostbyname() 函数。

此方法获取 hostname 的主机信息。

p Socket.gethostbyname("hal") #=> ["localhost", ["hal"], 2, "\x7F\x00\x00\x01"]
static VALUE
sock_s_gethostbyname(VALUE obj, VALUE host)
{
    rb_warn("Socket.gethostbyname is deprecated; use Addrinfo.getaddrinfo instead.");
    struct rb_addrinfo *res =
        rsock_addrinfo(host, Qnil, AF_UNSPEC, SOCK_STREAM, AI_CANONNAME);
    return rsock_make_hostent(host, res, sock_sockaddr);
}
gethostname → hostname 单击以切换源代码

返回主机名。

p Socket.gethostname #=> "hal"

请注意,不能保证可以使用 gethostbyname、getaddrinfo 等转换为 IP 地址。如果需要本地 IP 地址,请使用 Socket.ip_address_list

static VALUE
sock_gethostname(VALUE obj)
{
#if defined(NI_MAXHOST)
#  define RUBY_MAX_HOST_NAME_LEN NI_MAXHOST
#elif defined(HOST_NAME_MAX)
#  define RUBY_MAX_HOST_NAME_LEN HOST_NAME_MAX
#else
#  define RUBY_MAX_HOST_NAME_LEN 1024
#endif

    long len = RUBY_MAX_HOST_NAME_LEN;
    VALUE name;

    name = rb_str_new(0, len);
    while (gethostname(RSTRING_PTR(name), len) < 0) {
        int e = errno;
        switch (e) {
          case ENAMETOOLONG:
#ifdef __linux__
          case EINVAL:
            /* glibc before version 2.1 uses EINVAL instead of ENAMETOOLONG */
#endif
            break;
          default:
            rb_syserr_fail(e, "gethostname(3)");
        }
        rb_str_modify_expand(name, len);
        len += len;
    }
    rb_str_resize(name, strlen(RSTRING_PTR(name)));
    return name;
}
getifaddrs → [ifaddr1, ...] 单击以切换源代码

返回接口地址数组。数组的元素是 Socket::Ifaddr 的实例。

此方法可用于查找启用多播的接口

pp Socket.getifaddrs.reject {|ifaddr|
  !ifaddr.addr.ip? || (ifaddr.flags & Socket::IFF_MULTICAST == 0)
}.map {|ifaddr| [ifaddr.name, ifaddr.ifindex, ifaddr.addr] }
#=> [["eth0", 2, #<Addrinfo: 221.186.184.67>],
#    ["eth0", 2, #<Addrinfo: fe80::216:3eff:fe95:88bb%eth0>]]

GNU/Linux 上的示例结果

pp Socket.getifaddrs
#=> [#<Socket::Ifaddr lo UP,LOOPBACK,RUNNING,0x10000 PACKET[protocol=0 lo hatype=772 HOST hwaddr=00:00:00:00:00:00]>,
#    #<Socket::Ifaddr eth0 UP,BROADCAST,RUNNING,MULTICAST,0x10000 PACKET[protocol=0 eth0 hatype=1 HOST hwaddr=00:16:3e:95:88:bb] broadcast=PACKET[protocol=0 eth0 hatype=1 HOST hwaddr=ff:ff:ff:ff:ff:ff]>,
#    #<Socket::Ifaddr sit0 NOARP PACKET[protocol=0 sit0 hatype=776 HOST hwaddr=00:00:00:00]>,
#    #<Socket::Ifaddr lo UP,LOOPBACK,RUNNING,0x10000 127.0.0.1 netmask=255.0.0.0>,
#    #<Socket::Ifaddr eth0 UP,BROADCAST,RUNNING,MULTICAST,0x10000 221.186.184.67 netmask=255.255.255.240 broadcast=221.186.184.79>,
#    #<Socket::Ifaddr lo UP,LOOPBACK,RUNNING,0x10000 ::1 netmask=ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff>,
#    #<Socket::Ifaddr eth0 UP,BROADCAST,RUNNING,MULTICAST,0x10000 fe80::216:3eff:fe95:88bb%eth0 netmask=ffff:ffff:ffff:ffff::>]

FreeBSD 上的示例结果

pp Socket.getifaddrs
#=> [#<Socket::Ifaddr usbus0 UP,0x10000 LINK[usbus0]>,
#    #<Socket::Ifaddr re0 UP,BROADCAST,RUNNING,MULTICAST,0x800 LINK[re0 3a:d0:40:9a:fe:e8]>,
#    #<Socket::Ifaddr re0 UP,BROADCAST,RUNNING,MULTICAST,0x800 10.250.10.18 netmask=255.255.255.? (7 bytes for 16 bytes sockaddr_in) broadcast=10.250.10.255>,
#    #<Socket::Ifaddr re0 UP,BROADCAST,RUNNING,MULTICAST,0x800 fe80:2::38d0:40ff:fe9a:fee8 netmask=ffff:ffff:ffff:ffff::>,
#    #<Socket::Ifaddr re0 UP,BROADCAST,RUNNING,MULTICAST,0x800 2001:2e8:408:10::12 netmask=UNSPEC>,
#    #<Socket::Ifaddr plip0 POINTOPOINT,MULTICAST,0x800 LINK[plip0]>,
#    #<Socket::Ifaddr lo0 UP,LOOPBACK,RUNNING,MULTICAST LINK[lo0]>,
#    #<Socket::Ifaddr lo0 UP,LOOPBACK,RUNNING,MULTICAST ::1 netmask=ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff>,
#    #<Socket::Ifaddr lo0 UP,LOOPBACK,RUNNING,MULTICAST fe80:4::1 netmask=ffff:ffff:ffff:ffff::>,
#    #<Socket::Ifaddr lo0 UP,LOOPBACK,RUNNING,MULTICAST 127.0.0.1 netmask=255.?.?.? (5 bytes for 16 bytes sockaddr_in)>]
static VALUE
socket_s_getifaddrs(VALUE self)
{
    return rsock_getifaddrs();
}
getnameinfo(sockaddr [, flags]) → [hostname, servicename] 单击以切换源代码

获取 sockaddr 的名称信息。

sockaddr 应为以下项之一。

  • 打包的 sockaddr 字符串,例如 Socket.sockaddr_in(80, “127.0.0.1”)

  • 3 元素数组,例如 [“AF_INET”, 80, “127.0.0.1”]

  • 4 元素数组,例如 [“AF_INET”, 80, ignored, “127.0.0.1”]

flags 应为 Socket::NI_* 常量的按位或。

注意:最后一种形式与 IPSocket#addrIPSocket#peeraddr 兼容。

Socket.getnameinfo(Socket.sockaddr_in(80, "127.0.0.1"))       #=> ["localhost", "www"]
Socket.getnameinfo(["AF_INET", 80, "127.0.0.1"])              #=> ["localhost", "www"]
Socket.getnameinfo(["AF_INET", 80, "localhost", "127.0.0.1"]) #=> ["localhost", "www"]

如果首选 Addrinfo 对象,请使用 Addrinfo#getnameinfo

static VALUE
sock_s_getnameinfo(int argc, VALUE *argv, VALUE _)
{
    VALUE sa, af = Qnil, host = Qnil, port = Qnil, flags, tmp;
    char hbuf[1024], pbuf[1024];
    int fl;
    struct rb_addrinfo *res = NULL;
    struct addrinfo hints, *r;
    int error, saved_errno;
    union_sockaddr ss;
    struct sockaddr *sap;
    socklen_t salen;

    sa = flags = Qnil;
    rb_scan_args(argc, argv, "11", &sa, &flags);

    fl = 0;
    if (!NIL_P(flags)) {
        fl = NUM2INT(flags);
    }
    tmp = rb_check_sockaddr_string_type(sa);
    if (!NIL_P(tmp)) {
        sa = tmp;
        if (sizeof(ss) < (size_t)RSTRING_LEN(sa)) {
            rb_raise(rb_eTypeError, "sockaddr length too big");
        }
        memcpy(&ss, RSTRING_PTR(sa), RSTRING_LEN(sa));
        if (!VALIDATE_SOCKLEN(&ss.addr, RSTRING_LEN(sa))) {
            rb_raise(rb_eTypeError, "sockaddr size differs - should not happen");
        }
        sap = &ss.addr;
        salen = RSTRING_SOCKLEN(sa);
        goto call_nameinfo;
    }
    tmp = rb_check_array_type(sa);
    if (!NIL_P(tmp)) {
        sa = tmp;
        MEMZERO(&hints, struct addrinfo, 1);
        if (RARRAY_LEN(sa) == 3) {
            af = RARRAY_AREF(sa, 0);
            port = RARRAY_AREF(sa, 1);
            host = RARRAY_AREF(sa, 2);
        }
        else if (RARRAY_LEN(sa) >= 4) {
            af = RARRAY_AREF(sa, 0);
            port = RARRAY_AREF(sa, 1);
            host = RARRAY_AREF(sa, 3);
            if (NIL_P(host)) {
                host = RARRAY_AREF(sa, 2);
            }
            else {
                /*
                 * 4th element holds numeric form, don't resolve.
                 * see rsock_ipaddr().
                 */
#ifdef AI_NUMERICHOST /* AIX 4.3.3 doesn't have AI_NUMERICHOST. */
                hints.ai_flags |= AI_NUMERICHOST;
#endif
            }
        }
        else {
            rb_raise(rb_eArgError, "array size should be 3 or 4, %ld given",
                     RARRAY_LEN(sa));
        }
        hints.ai_socktype = (fl & NI_DGRAM) ? SOCK_DGRAM : SOCK_STREAM;
        /* af */
        hints.ai_family = NIL_P(af) ? PF_UNSPEC : rsock_family_arg(af);
        res = rsock_getaddrinfo(host, port, &hints, 0);
        sap = res->ai->ai_addr;
        salen = res->ai->ai_addrlen;
    }
    else {
        rb_raise(rb_eTypeError, "expecting String or Array");
    }

  call_nameinfo:
    error = rb_getnameinfo(sap, salen, hbuf, sizeof(hbuf),
                           pbuf, sizeof(pbuf), fl);
    if (error) goto error_exit_name;
    if (res) {
        for (r = res->ai->ai_next; r; r = r->ai_next) {
            char hbuf2[1024], pbuf2[1024];

            sap = r->ai_addr;
            salen = r->ai_addrlen;
            error = rb_getnameinfo(sap, salen, hbuf2, sizeof(hbuf2),
                                   pbuf2, sizeof(pbuf2), fl);
            if (error) goto error_exit_name;
            if (strcmp(hbuf, hbuf2) != 0|| strcmp(pbuf, pbuf2) != 0) {
                rb_freeaddrinfo(res);
                rb_raise(rb_eSocket, "sockaddr resolved to multiple nodename");
            }
        }
        rb_freeaddrinfo(res);
    }
    return rb_assoc_new(rb_str_new2(hbuf), rb_str_new2(pbuf));

  error_exit_name:
    saved_errno = errno;
    if (res) rb_freeaddrinfo(res);
    errno = saved_errno;
    rsock_raise_resolution_error("getnameinfo", error);

    UNREACHABLE_RETURN(Qnil);
}
getservbyname(service_name) → port_number 点击切换源代码
getservbyname(service_name, protocol_name) → port_number

获取 service_name 的端口号。

如果未提供 protocol_name,则假定为“tcp”。

Socket.getservbyname("smtp")          #=> 25
Socket.getservbyname("shell")         #=> 514
Socket.getservbyname("syslog", "udp") #=> 514
static VALUE
sock_s_getservbyname(int argc, VALUE *argv, VALUE _)
{
    VALUE service, proto;
    struct servent *sp;
    long port;
    const char *servicename, *protoname = "tcp";

    rb_scan_args(argc, argv, "11", &service, &proto);
    StringValue(service);
    if (!NIL_P(proto)) StringValue(proto);
    servicename = StringValueCStr(service);
    if (!NIL_P(proto)) protoname = StringValueCStr(proto);
    sp = getservbyname(servicename, protoname);
    if (sp) {
        port = ntohs(sp->s_port);
    }
    else {
        char *end;

        port = STRTOUL(servicename, &end, 0);
        if (*end != '\0') {
            rb_raise(rb_eSocket, "no such service %s/%s", servicename, protoname);
        }
    }
    return INT2FIX(port);
}
getservbyport(port [, protocol_name]) → service 点击切换源代码

获取 port 的端口号。

如果未提供 protocol_name,则假定为“tcp”。

Socket.getservbyport(80)         #=> "www"
Socket.getservbyport(514, "tcp") #=> "shell"
Socket.getservbyport(514, "udp") #=> "syslog"
static VALUE
sock_s_getservbyport(int argc, VALUE *argv, VALUE _)
{
    VALUE port, proto;
    struct servent *sp;
    long portnum;
    const char *protoname = "tcp";

    rb_scan_args(argc, argv, "11", &port, &proto);
    portnum = NUM2LONG(port);
    if (portnum != (uint16_t)portnum) {
        const char *s = portnum > 0 ? "big" : "small";
        rb_raise(rb_eRangeError, "integer %ld too %s to convert into `int16_t'", portnum, s);
    }
    if (!NIL_P(proto)) protoname = StringValueCStr(proto);

    sp = getservbyport((int)htons((uint16_t)portnum), protoname);
    if (!sp) {
        rb_raise(rb_eSocket, "no such service for port %d/%s", (int)portnum, protoname);
    }
    return rb_str_new2(sp->s_name);
}
ip_address_list → array 点击切换源代码

以数组形式返回本地 IP 地址。

该数组包含 Addrinfo 对象。

pp Socket.ip_address_list
#=> [#<Addrinfo: 127.0.0.1>,
     #<Addrinfo: 192.168.0.128>,
     #<Addrinfo: ::1>,
     ...]
static VALUE
socket_s_ip_address_list(VALUE self)
{
#if defined(HAVE_GETIFADDRS)
    struct ifaddrs *ifp = NULL;
    struct ifaddrs *p;
    int ret;
    VALUE list;

    ret = getifaddrs(&ifp);
    if (ret == -1) {
        rb_sys_fail("getifaddrs");
    }

    list = rb_ary_new();
    for (p = ifp; p; p = p->ifa_next) {
        if (p->ifa_addr != NULL && IS_IP_FAMILY(p->ifa_addr->sa_family)) {
            struct sockaddr *addr = p->ifa_addr;
#if defined(AF_INET6) && defined(__sun)
            /*
             * OpenIndiana SunOS 5.11 getifaddrs() returns IPv6 link local
             * address with sin6_scope_id == 0.
             * So fill it from the interface name (ifa_name).
             */
            struct sockaddr_in6 addr6;
            if (addr->sa_family == AF_INET6) {
                socklen_t len = (socklen_t)sizeof(struct sockaddr_in6);
                memcpy(&addr6, addr, len);
                addr = (struct sockaddr *)&addr6;
                if (IN6_IS_ADDR_LINKLOCAL(&addr6.sin6_addr) &&
                    addr6.sin6_scope_id == 0) {
                    unsigned int ifindex = if_nametoindex(p->ifa_name);
                    if (ifindex != 0) {
                        addr6.sin6_scope_id = ifindex;
                    }
                }
            }
#endif
            rb_ary_push(list, sockaddr_obj(addr, sockaddr_len(addr)));
        }
    }

    freeifaddrs(ifp);

    return list;
#elif defined(SIOCGLIFCONF) && defined(SIOCGLIFNUM)
    /* Solaris if_tcp(7P) */
    int fd = -1;
    int ret;
    struct lifnum ln;
    struct lifconf lc;
    const char *reason = NULL;
    int save_errno;
    int i;
    VALUE list = Qnil;

    lc.lifc_buf = NULL;

    fd = socket(AF_INET, SOCK_DGRAM, 0);
    if (fd == -1)
        rb_sys_fail("socket(2)");

    memset(&ln, 0, sizeof(ln));
    ln.lifn_family = AF_UNSPEC;

    ret = ioctl(fd, SIOCGLIFNUM, &ln);
    if (ret == -1) {
        reason = "SIOCGLIFNUM";
        goto finish;
    }

    memset(&lc, 0, sizeof(lc));
    lc.lifc_family = AF_UNSPEC;
    lc.lifc_flags = 0;
    lc.lifc_len = sizeof(struct lifreq) * ln.lifn_count;
    lc.lifc_req = xmalloc(lc.lifc_len);

    ret = ioctl(fd, SIOCGLIFCONF, &lc);
    if (ret == -1) {
        reason = "SIOCGLIFCONF";
        goto finish;
    }

    list = rb_ary_new();
    for (i = 0; i < ln.lifn_count; i++) {
        struct lifreq *req = &lc.lifc_req[i];
        if (IS_IP_FAMILY(req->lifr_addr.ss_family)) {
            if (req->lifr_addr.ss_family == AF_INET6 &&
                IN6_IS_ADDR_LINKLOCAL(&((struct sockaddr_in6 *)(&req->lifr_addr))->sin6_addr) &&
                ((struct sockaddr_in6 *)(&req->lifr_addr))->sin6_scope_id == 0) {
                struct lifreq req2;
                memcpy(req2.lifr_name, req->lifr_name, LIFNAMSIZ);
                ret = ioctl(fd, SIOCGLIFINDEX, &req2);
                if (ret == -1) {
                    reason = "SIOCGLIFINDEX";
                    goto finish;
                }
                ((struct sockaddr_in6 *)(&req->lifr_addr))->sin6_scope_id = req2.lifr_index;
            }
            rb_ary_push(list, sockaddr_obj((struct sockaddr *)&req->lifr_addr, req->lifr_addrlen));
        }
    }

  finish:
    save_errno = errno;
    xfree(lc.lifc_req);
    if (fd != -1)
        close(fd);
    errno = save_errno;

    if (reason)
        rb_syserr_fail(save_errno, reason);
    return list;

#elif defined(SIOCGIFCONF)
    int fd = -1;
    int ret;
#define EXTRA_SPACE ((int)(sizeof(struct ifconf) + sizeof(union_sockaddr)))
    char initbuf[4096+EXTRA_SPACE];
    char *buf = initbuf;
    int bufsize;
    struct ifconf conf;
    struct ifreq *req;
    VALUE list = Qnil;
    const char *reason = NULL;
    int save_errno;

    fd = socket(AF_INET, SOCK_DGRAM, 0);
    if (fd == -1)
        rb_sys_fail("socket(2)");

    bufsize = sizeof(initbuf);
    buf = initbuf;

  retry:
    conf.ifc_len = bufsize;
    conf.ifc_req = (struct ifreq *)buf;

    /* fprintf(stderr, "bufsize: %d\n", bufsize); */

    ret = ioctl(fd, SIOCGIFCONF, &conf);
    if (ret == -1) {
        reason = "SIOCGIFCONF";
        goto finish;
    }

    /* fprintf(stderr, "conf.ifc_len: %d\n", conf.ifc_len); */

    if (bufsize - EXTRA_SPACE < conf.ifc_len) {
        if (bufsize < conf.ifc_len) {
            /* NetBSD returns required size for all interfaces. */
            bufsize = conf.ifc_len + EXTRA_SPACE;
        }
        else {
            bufsize = bufsize << 1;
        }
        if (buf == initbuf)
            buf = NULL;
        buf = xrealloc(buf, bufsize);
        goto retry;
    }

    close(fd);
    fd = -1;

    list = rb_ary_new();
    req = conf.ifc_req;
    while ((char*)req < (char*)conf.ifc_req + conf.ifc_len) {
        struct sockaddr *addr = &req->ifr_addr;
        if (IS_IP_FAMILY(addr->sa_family)) {
            rb_ary_push(list, sockaddr_obj(addr, sockaddr_len(addr)));
        }
#ifdef HAVE_STRUCT_SOCKADDR_SA_LEN
# ifndef _SIZEOF_ADDR_IFREQ
#  define _SIZEOF_ADDR_IFREQ(r) \
          (sizeof(struct ifreq) + \
           (sizeof(struct sockaddr) < (r).ifr_addr.sa_len ? \
            (r).ifr_addr.sa_len - sizeof(struct sockaddr) : \
            0))
# endif
        req = (struct ifreq *)((char*)req + _SIZEOF_ADDR_IFREQ(*req));
#else
        req = (struct ifreq *)((char*)req + sizeof(struct ifreq));
#endif
    }

  finish:

    save_errno = errno;
    if (buf != initbuf)
        xfree(buf);
    if (fd != -1)
        close(fd);
    errno = save_errno;

    if (reason)
        rb_syserr_fail(save_errno, reason);
    return list;

#undef EXTRA_SPACE
#elif defined(_WIN32)
    typedef struct ip_adapter_unicast_address_st {
        unsigned LONG_LONG dummy0;
        struct ip_adapter_unicast_address_st *Next;
        struct {
            struct sockaddr *lpSockaddr;
            int iSockaddrLength;
        } Address;
        int dummy1;
        int dummy2;
        int dummy3;
        long dummy4;
        long dummy5;
        long dummy6;
    } ip_adapter_unicast_address_t;
    typedef struct ip_adapter_anycast_address_st {
        unsigned LONG_LONG dummy0;
        struct ip_adapter_anycast_address_st *Next;
        struct {
            struct sockaddr *lpSockaddr;
            int iSockaddrLength;
        } Address;
    } ip_adapter_anycast_address_t;
    typedef struct ip_adapter_addresses_st {
        unsigned LONG_LONG dummy0;
        struct ip_adapter_addresses_st *Next;
        void *dummy1;
        ip_adapter_unicast_address_t *FirstUnicastAddress;
        ip_adapter_anycast_address_t *FirstAnycastAddress;
        void *dummy2;
        void *dummy3;
        void *dummy4;
        void *dummy5;
        void *dummy6;
        BYTE dummy7[8];
        DWORD dummy8;
        DWORD dummy9;
        DWORD dummy10;
        DWORD IfType;
        int OperStatus;
        DWORD dummy12;
        DWORD dummy13[16];
        void *dummy14;
    } ip_adapter_addresses_t;
    typedef ULONG (WINAPI *GetAdaptersAddresses_t)(ULONG, ULONG, PVOID, ip_adapter_addresses_t *, PULONG);
    HMODULE h;
    GetAdaptersAddresses_t pGetAdaptersAddresses;
    ULONG len;
    DWORD ret;
    ip_adapter_addresses_t *adapters;
    VALUE list;

    h = LoadLibrary("iphlpapi.dll");
    if (!h)
        rb_notimplement();
    pGetAdaptersAddresses = (GetAdaptersAddresses_t)GetProcAddress(h, "GetAdaptersAddresses");
    if (!pGetAdaptersAddresses) {
        FreeLibrary(h);
        rb_notimplement();
    }

    ret = pGetAdaptersAddresses(AF_UNSPEC, 0, NULL, NULL, &len);
    if (ret != ERROR_SUCCESS && ret != ERROR_BUFFER_OVERFLOW) {
        errno = rb_w32_map_errno(ret);
        FreeLibrary(h);
        rb_sys_fail("GetAdaptersAddresses");
    }
    adapters = (ip_adapter_addresses_t *)ALLOCA_N(BYTE, len);
    ret = pGetAdaptersAddresses(AF_UNSPEC, 0, NULL, adapters, &len);
    if (ret != ERROR_SUCCESS) {
        errno = rb_w32_map_errno(ret);
        FreeLibrary(h);
        rb_sys_fail("GetAdaptersAddresses");
    }

    list = rb_ary_new();
    for (; adapters; adapters = adapters->Next) {
        ip_adapter_unicast_address_t *uni;
        ip_adapter_anycast_address_t *any;
        if (adapters->OperStatus != 1)  /* 1 means IfOperStatusUp */
            continue;
        for (uni = adapters->FirstUnicastAddress; uni; uni = uni->Next) {
#ifndef INET6
            if (uni->Address.lpSockaddr->sa_family == AF_INET)
#else
            if (IS_IP_FAMILY(uni->Address.lpSockaddr->sa_family))
#endif
                rb_ary_push(list, sockaddr_obj(uni->Address.lpSockaddr, uni->Address.iSockaddrLength));
        }
        for (any = adapters->FirstAnycastAddress; any; any = any->Next) {
#ifndef INET6
            if (any->Address.lpSockaddr->sa_family == AF_INET)
#else
            if (IS_IP_FAMILY(any->Address.lpSockaddr->sa_family))
#endif
                rb_ary_push(list, sockaddr_obj(any->Address.lpSockaddr, any->Address.iSockaddrLength));
        }
    }

    FreeLibrary(h);
    return list;
#endif
}
new(domain, socktype [, protocol]) → socket 点击切换源代码

创建一个新的 socket 对象。

domain 应该是一个通信域,例如::INET,:INET6,:UNIX 等。

socktype 应为套接字类型,例如::STREAM, :DGRAM, :RAW 等。

protocol 是可选的,并且应该是域中定义的协议。如果未提供协议,则内部使用 0。

Socket.new(:INET, :STREAM) # TCP socket
Socket.new(:INET, :DGRAM)  # UDP socket
Socket.new(:UNIX, :STREAM) # UNIX stream socket
Socket.new(:UNIX, :DGRAM)  # UNIX datagram socket
static VALUE
sock_initialize(int argc, VALUE *argv, VALUE sock)
{
    VALUE domain, type, protocol;
    int fd;
    int d, t;

    rb_scan_args(argc, argv, "21", &domain, &type, &protocol);
    if (NIL_P(protocol))
        protocol = INT2FIX(0);

    setup_domain_and_type(domain, &d, type, &t);
    fd = rsock_socket(d, t, NUM2INT(protocol));
    if (fd < 0) rb_sys_fail("socket(2)");

    return rsock_init_sock(sock, fd);
}
sockaddr_in(port, host) → sockaddr 点击切换源代码
pack_sockaddr_in(port, host) → sockaddr

porthost 打包为 AF_INET/AF_INET6 sockaddr 字符串。

Socket.sockaddr_in(80, "127.0.0.1")
#=> "\x02\x00\x00P\x7F\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00"

Socket.sockaddr_in(80, "::1")
#=> "\n\x00\x00P\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00"
static VALUE
sock_s_pack_sockaddr_in(VALUE self, VALUE port, VALUE host)
{
    struct rb_addrinfo *res = rsock_addrinfo(host, port, AF_UNSPEC, 0, 0);
    VALUE addr = rb_str_new((char*)res->ai->ai_addr, res->ai->ai_addrlen);

    rb_freeaddrinfo(res);

    return addr;
}
sockaddr_un(path) → sockaddr 点击切换源代码
pack_sockaddr_un(path) → sockaddr

path 打包为 AF_UNIX sockaddr 字符串。

Socket.sockaddr_un("/tmp/sock") #=> "\x01\x00/tmp/sock\x00\x00..."
static VALUE
sock_s_pack_sockaddr_un(VALUE self, VALUE path)
{
    struct sockaddr_un sockaddr;
    VALUE addr;

    StringValue(path);
    INIT_SOCKADDR_UN(&sockaddr, sizeof(struct sockaddr_un));
    if (sizeof(sockaddr.sun_path) < (size_t)RSTRING_LEN(path)) {
        rb_raise(rb_eArgError, "too long unix socket path (%"PRIuSIZE" bytes given but %"PRIuSIZE" bytes max)",
            (size_t)RSTRING_LEN(path), sizeof(sockaddr.sun_path));
    }
    memcpy(sockaddr.sun_path, RSTRING_PTR(path), RSTRING_LEN(path));
    addr = rb_str_new((char*)&sockaddr, rsock_unix_sockaddr_len(path));

    return addr;
}
pair(domain, type, protocol) → [socket1, socket2] 点击切换源代码
socketpair(domain, type, protocol) → [socket1, socket2]

创建一对相互连接的 socket。

domain 应该是一个通信域,例如::INET,:INET6,:UNIX 等。

socktype 应为套接字类型,例如::STREAM, :DGRAM, :RAW 等。

protocol 应该是域中定义的协议,默认为该域的 0。

s1, s2 = Socket.pair(:UNIX, :STREAM, 0)
s1.send "a", 0
s1.send "b", 0
s1.close
p s2.recv(10) #=> "ab"
p s2.recv(10) #=> ""
p s2.recv(10) #=> ""

s1, s2 = Socket.pair(:UNIX, :DGRAM, 0)
s1.send "a", 0
s1.send "b", 0
p s2.recv(10) #=> "a"
p s2.recv(10) #=> "b"
VALUE
rsock_sock_s_socketpair(int argc, VALUE *argv, VALUE klass)
{
    VALUE domain, type, protocol;
    int d, t, p, sp[2];
    int ret;
    VALUE s1, s2, r;

    rb_scan_args(argc, argv, "21", &domain, &type, &protocol);
    if (NIL_P(protocol))
        protocol = INT2FIX(0);

    setup_domain_and_type(domain, &d, type, &t);
    p = NUM2INT(protocol);
    ret = rsock_socketpair(d, t, p, sp);
    if (ret < 0) {
        rb_sys_fail("socketpair(2)");
    }

    s1 = rsock_init_sock(rb_obj_alloc(klass), sp[0]);
    s2 = rsock_init_sock(rb_obj_alloc(klass), sp[1]);
    r = rb_assoc_new(s1, s2);
    if (rb_block_given_p()) {
        return rb_ensure(pair_yield, r, io_close, s1);
    }
    return r;
}
sockaddr_in(port, host) → sockaddr 点击切换源代码
pack_sockaddr_in(port, host) → sockaddr

porthost 打包为 AF_INET/AF_INET6 sockaddr 字符串。

Socket.sockaddr_in(80, "127.0.0.1")
#=> "\x02\x00\x00P\x7F\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00"

Socket.sockaddr_in(80, "::1")
#=> "\n\x00\x00P\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00"
static VALUE
sock_s_pack_sockaddr_in(VALUE self, VALUE port, VALUE host)
{
    struct rb_addrinfo *res = rsock_addrinfo(host, port, AF_UNSPEC, 0, 0);
    VALUE addr = rb_str_new((char*)res->ai->ai_addr, res->ai->ai_addrlen);

    rb_freeaddrinfo(res);

    return addr;
}
sockaddr_un(path) → sockaddr 点击切换源代码
pack_sockaddr_un(path) → sockaddr

path 打包为 AF_UNIX sockaddr 字符串。

Socket.sockaddr_un("/tmp/sock") #=> "\x01\x00/tmp/sock\x00\x00..."
static VALUE
sock_s_pack_sockaddr_un(VALUE self, VALUE path)
{
    struct sockaddr_un sockaddr;
    VALUE addr;

    StringValue(path);
    INIT_SOCKADDR_UN(&sockaddr, sizeof(struct sockaddr_un));
    if (sizeof(sockaddr.sun_path) < (size_t)RSTRING_LEN(path)) {
        rb_raise(rb_eArgError, "too long unix socket path (%"PRIuSIZE" bytes given but %"PRIuSIZE" bytes max)",
            (size_t)RSTRING_LEN(path), sizeof(sockaddr.sun_path));
    }
    memcpy(sockaddr.sun_path, RSTRING_PTR(path), RSTRING_LEN(path));
    addr = rb_str_new((char*)&sockaddr, rsock_unix_sockaddr_len(path));

    return addr;
}
pair(domain, type, protocol) → [socket1, socket2] 点击切换源代码
socketpair(domain, type, protocol) → [socket1, socket2]

创建一对相互连接的 socket。

domain 应该是一个通信域,例如::INET,:INET6,:UNIX 等。

socktype 应为套接字类型,例如::STREAM, :DGRAM, :RAW 等。

protocol 应该是域中定义的协议,默认为该域的 0。

s1, s2 = Socket.pair(:UNIX, :STREAM, 0)
s1.send "a", 0
s1.send "b", 0
s1.close
p s2.recv(10) #=> "ab"
p s2.recv(10) #=> ""
p s2.recv(10) #=> ""

s1, s2 = Socket.pair(:UNIX, :DGRAM, 0)
s1.send "a", 0
s1.send "b", 0
p s2.recv(10) #=> "a"
p s2.recv(10) #=> "b"
VALUE
rsock_sock_s_socketpair(int argc, VALUE *argv, VALUE klass)
{
    VALUE domain, type, protocol;
    int d, t, p, sp[2];
    int ret;
    VALUE s1, s2, r;

    rb_scan_args(argc, argv, "21", &domain, &type, &protocol);
    if (NIL_P(protocol))
        protocol = INT2FIX(0);

    setup_domain_and_type(domain, &d, type, &t);
    p = NUM2INT(protocol);
    ret = rsock_socketpair(d, t, p, sp);
    if (ret < 0) {
        rb_sys_fail("socketpair(2)");
    }

    s1 = rsock_init_sock(rb_obj_alloc(klass), sp[0]);
    s2 = rsock_init_sock(rb_obj_alloc(klass), sp[1]);
    r = rb_assoc_new(s1, s2);
    if (rb_block_given_p()) {
        return rb_ensure(pair_yield, r, io_close, s1);
    }
    return r;
}
tcp(host, port, local_host=nil, local_port=nil, [opts]) {|socket| ... } 点击切换源代码
tcp(host, port, local_host=nil, local_port=nil, [opts])

创建一个新的 socket 对象,该对象使用 TCP/IP 连接到 host:port。

从 Ruby 3.4 开始,此方法默认按照 Happy Eyeballs Version 2 (RFC 8305) 算法运行。

有关 Happy Eyeballs Version 2 的详细信息,请参阅 Socket.tcp_fast_fallback=

要使其行为与 Ruby 3.3 及更早版本相同,请显式指定选项 fast_fallback:false。或者,设置 Socket.tcp_fast_fallback=false 将不仅为此方法禁用 Happy Eyeballs Version 2,还会为所有 Socket 全局禁用。

如果给定了 local_host:local_port,则 socket 将绑定到它。

可选的最后一个参数 opts 是由哈希表示的选项。opts 可能具有以下选项

:resolv_timeout

指定从主机名解析开始的超时时间(以秒为单位)。

:connect_timeout

此方法按顺序尝试连接到所有候选目标地址。
connect_timeout 指定从连接尝试开始到最后一个候选者的超时时间(以秒为单位)。
默认情况下,所有连接尝试都会持续到发生超时。
当显式指定 fast_fallback:false 时,
为每个连接尝试设置超时,并且任何超过其超时的连接尝试都将被取消。

:fast_fallback

启用 Happy Eyeballs Version 2 算法(默认启用)。

如果给出了块,则将使用该 socket 调用该块。返回该块的值。当此方法返回时,该 socket 将关闭。

如果没有给出块,则返回该 socket。

Socket.tcp("www.ruby-lang.org", 80) {|sock|
  sock.print "GET / HTTP/1.0\r\nHost: www.ruby-lang.org\r\n\r\n"
  sock.close_write
  puts sock.read
}
# File socket/lib/socket.rb, line 659
def self.tcp(host, port, local_host = nil, local_port = nil, connect_timeout: nil, resolv_timeout: nil, fast_fallback: tcp_fast_fallback, &) # :yield: socket
  sock = if fast_fallback && !(host && ip_address?(host))
    tcp_with_fast_fallback(host, port, local_host, local_port, connect_timeout:, resolv_timeout:)
  else
    tcp_without_fast_fallback(host, port, local_host, local_port, connect_timeout:, resolv_timeout:)
  end

  if block_given?
    begin
      yield sock
    ensure
      sock.close
    end
  else
    sock
  end
end
tcp_fast_fallback → true or false 点击切换源代码

返回是否启用或禁用 Happy Eyeballs Version 2 (RFC 8305),该版本在从 Ruby 3.4 开始使用 TCPSocket.newSocket.tcp 时提供。

如果为 true,则为 TCPSocket.newSocket.tcp 启用。(注意:在 Windows 上使用 TCPSocket.new 时,不提供 Happy Eyeballs Version 2。)

如果为 false,则禁用 Happy Eyeballs Version 2。

有关 Happy Eyeballs Version 2 的详细信息,请参阅 Socket.tcp_fast_fallback=

VALUE socket_s_tcp_fast_fallback(VALUE self) {
    return rb_ivar_get(rb_cSocket, tcp_fast_fallback);
}
tcp_fast_fallback= → true or false 点击切换源代码

全局启用或禁用 Happy Eyeballs Version 2 (RFC 8305),该版本在从 Ruby 3.4 开始使用 TCPSocket.newSocket.tcp 时提供。

设置为 true 时,该功能对 ‘TCPSocket.new` 和 `Socket.tcp` 均启用。(注意:在 Windows 上使用 TCPSocket.new 时,此功能不可用。)

设置为 false 时,行为将恢复为 Ruby 3.3 或更早版本。

如果未通过调用此方法显式设置值,则默认值为 true。但是,当设置环境变量 RUBY_TCP_NO_FAST_FALLBACK=1 时,默认值为 false。

要控制每个方法的设置,请为每个方法使用 fast_fallback 关键字参数。

Happy Eyeballs Version 2

Happy Eyeballs Version 2 (RFC 8305) 是一种旨在提高客户端 socket 连接性的算法。
它的目标是通过并行执行主机名解析和连接尝试,而不是串行执行,来实现更可靠和高效的连接。

从 Ruby 3.4 开始,此方法使用此算法按如下方式运行

  1. 同时开始解析 IPv6 和 IPv4 地址。

  2. 开始连接到首先获得的地址之一。
    如果首先获得 IPv4 地址,则该方法会等待 50 毫秒以进行 IPv6 名称解析,从而优先考虑 IPv6 连接。

  3. 在开始连接尝试后,等待 250 毫秒以建立连接。
    如果在此时间内未建立连接,则每 250 毫秒启动一个新连接
    直到建立连接或没有更多候选地址为止。
    (尽管 RFC 8305 严格指定了地址排序,
    但出于性能考虑,此方法仅在 IPv6/IPv4 地址之间交替使用)

  4. 建立连接后,将取消所有剩余的连接尝试。

VALUE socket_s_tcp_fast_fallback_set(VALUE self, VALUE value) {
    rb_ivar_set(rb_cSocket, tcp_fast_fallback, value);
    return value;
}
tcp_server_loop(host=nil, port) { |socket, client_addrinfo| ... } 点击切换源代码

port 上创建一个 TCP/IP 服务器,并为每个接受的连接调用该块。使用 socket 和作为 Addrinfo 对象的 client_address 调用该块。

如果指定了 host,则将其与 port 一起使用以确定服务器地址。

当块返回时,socket 不会关闭。因此,应用程序应显式关闭它。

此方法按顺序调用该块。这意味着在块返回之前,不会接受下一个连接。因此,应使用并发机制(例如线程)来同时为多个客户端提供服务。

请注意,使用 Addrinfo.getaddrinfo 来确定服务器 socket 地址。当 Addrinfo.getaddrinfo 返回两个或多个地址时(例如 IPv4 和 IPv6 地址),将使用所有地址。如果至少可以使用一个 socket,则 Socket.tcp_server_loop 将成功。

# Sequential echo server.
# It services only one client at a time.
Socket.tcp_server_loop(16807) {|sock, client_addrinfo|
  begin
    IO.copy_stream(sock, sock)
  ensure
    sock.close
  end
}

# Threaded echo server
# It services multiple clients at a time.
# Note that it may accept connections too much.
Socket.tcp_server_loop(16807) {|sock, client_addrinfo|
  Thread.new {
    begin
      IO.copy_stream(sock, sock)
    ensure
      sock.close
    end
  }
}
# File socket/lib/socket.rb, line 1272
def self.tcp_server_loop(host=nil, port, &b) # :yield: socket, client_addrinfo
  tcp_server_sockets(host, port) {|sockets|
    accept_loop(sockets, &b)
  }
end
tcp_server_sockets(host=nil, port) { |sockets| ... } 点击切换源代码

hostport 创建 TCP/IP 服务器 socket。host 是可选的。

如果没有给出块,则返回侦听 socket 的数组。

如果给出了块,则将使用 socket 调用该块。返回该块的值。当此方法返回时,该 socket 将关闭。

如果 port 为 0,则动态选择实际端口号。但是,结果中的所有 socket 都具有相同的端口号。

# tcp_server_sockets returns two sockets.
sockets = Socket.tcp_server_sockets(1296)
p sockets #=> [#<Socket:fd 3>, #<Socket:fd 4>]

# The sockets contains IPv6 and IPv4 sockets.
sockets.each {|s| p s.local_address }
#=> #<Addrinfo: [::]:1296 TCP>
#   #<Addrinfo: 0.0.0.0:1296 TCP>

# IPv6 and IPv4 socket has same port number, 53114, even if it is chosen dynamically.
sockets = Socket.tcp_server_sockets(0)
sockets.each {|s| p s.local_address }
#=> #<Addrinfo: [::]:53114 TCP>
#   #<Addrinfo: 0.0.0.0:53114 TCP>

# The block is called with the sockets.
Socket.tcp_server_sockets(0) {|sockets|
  p sockets #=> [#<Socket:fd 3>, #<Socket:fd 4>]
}
# File socket/lib/socket.rb, line 1172
def self.tcp_server_sockets(host=nil, port)
  if port == 0
    sockets = tcp_server_sockets_port0(host)
  else
    last_error = nil
    sockets = []
    begin
      Addrinfo.foreach(host, port, nil, :STREAM, nil, Socket::AI_PASSIVE) {|ai|
        begin
          s = ai.listen
        rescue SystemCallError
          last_error = $!
          next
        end
        sockets << s
      }
      if sockets.empty?
        raise last_error
      end
    rescue Exception
      sockets.each(&:close)
      raise
    end
  end
  if block_given?
    begin
      yield sockets
    ensure
      sockets.each(&:close)
    end
  else
    sockets
  end
end
tcp_with_fast_fallback(host, port, local_host = nil, local_port = nil, connect_timeout: nil, resolv_timeout: nil) 点击切换源代码
# File socket/lib/socket.rb, line 677
def self.tcp_with_fast_fallback(host, port, local_host = nil, local_port = nil, connect_timeout: nil, resolv_timeout: nil)
  if local_host || local_port
    local_addrinfos = Addrinfo.getaddrinfo(local_host, local_port, nil, :STREAM, timeout: resolv_timeout)
    resolving_family_names = local_addrinfos.map { |lai| ADDRESS_FAMILIES.key(lai.afamily) }.uniq
  else
    local_addrinfos = []
    resolving_family_names = ADDRESS_FAMILIES.keys
  end

  hostname_resolution_threads = []
  resolution_store = HostnameResolutionStore.new(resolving_family_names)
  connecting_sockets = {}
  is_windows_environment ||= (RUBY_PLATFORM =~ /mswin|mingw|cygwin/)

  now = current_clock_time
  resolution_delay_expires_at = nil
  connection_attempt_delay_expires_at = nil
  user_specified_connect_timeout_at = nil
  last_error = nil

  if resolving_family_names.size == 1
    family_name = resolving_family_names.first
    addrinfos = Addrinfo.getaddrinfo(host, port, family_name, :STREAM, timeout: resolv_timeout)
    resolution_store.add_resolved(family_name, addrinfos)
    hostname_resolution_result = nil
    hostname_resolution_notifier = nil
    user_specified_resolv_timeout_at = nil
  else
    hostname_resolution_result = HostnameResolutionResult.new(resolving_family_names.size)
    hostname_resolution_notifier = hostname_resolution_result.notifier

    hostname_resolution_threads.concat(
      resolving_family_names.map { |family|
        thread_args = [family, host, port, hostname_resolution_result]
        thread = Thread.new(*thread_args) { |*thread_args| resolve_hostname(*thread_args) }
        Thread.pass
        thread
      }
    )

    user_specified_resolv_timeout_at = resolv_timeout ? now + resolv_timeout : Float::INFINITY
  end

  loop do
    if resolution_store.any_addrinfos? &&
        !resolution_delay_expires_at &&
        !connection_attempt_delay_expires_at
      while (addrinfo = resolution_store.get_addrinfo)
        if local_addrinfos.any?
          local_addrinfo = local_addrinfos.find { |lai| lai.afamily == addrinfo.afamily }

          if local_addrinfo.nil? # Connecting addrinfoと同じアドレスファミリのLocal addrinfoがない
            if resolution_store.any_addrinfos?
              # Try other Addrinfo in next "while"
              next
            elsif connecting_sockets.any? || resolution_store.any_unresolved_family?
              # Exit this "while" and wait for connections to be established or hostname resolution in next loop
              # Or exit this "while" and wait for hostname resolution in next loop
              break
            else
              raise SocketError.new 'no appropriate local address'
            end
          end
        end

        begin
          if resolution_store.any_addrinfos? ||
             connecting_sockets.any? ||
             resolution_store.any_unresolved_family?
            socket = Socket.new(addrinfo.pfamily, addrinfo.socktype, addrinfo.protocol)
            socket.bind(local_addrinfo) if local_addrinfo
            result = socket.connect_nonblock(addrinfo, exception: false)
          else
            result = socket = local_addrinfo ?
              addrinfo.connect_from(local_addrinfo, timeout: connect_timeout) :
              addrinfo.connect(timeout: connect_timeout)
          end

          if result == :wait_writable
            connection_attempt_delay_expires_at = now + CONNECTION_ATTEMPT_DELAY
            if resolution_store.empty_addrinfos?
              user_specified_connect_timeout_at = connect_timeout ? now + connect_timeout : Float::INFINITY
            end

            connecting_sockets[socket] = addrinfo
            break
          else
            return socket # connection established
          end
        rescue SystemCallError => e
          socket&.close
          last_error = e

          if resolution_store.any_addrinfos?
            # Try other Addrinfo in next "while"
            next
          elsif connecting_sockets.any? || resolution_store.any_unresolved_family?
            # Exit this "while" and wait for connections to be established or hostname resolution in next loop
            # Or exit this "while" and wait for hostname resolution in next loop
            break
          else
            raise last_error
          end
        end
      end
    end

    ends_at =
      if resolution_store.any_addrinfos?
        resolution_delay_expires_at || connection_attempt_delay_expires_at
      else
        [user_specified_resolv_timeout_at, user_specified_connect_timeout_at].compact.max
      end

    hostname_resolved, writable_sockets, except_sockets = IO.select(
      hostname_resolution_notifier,
      connecting_sockets.keys,
      # Use errorfds to wait for non-blocking connect failures on Windows
      is_windows_environment ? connecting_sockets.keys : nil,
      second_to_timeout(current_clock_time, ends_at),
    )
    now = current_clock_time
    resolution_delay_expires_at = nil if expired?(now, resolution_delay_expires_at)
    connection_attempt_delay_expires_at = nil if expired?(now, connection_attempt_delay_expires_at)

    if writable_sockets&.any?
      while (writable_socket = writable_sockets.pop)
        is_connected = is_windows_environment || (
          sockopt = writable_socket.getsockopt(Socket::SOL_SOCKET, Socket::SO_ERROR)
          sockopt.int.zero?
        )

        if is_connected
          connecting_sockets.delete writable_socket
          return writable_socket
        else
          failed_ai = connecting_sockets.delete writable_socket
          writable_socket.close
          ip_address = failed_ai.ipv6? ? "[#{failed_ai.ip_address}]" : failed_ai.ip_address
          last_error = SystemCallError.new("connect(2) for #{ip_address}:#{failed_ai.ip_port}", sockopt.int)

          if writable_sockets.any? || connecting_sockets.any?
            # Try other writable socket in next "while"
            # Or exit this "while" and wait for connections to be established or hostname resolution in next loop
          elsif resolution_store.any_addrinfos? || resolution_store.any_unresolved_family?
            # Exit this "while" and try other connection attempt
            # Or exit this "while" and wait for hostname resolution in next loop
            connection_attempt_delay_expires_at = nil
            user_specified_connect_timeout_at = nil
          else
            raise last_error
          end
        end
      end
    end

    if except_sockets&.any?
      except_sockets.each do |except_socket|
        failed_ai = connecting_sockets.delete except_socket
        sockopt = except_socket.getsockopt(Socket::SOL_SOCKET, Socket::SO_CONNECT_TIME)
        except_socket.close
        ip_address = failed_ai.ipv6? ? "[#{failed_ai.ip_address}]" : failed_ai.ip_address
        last_error = SystemCallError.new("connect(2) for #{ip_address}:#{failed_ai.ip_port}", sockopt.int)

        if except_sockets.any? || connecting_sockets.any?
          # Cleanup other except socket in next "each"
          # Or exit this "while" and wait for connections to be established or hostname resolution in next loop
        elsif resolution_store.any_addrinfos? || resolution_store.any_unresolved_family?
          # Exit this "while" and try other connection attempt
          # Or exit this "while" and wait for hostname resolution in next loop
          connection_attempt_delay_expires_at = nil
          user_specified_connect_timeout_at = nil
        else
          raise last_error
        end
      end
    end

    if hostname_resolved&.any?
      while (family_and_result = hostname_resolution_result.get)
        family_name, result = family_and_result

        if result.is_a? Exception
          resolution_store.add_error(family_name, result)

          unless (Socket.const_defined?(:EAI_ADDRFAMILY)) &&
            (result.is_a?(Socket::ResolutionError)) &&
            (result.error_code == Socket::EAI_ADDRFAMILY)
            last_error = result
          end
        else
          resolution_store.add_resolved(family_name, result)
        end
      end

      if resolution_store.resolved?(:ipv4)
        if resolution_store.resolved?(:ipv6)
          hostname_resolution_notifier = nil
          resolution_delay_expires_at = nil
          user_specified_resolv_timeout_at = nil
        elsif resolution_store.resolved_successfully?(:ipv4)
          resolution_delay_expires_at = now + RESOLUTION_DELAY
        end
      end
    end

    if resolution_store.empty_addrinfos?
      if connecting_sockets.empty? && resolution_store.resolved_all_families?
        raise last_error
      end

      if (expired?(now, user_specified_resolv_timeout_at) || resolution_store.resolved_all_families?) &&
         (expired?(now, user_specified_connect_timeout_at) || connecting_sockets.empty?)
        raise Errno::ETIMEDOUT, 'user specified timeout'
      end
    end
  end
ensure
  hostname_resolution_threads.each do |thread|
    thread.exit
  end

  hostname_resolution_result&.close

  connecting_sockets.each_key do |connecting_socket|
    connecting_socket.close
  end
end
udp_server_loop(port) {|msg, msg_src| ... } 点击切换源代码
udp_server_loop(host, port) {|msg, msg_src| ... }

port 上创建一个 UDP/IP 服务器,并为每个到达的消息调用该块。使用消息及其源信息调用该块。

此方法在内部使用 port 分配 socket。如果指定了 host,则将其与 port 结合使用以确定服务器地址。

msg 是一个字符串。

msg_src 是一个 Socket::UDPSource 对象。它用于回复。

# UDP/IP echo server.
Socket.udp_server_loop(9261) {|msg, msg_src|
  msg_src.reply msg
}
# File socket/lib/socket.rb, line 1442
def self.udp_server_loop(host=nil, port, &b) # :yield: message, message_source
  udp_server_sockets(host, port) {|sockets|
    udp_server_loop_on(sockets, &b)
  }
end
udp_server_loop_on(sockets) {|msg, msg_src| ... } 点击切换源代码

在给定的 socket 上运行 UDP/IP 服务器循环。

Socket.udp_server_sockets 的返回值适合该参数。

它为接收到的每个消息调用该块。

# File socket/lib/socket.rb, line 1415
def self.udp_server_loop_on(sockets, &b) # :yield: msg, msg_src
  loop {
    readable, _, _ = IO.select(sockets)
    udp_server_recv(readable, &b)
  }
end
udp_server_recv(sockets) {|msg, msg_src| ... } 点击切换源代码

从给定的 sockets 接收 UDP/IP 数据包。对于接收到的每个数据包,都将调用该块。

该块接收 msgmsg_srcmsg 是一个字符串,它是接收到的数据包的有效负载。msg_src 是一个 Socket::UDPSource 对象,它用于回复。

可以使用此方法按如下方式实现 Socket.udp_server_loop

udp_server_sockets(host, port) {|sockets|
  loop {
    readable, _, _ = IO.select(sockets)
    udp_server_recv(readable) {|msg, msg_src| ... }
  }
}
# File socket/lib/socket.rb, line 1388
def self.udp_server_recv(sockets)
  sockets.each {|r|
    msg, sender_addrinfo, _, *controls = r.recvmsg_nonblock(exception: false)
    next if msg == :wait_readable
    ai = r.local_address
    if ai.ipv6? and pktinfo = controls.find {|c| c.cmsg_is?(:IPV6, :PKTINFO) }
      ai = Addrinfo.udp(pktinfo.ipv6_pktinfo_addr.ip_address, ai.ip_port)
      yield msg, UDPSource.new(sender_addrinfo, ai) {|reply_msg|
        r.sendmsg reply_msg, 0, sender_addrinfo, pktinfo
      }
    else
      yield msg, UDPSource.new(sender_addrinfo, ai) {|reply_msg|
        r.send reply_msg, 0, sender_addrinfo
      }
    end
  }
end
udp_server_sockets([host, ] port) 点击切换源代码

为 UDP 服务器创建 UDP/IP socket。

如果没有给出块,则返回 socket 的数组。

如果给出了块,则将使用 socket 调用该块。返回该块的值。当此方法返回时,socket 将关闭。

如果 port 为零,则选择某个端口。但是,所选端口用于所有 socket。

# UDP/IP echo server
Socket.udp_server_sockets(0) {|sockets|
  p sockets.first.local_address.ip_port     #=> 32963
  Socket.udp_server_loop_on(sockets) {|msg, msg_src|
    msg_src.reply msg
  }
}
# File socket/lib/socket.rb, line 1300
def self.udp_server_sockets(host=nil, port)
  last_error = nil
  sockets = []

  ipv6_recvpktinfo = nil
  if defined? Socket::AncillaryData
    if defined? Socket::IPV6_RECVPKTINFO # RFC 3542
      ipv6_recvpktinfo = Socket::IPV6_RECVPKTINFO
    elsif defined? Socket::IPV6_PKTINFO # RFC 2292
      ipv6_recvpktinfo = Socket::IPV6_PKTINFO
    end
  end

  local_addrs = Socket.ip_address_list

  ip_list = []
  Addrinfo.foreach(host, port, nil, :DGRAM, nil, Socket::AI_PASSIVE) {|ai|
    if ai.ipv4? && ai.ip_address == "0.0.0.0"
      local_addrs.each {|a|
        next unless a.ipv4?
        ip_list << Addrinfo.new(a.to_sockaddr, :INET, :DGRAM, 0);
      }
    elsif ai.ipv6? && ai.ip_address == "::" && !ipv6_recvpktinfo
      local_addrs.each {|a|
        next unless a.ipv6?
        ip_list << Addrinfo.new(a.to_sockaddr, :INET6, :DGRAM, 0);
      }
    else
      ip_list << ai
    end
  }
  ip_list.uniq!(&:to_sockaddr)

  if port == 0
    sockets = ip_sockets_port0(ip_list, false)
  else
    ip_list.each {|ip|
      ai = Addrinfo.udp(ip.ip_address, port)
      begin
        s = ai.bind
      rescue SystemCallError
        last_error = $!
        next
      end
      sockets << s
    }
    if sockets.empty?
      raise last_error
    end
  end

  sockets.each {|s|
    ai = s.local_address
    if ipv6_recvpktinfo && ai.ipv6? && ai.ip_address == "::"
      s.setsockopt(:IPV6, ipv6_recvpktinfo, 1)
    end
  }

  if block_given?
    begin
      yield sockets
    ensure
      sockets.each(&:close) if sockets
    end
  else
    sockets
  end
end
unix(path) { |socket| ... } 点击切换源代码

创建一个新的 socket,该 socket 使用 UNIX socket 连接到路径。

如果给出了块,则将使用该 socket 调用该块。返回该块的值。当此方法返回时,该 socket 将关闭。

如果没有给出块,则返回该 socket。

# talk to /tmp/sock socket.
Socket.unix("/tmp/sock") {|sock|
  t = Thread.new { IO.copy_stream(sock, STDOUT) }
  IO.copy_stream(STDIN, sock)
  t.join
}
# File socket/lib/socket.rb, line 1492
def self.unix(path) # :yield: socket
  addr = Addrinfo.unix(path)
  sock = addr.connect
  if block_given?
    begin
      yield sock
    ensure
      sock.close
    end
  else
    sock
  end
end
unix_server_loop(path) { |socket, client_addrinfo| ... } 点击切换源代码

path 上创建一个 UNIX socket 服务器。它为每个接受的 socket 调用该块。

如果指定了 host,则将其与 port 一起使用以确定服务器端口。

当块返回时,socket 不会关闭。因此,应用程序应关闭它。

此方法首先删除 path 指向的套接字文件(如果该文件是套接字文件,且属于应用程序的用户)。只有当 path 的目录没有被恶意用户更改时,才是安全的。因此,请不要使用 /tmp/malicious-users-directory/socket。请注意,假设 /tmp 具有粘性位,那么 /tmp/socket 和 /tmp/your-private-directory/socket 是安全的。

# Sequential echo server.
# It services only one client at a time.
Socket.unix_server_loop("/tmp/sock") {|sock, client_addrinfo|
  begin
    IO.copy_stream(sock, sock)
  ensure
    sock.close
  end
}
# File socket/lib/socket.rb, line 1579
def self.unix_server_loop(path, &b) # :yield: socket, client_addrinfo
  unix_server_socket(path) {|serv|
    accept_loop(serv, &b)
  }
end
unix_server_socket(path) { |s| ... } 点击以切换源码

path 上创建一个 UNIX 服务器套接字

如果没有给定块,则返回一个监听套接字。

如果给定了块,则会使用该套接字调用该块,并返回块的值。当块退出时,套接字将被关闭,并且套接字文件将被删除。

socket = Socket.unix_server_socket("/tmp/s")
p socket                  #=> #<Socket:fd 3>
p socket.local_address    #=> #<Addrinfo: /tmp/s SOCK_STREAM>

Socket.unix_server_socket("/tmp/sock") {|s|
  p s                     #=> #<Socket:fd 3>
  p s.local_address       #=> # #<Addrinfo: /tmp/sock SOCK_STREAM>
}
# File socket/lib/socket.rb, line 1522
def self.unix_server_socket(path)
  unless unix_socket_abstract_name?(path)
    begin
      st = File.lstat(path)
    rescue Errno::ENOENT
    end
    if st&.socket? && st.owned?
      File.unlink path
    end
  end
  s = Addrinfo.unix(path).listen
  if block_given?
    begin
      yield s
    ensure
      s.close
      unless unix_socket_abstract_name?(path)
        File.unlink path
      end
    end
  else
    s
  end
end
unpack_sockaddr_in(sockaddr) → [port, ip_address] 点击以切换源码

sockaddr 解包为端口和 IP 地址。

sockaddr 应该是一个字符串或一个用于 AF_INET/AF_INET6 的地址信息 (addrinfo)。

sockaddr = Socket.sockaddr_in(80, "127.0.0.1")
p sockaddr #=> "\x02\x00\x00P\x7F\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00"
p Socket.unpack_sockaddr_in(sockaddr) #=> [80, "127.0.0.1"]
static VALUE
sock_s_unpack_sockaddr_in(VALUE self, VALUE addr)
{
    struct sockaddr_in * sockaddr;
    VALUE host;

    sockaddr = (struct sockaddr_in*)SockAddrStringValuePtr(addr);
    if (RSTRING_LEN(addr) <
        (char*)&((struct sockaddr *)sockaddr)->sa_family +
        sizeof(((struct sockaddr *)sockaddr)->sa_family) -
        (char*)sockaddr)
        rb_raise(rb_eArgError, "too short sockaddr");
    if (((struct sockaddr *)sockaddr)->sa_family != AF_INET
#ifdef INET6
        && ((struct sockaddr *)sockaddr)->sa_family != AF_INET6
#endif
        ) {
#ifdef INET6
        rb_raise(rb_eArgError, "not an AF_INET/AF_INET6 sockaddr");
#else
        rb_raise(rb_eArgError, "not an AF_INET sockaddr");
#endif
    }
    host = rsock_make_ipaddr((struct sockaddr*)sockaddr, RSTRING_SOCKLEN(addr));
    return rb_assoc_new(INT2NUM(ntohs(sockaddr->sin_port)), host);
}
unpack_sockaddr_un(sockaddr) → path 点击以切换源码

sockaddr 解包为路径。

sockaddr 应该是一个字符串或一个用于 AF_UNIX 的地址信息 (addrinfo)。

sockaddr = Socket.sockaddr_un("/tmp/sock")
p Socket.unpack_sockaddr_un(sockaddr) #=> "/tmp/sock"
static VALUE
sock_s_unpack_sockaddr_un(VALUE self, VALUE addr)
{
    struct sockaddr_un * sockaddr;
    VALUE path;

    sockaddr = (struct sockaddr_un*)SockAddrStringValuePtr(addr);
    if (RSTRING_LEN(addr) <
        (char*)&((struct sockaddr *)sockaddr)->sa_family +
        sizeof(((struct sockaddr *)sockaddr)->sa_family) -
        (char*)sockaddr)
        rb_raise(rb_eArgError, "too short sockaddr");
    if (((struct sockaddr *)sockaddr)->sa_family != AF_UNIX) {
        rb_raise(rb_eArgError, "not an AF_UNIX sockaddr");
    }
    if (sizeof(struct sockaddr_un) < (size_t)RSTRING_LEN(addr)) {
        rb_raise(rb_eTypeError, "too long sockaddr_un - %ld longer than %d",
                 RSTRING_LEN(addr), (int)sizeof(struct sockaddr_un));
    }
    path = rsock_unixpath_str(sockaddr, RSTRING_SOCKLEN(addr));
    return path;
}

私有类方法

current_clock_time() 点击以切换源码
# File socket/lib/socket.rb, line 960
def self.current_clock_time
  Process.clock_gettime(Process::CLOCK_MONOTONIC)
end
expired?(started_at, ends_at) 点击以切换源码
# File socket/lib/socket.rb, line 973
def self.expired?(started_at, ends_at)
  second_to_timeout(started_at, ends_at)&.zero?
end
ip_address?(hostname) 点击以切换源码
# File socket/lib/socket.rb, line 945
def self.ip_address?(hostname)
  hostname.match?(IPV6_ADRESS_FORMAT) || hostname.match?(/\A([0-9]{1,3}\.){3}[0-9]{1,3}\z/)
end
resolve_hostname(family, host, port, hostname_resolution_result) 点击以切换源码
# File socket/lib/socket.rb, line 950
def self.resolve_hostname(family, host, port, hostname_resolution_result)
  begin
    resolved_addrinfos = Addrinfo.getaddrinfo(host, port, ADDRESS_FAMILIES[family], :STREAM)
    hostname_resolution_result.add(family, resolved_addrinfos)
  rescue => e
    hostname_resolution_result.add(family, e)
  end
end
second_to_timeout(started_at, ends_at) 点击以切换源码
# File socket/lib/socket.rb, line 965
def self.second_to_timeout(started_at, ends_at)
  return nil if ends_at == Float::INFINITY || ends_at.nil?

  remaining = (ends_at - started_at)
  remaining.negative? ? 0 : remaining
end
tcp_without_fast_fallback(host, port, local_host, local_port, connect_timeout:, resolv_timeout:) 点击以切换源码
# File socket/lib/socket.rb, line 906
def self.tcp_without_fast_fallback(host, port, local_host, local_port, connect_timeout:, resolv_timeout:)
  last_error = nil
  ret = nil

  local_addr_list = nil
  if local_host != nil || local_port != nil
    local_addr_list = Addrinfo.getaddrinfo(local_host, local_port, nil, :STREAM, nil)
  end

  Addrinfo.foreach(host, port, nil, :STREAM, timeout: resolv_timeout) {|ai|
    if local_addr_list
      local_addr = local_addr_list.find {|local_ai| local_ai.afamily == ai.afamily }
      next unless local_addr
    else
      local_addr = nil
    end
    begin
      sock = local_addr ?
        ai.connect_from(local_addr, timeout: connect_timeout) :
        ai.connect(timeout: connect_timeout)
    rescue SystemCallError
      last_error = $!
      next
    end
    ret = sock
    break
  }
  unless ret
    if last_error
      raise last_error
    else
      raise SocketError, "no appropriate local address"
    end
  end

  ret
end
unix_socket_abstract_name?(path) 点击以切换源码
# File socket/lib/socket.rb, line 1550
def unix_socket_abstract_name?(path)
  /linux/ =~ RUBY_PLATFORM && /\A(\0|\z)/ =~ path
end

公共实例方法

accept → [client_socket, client_addrinfo] 点击以切换源码

接受下一个连接。返回一个新的 Socket 对象和一个 Addrinfo 对象。

serv = Socket.new(:INET, :STREAM, 0)
serv.listen(5)
c = Socket.new(:INET, :STREAM, 0)
c.connect(serv.connect_address)
p serv.accept #=> [#<Socket:fd 6>, #<Addrinfo: 127.0.0.1:48555 TCP>]
static VALUE
sock_accept(VALUE server)
{
    union_sockaddr buffer;
    socklen_t length = (socklen_t)sizeof(buffer);

    VALUE peer = rsock_s_accept(rb_cSocket, server, &buffer.addr, &length);

    return rb_assoc_new(peer, rsock_io_socket_addrinfo(peer, &buffer.addr, length));
}
accept_nonblock([options]) → [client_socket, client_addrinfo] 点击以切换源码

在为底层文件描述符设置 O_NONBLOCK 之后,使用 accept(2) 接受传入的连接。它返回一个数组,其中包含传入连接的已接受套接字 client_socket 和一个 Addrinfoclient_addrinfo

示例

# In one script, start this first
require 'socket'
include Socket::Constants
socket = Socket.new(AF_INET, SOCK_STREAM, 0)
sockaddr = Socket.sockaddr_in(2200, 'localhost')
socket.bind(sockaddr)
socket.listen(5)
begin # emulate blocking accept
  client_socket, client_addrinfo = socket.accept_nonblock
rescue IO::WaitReadable, Errno::EINTR
  IO.select([socket])
  retry
end
puts "The client said, '#{client_socket.readline.chomp}'"
client_socket.puts "Hello from script one!"
socket.close

# In another script, start this second
require 'socket'
include Socket::Constants
socket = Socket.new(AF_INET, SOCK_STREAM, 0)
sockaddr = Socket.sockaddr_in(2200, 'localhost')
socket.connect(sockaddr)
socket.puts "Hello from script 2."
puts "The server said, '#{socket.readline.chomp}'"
socket.close

请参阅 Socket#accept,了解如果对 accept_nonblock 的调用失败可能抛出的异常。

Socket#accept_nonblock 可能会引发任何对应于 accept(2) 失败的错误,包括 Errno::EWOULDBLOCK。

如果异常是 Errno::EWOULDBLOCK、Errno::EAGAIN、Errno::ECONNABORTED 或 Errno::EPROTO,则会通过 IO::WaitReadable 进行扩展。因此,IO::WaitReadable 可以用于捕获异常以重试 accept_nonblock。

通过将关键字参数 exception 指定为 false,您可以指示 accept_nonblock 不应引发 IO::WaitReadable 异常,而是返回符号 :wait_readable

请参阅

# File socket/lib/socket.rb, line 598
def accept_nonblock(exception: true)
  __accept_nonblock(exception)
end
bind(local_sockaddr) → 0 点击以切换源码

绑定到给定的本地地址。

参数

  • local_sockaddr - 包含在字符串或 Addrinfo 对象中的 struct sockaddr

示例

require 'socket'

# use Addrinfo
socket = Socket.new(:INET, :STREAM, 0)
socket.bind(Addrinfo.tcp("127.0.0.1", 2222))
p socket.local_address #=> #<Addrinfo: 127.0.0.1:2222 TCP>

# use struct sockaddr
include Socket::Constants
socket = Socket.new( AF_INET, SOCK_STREAM, 0 )
sockaddr = Socket.pack_sockaddr_in( 2200, 'localhost' )
socket.bind( sockaddr )

基于 Unix 的异常

在基于 Unix 的系统中,如果对 bind 的调用失败,可能会引发以下系统异常

  • Errno::EACCES - 指定的 sockaddr 受保护,并且当前用户没有权限绑定到它

  • Errno::EADDRINUSE - 指定的 sockaddr 已经被使用

  • Errno::EADDRNOTAVAIL - 指定的 sockaddr 在本地计算机上不可用

  • Errno::EAFNOSUPPORT - 指定的 sockaddr 对于调用 socket 的族来说不是一个有效的地址

  • Errno::EBADF - 指定的 sockaddr 不是有效的文件描述符

  • Errno::EFAULT - 无法访问 sockaddr 参数

  • Errno::EINVAL - socket 已经绑定到一个地址,并且协议不支持绑定到新的 sockaddr,或者 socket 已经关闭。

  • Errno::EINVAL - 地址长度对于地址族不是有效的长度

  • Errno::ENAMETOOLONG - 解析的路径名长度超过了 PATH_MAX

  • Errno::ENOBUFS - 没有可用的缓冲区空间

  • Errno::ENOSR - 没有足够的 STREAMS 资源可用来完成操作

  • Errno::ENOTSOCK - socket 不是指套接字

  • Errno::EOPNOTSUPP - socket 的套接字类型不支持绑定到地址

在基于 Unix 的系统中,如果调用 socket 的地址族是 Socket::AF_UNIX,如果对 bind 的调用失败,可能会引发以下异常

  • Errno::EACCES - 前缀路径的组件被拒绝搜索权限,或者拒绝写入 socket 的访问权限

  • Errno::EDESTADDRREQ - sockaddr 参数是一个空指针

  • Errno::EISDIR - 与 Errno::EDESTADDRREQ 相同

  • Errno::EIO - 发生了 i/o 错误

  • Errno::ELOOP - 在转换 sockaddr 中的路径名时遇到了太多符号链接

  • Errno::ENAMETOOLLONG - 路径名的组件超过了 NAME_MAX 字符,或者整个路径名超过了 PATH_MAX 字符

  • Errno::ENOENT - 路径名的组件没有命名现有文件,或者路径名是空字符串

  • Errno::ENOTDIR - sockaddr 中路径名的路径前缀的组件不是目录

  • Errno::EROFS - 名称将驻留在只读文件系统上

Windows 异常

在 Windows 系统上,如果对 bind 的调用失败,可能会引发以下系统异常

  • Errno::ENETDOWN - 网络已关闭

  • Errno::EACCES - 尝试将数据报套接字连接到广播地址失败

  • Errno::EADDRINUSE - 套接字的本地地址已被使用

  • Errno::EADDRNOTAVAIL - 指定的地址对于此计算机不是有效的地址

  • Errno::EFAULT - 套接字的内部地址或地址长度参数太小,或者不是用户空间寻址的有效部分

  • Errno::EINVAL - socket 已经绑定到一个地址

  • Errno::ENOBUFS - 没有可用的缓冲区空间

  • Errno::ENOTSOCK - socket 参数不是指套接字

请参阅

  • 基于 Unix 的系统上的 bind 手册页

  • Microsoft Winsock 函数参考中的 bind 函数

static VALUE
sock_bind(VALUE sock, VALUE addr)
{
    VALUE rai;
    rb_io_t *fptr;

    SockAddrStringValueWithAddrinfo(addr, rai);
    GetOpenFile(sock, fptr);
    if (bind(fptr->fd, (struct sockaddr*)RSTRING_PTR(addr), RSTRING_SOCKLEN(addr)) < 0)
        rsock_sys_fail_raddrinfo_or_sockaddr("bind(2)", addr, rai);

    return INT2FIX(0);
}
connect(remote_sockaddr) → 0 点击以切换源码

请求在给定的 remote_sockaddr 上建立连接。如果成功,则返回 0;否则,会引发异常。

参数

  • remote_sockaddr - 包含在字符串或 Addrinfo 对象中的 struct sockaddr

示例:

# Pull down Google's web page
require 'socket'
include Socket::Constants
socket = Socket.new( AF_INET, SOCK_STREAM, 0 )
sockaddr = Socket.pack_sockaddr_in( 80, 'www.google.com' )
socket.connect( sockaddr )
socket.write( "GET / HTTP/1.0\r\n\r\n" )
results = socket.read

基于 Unix 的异常

在基于 Unix 的系统中,如果对 connect 的调用失败,可能会引发以下系统异常

  • Errno::EACCES - 前缀路径的组件被拒绝搜索权限,或者拒绝写入 socket 的访问权限

  • Errno::EADDRINUSE - sockaddr 已经被使用

  • Errno::EADDRNOTAVAIL - 指定的 sockaddr 在本地计算机上不可用

  • Errno::EAFNOSUPPORT - 指定的 sockaddr 对于指定 socket 的地址族来说不是有效的地址

  • Errno::EALREADY - 指定的套接字已经正在进行连接

  • Errno::EBADF - socket 不是有效的文件描述符

  • Errno::ECONNREFUSED - 目标 sockaddr 没有监听连接,拒绝了连接请求

  • Errno::ECONNRESET - 远程主机重置了连接请求

  • Errno::EFAULT - 无法访问 sockaddr

  • Errno::EHOSTUNREACH - 无法到达目标主机(可能是因为主机已关闭或远程路由器无法到达它)

  • Errno::EINPROGRESS - 为 socket 设置了 O_NONBLOCK,并且无法立即建立连接;连接将异步建立

  • Errno::EINTR - 建立连接的尝试被捕获的信号传递中断;连接将异步建立

  • Errno::EISCONN - 指定的 socket 已经连接

  • Errno::EINVAL - 用于 sockaddr 的地址长度对于地址族不是有效的长度,或者 sockaddr 中存在无效的族

  • Errno::ENAMETOOLONG - 解析的路径名长度超过了 PATH_MAX

  • Errno::ENETDOWN - 用于到达目的地的本地接口已关闭

  • Errno::ENETUNREACH - 不存在到网络的路由

  • Errno::ENOBUFS - 没有可用的缓冲区空间

  • Errno::ENOSR - 没有足够的 STREAMS 资源可用来完成操作

  • Errno::ENOTSOCK - socket 参数不是指套接字

  • Errno::EOPNOTSUPP - 调用的 socket 正在监听,无法连接

  • Errno::EPROTOTYPE - sockaddr 的类型与绑定到指定对等地址的套接字类型不同

  • Errno::ETIMEDOUT - 在建立连接之前,连接尝试超时。

在基于 Unix 的系统中,如果调用 socket 的地址族是 AF_UNIX,如果对 connect 的调用失败,可能会引发以下异常

  • Errno::EIO - 在从文件系统读取或写入文件系统时发生了 i/o 错误

  • Errno::ELOOP - 在转换 sockaddr 中的路径名时遇到了太多符号链接

  • Errno::ENAMETOOLLONG - 路径名的组件超过了 NAME_MAX 字符,或者整个路径名超过了 PATH_MAX 字符

  • Errno::ENOENT - 路径名的组件没有命名现有文件,或者路径名是空字符串

  • Errno::ENOTDIR - sockaddr 中路径名的路径前缀的组件不是目录

Windows 异常

在 Windows 系统上,如果对 connect 的调用失败,可能会引发以下系统异常

  • Errno::ENETDOWN - 网络已关闭

  • Errno::EADDRINUSE - 套接字的本地地址已被使用

  • Errno::EINTR - 套接字已取消

  • Errno::EINPROGRESS - 阻塞套接字正在进行中,或者服务提供商仍在处理回调函数。或者在 socket 上正在进行非阻塞 connect 调用。

  • Errno::EALREADY - 请参阅 Errno::EINVAL

  • Errno::EADDRNOTAVAIL - 远程地址不是有效的地址,例如 ADDR_ANY TODO 检查 ADDRANY 到 INADDR_ANY

  • Errno::EAFNOSUPPORT - 指定族中的地址不能与此 socket 一起使用

  • Errno::ECONNREFUSED - 目标 sockaddr 没有监听连接,拒绝了连接请求

  • Errno::EFAULT - 套接字的内部地址或地址长度参数太小,或者不是用户空间地址的有效部分

  • Errno::EINVAL - socket 是监听套接字

  • Errno::EISCONN - socket 已经连接

  • Errno::ENETUNREACH - 此时无法从此主机到达网络

  • Errno::EHOSTUNREACH - 不存在到网络的路由

  • Errno::ENOBUFS - 没有可用的缓冲区空间

  • Errno::ENOTSOCK - socket 参数不是指套接字

  • Errno::ETIMEDOUT - 在建立连接之前,连接尝试超时。

  • Errno::EWOULDBLOCK - 套接字被标记为非阻塞,并且无法立即完成连接

  • Errno::EACCES - 尝试将数据报套接字连接到广播地址失败

请参阅

  • 基于 Unix 的系统上的 connect 手册页

  • Microsoft Winsock 函数参考中的 connect 函数

static VALUE
sock_connect(VALUE self, VALUE addr)
{
    VALUE rai;

    SockAddrStringValueWithAddrinfo(addr, rai);
    addr = rb_str_new4(addr);

    int result = rsock_connect(self, (struct sockaddr*)RSTRING_PTR(addr), RSTRING_SOCKLEN(addr), 0, RUBY_IO_TIMEOUT_DEFAULT);

    if (result < 0) {
        rsock_sys_fail_raddrinfo_or_sockaddr("connect(2)", addr, rai);
    }

    return INT2FIX(result);
}
connect_nonblock(remote_sockaddr, [options]) → 0 点击以切换源码

在为底层文件描述符设置 O_NONBLOCK 之后,请求在给定的 remote_sockaddr 上建立连接。如果成功,则返回 0;否则,会引发异常。

参数

  • remote_sockaddr - 包含在字符串或 Addrinfo 对象中的 struct sockaddr

示例:

# Pull down Google's web page
require 'socket'
include Socket::Constants
socket = Socket.new(AF_INET, SOCK_STREAM, 0)
sockaddr = Socket.sockaddr_in(80, 'www.google.com')
begin # emulate blocking connect
  socket.connect_nonblock(sockaddr)
rescue IO::WaitWritable
  IO.select(nil, [socket]) # wait 3-way handshake completion
  begin
    socket.connect_nonblock(sockaddr) # check connection failure
  rescue Errno::EISCONN
  end
end
socket.write("GET / HTTP/1.0\r\n\r\n")
results = socket.read

请参阅 Socket#connect,了解如果对 connect_nonblock 的调用失败可能抛出的异常。

Socket#connect_nonblock 可能会引发任何对应于 connect(2) 失败的错误,包括 Errno::EINPROGRESS。

如果异常是 Errno::EINPROGRESS,它会被 IO::WaitWritable 扩展。因此,可以使用 IO::WaitWritable 来捕获异常,以便重试 connect_nonblock。

通过将关键字参数 exception 指定为 false,你可以指示 connect_nonblock 不应引发 IO::WaitWritable 异常,而是返回符号 :wait_writable

请参阅

# File socket/lib/socket.rb, line 1629
def connect_nonblock(addr, exception: true)
  __connect_nonblock(addr, exception)
end
ipv6only!() 点击以切换源代码

如果 IPV6_V6ONLY 可用,则启用套接字选项 IPV6_V6ONLY

# File socket/lib/socket.rb, line 468
def ipv6only!
  if defined? Socket::IPV6_V6ONLY
    self.setsockopt(:IPV6, :V6ONLY, 1)
  end
end
listen( int ) → 0 点击以切换源代码

使用指定的 int 作为积压队列长度来监听连接。只有当 socket 的类型为 SOCK_STREAMSOCK_SEQPACKET 时,调用 listen 才会生效。

参数

  • backlog - 等待连接队列的最大长度。

示例 1

require 'socket'
include Socket::Constants
socket = Socket.new( AF_INET, SOCK_STREAM, 0 )
sockaddr = Socket.pack_sockaddr_in( 2200, 'localhost' )
socket.bind( sockaddr )
socket.listen( 5 )

示例 2 (监听任意端口,仅限基于 Unix 的系统):

require 'socket'
include Socket::Constants
socket = Socket.new( AF_INET, SOCK_STREAM, 0 )
socket.listen( 1 )

基于 Unix 的异常

在基于 Unix 的系统中,上述代码会工作,因为在地址 ADDR_ANY 上为内核分配的任意端口号创建了一个新的 sockaddr 结构。 它在 Windows 上不起作用,因为 Windows 要求在 listen 之前通过调用 bind 来绑定 socket

如果 backlog 的数量超过了依赖于实现的最大队列长度,则将使用该实现的最大队列长度。

在基于 Unix 的系统中,如果调用 listen 失败,可能会引发以下系统异常

  • Errno::EBADF - socket 参数不是有效的文件描述符

  • Errno::EDESTADDRREQ - socket 未绑定到本地地址,并且该协议不支持在未绑定的套接字上进行监听

  • Errno::EINVAL - socket 已连接

  • Errno::ENOTSOCK - socket 参数不是指套接字

  • Errno::EOPNOTSUPP - socket 协议不支持监听

  • Errno::EACCES - 调用进程没有适当的权限

  • Errno::EINVAL - socket 已关闭

  • Errno::ENOBUFS - 系统中没有足够的资源来完成调用

Windows 异常

在 Windows 系统上,如果调用 listen 失败,可能会引发以下系统异常

  • Errno::ENETDOWN - 网络已关闭

  • Errno::EADDRINUSE - 套接字的本地地址已被使用。这通常发生在执行 bind 期间,但如果对部分通配符地址(涉及 ADDR_ANY)调用 bind,并且需要在调用 listen 时提交特定地址,则可能会延迟发生

  • Errno::EINPROGRESS - 一个 Windows Sockets 1.1 调用正在进行中,或者服务提供程序仍在处理回调函数

  • Errno::EINVAL - socket 未通过调用 bind 进行绑定。

  • Errno::EISCONN - socket 已经连接

  • Errno::EMFILE - 没有更多的套接字描述符可用

  • Errno::ENOBUFS - 没有可用的缓冲区空间

  • Errno::ENOTSOC - socket 不是套接字

  • Errno::EOPNOTSUPP - 引用的 socket 不是支持 listen 方法的类型

请参阅

  • 基于 Unix 的系统上的 listen 手册页

  • Microsoft Winsock 函数参考中的 listen 函数

VALUE
rsock_sock_listen(VALUE sock, VALUE log)
{
    rb_io_t *fptr;
    int backlog;

    backlog = NUM2INT(log);
    GetOpenFile(sock, fptr);
    if (listen(fptr->fd, backlog) < 0)
        rb_sys_fail("listen(2)");

    return INT2FIX(0);
}
recvfrom(maxlen) → [mesg, sender_addrinfo] 点击以切换源代码
recvfrom(maxlen, flags) → [mesg, sender_addrinfo]

socket 接收最多 maxlen 个字节。flags 是零个或多个 MSG_ 选项。结果的第一个元素 mesg 是接收到的数据。第二个元素 sender_addrinfo 包含发送方的特定于协议的地址信息。

参数

  • maxlen - 从套接字接收的最大字节数

  • flags - 零个或多个 MSG_ 选项

示例

# In one file, start this first
require 'socket'
include Socket::Constants
socket = Socket.new( AF_INET, SOCK_STREAM, 0 )
sockaddr = Socket.pack_sockaddr_in( 2200, 'localhost' )
socket.bind( sockaddr )
socket.listen( 5 )
client, client_addrinfo = socket.accept
data = client.recvfrom( 20 )[0].chomp
puts "I only received 20 bytes '#{data}'"
sleep 1
socket.close

# In another file, start this second
require 'socket'
include Socket::Constants
socket = Socket.new( AF_INET, SOCK_STREAM, 0 )
sockaddr = Socket.pack_sockaddr_in( 2200, 'localhost' )
socket.connect( sockaddr )
socket.puts "Watch this get cut short!"
socket.close

基于 Unix 的异常

在基于 Unix 的系统中,如果调用 recvfrom 失败,可能会引发以下系统异常

  • Errno::EAGAIN - socket 文件描述符被标记为 O_NONBLOCK,并且没有数据等待被接收;或者设置了 MSG_OOB,并且没有带外数据可用,并且 socket 文件描述符被标记为 O_NONBLOCK 或 socket 不支持阻塞来等待带外数据

  • Errno::EWOULDBLOCK - 请参阅 Errno::EAGAIN

  • Errno::EBADF - socket 不是有效的文件描述符

  • Errno::ECONNRESET - 连接被对等方强制关闭

  • Errno::EFAULT - 无法访问或写入套接字的内部缓冲区、地址或地址长度

  • Errno::EINTR - 信号在任何数据可用之前中断了 recvfrom

  • Errno::EINVAL - 设置了 MSG_OOB 标志,并且没有带外数据可用

  • Errno::EIO - 从文件系统读取或写入时发生 i/o 错误

  • Errno::ENOBUFS - 系统中没有足够的资源来执行操作

  • Errno::ENOMEM - 没有足够的内存来满足请求

  • Errno::ENOSR - 没有足够的 STREAMS 资源可用来完成操作

  • Errno::ENOTCONN - 尝试在未连接的连接模式套接字上进行接收

  • Errno::ENOTSOCK - socket 不是指套接字

  • Errno::EOPNOTSUPP - 此套接字类型不支持指定的标志

  • Errno::ETIMEDOUT - 在连接建立期间或由于活动连接上的传输超时而导致连接超时

Windows 异常

在 Windows 系统上,如果调用 recvfrom 失败,可能会引发以下系统异常

  • Errno::ENETDOWN - 网络已关闭

  • Errno::EFAULT - socket 上的内部缓冲区和 from 参数不是用户地址空间的一部分,或者内部 fromlen 参数太小,无法容纳对等地址

  • Errno::EINTR - (阻塞)调用被内部调用 WinSock 函数 WSACancelBlockingCall 取消

  • Errno::EINPROGRESS - 一个阻塞的 Windows Sockets 1.1 调用正在进行中,或者服务提供程序仍在处理回调函数

  • Errno::EINVAL - socket 未通过调用 bind 进行绑定,或者指定了未知的标志,或者为启用了 SO_OOBINLINE 的套接字指定了 MSG_OOB,或者(仅对于字节流式套接字)socket 上的内部 len 参数为零或负数

  • Errno::EISCONN - socket 已连接。在面向连接或无连接的套接字上,不允许对已连接的套接字调用 recvfrom

  • Errno::ENETRESET - 由于在操作进行期间检测到故障的保持活动活动,连接已断开。

  • Errno::EOPNOTSUPP - 指定了 MSG_OOB,但 socket 不是流式类型,例如 SOCK_STREAM 类型。与 socket 关联的通信域中不支持 OOB 数据,或者 socket 是单向的,仅支持发送操作

  • Errno::ESHUTDOWN - socket 已关闭。在调用 shutdown 之后,无法在套接字上调用 recvfrom

  • Errno::EWOULDBLOCK - socket 被标记为非阻塞,并且对 recvfrom 的调用会阻塞。

  • Errno::EMSGSIZE - 消息太大,无法放入指定的缓冲区中,并被截断。

  • Errno::ETIMEDOUT - 连接已断开,因为网络故障或因为另一端的系统在没有通知的情况下关闭

  • Errno::ECONNRESET - 远程端执行硬关闭或中止关闭重置了虚拟电路。应用程序应关闭套接字;它不再可用。在 UDP 数据报套接字上,此错误指示先前的发送操作导致了 ICMP 端口不可达消息。

static VALUE
sock_recvfrom(int argc, VALUE *argv, VALUE sock)
{
    return rsock_s_recvfrom(sock, argc, argv, RECV_SOCKET);
}
recvfrom_nonblock(maxlen[, flags[, outbuf[, opts]]]) → [mesg, sender_addrinfo] 点击以切换源代码

在为底层文件描述符设置 O_NONBLOCK 后,使用 recvfrom(2) 从 socket 接收最多 maxlen 个字节。flags 是零个或多个 MSG_ 选项。结果的第一个元素 mesg 是接收到的数据。第二个元素 sender_addrinfo 包含发送方的特定于协议的地址信息。

当 recvfrom(2) 返回 0 时,Socket#recv_nonblock 返回 nil。在大多数情况下,这意味着连接已关闭,但对于 UDP 连接,这可能意味着收到了一个空数据包,因为底层 API 无法区分这两种情况。

参数

  • maxlen - 从套接字接收的最大字节数

  • flags - 零个或多个 MSG_ 选项

  • outbuf - 目标 String 缓冲区

  • opts - 关键字哈希,支持 'exception: false`

示例

# In one file, start this first
require 'socket'
include Socket::Constants
socket = Socket.new(AF_INET, SOCK_STREAM, 0)
sockaddr = Socket.sockaddr_in(2200, 'localhost')
socket.bind(sockaddr)
socket.listen(5)
client, client_addrinfo = socket.accept
begin # emulate blocking recvfrom
  pair = client.recvfrom_nonblock(20)
rescue IO::WaitReadable
  IO.select([client])
  retry
end
data = pair[0].chomp
puts "I only received 20 bytes '#{data}'"
sleep 1
socket.close

# In another file, start this second
require 'socket'
include Socket::Constants
socket = Socket.new(AF_INET, SOCK_STREAM, 0)
sockaddr = Socket.sockaddr_in(2200, 'localhost')
socket.connect(sockaddr)
socket.puts "Watch this get cut short!"
socket.close

如果调用 recvfrom_nonblock 失败,请参阅 Socket#recvfrom 了解可能引发的异常。

Socket#recvfrom_nonblock 可能会引发任何与 recvfrom(2) 失败对应的错误,包括 Errno::EWOULDBLOCK。

如果异常是 Errno::EWOULDBLOCK 或 Errno::EAGAIN,它会被 IO::WaitReadable 扩展。因此,可以使用 IO::WaitReadable 来捕获异常,以便重试 recvfrom_nonblock。

通过将关键字参数 exception 指定为 false,你可以指示 recvfrom_nonblock 不应引发 IO::WaitReadable 异常,而是返回符号 :wait_readable

请参阅

# File socket/lib/socket.rb, line 541
def recvfrom_nonblock(len, flag = 0, str = nil, exception: true)
  __recvfrom_nonblock(len, flag, str, exception)
end
sysaccept → [client_socket_fd, client_addrinfo] 点击以切换源代码

接受传入的连接,返回一个数组,其中包含传入连接的(整数)文件描述符 client_socket_fd 和一个 Addrinfo client_addrinfo

示例

# In one script, start this first
require 'socket'
include Socket::Constants
socket = Socket.new( AF_INET, SOCK_STREAM, 0 )
sockaddr = Socket.pack_sockaddr_in( 2200, 'localhost' )
socket.bind( sockaddr )
socket.listen( 5 )
client_fd, client_addrinfo = socket.sysaccept
client_socket = Socket.for_fd( client_fd )
puts "The client said, '#{client_socket.readline.chomp}'"
client_socket.puts "Hello from script one!"
socket.close

# In another script, start this second
require 'socket'
include Socket::Constants
socket = Socket.new( AF_INET, SOCK_STREAM, 0 )
sockaddr = Socket.pack_sockaddr_in( 2200, 'localhost' )
socket.connect( sockaddr )
socket.puts "Hello from script 2."
puts "The server said, '#{socket.readline.chomp}'"
socket.close

如果调用 sysaccept 失败,请参阅 Socket#accept 了解可能引发的异常。

请参阅

static VALUE
sock_sysaccept(VALUE server)
{
    union_sockaddr buffer;
    socklen_t length = (socklen_t)sizeof(buffer);

    VALUE peer = rsock_s_accept(0, server, &buffer.addr, &length);

    return rb_assoc_new(peer, rsock_io_socket_addrinfo(peer, &buffer.addr, length));
}