class Socket
Class Socket 提供了对底层操作系统套接字实现的访问。与特定协议的套接字类相比,它可用于提供更多操作系统特定的功能。
在 Socket::Constants 下定义的常量也定义在 Socket 下。例如,Socket::AF_INET 可用,Socket::Constants::AF_INET 也是如此。有关常量列表,请参阅 Socket::Constants。
什么是套接字?
套接字是双向通信通道的端点。套接字可以在同一进程内、同一台机器上的进程之间或不同机器之间进行通信。套接字有许多类型:例如 TCPSocket、UDPSocket 或 UNIXSocket。
套接字有自己的词汇
domain: 协议族
type: 两个端点之间的通信类型,通常是
protocol: 通常为零。这可用于标识协议的变体。
hostname: 网络接口的标识符
-
字符串(主机名、IPv4 或 IPv6 地址或指定广播地址的
broadcast) -
零长度字符串,指定
INADDR_ANY -
整数(在主机字节序中解释为二进制地址)。
快速入门
许多类,如 TCPSocket、UDPSocket 或 UNIXSocket,与等效的 C 编程接口相比,简化了套接字的使用。
让我们以类 C 的方式创建一个使用 IPv4 协议的互联网套接字
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
Exception 处理
Ruby 的 Socket 实现根据系统依赖的实现生成的错误引发异常。这就是为什么文档以区分 Unix 系统异常和 Windows 系统异常的方式编写方法的原因。如果需要有关特定异常的更多信息,请参阅 Unix 手册页或 Windows WinSock 参考。
便捷方法
虽然创建套接字的一般方法是 Socket.new,但对于大多数情况,有几种套接字创建方法。
- TCP 客户端套接字
- TCP 服务器套接字
- UNIX 客户端套接字
- UNIX 服务器套接字
文档作者
-
Zach Dennis
-
Sam Roberts
-
来自 The Pragmatic Bookshelf 的《Programming Ruby》。
本手册中的大部分内容经允许摘自 The Pragmatic Bookshelf 的《Programming Ruby》。
Constants
- AF_ALG
-
内核加密 API 接口
- AF_APPLETALK
-
AppleTalk 协议
- AF_ATM
-
异步传输模式
- AF_AX25
-
AX.25 协议
- AF_BLUETOOTH
-
低级别蓝牙套接字协议
- AF_CAN
-
控制器区域网络汽车总线协议
- AF_CCITT
-
CCITT(现为 ITU-T)协议
- AF_CHAOS
-
MIT 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
-
欧洲计算机制造商协议
- AF_HYLINK
-
NSC Hyperchannel 协议
- AF_IB
-
InfiniBand 原生寻址
- AF_IMPLINK
-
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_LINK
-
链路层接口
- AF_LLC
-
逻辑链路控制(IEEE 802.2 LLC)协议
- AF_LOCAL
-
主机内部协议
- AF_MAX
-
此平台的最大地址族
- AF_MPLS
-
多协议标签交换
- AF_NATM
-
原生 ATM 接入
- AF_NDRV
-
网络驱动程序原始访问
- AF_NETBIOS
-
NetBIOS
- AF_NETGRAPH
-
Netgraph 套接字
- AF_NETLINK
-
内核用户接口设备
- 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
-
简单 Internet 协议
- AF_SNA
-
IBM SNA 协议
- AF_SYSTEM
-
内核事件消息
- AF_TIPC
-
TIPC,“集群域套接字”协议
- AF_UNIX
-
UNIX 套接字
- AF_UNSPEC
-
未指定的协议,任何支持的地址族
- AF_VSOCK
-
VSOCK(最初为“VMWare VSockets”)协议,用于虚拟机监控程序-客户机通信
- AF_XDP
-
XDP(Express Data Path)接口
- 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 映射的地址
- EAI_ADDRFAMILY
-
主机名地址族不支持
- EAI_AGAIN
-
名称解析暂时失败
- EAI_BADFLAGS
-
无效标志
- EAI_BADHINTS
-
hints 的值无效
- EAI_FAIL
-
名称解析不可恢复的失败
- EAI_FAMILY
-
地址族不支持
- EAI_MAX
-
getaddrinfo 的最大错误代码
- EAI_MEMORY
-
内存分配失败
- EAI_NODATA
-
主机名没有关联的地址
- EAI_NONAME
-
主机名或服务名不存在,或未知
- EAI_OVERFLOW
-
参数缓冲区溢出
- EAI_PROTOCOL
-
解析出的协议未知
- EAI_SERVICE
-
服务名不适用于套接字类型
- EAI_SOCKTYPE
-
套接字类型不支持
- EAI_SYSTEM
-
errno 中返回的系统错误
- 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 开启
- 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
-
传输时共享 skbs
- 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
-
bind 或 connect 的默认最小地址
- IPPORT_USERRESERVED
-
bind 或 connect 的默认最大地址
- IPPROTO_AH
-
IP6 认证头
- 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
-
Express 传输协议
- IPV6_CHECKSUM
-
原始套接字的校验和偏移
- IPV6_DONTFRAG
-
禁止分片数据包
- IPV6_DSTOPTS
-
目标选项
- IPV6_HOPLIMIT
-
跳数限制
- IPV6_HOPOPTS
-
跳对跳选项
- IPV6_JOIN_GROUP
-
加入组播组成员
- IPV6_LEAVE_GROUP
-
离开组播组成员
- IPV6_MTU_DISCOVER
-
路径 MTU 发现
- IPV6_MULTICAST_HOPS
-
IPv6 组播跳数
- IPV6_MULTICAST_IF
-
IPv6 组播接口
- IPV6_MULTICAST_LOOP
-
IPv6 组播环回
- IPV6_NEXTHOP
-
下一跳地址
- IPV6_PATHMTU
-
检索当前路径 MTU
- IPV6_PKTINFO
-
接收带数据报的数据包信息
- IPV6_RECVDSTOPTS
-
接收所有 IPv6 响应选项
- 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
-
IPv6 单播跳数
- IPV6_USE_MIN_MTU
-
使用最小 MTU 大小
- IPV6_V6ONLY
-
仅在通配符绑定时绑定 IPv6
- 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
- LOCAL_CONNWAIT
-
connect 阻塞直到接受
- 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_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_SEND
-
发送 so_temp 中的数据包
- 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
-
MIT CHAOS 协议
- PF_CNT
-
计算机网络技术
- PF_COIP
-
面向连接的 IP
- PF_DATAKIT
-
Datakit 协议
- PF_DEC
-
DECnet 协议
- PF_DECnet
-
DECnet 协议
- PF_DLI
-
DEC 直接数据链路接口协议
- PF_ECMA
-
欧洲计算机制造商协议
- PF_HYLINK
-
NSC Hyperchannel 协议
- PF_IB
-
InfiniBand 原生寻址
- PF_IMPLINK
-
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_LINK
-
链路层接口
- PF_LLC
-
逻辑链路控制(IEEE 802.2 LLC)协议
- PF_LOCAL
-
主机内部协议
- PF_MAX
-
此平台的最大地址族
- PF_MPLS
-
多协议标签交换
- PF_NATM
-
原生 ATM 接入
- PF_NDRV
-
网络驱动程序原始访问
- PF_NETBIOS
-
NetBIOS
- PF_NETGRAPH
-
Netgraph 套接字
- PF_NETLINK
-
内核用户接口设备
- 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
-
简单 Internet 协议
- PF_SNA
-
IBM SNA 协议
- PF_SYSTEM
-
内核事件消息
- PF_TIPC
-
TIPC,“集群域套接字”协议
- PF_UNIX
-
UNIX 套接字
- PF_UNSPEC
-
未指定的协议,任何支持的地址族
- PF_VSOCK
-
VSOCK(最初为“VMWare VSockets”)协议,用于虚拟机监控程序-客户机通信
- PF_XDP
-
XDP(Express Data Path)接口
- PF_XTP
-
Express 传输协议
- SCM_BINTIME
-
时间戳(二进制时间)
- SCM_CREDENTIALS
-
发送者的凭证
- SCM_CREDS
-
进程凭证
- SCM_RIGHTS
-
访问权限
- SCM_TIMESTAMP
-
时间戳(timeval)
- SCM_TIMESTAMPING
-
时间戳(timespec 列表)(Linux 2.6.30)
- SCM_TIMESTAMPNS
-
timespec(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
-
为新文件描述符引用的打开文件描述(参见 open(2))设置 O_NONBLOCK 文件状态标志。
- 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
-
接收带数据报的时间戳(二进制时间)
- 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_ENCRYPTION_NETWORK
- 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
-
尽可能绕过硬件
- SO_USER_COOKIE
-
主要用于设置 ipfw 的标识符
- SO_WANTMORE
-
当更多数据可用时发出提示
- SO_WANTOOBFLAG
-
接收时,希望在 MSG_FLAG 中包含 OOB 数据
- SO_WIFI_STATUS
-
切换 cmsg 以获取 Wi-Fi 状态 (Linux 3.3)
- TCP_CONGESTION
-
TCP 拥塞控制算法 (Linux 2.6.13, glibc 2.6)
- TCP_CONNECTION_INFO
-
检索有关此套接字的信息 (macOS)
- TCP_COOKIE_TRANSACTIONS
-
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
-
在发送 keepalive 探测之前空闲的时间 (macOS)
- TCP_KEEPCNT
-
在断开连接之前允许的最大 keepalive 探测次数 (Linux 2.4, glibc 2.2)
- TCP_KEEPIDLE
-
在发送 keepalive 探测之前空闲的时间 (Linux 2.4, glibc 2.2)
- TCP_KEEPINTVL
-
keepalive 探测之间的时间间隔 (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)
Public Class Methods
Source
# File ext/socket/lib/socket.rb, line 1258 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
对通过给定套接字接受的每个连接,yield 套接字和客户端地址。
参数是套接字列表。单个参数应该是套接字或套接字数组。
此方法顺序地 yield 块。这意味着直到块返回,下一个连接才被接受。因此,应使用并发机制(例如线程)来同时服务多个客户端。
Source
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, Qnil);
ret = make_addrinfo(res, norevlookup);
rb_freeaddrinfo(res);
return ret;
}
获取 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。
Source
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;
}
请改用 Addrinfo#getnameinfo。由于以下原因,此方法已弃用
-
不常见的地址表示:4/16 字节二进制字符串用于表示 IPv4/IPv6 地址。
-
gethostbyaddr() 可能耗时较长,并且可能阻塞其他线程。(GVL 无法释放,因为 gethostbyname() 不是线程安全的)
-
此方法使用了已从 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"]
Source
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, Qnil);
return rsock_make_hostent(host, res, sock_sockaddr);
}
请改用 Addrinfo.getaddrinfo。由于以下原因,此方法已弃用
-
结果的第三个元素是第一个地址的地址族。其余地址的地址族未返回。
-
不常见的地址表示:4/16 字节二进制字符串用于表示 IPv4/IPv6 地址。
-
gethostbyname() 可能耗时较长,并且可能阻塞其他线程。(GVL 无法释放,因为 gethostbyname() 不是线程安全的)
-
此方法使用了已从 POSIX 中删除的 gethostbyname() 函数。
此方法获取 hostname 的主机信息。
p Socket.gethostbyname("hal") #=> ["localhost", ["hal"], 2, "\x7F\x00\x00\x01"]
Source
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;
}
返回主机名。
p Socket.gethostname #=> "hal"
请注意,不能保证可以使用 gethostbyname、getaddrinfo 等转换为 IP 地址。如果您需要本地 IP 地址,请使用 Socket.ip_address_list。
Source
static VALUE
socket_s_getifaddrs(VALUE self)
{
return rsock_getifaddrs();
}
返回接口地址数组。数组的元素是 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)>]
Source
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, Qnil);
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);
}
获取 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#addr 和 IPSocket#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。
Source
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);
}
获取 service_name 的端口号。
如果未给出 protocol_name,则假定为“tcp”。
Socket.getservbyname("smtp") #=> 25 Socket.getservbyname("shell") #=> 514 Socket.getservbyname("syslog", "udp") #=> 514
Source
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);
}
获取 port 的端口号。
如果未给出 protocol_name,则假定为“tcp”。
Socket.getservbyport(80) #=> "www" Socket.getservbyport(514, "tcp") #=> "shell" Socket.getservbyport(514, "udp") #=> "syslog"
Source
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
}
将本地 IP 地址作为数组返回。
数组包含 Addrinfo 对象。
pp Socket.ip_address_list
#=> [#<Addrinfo: 127.0.0.1>,
#<Addrinfo: 192.168.0.128>,
#<Addrinfo: ::1>,
...]
Source
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);
}
创建一个新的套接字对象。
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
Source
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, Qnil);
VALUE addr = rb_str_new((char*)res->ai->ai_addr, res->ai->ai_addrlen);
rb_freeaddrinfo(res);
return addr;
}
将 port 和 host 打包为 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"
Source
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;
}
将 path 打包为 AF_UNIX sockaddr 字符串。
Socket.sockaddr_un("/tmp/sock") #=> "\x01\x00/tmp/sock\x00\x00..."
Source
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;
}
创建一个相互连接的套接字对。
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"
Source
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, Qnil);
VALUE addr = rb_str_new((char*)res->ai->ai_addr, res->ai->ai_addrlen);
rb_freeaddrinfo(res);
return addr;
}
将 port 和 host 打包为 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"
Source
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;
}
将 path 打包为 AF_UNIX sockaddr 字符串。
Socket.sockaddr_un("/tmp/sock") #=> "\x01\x00/tmp/sock\x00\x00..."
Source
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;
}
创建一个相互连接的套接字对。
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"
Source
# File ext/socket/lib/socket.rb, line 662 def self.tcp(host, port, local_host = nil, local_port = nil, connect_timeout: nil, resolv_timeout: nil, open_timeout: nil, fast_fallback: tcp_fast_fallback, &) # :yield: socket if open_timeout && (connect_timeout || resolv_timeout) raise ArgumentError, "Cannot specify open_timeout along with connect_timeout or resolv_timeout" end sock = if fast_fallback && !(host && ip_address?(host)) tcp_with_fast_fallback(host, port, local_host, local_port, connect_timeout:, resolv_timeout:, open_timeout:) else tcp_without_fast_fallback(host, port, local_host, local_port, connect_timeout:, resolv_timeout:, open_timeout:) end if block_given? begin yield sock ensure sock.close end else sock end end
使用 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,而不仅限于此方法。
如果给出 local_host:local_port,则套接字将绑定到它。
可选的最后一个参数 opts 是由哈希表示的选项。opts 可能包含以下选项
- :resolv_timeout
-
指定从主机名解析开始的超时(以秒为单位)。
- :connect_timeout
-
此方法顺序尝试连接到所有候选目标地址。
connect_timeout指定从连接尝试开始到最后一个候选地址的超时(以秒为单位)。
默认情况下,所有连接尝试将一直进行到超时发生。
当显式指定fast_fallback:false时,
将为每次连接尝试设置超时,任何超过其超时的连接尝试都将被取消。 - :open_timeout
-
指定从方法执行开始的超时(以秒为单位)。
如果在此超时时间内仍有尚未尝试连接的地址,则不会进行任何进一步的尝试。
如果此选项与其他超时选项一起指定,将引发ArgumentError。 - :fast_fallback
-
启用 Happy Eyeballs Version 2 算法(默认启用)。
如果给出块,则使用套接字调用该块。返回块的值。套接字在方法返回时关闭。
如果没有给出块,则返回套接字。
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 }
Source
VALUE socket_s_tcp_fast_fallback(VALUE self) {
return rb_ivar_get(rb_cSocket, tcp_fast_fallback);
}
返回 Happy Eyeballs Version 2 (RFC 8305) 是否已启用或禁用,该算法从 Ruby 3.4 开始在 TCPSocket.new 和 Socket.tcp 中提供。
如果为 true,则对 TCPSocket.new 和 Socket.tcp 启用。(注意:在使用 Windows 上的 TCPSocket.new 时,不提供 Happy Eyeballs Version 2。)
如果为 false,则禁用 Happy Eyeballs Version 2。
有关 Happy Eyeballs Version 2 的详细信息,请参阅 Socket.tcp_fast_fallback=。
Source
VALUE socket_s_tcp_fast_fallback_set(VALUE self, VALUE value) {
rb_ivar_set(rb_cSocket, tcp_fast_fallback, value);
return value;
}
全局启用或禁用 Happy Eyeballs Version 2 (RFC 8305),该算法从 Ruby 3.4 开始在 TCPSocket.new 和 Socket.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) 是一种旨在提高客户端套接字连接性的算法。
它通过并行执行主机名解析和连接尝试,而不是串行执行,从而实现更可靠高效的连接。
从 Ruby 3.4 开始,此方法在此算法下按如下方式操作:
-
并发解析 IPv6 和 IPv4 地址。
-
尝试连接到首先获取的地址之一。
如果首先获取 IPv4 地址,则方法会等待 50 毫秒进行 IPv6 名称解析,以优先考虑 IPv6 连接。 -
开始连接尝试后,等待 250 毫秒以建立连接。
如果在该时间内未建立连接,则每 250 毫秒启动一次新连接,
直到建立连接或没有更多候选地址。
(尽管 RFC 8305 严格规定了地址排序,
但出于性能考虑,此方法仅在 IPv6 / IPv4 地址之间交替) -
一旦建立连接,所有剩余的连接尝试都将被取消。
Source
# File ext/socket/lib/socket.rb, line 1314 def self.tcp_server_loop(host=nil, port, &b) # :yield: socket, client_addrinfo tcp_server_sockets(host, port) {|sockets| accept_loop(sockets, &b) } end
在 port 上创建一个 TCP/IP 服务器,并为每个接受的连接调用块。该块与套接字和客户端地址(作为 Addrinfo 对象)一起调用。
如果指定了 host,它将与 port 一起用于确定服务器地址。
套接字在块返回时**不会**关闭。因此,应用程序应显式关闭它。
此方法顺序调用块。这意味着直到块返回,下一个连接才被接受。因此,应使用并发机制(例如线程)来同时服务多个客户端。
注意,Addrinfo.getaddrinfo 用于确定服务器套接字地址。当 Addrinfo.getaddrinfo 返回两个或多个地址(例如 IPv4 和 IPv6 地址)时,将使用所有这些地址。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 } }
Source
# File ext/socket/lib/socket.rb, line 1214 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
为 host 和 port 创建 TCP/IP 服务器套接字。host 是可选的。
如果没有给出块,则返回套接字数组。
如果给出块,则使用套接字调用该块。返回块的值。套接字在方法返回时关闭。
如果 port 为 0,则动态选择实际端口号。但是,结果中的所有套接字都具有相同的端口号。
# 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>] }
Source
# File ext/socket/lib/socket.rb, line 1484 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
在 port 上创建一个 UDP/IP 服务器,并为每个到达的消息调用块。该块与消息及其源信息一起调用。
此方法使用 port 在内部分配套接字。如果指定了 host,它将与 port 一起用于确定服务器地址。
msg 是一个字符串。
msg_src 是一个 Socket::UDPSource 对象。它用于回复。
# UDP/IP echo server. Socket.udp_server_loop(9261) {|msg, msg_src| msg_src.reply msg }
Source
# File ext/socket/lib/socket.rb, line 1457 def self.udp_server_loop_on(sockets, &b) # :yield: msg, msg_src loop { readable, _, _ = IO.select(sockets) udp_server_recv(readable, &b) } end
Source
# File ext/socket/lib/socket.rb, line 1430 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
从给定的 sockets 接收 UDP/IP 数据包。对于每个接收到的数据包,都会调用块。
块接收 msg 和 msg_src。msg 是表示接收到的数据包有效载荷的字符串。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| ... }
}
}
Source
# File ext/socket/lib/socket.rb, line 1342 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
为 UDP 服务器创建 UDP/IP 套接字。
如果没有给出块,则返回套接字数组。
如果给出块,则使用套接字调用该块。返回块的值。套接字在方法返回时关闭。
如果 port 为零,则选择一个端口。但选择的端口用于所有套接字。
# 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 } }
Source
# File ext/socket/lib/socket.rb, line 1534 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 套接字创建一个连接到 path 的新套接字。
如果给出块,则使用套接字调用该块。返回块的值。套接字在方法返回时关闭。
如果没有给出块,则返回套接字。
# 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 }
Source
# File ext/socket/lib/socket.rb, line 1626 def self.unix_server_loop(path, &b) # :yield: socket, client_addrinfo unix_server_socket(path) {|serv| accept_loop(serv, &b) } end
在 path 上创建一个 UNIX 套接字服务器。它为每个接受的套接字调用块。
如果指定了 host,它将与 port 一起用于确定服务器端口。
套接字在块返回时**不会**关闭。因此,应用程序应关闭它。
此方法首先删除 path 指向的套接字文件,如果该文件是套接字文件且由应用程序用户拥有。仅当 path 的目录未被恶意用户更改时,这才是安全的。因此,请勿使用 /tmp/malicious-users-directory/socket。请注意,/tmp/socket 和 /tmp/your-private-directory/socket 是安全的,前提是 /tmp 具有粘滞位。
# 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 }
Source
# File ext/socket/lib/socket.rb, line 1564 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
在 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> }
Source
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);
}
将 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"]
Source
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;
}
将 sockaddr 解包为路径。
sockaddr 应为字符串或 AF_UNIX 的 addrinfo。
sockaddr = Socket.sockaddr_un("/tmp/sock") p Socket.unpack_sockaddr_un(sockaddr) #=> "/tmp/sock"
Public Instance Methods
Source
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));
}
Source
# File ext/socket/lib/socket.rb, line 598 def accept_nonblock(exception: true) __accept_nonblock(exception) end
在底层文件描述符设置为 O_NONBLOCK 后,使用 accept(2) 接受传入连接。它返回一个包含传入连接的接受套接字 client_socket 和一个 Addrinfo client_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
如果 accept_nonblock 调用失败,可能引发的异常请参考 Socket#accept。
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。
请参阅
Source
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);
}
绑定到指定的本地地址。
参数
-
local_sockaddr- 包含在字符串中的structsockaddr 或Addrinfo对象
示例
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 的系统上,如果调用 bind 失败,并且调用 socket 的地址族为 Socket::AF_UNIX,则可能引发以下异常:
-
Errno::EACCES - 前缀路径的组件被禁止搜索,或禁止对
socket进行写入访问 -
Errno::EDESTADDRREQ - sockaddr 参数为 null 指针
-
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 函数
Source
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);
}
请求在给定的 remote_sockaddr 上建立连接。成功则返回 0,否则引发异常。
参数
-
remote_sockaddr- 包含在字符串中的structsockaddr 或Addrinfo对象
示例
# 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 的系统上,如果调用 connect 失败,并且调用 socket 的地址族为 AF_UNIX,则可能引发以下异常:
-
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 - 正在进行阻塞套接字调用,或服务提供商仍在处理回调函数。或者,正在套接字上进行非阻塞 connect 调用。
-
Errno::EALREADY - 请参阅 Errno::EINVAL
-
Errno::EADDRNOTAVAIL - 远程地址不是有效地址,例如 ADDR_ANY TODO 检查 ADDRANY TO
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 函数
Source
# File ext/socket/lib/socket.rb, line 1676 def connect_nonblock(addr, exception: true) __connect_nonblock(addr, exception) end
在底层文件描述符设置为 O_NONBLOCK 后,请求在给定的 remote_sockaddr 上建立连接。成功则返回 0,否则引发异常。
参数
-
remote_sockaddr- 包含在字符串中的structsockaddr 或Addrinfo对象
示例
# 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
如果 connect_nonblock 调用失败,可能引发的异常请参考 Socket#connect。
Socket#connect_nonblock 可能引发与 connect(2) 失败相对应的任何错误,包括 Errno::EINPROGRESS。
如果异常为 Errno::EINPROGRESS,则它会被 IO::WaitWritable 扩展。因此,IO::WaitWritable 可用于捕获异常以重试 connect_nonblock。
通过指定关键字参数 exception 为 false,可以指示 connect_nonblock 不引发 IO::WaitWritable 异常,而是返回符号 :wait_writable。
请参阅
Source
# File ext/socket/lib/socket.rb, line 468 def ipv6only! if defined? Socket::IPV6_V6ONLY self.setsockopt(:IPV6, :V6ONLY, 1) end end
如果 IPV6_V6ONLY 可用,则启用套接字选项 IPV6_V6ONLY。
Source
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);
}
监听连接,使用指定的 int 作为队列长度。listen 调用仅适用于 socket 类型为 SOCK_STREAM 或 SOCK_SEQPACKET 的情况。
参数
-
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 的系统上,上述代码将正常工作,因为会为任意端口号创建一个新的 sockaddr 结构,该端口号由内核分配。它在 Windows 上将不起作用,因为 Windows 要求在调用 listen 之前先调用 bind 绑定 socket。
如果 backlog 数量超过了实现定义的队列最大长度,则将使用实现的最大队列长度。
在基于 Unix 的系统上,如果 listen 调用失败,可能会引发以下系统异常:
-
Errno::EBADF - socket 参数不是有效的文件描述符
-
Errno::EDESTADDRREQ - socket 未绑定到本地地址,且协议不支持监听未绑定套接字
-
Errno::EINVAL - socket 已连接
-
Errno::ENOTSOCK - socket 参数不指向套接字
-
Errno::EOPNOTSUPP - socket 协议不支持 listen
-
Errno::EACCES - 调用进程没有适当的权限
-
Errno::EINVAL - socket 已关闭
-
Errno::ENOBUFS - 系统中可用资源不足,无法完成调用
Windows 异常
在 Windows 系统上,如果 listen 调用失败,可能会引发以下系统异常:
-
Errno::ENETDOWN - 网络已断开
-
Errno::EADDRINUSE - 套接字本地地址已在使用中。这通常发生在执行 bind 时,但如果 bind 调用的是部分通配地址(涉及 ADDR_ANY),并且在调用 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 函数
Source
static VALUE
sock_recvfrom(int argc, VALUE *argv, VALUE sock)
{
return rsock_s_recvfrom(sock, argc, argv, RECV_SOCKET);
}
从 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 - 在操作进行过程中,keep-alive 活动检测到失败,导致连接中断。
-
Errno::EOPNOTSUPP - 指定了
MSG_OOB,但socket不是流式套接字,例如类型为SOCK_STREAM。OOB 数据不受与socket关联的通信域支持,或者socket是单向的,仅支持发送操作 -
Errno::ESHUTDOWN -
socket已关闭。调用 shutdown 后,无法在套接字上调用 recvfrom。 -
Errno::EWOULDBLOCK -
socket被标记为非阻塞,并且调用 recvfrom 将会阻塞。 -
Errno::EMSGSIZE - 消息过大,无法放入指定的缓冲区,已被截断。
-
Errno::ETIMEDOUT - 连接已断开,原因可能是网络故障,或者对方系统已无通知地关闭。
-
Errno::ECONNRESET - 远程端执行了硬关闭或中止关闭,导致虚拟电路被重置。应用程序应关闭套接字;它不再可用。对于 UDP 数据报套接字,此错误表示先前的发送操作导致了 ICMP 端口不可达消息。
Source
# File ext/socket/lib/socket.rb, line 541 def recvfrom_nonblock(len, flag = 0, str = nil, exception: true) __recvfrom_nonblock(len, flag, str, exception) end
在为底层文件描述符设置 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。
请参阅
Source
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));
}
接受一个传入连接,返回一个包含传入连接的(整数)文件描述符 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。