概要
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のほうが楽だと感じた。
本件、学び始めて日も浅いので、指摘等ございましたらいただけると幸いです。