Ruby Socket
最后修改于 2023 年 10 月 18 日
在本文中,我们将介绍如何使用套接字进行 Ruby 网络编程。套接字编程是低级的。本教程的目标是介绍网络编程,包括这些底层细节。
在编程中,套接字 是在网络上运行的两个程序之间通信的端点。套接字用于在客户端程序和服务器程序之间创建连接。
Ruby 的 socket 模块提供了对 Berkeley 套接字 API 的接口。
网络协议
TCP/IP 是一套协议,设备使用它通过 Internet 和大多数本地网络进行通信。TCP 更可靠,具有广泛的错误检查,需要更多的资源。HTTP、SMTP 和 FTP 等服务使用它。UDP 的可靠性差得多,错误检查有限,需要更少的资源。VoIP 等服务使用它。
Socket::SOCK_STREAM 用于创建 TCP 套接字,而 Socket::SOCK_DGRAM 用于 UDP。
地址族
创建套接字时,我们必须指定其地址族。然后,我们只能使用该类型的地址与该套接字。
- AF_UNIX, AF_LOCAL - 本地通信
- AF_INET - IPv4 Internet 协议
- AF_INET6 - IPv6 Internet 协议
- AF_IPX - IPX - Novell 协议
- AF_BLUETOOTH - 无线蓝牙协议
- AF_PACKET - 低级数据包接口
对于 AF_INET 地址族,指定一个 (主机,端口) 对。主机 是一个字符串,表示 Internet 域表示法中的主机名(如 example.com)或 IPv4 地址(如 93.184.216.34),端口是一个整数。
Ruby Socket.getnameinfo
使用 Socket.getnameinfo 获取有关套接字地址的信息。
#!/usr/bin/ruby require 'socket' p Socket.getnameinfo Socket.sockaddr_in 80, "example.com" p Socket.getnameinfo ["AF_INET", 80, "webcode.me"]
示例打印 example.com 和 webcode.me 的 IP 地址。
require 'socket'
Ruby 套接字 API 在 socket 模块中。
p Socket.getnameinfo Socket.sockaddr_in 80, "example.com" p Socket.getnameinfo ["AF_INET", 80, "webcode.me"]
套接字地址是通过 Socket.sockaddr_in 创建的,或者通过包含协议名称、端口和主机名的值数组创建的。
$ ./get_addr.rb ["2606:2800:220:1:248:1893:25c8:1946", "http"] ["46.101.248.126", "http"]
Socket.getifaddrs & Socket.ip_address_list
Socket.getifaddrs 返回一个接口地址数组,Socket.ip_address_list 以数组形式返回本地 IP 地址。
#!/usr/bin/ruby require 'socket' pp Socket.getifaddrs p '----------------------------' pp Socket.ip_address_list
示例列出所有本地接口和 IP 地址。
Ruby UDP 套接字示例
UDP 是一种通信协议,通过网络传输独立的数据包,不保证到达,也不保证传递顺序。回显是一项使用 UDP 的服务。
回显协议是 RFC 862 中定义的 Internet 协议套件中的一项服务。回显协议可以使用 TCP 或 UDP,端口号为 7。服务器会发送回它接收到的数据的相同副本。
$ cat /etc/services | grep echo | head -4 echo 7/tcp echo 7/udp echo 4/ddp # AppleTalk Echo Protocol
端口 7 保留给回显服务。
出于安全原因,大多数情况下禁用回显服务。因此,我们在本地网络中创建自己的服务。
我们在本地网络中的另一台计算机上启动回显服务。
# apt install xinetd
我们安装了 xinetd 包。该包包含 xinetd 守护进程,它是一个 TCP 包装的超级服务,用于访问回显、FTP、IMAP 和 telnet 等部分常用网络服务。
...
# This is the udp version.
service echo
{
disable = no
type = INTERNAL
id = echo-dgram
socket_type = dgram
protocol = udp
user = root
wait = yes
}
在 /etc/xinetd.d/echo 文件中,我们将 disable 选项设置为 no。
# systemctl start xinetd
我们启动了该服务。
我们使用 ip addr 检查此机器的 IP 地址;在我们的例子中,IP 地址是 192.168.0.32。
#!/usr/bin/ruby require 'socket' host = '192.168.0.32' port = 7 msg = ARGV[0] || "hello" s = UDPSocket.new s.connect host, port s.sendmsg msg + "\n" puts s.recv 20 s.close
该示例将一条小消息发送到本地网络机器上的回显服务。消息被回显回来。
s = UDPSocket.new
使用 UDPSocket.new 创建新的 UDP 套接字。
s.connect host, port
我们连接到指定端口的主机。
s.sendmsg msg + "\n"
我们使用 sendmsg 方法将消息发送到回显服务。
puts s.recv 20
recv 方法接收消息;其参数是 maxlen,即要接收的最大字节数。
s.close
我们使用 close 关闭套接字。
$ ./echo_client.rb hello $ ./echo_client.rb cau cau
我们的消息被回显给我们。
# tcpdump -i eth1 -Xn -Q in udp port 7
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on eth1, link-type EN10MB (Ethernet), capture size 262144 bytes
13:16:27.330620 IP 192.168.0.20.50919 > 192.168.0.32.7: UDP, length 6
0x0000: 4500 0022 95cb 4000 4011 237b c0a8 0014 E.."..@.@.#{....
0x0010: c0a8 0020 c6e7 0007 000e 7382 6865 6c6c ..........s.hell
0x0020: 6f0a o.
13:16:28.942496 IP 192.168.0.20.57886 > 192.168.0.32.7: UDP, length 4
0x0000: 4500 0020 970f 4000 4011 2239 c0a8 0014 E.....@.@."9....
0x0010: c0a8 0020 e21e 0007 000c c3bf 6361 750a ............cau.
在启动回显服务的机器上,我们可以使用 tcpdum 工具以交互方式监视流量。
Ruby TCP 套接字示例
TCP 在通过 IP 网络通信的主机上运行的应用程序之间,提供可靠、有序且经过错误检查的字节流的传输。
$ cat /etc/services | grep qotd qotd 17/tcp quote
端口 17 保留给每日名言服务。
每日引语服务是一种有用的调试和测量工具。每日引语服务只是发送一条短消息,而不考虑输入。
#!/usr/bin/ruby require 'socket' s = TCPSocket.new 'djxmmx.net', 17 s.send '', 0 puts s.recv 350 s.close
该示例创建了一个连接到 QOTD 服务的客户端程序。
s = TCPSocket.new 'djxmmx.net', 17
使用 TCPSocket.new 创建 TCP/IP 套接字。我们提供主机名和端口号。
s.send '', 0
我们向套接字发送了一条空消息。
puts s.recv 350 s.close
我们接收输出并关闭套接字。
$ ./qotd.rb "When a stupid man is doing something he is ashamed of, he always declares that it is his duty." George Bernard Shaw (1856-1950)
Ruby Socket HTTP HEAD 请求
HEAD 请求是一种 HTTP GET 请求,但不包含消息正文。请求/响应的头部包含元数据,例如 HTTP 协议版本或内容类型。
#!/usr/bin/ruby require 'socket' s = TCPSocket.new 'webcode.me', 80 s.write "HEAD / HTTP/1.0\r\n" s.write "Host: webcode.me\r\n" s.write "User-Agent: Console Http Client\r\n" s.write "Accept: text/html\r\n" s.write "Accept-Language: en-US\r\n" s.write "Connection: close\r\n\r\n" res = s.read puts res s.close
在示例中,我们向 webcode.me 发送了一个 HEAD 请求。
s.write "HEAD / HTTP/1.0\r\n" s.write "Host: webcode.me\r\n" s.write "User-Agent: Console Http Client\r\n" s.write "Accept: text/html\r\n" s.write "Accept-Language: en-US\r\n" s.write "Connection: close\r\n\r\n"
HEAD 请求使用 HEAD 命令后跟资源 URL 和 HTTP 协议版本发出。请注意,\r\n 是通信过程的强制组成部分。详细信息在 RFC 7231 文档中进行了说明。
$ ./http_head.rb HTTP/1.1 200 OK Server: nginx/1.6.2 Date: Tue, 02 Feb 2021 13:43:42 GMT Content-Type: text/html Content-Length: 348 Last-Modified: Sat, 20 Jul 2019 11:49:25 GMT Connection: close ETag: "5d32ffc5-15c" Accept-Ranges: bytes
Ruby Socket GET 请求
HTTP GET 方法请求指定资源的表示。使用 GET 的请求应该只检索数据。
#!/usr/bin/ruby require 'socket' s = TCPSocket.new 'webcode.me', 80 s.write "GET / HTTP/1.0\r\n\r\n" res = s.read puts res s.close
该示例使用 GET 请求读取 webcode.me 的主页。
s.write "GET / HTTP/1.0\r\n\r\n"
我们向 Socket 写入了一个简单的 GET 请求。
$ ./http_get.rb
HTTP/1.1 200 OK
Server: nginx/1.6.2
Date: Tue, 02 Feb 2021 13:46:24 GMT
Content-Type: text/html
Content-Length: 348
Last-Modified: Sat, 20 Jul 2019 11:49:25 GMT
Connection: close
ETag: "5d32ffc5-15c"
Access-Control-Allow-Origin: *
Accept-Ranges: bytes
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>My html page</title>
</head>
<body>
<p>
Today is a beautiful day. We go swimming and fishing.
</p>
<p>
Hello there. How are you?
</p>
</body>
</html>
Ruby 客户端/服务器示例
在以下部分,我们将创建一个 UDP 回显客户端和服务器。
#!/usr/bin/ruby
require 'socket'
puts 'starting echo server'
Socket.udp_server_loop 4444 do |data, src|
src.reply data
end
回显服务器使用 Socket.udp_server_loop 创建。它在端口上创建一个 UDP/IP 服务器,并为收到的每条消息调用块。服务器将消息发送回客户端。
#!/usr/bin/ruby require 'socket' s = UDPSocket.new s.connect 'localhost', 4444 s.send "hello", 0 puts s.recv 50 s.close
客户端将消息发送到回显服务器。
Ruby socket 简单 Web 服务器
以下示例创建了一个简单的 Web 服务器。
#!/usr/bin/ruby
require 'socket'
port = (ARGV[0] || 8080).to_i
server = TCPServer.new port
p server
puts "server started on port #{port}"
loop do
client = server.accept
puts "Request: #{client.gets}"
client.print "HTTP/1.1 200/OK\r\nContent-type: text/html\r\n\r\n"
client.print "<html><body><h1>#{Time.now}</h1></body></html>\r\n"
client.close
end
服务器在 HTML 输出中发送当前时间。accept 方法接受传入连接。它返回一个新的 TCPSocket 对象。
$ ./web_server.rb #<TCPServer:fd 8, AF_INET, 0.0.0.0, 8080> server started on port 8080
我们启动服务器。
$ nc localhost 8080 GET / HTTP/1.1 200/OK Content-type: text/html <html><body><h1>2021-02-03 10:02:19 +0100</h1></body></html>
我们使用 nc 工具测试服务器。
在本文中,我们展示了如何使用 Ruby 中的套接字创建简单的网络程序。