RubyでTCPサーバ/クライアントを作ってみた

gihyo.jp この書籍を参考に、RubyTCPサーバ/クライアントを作ってみました。

目次

TCPとソケット

TCP(Transmission Control Protocol)は、コンピュータネットワーク上で信頼性のあるデータ転送を提供する通信プロトコルです。TCP通信には、ソケット(socket)というAPIを使用します。サーバ側ソケットがクライアントからの接続を待ち受け、クライアント側ソケットでサーバのホストとポートを指定して接続します。これにより、双方向のデータ送信が可能な通信経路が確立されます。

今回は、Rubysocketライブラリを利用して、簡単なTCPサーバ/クライアントを作ってみました。

動作環境

サーバ側

初期化

class TcpServer
  def initialize
    @server = TCPServer.open(8001)
    @client = nil
  end
end

TCPServer クラスのopenメソッドを使用して、ポート番号 8001 での接続を待ち受けるサーバーソケットを作成します。 TCPServer.openメソッドは、指定したポート番号にバインドされた新しい TCPServer オブジェクトを返します。 これでクライアントからの接続を受け付ける準備が整います。

クライアントとの接続を表すソケットを初期化するために、@clientインスタンス変数を nilで初期化しています。 この変数は、クライアントとの接続が確立された後にソケットオブジェクトを保持するために使用します。

待機

def accept
    puts 'waiting for connection...'
    @client = @server.accept
end

acceptメソッドは、クライアントからの接続があるまで処理をブロックします。 接続があるとクライアントとの通信用のソケットを返すので、それを@client変数に代入します。

データ受信

def recv
    puts 'connected'
    # 受け取った内容をsever_recv.txtに書き込む
    File.open('server_recv.txt', 'w') do |f|
      while line = @client.gets
        break if line.chomp == '0'
        f.write(line)
      end
    end
 end

TCPサーバー内でクライアントからのデータを受信し、それを server_recv.txtファイルに書き込むための処理を行っています。 ファイルを書き込みモードで開きます。 @client.getsで、クライアントからのデータを1行ずつ取得し、受信したデータが '0'のときにループを終了します。 受信したデータを server_recv.txtファイルに書き込みます。

データ送信

def send(_data)
    File.open('server_send.txt', 'r') do |f|
      while line = f.gets
        @client.puts(line)
      end
    end
end

server_send.txtファイルを読み込みモードで開き、ファイルから行ごとにデータを読み込みます。 putsは、クライアントのソケットに対してデータを送信するメソッドで、ファイルから読み込んだ行ごとにクライアントにデータを送信しています。

切断

def close
    @client.close
end

切断します。

ソースコード

require 'socket'

# tcpサーバークラス
class TcpServer
  # 初期化
  def initialize
    @server = TCPServer.open(8001)
    @client = nil
  end

  def run
    accept
    recv
    send('server_send.txt')
    close
  end

  private

  # クライアントからの接続を待つ
  def accept
    puts 'waiting for connection...'
    
    @client = @server.accept
  end

  # クライアントからのデータを受信する
  def recv
    puts 'connected'
    # 受け取った内容をsever_recv.txtに書き込む
    File.open('server_recv.txt', 'w') do |f|
      while line = @client.gets
        break if line.chomp == '0'
        f.write(line)
      end
    end
  end

  # クライアントにデータを送信する
  def send(_data)
    File.open('server_send.txt', 'r') do |f|
      while line = f.gets
        @client.puts(line)
      end
    end
  end

  def close
    puts 'connection closed'
    @client.close
  end
end

# サーバーの起動
server = TcpServer.new
server.run

クライアント側

初期化

class TcpClient
  # 初期化
  def initialize
    @client = TCPSocket.open('localhost', 8001)
  end
end

TCPSocket.openメソッドを使用して、指定されたホスト名とポート番号でソケットを開きます。

送信

def send(_data)
    # client_send.txtの内容をサーバーに送信する
    File.open('client_send.txt', 'r') do |f|
      while line = f.gets
        @client.puts(line)
      end
    end
    # サーバーに0を送信する
    @client.puts('0')
  end

putsは、サーバーのソケットに対してデータを送信するメソッドで、ファイルから読み込んだ行ごとにサーバーにデータを送信しています。 最後にサーバーに '0' を送信し、通信の終了を示します。

ソースコード

require 'socket'

# tcpクライエントクラス
class TcpClient
  # 初期化
  def initialize
    @client = TCPSocket.open('localhost', 8001)
  end

  def run
    send('client_send.txt')
    recv
    close
  end

  private

  def send(_data)
    # client_send.txtの内容をサーバーに送信する
    File.open('client_send.txt', 'r') do |f|
      while line = f.gets
        @client.puts(line)
      end
    end
    # サーバーに0を送信する
    @client.puts('0')
  end

  def recv
    # サーバーからのデータを受信する
    File.open('client_recv.txt', 'w') do |f|
      while line = @client.gets
        f.write(line)
      end
    end
  end

  def close
    @client.close
  end
end

# クライアントの起動
client = TcpClient.new
client.run

実行

client_send.txtserver_send.txtファイルを用意する

  • client_send.txt
My name is Mike
  • server_send.txt
Hello, Mike!

起動

  • サーバ側
$ ruby TCP/tcp_server.rb
waiting for connection...
  • クライアント側
$ ruby TCP/tcp_client.rb

確認

server_send.txtと同内容のclient_recv.txtが出来ていれば成功です。

TCPサーバをWebブラウザで叩く

tcp_server.rbを本物のWebブラウザで叩いてみます。

サーバ起動

$ ruby TCP/tcp_server.rb

URL入力

ブラウザにhttp://localhost:8001/index.htmlと入力する

確認

server_recv.txtでHTTPリクエストを確認できます。

TCPクライアントでWebサーバを叩く

次はWebサーバがブラウザに対して何を返すのか確認してみます。 書籍ではApacheを使っていますが、面倒なので、実際のWebサイトのアドレスを叩いてみます。

tcp_client.rbの書き換え

def initialize
    @client = TCPSocket.new(ENV.fetch('WEBSITE', nil), 80)
end

TCPSocket.new メソッドを使用して、指定されたホストとポート番号での新しいソケットを作成します。 secureでない接続が出来てしまうサイトを晒すのもどうかと思うので、URLは環境変数にして読み込んでいます。

def send
    request = "GET / HTTP/1.1\r\n"
    request += ENV.fetch('HOST', nil)
    request += "Connection: close\r\n\r\n"

    @client.puts(request)
 end

sendメソッドも書き換えます。

# @client.puts('0')

'0'の送信は不要なのでコメントアウトします

起動

$ ruby TCP/tcp_client.rb

確認

client_recv.txtにHPPTレスポンスが出力されたら成功です。

思ったより簡単にできました🎉

ソースコード

https://github.com/kei-kmj/HenacatRuby/commit/def12ca9a209686fd1b9910947a9f653f5b42c5f