Working with TCP Sockets 読書メモ 第6章 はじめてのクライアント/サーバ

目次


はじめてのクライアント/サーバ

サーバ

  • 以下は、シンプルなNoSQLのクライアントとサーバの実装例
# cloud_hash/server.rb

require 'socket'

module CloudHash
  class Server
    def initialize(port)
      # 基礎になるサーバソケットを作成する
      @server = TCPServer.new(port)
      puts "Listening on port #{@server.local_address.ip_port}"
      @storage = {}
    end

    def start
      Socket.accept_loop(@server) do |connection|
        handle(connection)
        connection.close
      end
    end

    def handle(connection)
      # EOFまでをconnectionから読み取り
      request = connection.read

      # ハッシュを処理した結果を返す
      connection.write process(request)
    end

    # サポートされている操作は以下
    # SET key value
    # GET key
    def process(request)
      command, key, value = request.split

      case command.upcase
        when 'GET'
          @storage[key]
        when 'SET'
          @storage[key] = value
      end
    end
  end
end

server = CloudHash::Server.new(4481)
server.start
# cloud_hash/client.rb

require 'socket'

module CloudHash
  class Client
    class << self
      attr_accessor :host, :port
    end

    def self.get(key)
      request "GET #{key}"
    end

    def self.set(key, value)
      request "SET #{key} #{value}"
    end

    def self.request(string)
      # 処理のためのconnectionを作成
      @client = TCPSocket.new(host, port)
      @client.write(string)

      # リクエストの書き込み後にEOFを送信する
      @client.close_write

      # レスポンスを受け取ってEOFまで読み込む
      @client.read
    end
  end
end

CloudHash::Client.host = 'localhost'
CloudHash::Client.port = 4481

puts CloudHash::Client.set 'prez', 'obama'
puts CloudHash::Client.get 'prez'
puts CloudHash::Client.get 'vp'
  • 現在のCloudHashには欠陥もある
  • そのうちの1つは、クライアントがリクエストの送信の度に接続する必要があること
  • サーバに並列性(concurrency)を導入する必要がある
    • 具体的な実装は次章以降

ソケットのオプション

  • ソケットの挙動を細かくカスタマイズするにはオプションを使用する

SO_TYPE

require 'socket'

socket = TCPSocket.new('google.com', 80)

# Socket::Optionインスタンスを取得
opt = socket.getsockopt(Socket::SOL_SOCKET, Socket::SO_TYPE)

# Optionの種類を調べる
p opt.int == Socket::SOCK_STREAM #=> true
p opt.int == Socket::SOCK_DGRAM #=> false
  • getsockoptを実行するとSocket::Optionのインスタンスが取得できる
  • SocketOption#intを実行すると、オプションの値(整数)が取得できる

SO_REUSE_ADDR

  • 全ての サーバはこのオプションを設定すべき
  • SO_REUSE_ADDRオプションを使うと、同一のローカルアドレスの使い回しができる
    • 条件は、サーバがTCPのTIME_WAITという状態であること

TIME_WAITとは

  • closeを実行すると、ソケットはTIME_WAIT状態になる
  • ソケットはバッファー内で待ち状態(pending)になっている
  • デフォルトでは、TIME_WAIT状態のソケットと同じアドレスは使えない
    • Errno::EADDRINUSEが発生する
  • SO_REUSE_ADDRを設定するとこの問題が発生しなくなる
require 'socket'

server = TCPServer.new('localhost', 4481)
server.setsockopt(:SOCKET, :REUSEADDR, true)

p server.getsockopt(:SOCKET, :REUSEADDR) #=> true
  • TCPServer.newSocket.tcp_server_loopなどではこのオプションがデフォルトで有効化されている

本章で扱ったシステムコール

  • setsockopt(2)
  • getsockopt(2)

ディスカッションに参加

2件のコメント

コメントをどうぞ

コメントを残す