LoginSignup
16
12

More than 5 years have passed since last update.

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

Last updated at Posted at 2019-02-02

概要

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

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

STREAM
コネクションあり(通信保証あり)
DGRAM
コネクションレス(通信保証なし)
UNIX
プロセス通信
UNIX ドメイン
ストリーム型ソケット
UNIX ドメイン
DGRAM型ソケット
INET
ネットワーク通信
TCP UDP

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

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

動作環境

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

クライアント側

# 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

サーバー側

# 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のほうが楽だと感じた。

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

16
12
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
16
12