Ruby Socket 编程
Socket编程是网络编程的基础,它允许程序通过网络与其他程序进行通信。Ruby提供了强大的Socket库,使得网络编程变得简单而直观。无论是创建服务器还是客户端,Ruby都能轻松应对。本章将详细介绍Ruby中的Socket编程,包括TCP、UDP通信以及HTTP客户端的实现。
🎯 Socket基础
什么是Socket
Socket是网络编程中的一个抽象概念,它是网络通信的端点。通过Socket,应用程序可以在网络上发送和接收数据。Socket提供了一种标准的通信机制,使得不同主机上的程序能够相互交流。
Socket类型
- 流式Socket (SOCK_STREAM): 使用TCP协议,提供可靠的、面向连接的通信
- 数据报Socket (SOCK_DGRAM): 使用UDP协议,提供无连接的通信
- 原始Socket (SOCK_RAW): 提供对底层协议的直接访问
Socket寻址
Socket通过IP地址和端口号来标识网络上的一个端点:
- IP地址: 标识网络上的主机(如 192.168.1.100)
- 端口号: 标识主机上的特定服务(如 80、443、3000)
🔌 TCP Socket编程
TCP客户端
TCP(传输控制协议)是一种面向连接的协议,提供可靠的数据传输。
ruby
require 'socket'
# 创建TCP客户端
def tcp_client(server_host, server_port, message)
# 创建TCP Socket
socket = Socket.new(:INET, :STREAM)
# 连接到服务器
sockaddr = Socket.sockaddr_in(server_port, server_host)
socket.connect(sockaddr)
# 发送消息
socket.write(message)
puts "已发送: #{message}"
# 接收响应
response = socket.read(1024)
puts "收到响应: #{response}"
# 关闭连接
socket.close
end
# 使用示例(需要先运行服务器)
# tcp_client('localhost', 3000, "你好,服务器!")
# 更简单的写法
def simple_tcp_client(server_host, server_port, message)
socket = TCPSocket.new(server_host, server_port)
socket.puts message
response = socket.gets
puts "服务器响应: #{response}"
socket.close
end
# simple_tcp_client('localhost', 3000, "你好,服务器!")TCP服务器
ruby
require 'socket'
# 创建TCP服务器
def tcp_server(port)
# 创建服务器Socket
server = TCPServer.new(port)
puts "TCP服务器启动,监听端口 #{port}"
loop do
# 等待客户端连接
client = server.accept
puts "客户端已连接: #{client.peeraddr[2]}:#{client.peeraddr[1]}"
# 创建新线程处理客户端
Thread.new(client) do |client_socket|
begin
# 读取客户端消息
while message = client_socket.gets
puts "收到消息: #{message.chomp}"
# 回复客户端
response = "服务器收到: #{message}"
client_socket.puts response
# 如果收到退出命令则断开连接
break if message.chomp == 'quit'
end
rescue => e
puts "客户端处理错误: #{e.message}"
ensure
client_socket.close
puts "客户端连接已关闭"
end
end
end
end
# 启动服务器(在单独的终端中运行)
# tcp_server(3000)
# 更完整的TCP服务器示例
class TCPServerExample
def initialize(port)
@port = port
@server = nil
@clients = []
end
def start
@server = TCPServer.new(@port)
puts "服务器启动,监听端口 #{@port}"
# 处理中断信号
trap('INT') { shutdown }
loop do
client = @server.accept
@clients << client
puts "新客户端连接: #{client.peeraddr[2]}:#{client.peeraddr[1]}"
# 为每个客户端创建处理线程
handle_client(client)
end
end
private
def handle_client(client)
Thread.new do
begin
client.puts "欢迎连接到服务器!输入 'quit' 退出。"
while message = client.gets
message = message.chomp
puts "收到消息: #{message} (来自 #{client.peeraddr[2]})"
case message
when 'quit'
client.puts "再见!"
break
when 'time'
client.puts "当前时间: #{Time.now}"
when 'clients'
client.puts "当前连接数: #{@clients.length}"
else
client.puts "服务器回复: #{message}"
end
end
rescue => e
puts "客户端错误: #{e.message}"
ensure
@clients.delete(client)
client.close
puts "客户端断开连接"
end
end
end
def shutdown
puts "正在关闭服务器..."
@server.close if @server
@clients.each(&:close)
exit
end
end
# 使用示例
# server = TCPServerExample.new(3001)
# server.start📨 UDP Socket编程
UDP客户端
UDP(用户数据报协议)是一种无连接的协议,不保证数据传输的可靠性,但速度更快。
ruby
require 'socket'
# UDP客户端
def udp_client(server_host, server_port, message)
# 创建UDP Socket
socket = UDPSocket.new
# 发送消息到服务器
socket.send(message, 0, server_host, server_port)
puts "已发送: #{message}"
# 接收响应
response, sender = socket.recvfrom(1024)
puts "收到响应: #{response} (来自 #{sender[2]}:#{sender[1]})"
# 关闭Socket
socket.close
end
# 使用示例(需要先运行UDP服务器)
# udp_client('localhost', 3002, "UDP消息测试")UDP服务器
ruby
require 'socket'
# UDP服务器
def udp_server(port)
# 创建UDP Socket
socket = UDPSocket.new
socket.bind('localhost', port)
puts "UDP服务器启动,监听端口 #{port}"
loop do
# 接收消息
message, sender = socket.recvfrom(1024)
puts "收到消息: #{message} (来自 #{sender[2]}:#{sender[1]})"
# 发送响应
response = "服务器收到: #{message}"
socket.send(response, 0, sender[2], sender[1])
end
end
# 启动UDP服务器(在单独的终端中运行)
# udp_server(3002)
# 更完整的UDP服务器示例
class UDPServerExample
def initialize(port)
@port = port
@socket = nil
end
def start
@socket = UDPSocket.new
@socket.bind('0.0.0.0', @port)
puts "UDP服务器启动,监听端口 #{@port}"
# 处理中断信号
trap('INT') { shutdown }
loop do
message, sender = @socket.recvfrom(1024)
handle_message(message, sender)
end
end
private
def handle_message(message, sender)
client_ip, client_port = sender[2], sender[1]
puts "收到消息: #{message.chomp} (来自 #{client_ip}:#{client_port})"
response = case message.chomp
when 'time'
Time.now.to_s
when 'ping'
'pong'
else
"服务器收到: #{message}"
end
@socket.send(response, 0, client_ip, client_port)
end
def shutdown
puts "正在关闭UDP服务器..."
@socket.close if @socket
exit
end
end
# 使用示例
# server = UDPServerExample.new(3003)
# server.start🌐 HTTP客户端
基本HTTP请求
ruby
require 'socket'
# 简单的HTTP GET请求
def simple_http_get(host, port = 80, path = '/')
# 创建TCP连接
socket = TCPSocket.new(host, port)
# 发送HTTP请求
request = "GET #{path} HTTP/1.1\r\n"
request += "Host: #{host}\r\n"
request += "Connection: close\r\n"
request += "\r\n"
socket.write(request)
# 读取响应
response = socket.read
socket.close
response
end
# 使用示例
# response = simple_http_get('httpbin.org', 80, '/get')
# puts response
# 解析HTTP响应
def parse_http_response(response)
# 分离头部和主体
header, body = response.split("\r\n\r\n", 2)
# 解析状态行
status_line = header.lines.first
http_version, status_code, status_message = status_line.split(' ', 3)
# 解析头部
headers = {}
header.lines[1..-1].each do |line|
key, value = line.split(':', 2)
headers[key.strip] = value.strip if key && value
end
{
status_code: status_code.to_i,
status_message: status_message,
headers: headers,
body: body
}
end
# response = simple_http_get('httpbin.org', 80, '/get')
# parsed = parse_http_response(response)
# puts "状态码: #{parsed[:status_code]}"
# puts "内容类型: #{parsed[:headers]['Content-Type']}"HTTP POST请求
ruby
require 'socket'
# HTTP POST请求
def simple_http_post(host, port = 80, path, data)
# 创建TCP连接
socket = TCPSocket.new(host, port)
# 准备POST数据
post_data = data.is_a?(Hash) ? data.map { |k, v| "#{k}=#{v}" }.join('&') : data.to_s
# 发送HTTP请求
request = "POST #{path} HTTP/1.1\r\n"
request += "Host: #{host}\r\n"
request += "Content-Type: application/x-www-form-urlencoded\r\n"
request += "Content-Length: #{post_data.length}\r\n"
request += "Connection: close\r\n"
request += "\r\n"
request += post_data
socket.write(request)
# 读取响应
response = socket.read
socket.close
response
end
# 使用示例
# post_data = { name: '张三', email: 'zhangsan@example.com' }
# response = simple_http_post('httpbin.org', 80, '/post', post_data)
# puts response🧪 Socket实用工具
端口扫描器
ruby
require 'socket'
require 'timeout'
class PortScanner
def initialize(host, port_range)
@host = host
@port_range = port_range
@open_ports = []
end
def scan(timeout = 1)
puts "开始扫描 #{@host} 的端口 #{@port_range.first}-#{@port_range.last}"
@port_range.each do |port|
if port_open?(@host, port, timeout)
@open_ports << port
puts "端口 #{port} 开放"
end
end
puts "扫描完成,发现 #{@open_ports.length} 个开放端口"
@open_ports
end
private
def port_open?(host, port, timeout)
begin
Timeout.timeout(timeout) do
socket = TCPSocket.new(host, port)
socket.close
true
end
rescue
false
end
end
end
# 使用示例
# scanner = PortScanner.new('localhost', 3000..3010)
# open_ports = scanner.scan(0.5)网络聊天室
ruby
require 'socket'
require 'thread'
# 聊天室服务器
class ChatServer
def initialize(port)
@port = port
@server = nil
@clients = {}
@mutex = Mutex.new
end
def start
@server = TCPServer.new(@port)
puts "聊天室服务器启动,监听端口 #{@port}"
trap('INT') { shutdown }
loop do
client = @server.accept
handle_new_client(client)
end
end
private
def handle_new_client(client)
Thread.new do
begin
# 获取客户端昵称
client.puts "欢迎来到聊天室!请输入您的昵称:"
nickname = client.gets.chomp
nickname = "用户#{rand(1000)}" if nickname.empty?
# 添加客户端到列表
@mutex.synchronize do
@clients[client] = nickname
end
broadcast("#{nickname} 加入了聊天室", client)
client.puts "您已加入聊天室,输入 '/quit' 退出"
# 处理消息
while message = client.gets
message = message.chomp
break if message == '/quit'
if message.start_with?('/nick ')
new_nickname = message[6..-1]
old_nickname = @clients[client]
@clients[client] = new_nickname
broadcast("#{old_nickname} 改名为 #{new_nickname}")
else
broadcast("#{@clients[client]}: #{message}", client)
end
end
rescue => e
puts "客户端错误: #{e.message}"
ensure
nickname = @clients[client]
@clients.delete(client)
client.close
broadcast("#{nickname} 离开了聊天室") if nickname
puts "客户端断开连接"
end
end
end
def broadcast(message, exclude_client = nil)
@mutex.synchronize do
@clients.each do |client, nickname|
next if client == exclude_client
begin
client.puts message
rescue
# 客户端可能已断开连接
end
end
end
end
def shutdown
puts "正在关闭聊天室服务器..."
@server.close if @server
@clients.keys.each(&:close)
exit
end
end
# 聊天室客户端
class ChatClient
def initialize(host, port)
@host = host
@port = port
@socket = nil
end
def start
@socket = TCPSocket.new(@host, @port)
# 启动接收消息的线程
Thread.new { receive_messages }
# 主线程处理用户输入
while line = gets
@socket.puts line.chomp
end
end
private
def receive_messages
while message = @socket.gets
puts message
end
rescue => e
puts "连接错误: #{e.message}"
ensure
@socket.close if @socket
end
end
# 使用示例
# 启动服务器: ChatServer.new(3004).start
# 启动客户端: ChatClient.new('localhost', 3004).start🌍 网络工具
HTTP服务器
ruby
require 'socket'
class SimpleHTTPServer
def initialize(port = 8080, document_root = './public')
@port = port
@document_root = document_root
@server = nil
end
def start
@server = TCPServer.new(@port)
puts "HTTP服务器启动,监听端口 #{@port}"
puts "文档根目录: #{@document_root}"
trap('INT') { shutdown }
loop do
client = @server.accept
handle_request(client)
end
end
private
def handle_request(client)
Thread.new do
begin
# 读取请求
request_line = client.gets
return unless request_line
method, path, version = request_line.split
path = '/' if path == ''
path = '/index.html' if path == '/'
# 读取请求头
headers = {}
while line = client.gets
break if line == "\r\n"
key, value = line.split(':', 2)
headers[key.strip] = value.strip if key && value
end
# 处理请求
file_path = File.join(@document_root, path[1..-1])
serve_file(client, file_path)
rescue => e
puts "请求处理错误: #{e.message}"
send_error(client, 500, "Internal Server Error")
ensure
client.close
end
end
end
def serve_file(client, file_path)
if File.exist?(file_path) && !File.directory?(file_path)
content_type = get_content_type(file_path)
content = File.read(file_path)
response = "HTTP/1.1 200 OK\r\n"
response += "Content-Type: #{content_type}\r\n"
response += "Content-Length: #{content.length}\r\n"
response += "Connection: close\r\n"
response += "\r\n"
response += content
client.write(response)
else
send_error(client, 404, "Not Found")
end
end
def send_error(client, status_code, message)
content = "<html><body><h1>#{status_code} #{message}</h1></body></html>"
response = "HTTP/1.1 #{status_code} #{message}\r\n"
response += "Content-Type: text/html\r\n"
response += "Content-Length: #{content.length}\r\n"
response += "Connection: close\r\n"
response += "\r\n"
response += content
client.write(response)
end
def get_content_type(file_path)
ext = File.extname(file_path).downcase
case ext
when '.html' then 'text/html'
when '.css' then 'text/css'
when '.js' then 'application/javascript'
when '.png' then 'image/png'
when '.jpg', '.jpeg' then 'image/jpeg'
when '.gif' then 'image/gif'
else 'text/plain'
end
end
def shutdown
puts "正在关闭HTTP服务器..."
@server.close if @server
exit
end
end
# 使用示例
# server = SimpleHTTPServer.new(8080, './public')
# server.start网络时间协议(NTP)客户端
ruby
require 'socket'
class NTPClient
NTP_SERVER = 'pool.ntp.org'
NTP_PORT = 123
def get_time
# 创建UDP Socket
socket = UDPSocket.new
# 构造NTP请求包
ntp_packet = construct_ntp_packet
# 发送请求
socket.send(ntp_packet, 0, NTP_SERVER, NTP_PORT)
# 接收响应
response, = socket.recvfrom(48)
socket.close
# 解析响应
parse_ntp_response(response)
end
private
def construct_ntp_packet
# 构造48字节的NTP请求包
packet = [0] * 48
packet[0] = 0x1B # LI(0) | VN(3) | Mode(3) - 客户端模式
packet.pack('C48')
end
def parse_ntp_response(packet)
# 解析NTP响应包
data = packet.unpack('C1 N1 N1 N1 N1 N1 N1 N1 N1 N1 N1 N1')
# 提取时间戳(第41-48字节)
timestamp = data[10] * (2**32) + data[11]
# 转换为Unix时间戳
# NTP时间戳从1900年1月1日开始,Unix时间戳从1970年1月1日开始
# 差值为2208988800秒
unix_timestamp = timestamp - 2208988800
Time.at(unix_timestamp)
end
end
# 使用示例
# client = NTPClient.new
# network_time = client.get_time
# puts "网络时间: #{network_time}"
# puts "本地时间: #{Time.now}"📊 Socket性能优化
连接池
ruby
require 'socket'
require 'thread'
class SocketPool
def initialize(host, port, size = 5)
@host = host
@port = port
@size = size
@pool = Queue.new
@mutex = Mutex.new
# 初始化连接池
@size.times do
@pool.push(create_socket)
end
end
def with_socket
socket = @pool.pop
begin
yield socket
ensure
@pool.push(socket)
end
end
private
def create_socket
TCPSocket.new(@host, @port)
end
end
# 使用示例
# pool = SocketPool.new('httpbin.org', 80)
#
# 10.times do
# pool.with_socket do |socket|
# socket.puts "GET /get HTTP/1.1"
# socket.puts "Host: httpbin.org"
# socket.puts "Connection: close"
# socket.puts
#
# response = socket.read
# puts "响应长度: #{response.length}"
# end
# end非阻塞Socket
ruby
require 'socket'
class NonBlockingClient
def initialize(host, port)
@host = host
@port = port
@socket = nil
end
def connect
@socket = Socket.new(:INET, :STREAM)
# 设置为非阻塞模式
@socket.fcntl(Fcntl::F_SETFL, Fcntl::O_NONBLOCK)
sockaddr = Socket.sockaddr_in(@port, @host)
begin
@socket.connect(sockaddr)
rescue Errno::EINPROGRESS
# 连接正在进行中
wait_for_write
end
end
def send_data(data)
@socket.write(data)
end
def receive_data(max_bytes = 1024)
@socket.read(max_bytes)
end
private
def wait_for_write
# 等待Socket变为可写状态
readable, writable, _ = IO.select(nil, [@socket], nil, 5)
if writable && writable.include?(@socket)
# 检查连接是否成功
error = @socket.getsockopt(Socket::SOL_SOCKET, Socket::SO_ERROR).int
raise SocketError, "连接失败" unless error == 0
else
raise SocketError, "连接超时"
end
end
end
# 使用示例
# client = NonBlockingClient.new('httpbin.org', 80)
# client.connect
# client.send_data("GET /get HTTP/1.1\r\nHost: httpbin.org\r\n\r\n")
# response = client.receive_data
# puts response🎯 Socket最佳实践
1. 错误处理
ruby
require 'socket'
class RobustSocketClient
def initialize(host, port, timeout = 5)
@host = host
@port = port
@timeout = timeout
end
def send_message(message)
socket = nil
begin
# 创建Socket并设置超时
socket = TCPSocket.new(@host, @port)
socket.write(message)
# 设置读取超时
socket.read_timeout = @timeout
response = socket.read(1024)
response
rescue SocketError => e
puts "Socket错误: #{e.message}"
nil
rescue Errno::ECONNREFUSED => e
puts "连接被拒绝: #{e.message}"
nil
rescue Timeout::Error => e
puts "连接超时: #{e.message}"
nil
ensure
socket&.close
end
end
end
# 使用示例
# client = RobustSocketClient.new('localhost', 9999)
# response = client.send_message("测试消息")
# puts response || "发送失败"2. 资源管理
ruby
class ManagedSocket
def initialize
@sockets = []
end
def create_tcp_socket(host, port)
socket = TCPSocket.new(host, port)
@sockets << socket
socket
end
def create_udp_socket
socket = UDPSocket.new
@sockets << socket
socket
end
def close_all
@sockets.each do |socket|
begin
socket.close unless socket.closed?
rescue
# 忽略关闭错误
end
end
@sockets.clear
end
def socket_count
@sockets.length
end
end
# 使用示例
# manager = ManagedSocket.new
#
# socket1 = manager.create_tcp_socket('httpbin.org', 80)
# socket2 = manager.create_udp_socket
#
# puts "当前管理的Socket数量: #{manager.socket_count}"
#
# # 应用结束时关闭所有Socket
# at_exit { manager.close_all }3. 安全Socket (SSL/TLS)
ruby
require 'socket'
require 'openssl'
class SecureSocketClient
def initialize(host, port)
@host = host
@port = port
end
def connect
# 创建TCP连接
tcp_socket = TCPSocket.new(@host, @port)
# 创建SSL上下文
context = OpenSSL::SSL::SSLContext.new
context.verify_mode = OpenSSL::SSL::VERIFY_PEER
# 创建SSL Socket
ssl_socket = OpenSSL::SSL::SSLSocket.new(tcp_socket, context)
ssl_socket.hostname = @host
ssl_socket.connect
ssl_socket
end
def send_https_request(path = '/')
socket = nil
begin
socket = connect
# 发送HTTPS请求
request = "GET #{path} HTTP/1.1\r\n"
request += "Host: #{@host}\r\n"
request += "Connection: close\r\n"
request += "\r\n"
socket.write(request)
response = socket.read
response
rescue => e
puts "HTTPS请求失败: #{e.message}"
nil
ensure
socket&.close
end
end
end
# 使用示例
# client = SecureSocketClient.new('httpbin.org', 443)
# response = client.send_https_request('/get')
# puts response📚 下一步学习
掌握了Ruby Socket编程后,建议继续学习:
- Ruby XML, XSLT 和 XPath 教程 - 学习XML数据处理
- Ruby Web服务 - 了解Web服务开发
- Ruby 多线程 - 掌握并发编程
- Ruby 数据库访问 - 学习数据库操作
继续您的Ruby学习之旅吧!