Ruby-Programmierung: Netzwerkprogrammierung

Zurück zum Inhaltsverzeichnis.

Mit der Hochverfügbarkeit von Internet seit dem letzten Jahrtausendwechsel bekamen auch alle Programme die Chance die Vernetzung zwischen Nutzern und Computern zu nutzen. Das erlaubt Ihnen als Programmentwickler beispielsweise einfache Updatemethoden oder Feedbackmachnismen zu implementieren.

Trotz der neuartigen Verfügbarkeit des Internets ist die zugrundeliegende Technologie bereits sehr alt. So bauen alle Netzwerkverbindungen heutzutage auf der Berkley Socket API für die Programmiersprache C auf, die in den siebziger Jahren entwickelt wurde. Daher wundert es nicht, das die Benutzung der Socketklasse in Ruby zwar in den meisten Fällen eine sehr elegante Lösung erlaubt, in einigen Grenzfällen jedoch Merkwürdigkeiten aufzeigt.

Sockets und Protokolle und ...

Bearbeiten

Bei der Verwendung von Netzwerken und insbesondere in diesem Kapitel sind und werden relativ viele Bezeichnungen benötigt, die dem einsteigenden Leser vielleicht ohne weitere Recherche überfordern würden, daher dient dieser Abschnitt dazu die einzelnen Stichworte kurz zu erläutern und sie miteinander in Kontext zu setzen.

  • Sockets sind bidirektionale Verbindungen, die es erlauben Daten zwischen den beiden Endpunkten der Verbindung austauschen. Dies können Netzwerkverbindungen sein, oder UNIXSockets, die zwei Programme verbinden.
  • Protokolle sind Vereinbarungen zwischen den beteiligten Parteien (Computer, Netzwerkinfrastruktur) über die Art und Weise der Kommunikation
    • IP das internet protocol ist vor allem in Verbindung mit der IPNummer wichtig, die jeden Rechner in einem Netzwerk eindeutig addressiert und ihn so für andere Rechner erreichbar macht.
    • TCP ist ein Protokoll darüber, wie die übermittelten Daten aussehen, insbesondere erzwingt TCP das Erreichen der Daten beim Kommunikationspartner in der richtigen Reihenfolge. Im Gegensatz dazu stellt UDP keine solche Funktionalität bereit und eignet sich daher besser für Echtzeitanwendungen.
  • Client-Server-Modell ist eine hierarchische Methode mehrere Computer miteinander zu verbinden. Jeder Client verbindet sich zu einem Server, der die Kommunikation verwaltet. Dabei erfolgt jede Kommunikation zwischen den Clients über den Server. Im Gegensatz dazu verwalten die Kommunikation beim peer-to-peer die Programme selbst.

Um die recht theoretischen Betrachtungen des letzten Abschnitts in einen praktischen Kontext zu setzen, sollen zwei Programme entwickelt werden, die einen Chat zwischen beliebig vielen Personen erlaubt.

Um eine korrekte Übermittlung von Daten auf der Netzwerkseite zu verbessern bietet sich TCP als Protokoll an. Da beliebig viele Nutzer erlaubt sein sollen bietet sich ein Client-Server-Modell an, sodass ein Server die Kommuonikation zwischen allen verbundenen Clients verwaltet. Der Server wird außerdem für jede Verbindung mit einem Client einen Thread benötigen, um den entsprechenden Socket zu verwalten.

Nach diesen Vorüberlegungen widmen wir uns zunächst der Clientseite.

require "socket"

host = 'localhost'
port = 2000
sock = TCPSocket.open(host, port)

puts "A Simple Chat"

# recieving messages
Thread.new { loop { puts sock.gets } }

# sending messages
catch :exit do
  loop do
    input = gets.chomp
    
    case input
    when "!exit", "!e"
      sock.puts input
      throw :exit
    else
      sock.puts input
    end
  end
end

sock.close

Das Programm besteht im wesentlichen aus drei Teilen: Initialisierung, Empfang und Senden.

Beim Initialisieren öffnen wir eine Verbindung über den Netzwerkstack des Betriebssystem zum eigenen Rechner an Port 2000. Das es sich nicht über eine wirkliche Verbindung über ein Netzwerk zu einem anderen Rechner handelt ist dabei irrelevant, da durch Austauschen der IP eine Verbindung hergestellt werden kann.

Das Empfangen von Nachrichten soll zeitlich unabhängig vom Senden funktionieren, daher benötigt der Client eine eigene parallel ausgeführte Schleife zum Empfangen. Die Nachrichten werden einfach ausgegeben.

Das Senden funktioniert analog zum Empfangen mit dem Unterschied, das jetzt auf den Socket geschrieben wird anstatt ihn zu lesen. Beendet wird das Programm mit !e oder !exit.

require "socket"

class User 
  attr_reader :name, :sock

  def initialize(name, sock)
    @name = name
    @sock = sock
  end
end

port = 2000
server = TCPServer.new(port)

users = []

loop do
  Thread.start(server.accept) do |sock|
    
    sock.puts "Please enter a name."
    name = sock.gets.chomp
    user = User.new(name, sock)
    users << user

    loop do
      msg = sock.gets.chomp

      case msg
      when "!exit", "!e"
        users.delete user
      else
        users.each do |u|
          u.sock.puts "#{ user.name }: #{ msg }"
        end
      end
    end
  end
end

Da viele Sachen Ihnen bekannt vorkommen sollten, wird nur näher auf die eigentliche Serverloop eingegangen. Bei jeder eingehenden Verbindung server.accept wird ein neuer Thread gestartet, der die Initialisierung dieser Verbindung übernimmt. Der Nutzer wird nach einem Namen gefragt und dieser zusammen mit seinem Socket in einem Array gespeichert. Bei einer eingehenden Nachricht wird überprüft ob es sich um das ausloggen des Nutzers handelt (!e) oder um eine gewöhnliche Nachricht, diese wird an alle Nutzer weitergeleitet.

Das interessante an der Verwendung von Netzwerken ist, dass Sie sehr wenig neues in diesem Kapitel gelernt haben. Hätten Sie vordem Lesen dieses Kapitels die Quelltexte überflogen, dann hätten Sie bis auf zwei, drei Zeilen keinen Code entdecken können der Ihnen unbekannt vorkommt. Wichtig bei der Verwendung der Verbindung zwischen zwei Rechnern ist weniger die Tatsache, sondern was das Programm damit anstellt.

In der Einleitung wurde über das Internet gesprochen, dann jedoch nicht weiter erwähnt. TCP ist ein Netzwerkprotokoll und hat mit dem Internet grundlegend sehr viel zu tun. Mehr jedoch mit HTTP, dem Protokoll mit dem die meisten Dateien (zum Beispiel Webseiten) im Internet übertragen werden.

Die Bibliothek Net::HTTP unterscheidet sich ganz grundlegend von den oben kennengelernten Sockets, da zwar die Kommunikation über die Sockets läuft, aber HTTP keine Verbindungen kennt. Der Client schickt eine Anfrage an den Server und dieser Antwortet danach, es wird in seiner einfachen Form keine Verbindung aufrecht erhalten, sondern jede Abfrage muss über eine neue Verbindung ausgeführt werden.

Ein einfacher Browser

Bearbeiten
HELP = "VSB - very simple browsing
!exit, !e  --- exits the program
!help, !h  --- prints this help
Anything else is assumed as URL."

def sanitize(input)
  return input if input =~ /http:\/\//
  "http://#{ input }"
end

require "uri"
require "net/http"

input = ""

catch :exit do
  loop do
    puts "VSB - very simple browsing - try !help"
    print "CMD: "
    input = gets.chomp
    content = ""

    case input
    when "!e", "!q", "!exit", "!quit"
      throw :exit
    when "!h", "!help"
      puts HELP
    else
      input = sanitize(input)
      uri = URI(input)
      content = Net::HTTP.get(uri)
      content.gsub!(/<.+?>/, "").gsub!(/\s+/, " ")
      puts content
    end
  end
end

Dieser sehr einfache Browser nutzt die Bibliotheken Net::HTTP, um eine Website zu besuchen und URI um eine gegebene Adresse zu parsen. Der HTMLCode der entsprechenden Seite wird danach etwas aufbereitet und angezeigt.

Die Methode sanitize garantiert das am Anfang der Adresse das Protokoll angegeben wird, dies ist notwendig, um dem Nutzer auf die Engabe des Protokolls zu verzichten.