使用 Ruby 进行 Socket 编程

网络是千万台计算机通过 TCP/IP 通信的结果。客户端请求执行某些操作,服务端执行该操作并响应客户端,这种被我们成为请求-响应模型。宏观上来看,就是当用户通过浏览器浏览网站时,请求发送到适当的 web 服务器,服务器通过发送适当的HTML页面来响应客户端。本章节主要带大家了解一下 Ruby中如何进行 Socket 应用程序开发。

1. 端口

在学习Socket编程之前我们要了解一下概念。

端口不是物理设备,而是促进服务器和客户端之间通信的抽象概念。

端口是由一个 2 的 16 次幂的整数表示的,所以,一台机器最多可以有65536个端口(0~65535)。

端口一共分为三个种类:

  1. 知名端口:0 ~ 1023(例如:80端口用于http,25端口用于smtp)。
  2. 注册端口:1024 ~ 49151。
  3. 动态/私有端口:49152 ~ 65535。

2. IP地址

这里我们只说 IPV4,它是由 4 个 4 个字节数字组成的主机地址,比如:124.56.124.103。

127.0.0.1(localhost)代表了本地回送地址,它是一个特殊的地址,代表了本地计算机。

3. Socket

3.1 什么是 Socket

Socket 表示两个网络应用程序之间的单个连接。这两个应用程序名义上可以在不同的计算机上运行,并且Socket也可以在单台计算机上实现进程之间的通信。应用程序可以创建多个用于相互通信的Socket。Socket 是双向的,代表连接它的任何一方都可以发送和接收数据。

3.2 Ruby中Socket类

Ruby 中拥有非常丰富的 Socket 的类型。

图片描述

上面是一张出自《Programming Ruby》的图片,介绍了 Rub y中 Socket 的类型。

如上图所示,所有的类都继承了 IO 类。

下面的例子中我们会使用TCPSocket来进行 Socket 的连接,使用TCPServer来创建一个TCP的服务器。

Tips:所有的Socket类都是标准库的一部分(不是核心类的一部分),因此要使用require 'socket'才可以在程序中使用它。

下面的是服务端的代码:

实例:

require 'socket'

server = TCPServer.new('127.0.0.1', 18000)
loop do
  Thread.start(server.accept) do |s|
    puts "#{s} is accepted."
    s.write(Time.now)
    puts "#{s} is gone."
    s.close
  end
end

解释:

我们使用TCPServer在本地创建了一个TCP的Socket服务器serverThread.start创建了一个新的线程(线程详细会在多线程章节中解释)并执行块中的代码指令。server.accept方法等待server上的链接,并返回链接到调用者的新TCPSocket对象s。loop do会一直迭代,直到退出循环。s.write会将时间写入到Socket之中。s.close会关闭socket。

这个脚本需要先启动,刚启动时你不会看到任何输出内容。

下面是客户端的代码:

实例:

require 'socket'  

stream = TCPSocket.new('127.0.0.1', 18000)

string = stream.recv( 100 )  
puts string  
stream.close  

解释:

我们使用TCPSocket.new('127.0.0.1', 18000)打开了TCP连接。语句string = stream.recv( 100 )

定义我们从socket最多接收100个字节。然后打印从服务器端获取到的时间,最后关闭socket。

服务端输出内容:

#<TCPSocket:0x00007fb54497d898> is accepted.
#<TCPSocket:0x00007fb54497d898> is gone.

客户端输出内容:

2020-08-24 00:23:14 +0800

这是一个服务端发送,客户端接收的例子。反过来依旧可以。

下面是服务端的代码:

require 'socket'

server = TCPServer.new('127.0.0.1', 18000)
loop do
  Thread.start(server.accept) do |s|
    puts "#{s} is accepted."
    string = s.recv( 100 )  
    puts string
    puts "#{s} is gone."
    s.close
  end
end

下面是客户端的代码:

require 'socket'  

stream = TCPSocket.new('127.0.0.1', 18000)
stream.write(Time.now)

服务端输出结果:

#<TCPSocket:0x00007fd6d714d1f0> is accepted.
2020-08-24 00:29:15 +0800
#<TCPSocket:0x00007fd6d714d1f0> is gone.

客户端输出结果:

# 无内容

4. 小结

本节我们讨论了 Socket 编程的基本类(例如Socket类),初步了解了 IP 以及端口号,以及有助于简化 Ruby 中 Socket 编程的类,例如TCPSocketTCPServer,学习了使用 Socket 进行交互的例子。