RubyでいろいろなSocket作って遊んでみた。

# 概要

Socketをつかってプロセス間、ネットワーク間で通信する機会があったので、学んだことをまとめてみる。

今回は下表の4種類のSocketを作って検証した。

||STREAM<br>コネクションあり（通信保証あり）|DGRAM<br>コネクションレス（通信保証なし）|
|:---|:---|:---|
|UNIX<br>プロセス通信|UNIX ドメイン<br>ストリーム型ソケット|UNIX ドメイン<br>DGRAM型ソケット|
|INET<br>ネットワーク通信|TCP|UDP|

Rubyでは、Socketクラスを使って上記４種類の通信を実現できる。
また、TCP、UDP、UNIX ドメインストリーム型ソケットにはラッパークラスも用意されており、より簡単な実装できるようだ。（UNIX ドメインDGRAM型ソケットのラッパーは見当たらない）

以下に、私が行った実装を載せておく。

# 動作環境

* macOS Mojave (10.14.2)
* ruby 2.3.3p222 (2016-11-21 revision 56859) [x86_64-darwin16]

# クライアント側

```ruby
# frozen_string_literal: true

require 'socket'

Socket.open(:INET, :DGRAM) do |sock|
  addr = Socket.sockaddr_in(9001, '127.0.0.1')
  sock.send('hello inet dgram', 0, addr)
end

Socket.open(:UNIX, :DGRAM) do |sock|
  addr = Socket.sockaddr_un('/tmp/unix_dgram.socket')
  sock.send('hello unix dgram', 0, addr)
end

Socket.open(:INET, :STREAM) do |sock|
  addr = Socket.sockaddr_in(9002, '127.0.0.1')
  sock.connect addr
  sock.write 'hello inet stream'
  p sock.gets
end

Socket.open(:UNIX, :STREAM) do |sock|
  addr = Socket.sockaddr_un('/tmp/unix_stream.socket')
  sock.connect addr
  sock.write 'hello unix stream'
  p sock.gets
end

UDPSocket.open do |sock|
  sock.send('hello udp', 0, '127.0.0.1', 9003)
end

TCPSocket.open('127.0.0.1', 9004) do |sock|
  sock.write 'hello tcp'
  p sock.gets
end

UNIXSocket.open('/tmp/unix_server.socket') do |sock|
  sock.write 'hello unix server'
  p sock.gets
end
```

# サーバー側

```ruby
# frozen_string_literal: true

require 'socket'

# INET DGRAM ( = UDP)
inet_dgram = Socket.new(:INET, :DGRAM)
inet_dgram.bind Socket.sockaddr_in(9001, '<any>')

# INET STREAM ( = TCP)
inet_stream = Socket.new(:INET, :STREAM)
inet_stream.bind Socket.sockaddr_in(9002, '<any>')
inet_stream.listen(5)

# UNIX DGRAM
unix_dgram = Socket.new(:UNIX, :DGRAM)
file0 = '/tmp/unix_dgram.socket'
File.unlink(file0) if File.exist? file0
unix_dgram.bind Socket.sockaddr_un(file0)

# UNIX STREAM
unix_stream = Socket.new(:UNIX, :STREAM)
file1 = '/tmp/unix_stream.socket'
File.unlink(file1) if File.exist? file1
unix_stream.bind Socket.sockaddr_un(file1)
unix_stream.listen(5)

# UDPSocket ( = INET DGRAM)
udp = UDPSocket.open
udp.bind('<any>', 9003)

# TCPServer ( = INET STREAM)
tcp = TCPServer.open(9004)

# UNIXServer ( = UNIX STREAM)
file2 = '/tmp/unix_server.socket'
File.unlink(file2) if File.exist? file2
unix_server = UNIXServer.new(file2)

sockets = [
  inet_dgram,
  unix_dgram,
  inet_stream,
  unix_stream,
  udp,
  tcp,
  unix_server
]

loop do
  res = IO.select(sockets)
  res.first.each do |sock|
    case sock
    when inet_dgram, unix_dgram, udp
      p sock.recvfrom(1024)
    when inet_stream, unix_stream, tcp, unix_server
      client, from = sock.accept
      data = client.recv(1024)
      p from
      p data
      client.puts 'OK'
      client.close
    end
  end
end

sockets.each(&:close)
```

# 実行結果

サーバー側のみ

```
["hello inet dgram", #<Addrinfo: 127.0.0.1:55843 UDP>]
["hello unix dgram", #<Addrinfo: empty-path-AF_UNIX-sockaddr SOCK_DGRAM>]
#<Addrinfo: 127.0.0.1:50513 TCP>
"hello inet stream"
#<Addrinfo: empty-path-AF_UNIX-sockaddr SOCK_STREAM>
"hello unix stream"
["hello udp", ["AF_INET", 53561, "127.0.0.1", "127.0.0.1"]]
nil
"hello tcp"
nil
"hello unix server"
```

通信成功したことがわかる。
また送信元のIPアドレス（127.0.0.1のこと）が取得できるケースとそうでないケース（nil）があるようだった。（TCPServerとUNIXServerだとこのやり方では取得できないっぽい。する方法はあると思うけど。）

# 所感

今回はRubyで実装したが、socket通信は言語の壁を超えるので学んでおくと便利そうだなと感じた。（JavaプロセスとPythonプロセスが通信する、WindowsPCとMacOSが通信するなど）
C言語でも同様の処理を実装してみたが、やはりRubyのほうが楽だと感じた。

本件、学び始めて日も浅いので、指摘等ございましたらいただけると幸いです。