Ruby Socket 编程
Ruby Socket 编程
Section titled “Ruby Socket 编程”Ruby 提供了多个级别的网络服务访问。在低级别,socket 标准库允许您使用底层操作系统的 socket 支持。这使得能够实现面向连接(如 TCP)和无连接(如 UDP)协议的客户端和服务器。
Ruby 也有针对特定应用层网络协议的更高级别的库(例如 net/http、net/ftp)。本教程侧重于使用 TCP 的基本 socket 编程。
什么是 Socket?
Section titled “什么是 Socket?”Socket 是双向通信通道的端点。它们可以促进同一进程内、同一机器上不同进程间,或网络上不同机器上不同进程间的通信。
socket 库提供了用于各种传输类型的类,如 Unix 域 socket、TCP、UDP 以及用于其他类型的通用接口。
Socket 术语:
| Term | Description |
|---|---|
| 术语 | 描述 |
| 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’),该字符串会被解析。 |
简单的 TCP 客户端
Section titled “简单的 TCP 客户端”此客户端连接到指定的主机和端口,发送消息,读取服务器的响应,然后关闭连接。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简单的 TCP 服务器
Section titled “简单的 TCP 服务器”此服务器监听端口等待传入连接。当客户端连接时,它接受连接,发送问候语,然后关闭客户端 socket。TCPServer.new(port) 创建一个服务器 socket。
#!/usr/bin/env ruby# frozen_string_literal: true
require 'socket'
port = 2000server = TCPServer.new(port) # 在端口 2000 上监听的 socketputs "服务器正在端口 #{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 "已关闭与客户端的连接。" endend要测试,请在一个终端中运行服务器脚本,然后在另一个终端中运行客户端脚本。
多客户端 TCP 服务器 (使用线程)
Section titled “多客户端 TCP 服务器 (使用线程)”实际应用中的服务器需要并发处理多个客户端。在 Ruby 中实现此目的的一种基本方法是为每个客户端连接生成一个新的线程。
#!/usr/bin/env ruby# frozen_string_literal: true
require 'socket'
port = 2000server = 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 endend这种方法为每个 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 socketend使用 Net::HTTP 进行 Web 请求
Section titled “使用 Net::HTTP 进行 Web 请求”对于 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}"endRuby 为其他常见的互联网协议提供了类似的标准库(Net::FTP、Net::SMTP、Net::IMAP、Net::POP3)。
进一步学习:
Section titled “进一步学习:”这是 Ruby 中 socket 编程的简要介绍。要更深入地学习,请探索:
- 官方 Ruby 文档中关于
Socket、TCPSocket、TCPServer、UDPSocket类的部分。 - 非阻塞 I/O 和事件驱动的编程模型(例如使用
asyncgem 或IO.select)。 - 使用
OpenSSL::SSL::SSLSocket进行安全套接字层(SSL/TLS)加密通信。