Skip to content

Ruby Socket 编程

Ruby 提供了多个级别的网络服务访问。在低级别,socket 标准库允许您使用底层操作系统的 socket 支持。这使得能够实现面向连接(如 TCP)和无连接(如 UDP)协议的客户端和服务器。

Ruby 也有针对特定应用层网络协议的更高级别的库(例如 net/http、net/ftp)。本教程侧重于使用 TCP 的基本 socket 编程。

Socket 是双向通信通道的端点。它们可以促进同一进程内、同一机器上不同进程间,或网络上不同机器上不同进程间的通信。

socket 库提供了用于各种传输类型的类,如 Unix 域 socket、TCP、UDP 以及用于其他类型的通用接口。

Socket 术语:

TermDescription
术语描述
Domain协议族,例如 Socket::PF_INET (用于 IPv4)、Socket::PF_INET6 (用于 IPv6)、Socket::PF_UNIX (用于本地 Unix 域 socket)。
Type通信类型,例如 Socket::SOCK_STREAM (用于面向连接的协议,如 TCP)、Socket::SOCK_DGRAM (用于无连接的协议,如 UDP)。
Protocol通常为 0 或 Socket::IPPROTO_TCP / Socket::IPPROTO_UDP。它可以指定一个域和类型中的变体。
Hostname网络接口标识符: 一个主机名字符串 (例如 ‘localhost’, ‘www.example.com’)、一个 IP 地址字符串 (例如 ‘127.0.0.1’, ‘::1’),或特殊字符串,如 ''。
Port主机上服务的数字标识符。服务器监听端口;客户端连接到端口。它可以是一个整数 (例如 80, 443) 或一个服务名字符串 (例如 ‘http’, ‘https’),该字符串会被解析。

此客户端连接到指定的主机和端口,发送消息,读取服务器的响应,然后关闭连接。TCPSocket.new(hostname, port)(或 TCPSocket.open)建立一个 TCP 连接。

#!/usr/bin/env ruby
# frozen_string_literal: true
require 'socket'
hostname = 'localhost'
port = 2000
begin
socket = TCPSocket.new(hostname, port)
puts "Connected to #{hostname}:#{port}"
# 向服务器发送消息
message = "Hello from client!"
socket.puts message
puts "Sent: #{message}"
# 从 socket 读取行
while line = socket.gets # Read lines from the socket
puts "Received: #{line.chomp}" # chomp 移除换行符
end
rescue Errno::ECONNREFUSED => e
puts "连接被拒绝。服务器是否正在 #{hostname}:#{port} 运行?"
rescue StandardError => e
puts "发生错误: #{e.message}"
ensure
socket.close if socket # 完成后关闭 socket
puts "连接已关闭。"
end

此服务器监听端口等待传入连接。当客户端连接时,它接受连接,发送问候语,然后关闭客户端 socket。TCPServer.new(port) 创建一个服务器 socket。

#!/usr/bin/env ruby
# frozen_string_literal: true
require 'socket'
port = 2000
server = TCPServer.new(port) # 在端口 2000 上监听的 socket
puts "服务器正在端口 #{port} 监听..."
loop do # 服务器通常永远运行
begin
client_socket = server.accept # 等待客户端连接
client_addr = client_socket.peeraddr # 获取客户端地址信息
puts "Accepted connection from #{client_addr[2]}:#{client_addr[1]}" # IP:端口
# 读取客户端消息
client_message = client_socket.gets.chomp
puts "Client said: #{client_message}"
# 向客户端发送响应
server_response = "Hello from server! You said: #{client_message}"
client_socket.puts server_response
client_socket.puts "Server closing the connection. Bye!"
rescue StandardError => e
puts "处理客户端时出错: #{e.message}"
ensure
client_socket.close if client_socket # 与客户端断开连接
puts "已关闭与客户端的连接。"
end
end

要测试,请在一个终端中运行服务器脚本,然后在另一个终端中运行客户端脚本。

实际应用中的服务器需要并发处理多个客户端。在 Ruby 中实现此目的的一种基本方法是为每个客户端连接生成一个新的线程。

#!/usr/bin/env ruby
# frozen_string_literal: true
require 'socket'
port = 2000
server = TCPServer.new(port)
puts "多线程服务器正在端口 #{port} 监听..."
loop do
Thread.start(server.accept) do |client_socket| # 将接受的 socket 传递给新线程
begin
client_addr = client_socket.peeraddr
thread_id = Thread.current.object_id # 用于日志记录
puts "[线程 #{thread_id}] 已接受来自 #{client_addr[2]}:#{client_addr[1]} 的连接"
client_message = client_socket.gets.chomp
puts "[线程 #{thread_id}] 客户端说: #{client_message}"
server_response = "Hello from threaded server! Processed by thread #{thread_id}."
client_socket.puts server_response
client_socket.puts "服务器正在关闭您的连接。再见!"
rescue StandardError => e
puts "[线程 #{thread_id}] 处理客户端时出错: #{e.message}"
ensure
client_socket.close if client_socket
puts "[线程 #{thread_id}] 已关闭与客户端的连接。"
end
end
end

这种方法为每个 server.accept 创建一个新的线程。虽然简单,但对于非常高的并发场景,可能需要考虑更高级的模式(如线程池、使用 async 等库实现的事件驱动 I/O)以更有效地管理资源。然而,对于许多应用程序来说,Ruby 的线程是有效的。

一个微型 Web 浏览器 (HTTP GET 客户端)

Section titled “一个微型 Web 浏览器 (HTTP GET 客户端)”

我们可以使用 socket 来实现像 HTTP 这样的高级协议。此示例获取一个网页。

#!/usr/bin/env ruby
# frozen_string_literal: true
require 'socket'
host = 'www.example.com' # 目标 Web 服务器
port = 80 # 默认 HTTP 端口
path = "/" # 资源路径 (例如根页面)
# 构建 HTTP GET 请求
request = "GET #{path} HTTP/1.1\r\n"
request << "Host: #{host}\r\n"
request << "Connection: close\r\n"
request << "User-Agent: SimpleRubyBrowser/1.0\r\n"
request << "\r\n" # 空行表示请求头结束
begin
socket = TCPSocket.new(host, port)
socket.print(request) # 发送请求
response = socket.read # 读取完整的响应
# 将响应分割为头部和正文
headers, body = response.split("\r\n\r\n", 2)
puts "--- 头部 ---"
puts headers
puts "\n--- 正文 ---"
puts body if body
rescue StandardError => e
puts "获取页面时出错: #{e.message}"
ensure
socket.close if socket
end

对于 HTTP,Ruby 的标准库 Net::HTTP 提供了比原始 socket 更方便和健壮的接口。

#!/usr/bin/env ruby
# frozen_string_literal: true
require 'net/http'
require 'uri'
uri_string = 'http://www.example.com/'
begin
uri = URI(uri_string)
response = Net::HTTP.get_response(uri) # 发送 GET 请求
puts "--- 状态 ---"
puts "#{response.code} #{response.message}"
puts "\n--- 头部 ---"
response.each_header { |key, value| puts "#{key}: #{value}" }
if response.is_a?(Net::HTTPSuccess) # 检查 2xx 状态码
puts "\n--- 正文 ---"
puts response.body
else
puts "\n请求失败或重定向。"
puts "正文 (如果存在):\n#{response.body}"
end
rescue StandardError => e
puts "Net::HTTP 发生错误: #{e.message}"
end

Ruby 为其他常见的互联网协议提供了类似的标准库(Net::FTP、Net::SMTP、Net::IMAP、Net::POP3)。

这是 Ruby 中 socket 编程的简要介绍。要更深入地学习,请探索:

  • 官方 Ruby 文档中关于 Socket、TCPSocket、TCPServer、UDPSocket 类的部分。
  • 非阻塞 I/O 和事件驱动的编程模型(例如使用 async gem 或 IO.select)。
  • 使用 OpenSSL::SSL::SSLSocket 进行安全套接字层(SSL/TLS)加密通信。