Chat Server - Clients

Ganz ehrlich würde ich eine BlockingQueue nutzen. Wenn du in die JavaDocs zu BlockingQueue schaust, dann siehst du da sogar ein Beispiel dazu. Mit indexen zu arbeiten kann immer für Ärger sorgen (NPE).

Angenommen clientSockets, wäre eine BlockingQueue (z.B. LinkedBlockingQueue)

dann sieht das ganze so aus

  Socket socket = clientSockets.take(); //Holt Socket aus Queue
  ...workWithClientSocketLikebefore...
  clientSockets.put(socket); //Fügt ihn hinten in der Queue ein um ihn nachher wieder abarbeiten zu können.
}```

Da das take blockiert, wartet es so lange, bis auch ein Socket in der queue ist. Das hinzufügen läuft in einem eigenen Thread ab.
Wenn du eine Blocking Queue nutzst, dann kannst du auch mehrere Threads verwenden, welche die Sockets parallel abarbeiten.

Die einfachste Lösung wäre wohl eine

List<ClientUser> list = new CopyOnWriteArrayList<ClientUser>();

was macht das denn?

*** Edit ***

Danke für deinen Tipp mit der Queue, klappt super @Majora ^^
Brauch ich auch kein synchronized mehr.
Statt available(), was mir 90% der nachrichten einfach verschluckt hat, hab ich jetzt
einfach BufferedReader.ready() genommen. Das hat mir bisher noch keine probleme bereitet.

Danke euch, ich melde mich wahrscheinlich bald wieder, wenn ich mal wieder irgendwo prüfe ob false == true ist…
oder ich mehr probleme mit sockets hab :wink:

ein ThreadPool ist doch keine automatische Lösung,

man muss erst die Technik dazu haben,
Standard ist blockierendes Lesen, dann 50 Threads falls man auf alle unmittelbar reagieren können will,
(und ich schätze nebenbei mal grob die Verwaltung derselben macht maximal Promille aus, keine wesentliche Störung)

wenn avaiable() geht oder man einen Timeout von wenigen ms definieren kann, dann kann auch ein Thread selber alles durchlaufen,
das muss man programmieren, da hilft kein ThreadPool

eine ankommende Nachricht dürfte hier so schnell verarbeitet sein (selbst mit Weitersenden an alle), dass ein Thread alleine klar kommt,
sofern aber in anderen Szenarien jede Nachricht mehrere Sekunden Arbeit erahnen läßt ist es schlau, diese Arbeit abzugeben,

ob man dann einfach jeweils einen neuen Thread aufmacht, selber ein paar dauerhaft laufen läßt und einen freien davon wählt, oder einen ThreadPool bemüht,
macht nicht so viel Unterschied,
schon sieht man wie unspektakulär wenig der Pool dabei leistet, einfach so 50 Threads ersetzt er schon gar nicht

Blocking-IO, mehrere Clients und nur ein Thread… das beisst sich doch?!

Es gibt da 3 Lösungen:

  • NIO
  • AIO
  • oder eben was fertiges benutzen

Aber Blocking-IO mit nur einem Thread ist schon ein wenig Knieschuss. Auch das verwalten der Sockets mit einer Hand voll Threads macht’s nicht besser.

Entweder NIO oder AIO.

oder einfach mymaksimus’sch.
So wie ich es jetzt hab klappt es ganz gut, nun aber die nächste sache:
wie mache ich es am besten eine neue nachricht an alle anderen zu schicken?
(damit jeder client anzeigt wer was wann postet…)
Geht eine einfache for schleife durch alle nutzer, oder gehts auch anders?

Schleife

[QUOTE=SlaterB]ein ThreadPool ist doch keine automatische Lösung,

man muss erst die Technik dazu haben,
Standard ist blockierendes Lesen, dann 50 Threads falls man auf alle unmittelbar reagieren können will,
(und ich schätze nebenbei mal grob die Verwaltung derselben macht maximal Promille aus, keine wesentliche Störung)

wenn avaiable() geht oder man einen Timeout von wenigen ms definieren kann, dann kann auch ein Thread selber alles durchlaufen,
das muss man programmieren, da hilft kein ThreadPool

eine ankommende Nachricht dürfte hier so schnell verarbeitet sein (selbst mit Weitersenden an alle), dass ein Thread alleine klar kommt,
sofern aber in anderen Szenarien jede Nachricht mehrere Sekunden Arbeit erahnen läßt ist es schlau, diese Arbeit abzugeben,

ob man dann einfach jeweils einen neuen Thread aufmacht, selber ein paar dauerhaft laufen läßt und einen freien davon wählt, oder einen ThreadPool bemüht,
macht nicht so viel Unterschied,
schon sieht man wie unspektakulär wenig der Pool dabei leistet, einfach so 50 Threads ersetzt er schon gar nicht[/QUOTE]

Das ein ThreadPool nicht die Woll-Milch Sau ist, sollte klar sein. Ich wollte ihm nur auf die Frage antworten ob es von Nöten sei 50 Threads für 50 Clients zu öffnen.
Natürlich braucht man intern einen “Scheduler” der das ganze verwaltet, was in der Tat eine interessante Aufgabe ist. Wobei sich das Runable, wenn kein Input mehr vorhanden ist einfach selbst beenden kann, so ist wieder ein Platz für ein anderen Task bereit und in der Queue kann ein Task aufrücken.
@tuxedo
Der Aufwand für NIO (AIO sagt mir jetzt auf die schnelle nichts) ist verhältnismäßig groß für das Projekt. Ich kann mir nicht vorstellen das es sich Lohnt für simple Chats so etwas zu implementieren.

Wobei der Aufwand für NIO (AIO sagt mir jetzt auf die schnelle nichts) verhältnismäßig groß ist für das Projekt.

Wenn man NIO zu Fuß macht: Ja. Aber wieso bei allem das Rad neu erfinden? xSocket, MINA, Netty, …

AIO → Asynchronous IO (gibt’s seit Java7). Besser/schneller als NIO, und auch deutlich einfacher. Nur darf man sich seine Gehirnwinduingen bei der ganzen indirektion durch die Listener/Handler nicht verknoten.

Ich kann mir nicht vorstellen das es sich Lohnt für simple Chats so etwas zu implementieren.

Genau das ist der Knackpunkt. Wenn das soo eine simple Anwendung ist, warum dann nicht beim 1-Thread-pro-Client-Prinzip bleiben? Immerhin gibt’s viele Große Libs/APIs die das so machen. Allem voran RMI. Und RMI wird als Remote-EJB im JEE Umfeld groß und breit eingesetzt. 50 Clients sind da oft „gar nix“.

In meinen Augen lohnt es sich nicht die paar Threads für einen Chat-Server zu sparen und dafür Stunden in die Implementierung eines Schedulers zu stecken, weil man das doch recht einfache Netty oder Mina für „zu viel Aufwand“ hält.

Oder anders gesagt:

Je nach Server-Leistung würde ich mit standard blocking-io und 1-Thread-pro-Client locker mal 500-1500 Clients auf den Server lassen. Erst wenn es mehr werden würde ich mir überlegen ob ich nicht eine andere Technik brauche oder den Server in mehrere Chat-Bereiche (sprich mehrere Server) splitte und so auch die Anzahl der Clients pro Server halbiere.

Aber es lohnt auf jeden Fall sich mal mit AIO zu beschäftigen… Leider ist die Doku dazu noch nicht sehr groß. Aber das Prinzip ist schnell erlernt und verstanden.

Hier mal ein Beispiel von mir zu AIO aus dem „alten JF.org“: http://www.java-forum.org/allgemeine-java-themen/131309-java7-faszination-file-aio.html
Geht zwar um Filezugriffe, aber auf Sockets lässt sich das fast 1:1 auch so anwenden.

So wie ich das verstanden haben, ist das ein Schulprojekt. Ich gehe davon aus, dass er keine thrid party libs verwenden darf. AIO scheint recht interessant zu sein.
Ich hab zu NIO diesen Link gefunden:
https://www.ibm.com/developerworks/java/tutorials/j-nio/section9.html

Das scheint in der Tat auch nicht wirklich mehr Aufwand zu sein als blocking I/Os.

Edit: Zum AIO: Wäre interessant zu wissen ob ein ByteBuffer oder ein MappedByteBuffer (also vom OS direkt verwalteter Speicher) schneller ist.

Naja, NIO ist schon aufwendiger. Und man kann viel mehr falsch machen. Die Lernkurve ist (bis man das ganze mal am laufen hat) deutlich höher.

Ich hab selbst mein RMI Ersatz SIMON mal zu Fuß mit NIO ausgestattet. Hat nach einigen Wochen auch funktioniert. Aber es war hinsichtlich der Performance nicht optimal. Hab dann Apache MINA für mich entdeckt und bin soweit damit zufrieden. Damit hab ich noch mehr Performance rausgekitzelt.

NIO2 aka. AIO ist die nächste Steigerung. Wenn ich wieder etwas zu Fuß machen müsste, dann würde ich AIO in Betracht ziehen. Oder aber Apache MINA um AIO erweitern.

Aber wie gesagt: Mit dem Standard Blocking-IO kannst du locker einige hundert bis wenige tausend Clients bedienen. Erst recht in einem Chat. Die Netzwerklast ist da ja entsprechend gering und der Aufwand für jeden einzelnen Thread ebenso. Einen Fileserver der von 500 und mehr Clients gleichzeitig Daten annimmt oder an diese ausliefert würde ich aber mit NIO oder AIO machen.

ich werf mal noch was ganz anderes ein

while(true)
//gedöns
if(breakCondition) break loop;```
OUCH !

ja, SO kann man labels nutzen ... aber das ist nicht sinn und zweck dieser (warum gibt es diesen absoluten bullshit eigentlich ? ich kenne KEIN halbwegs vernünftiges beispiel)

besser wäre deine breakCondition als parameter für den loop zu nehmen


zu den 50 threads für 50 clients
ja, optimal ist es sicher nicht, aber für heutige systeme kein problem
man kanns ja erstmal nutzen um die grundlagen mit blocking-io zu lernen


und weil tante google scheinbar mal wieder nich geht ... quick'n'dirty
```import java.io.*;
import java.net.*;
import java.util.*;
public class Server
{
 private ServerSocket serverSocket=null;
 private ArrayList<ClientRunnable> clients=null;
 private static Server server=null;
 public static void main(String[] args)
 {
  System.out.println("start-up chat-server");
  server=new Server();
  server.startup();
  System.out.println("start-up complete");
 }
 private Server()
 {
  System.out.print("try to create ServerSocket ... ");
  try
  {
   serverSocket=new ServerSocket(12345);
  }
  catch(Exception e)
  {
   System.out.println("failed to create ServerSocket");
   e.printStackTrace();
   System.exit(1);
  }
  System.out.println("ok");
  clients=new ArrayList<ClientRunnable>();
 }
 private void startup()
 {
  System.out.print("setting up accept-thread ... ");
  Thread thread=new Thread(new Runnable()
  {
   public void run()
   {
    while(true)
    {
     try
     {
      Socket client=serverSocket.accept();
      ClientRunnable clientRunnable=new ClientRunnable(server, client);
      (new Thread(clientRunnable)).start();
      clients.add(clientRunnable);
     }
     catch(Exception e)
     {
      System.out.println("failed to accept client");
      e.printStackTrace();
     }
    }
   }
  });
  System.out.println("ok");
  System.out.println("starting accept-thread");
  thread.start();
 }
 protected void broadcast(String msg)
 {
  for(ClientRunnable client : clients)
  {
   client.send(msg);
  }
 }
 protected void disconnect(ClientRunnable client)
 {
  clients.remove(client);
 }
}```
```import java.io.*;
import java.net.*;
class ClientRunnable implements Runnable
{
 private Server server=null;
 private Socket socket=null;
 private BufferedReader in=null;
 private PrintStream out=null;
 protected ClientRunnable(Server server, Socket socket)
 {
  this.server=server;
  this.socket=socket;
 }
 public void run()
 {
  try
  {
   in=new BufferedReader(new InputStreamReader(socket.getInputStream()));
   out=new PrintStream(socket.getOutputStream());
  }
  catch(Exception e)
  {
   System.out.println("cant get client-streams");
   e.printStackTrace();
   return;
  }
  String line="";
  while(true)
  {
   try
   {
    line=in.readLine();
    if(line==null)
    {
     break;
    }
    if(line.equals(""))
    {
     continue;
    }
    if(line.equals("DC"))
    {
     break;
    }
    server.broadcast(line);
   }
   catch(Exception e)
   {
    System.out.println("failed to read message");
    e.printStackTrace();
   }
  }
  try
  {
   in.close();
   out.close();
   socket.close();
  }
  catch(Exception e)
  {
   System.out.println("cant close streams/socket");
   e.printStackTrace();
  }
  server.disconnect(this);
 }
 protected void send(String msg)
 {
  try
  {
   out.println(msg);
  }
  catch(Exception e)
  {
   System.out.println("cant send message");
   e.printStackTrace();
  }
 }
}```
```import java.io.*;
import java.net.*;
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
public class ClientApplet extends JApplet
{
 private Socket socket=null;
 private BufferedReader in=null;
 private PrintStream out=null;
 private JTextField jTextField=null;
 private JTextArea jTextArea=null;
 public ClientApplet()
 {
  setLayout(new BorderLayout());
  setBackground(Color.LIGHT_GRAY);
  setForeground(Color.BLACK);
 }
 public void init()
 {
  jTextField=new JTextField();
  jTextField.setBackground(Color.WHITE);
  jTextField.addActionListener(new ActionListener()
  {
   public void actionPerformed(ActionEvent e)
   {
    try
    {
     String msg=jTextField.getText();
     if(!msg.equalsIgnoreCase("DC"))
     {
      out.println(msg);
     }
     jTextField.setText("");
    }
    catch(Exception ex)
    {
     jTextArea.append(ex.toString()+"
");
    }
   }
  });
  jTextArea=new JTextArea();
  jTextArea.setBackground(Color.WHITE);
  jTextArea.setEditable(false);
  this.add("South", jTextField);
  this.add("Center", jTextArea);
 }
 public void start()
 {
  try
  {
   socket=new Socket(this.getCodeBase().getHost(), 12345);
   in=new BufferedReader(new InputStreamReader(socket.getInputStream()));
   out=new PrintStream(socket.getOutputStream());
  }
  catch(Exception e)
  {
   jTextArea.append(e.toString()+"
");
   jTextArea.append("cant connect to server");
   return;
  }
  jTextArea.append("connected
");
  (new Thread(new Runnable()
  {
   public void run()
   {
    String line="";
    while(true)
    {
     try
     {
      line=in.readLine();
      if(line==null)
      {
       break;
      }
      if(line.equals(""))
      {
       continue;
      }
      jTextArea.append(line+"
");
     }
     catch(Exception e)
     {
      jTextArea.append(e.toString()+"
");
     }
    }
    jTextArea.append("ERROR");
    try
    {
     in.close();
     out.close();
     socket.close();
    }
    catch(Exception e)
    {
     jTextArea.append(e.toString()+"
");
    }
    return;
   }
  })).start();
 }
 public void stop()
 {
 }
 public void destroy()
 {
  try
  {
   out.println("DC");
   in.close();
   out.close();
   socket.close();
  }
  catch(Exception e)
  {
   jTextArea.append(e.toString()+"
");
  }
 }
}```

ja .. ich weis ... ich nutz auch while(true) und break ... aber nicht in verbindung mit labels

[/OT]
Man, leute: Meinem Lehrer wäre es egal, eigentlich zumindest. ich selbst werde mich frühestens bis zu meinem berufsleben weigern für eigene projekte third party librarys zu verwenden. [<- = punkt]
Ich mach es einfach nicht. Das soll nicht heißen, das ich nie librarys benutze um mal anzusehen wie sowas funkioniert, wie man das einbindet etc, diese erfahrung fehlt mir keineswegs.
Am meisten ärgern mich solche aussagen:

Meine Meinung dazu: Das Rad neu zu erfinden, wäre sich eine eigene programmiersprache zu entwickeln.
Programmieren heißt für mich, ein rad nehmen, und ein neues auto zu entwickeln, welches neuer besser oder zumindest anders ist als alle bisher existierenden!
Was ist daran noch programmieren, wenn man sich an der arbeit anderer bedient? (auch wenn diese arbeit extra dafür gemacht wurde, für leute die keine lust keine zeit oder was auch immer haben um zu „richtig“ zu programmieren)
[OT]

Naja was solls. Gut ich versuchs mit der schleife.
Aaaber: Wie kann ich eine schleife durch die blockingqueue machen? Ein iterator würde ja wieder zu concurrent dingsbums führen…

synchronized war ja schon ein Thema, nicht ganz klar was da so alles rauskam,
um die Schleife herum ein synchronized-Block, außerdem bei add-Aufrufen usw., dann kann nichts schiefgehen

gut, versuch ich mal
stimmt, das mit dem label ist totaler schwachsinn, danke unregistered xD

@mymaksimus

Du musst das Rad nicht neu erfinden, aber dir auch kein Bein brechen nur um unnötigerweise ein paar Threads zu sparen.

Mach pro Client ein Thread und fertig. [<- = punkt]

Was ist daran noch programmieren, wenn man sich an der arbeit anderer bedient? (auch wenn diese arbeit extra dafür gemacht wurde, für leute die keine lust keine zeit oder was auch immer haben um zu „richtig“ zu programmieren)

Sorry, aber wenn man keine Ahnung hat …
Wenn ich jetzt einen Anwendungsserver haben will, dann kann ich hingehen und mir eine eigene JEE-ähnliche Welt entwicklen und Mannjahre an Entwicklung da rein stecken. Oder ich nehme fertige Libs, habe vielleicht wenige Wochen einarbeitungszeit und komme auch in endlicher Zeit zu einem brauchbaren Ergebnis.
Und das tolle daran ist: Ich bediene mich bekannter Schnittstellen. So profitiere nicht nur ich davon, nein, auch andere haben etwas davon. Nämlich wenn sie ein Plugin für meinen Anwendungsserver basteln wollen. Denn die müssen nicht bei jedem Anwendungsserver den die in die Finger bekommen erneut alles von vorne lernen. Nein, die kennen die Schnittstellen und machen das einmal. Und das Ergebnis läuft dann auf vielen Servern gleichermaßen.

Klar, Probleme sind da um gelöst zu werden. Aber es macht keinen Sinn ein und das selbe Problem in der Millionsten Ausfertigung nochmal zu lösen. Löse lieber Probleme die noch kein anderer gelöst hat (und stelle das Ergebnis dann anderen zur Verfügung). **DAS **ist richtiges programmieren…

  • Alex

auch dieses Beispiel ist nicht günstig, kein Mensch investiert mal eben Mannjahre für einen vollumfänglichen Server,
gegen 100 Zeilen Sockets spricht aber nichts,

auch experimentell andere Funktionen wie Session usw. kann man durchaus angehen, in Tagen statt Jahren

Klar, Probleme sind da um gelöst zu werden. Aber es macht keinen Sinn ein und das selbe Problem in der Millionsten Ausfertigung nochmal zu lösen. Löse lieber Probleme die noch kein anderer gelöst hat (und stelle das Ergebnis dann anderen zur Verfügung). **DAS **ist richtiges programmieren…

wer aber nie einen Chat mit Sockets selber begonnen hat kann zumindest in diesem Feld der direkten Netzwerkkommunikation rein gar nichts mehr leisten,

stell dir vor du wolltest eine RMI-Alternative, z.B. mit Namen SIMON ;), basteln,
aber da du nie Sockets anschauen durftest kannst du nur auf RMI aufsetzen und vielleicht paar Nutzdaten austauschen ?!


das Nachbauen einfacher bekannter Strukturen halte ich für ein wesentliches Lernfeld der Programmierung,
ganz und gar nicht ‚Rad neu erfinden‘,
wenn man es dann kennt und die Mangel der eigenen zusammengeschusterten Klassen sieht, kann man immer noch wechseln

Klar ich hab natürlich keine ahnung :smiley:
aber naja, jeder hat seine eigene meinung denke ich, auch wenn meine kaum einer unterstützen wird ^^

Ich hab mich gegen die client = 1 Thread lösung entschieden. ich glaub hochgerechnet macht das einfach keinen sinn.

[QUOTE=mymaksimus]
Ich hab mich gegen die client = 1 Thread lösung entschieden. [/QUOTE]
das macht ja die gewählte Alternative umso spannender, noch nicht entschieden oder kein Kommentar?

@Slater

Ich hab ihn doch nicht davon abgehalten das mit Sockets zu lösen? Ich hab ihm nur geraten den „normalen“ Socketweg zu gehen, mit drm HIntergrund, dass er ja den Aufwand für eine externe Lib nicht will, aber offensichtlich den Aufwand einer Socketimplementierung mit reduzierter Anzahl Threads nicht scheut → Gegenspruch. Unter’m Strich also unnötiger Aufwand mit fraglichem Erfolg.

Der Bezug zu JEE kam nur aus dem Umfeld „lieber selbst machen statt sich die Arbeit sparen und eine fertig Lib von jemand anderem verwenden, weil das wäre ja nicht richtig programmiert“. Wenn er das auf die Spitze treiben will, dann sollte er auch weite Teile der Java RT.jar meiden und besser selbst schreiben :slight_smile: