# Serverproblem



## vR34kSH0w (27. Nov 2011)

Hi,
ich versuche gerade, einen kleinen chat server zu programmieren, funktioniert auch ziemlich gut, aber nur unter einer meiner meinung nach etwas merkwürdigen bedingung.

folgender programmteil soll (wenn dann ausgebaut) alle nachrichten speichern:

```
import java.io.*;
import java.util.ArrayList;

public class MessageThreadTest extends Thread {
	public ArrayList <BufferedReader> br = new ArrayList <BufferedReader>();
	
	private ArrayList <String> messages = new ArrayList <String>();
	
	public void run() {
		while (
				
		true) {
			
		for (
				
		int i=0;
				
		i<br.size();
				
		i++) {
		
		try {
			
		if (
				
		br.get(i).ready()) {
			
		messages.add(br.get(i).readLine());
		}
		} catch (IOException ioe) {
			System.err.println(ioe);
		}
		}
		
		for (
				
		int i=0;
				
		i<messages.size();
				
		i++) {
		
		System.out.println("Message " + (i+1) + ": " + messages.get(i));
		}
		}
	}
}
```

so, wie es da oben steht, funktionierts nicht. füge ich in zeile 13 ein system.out.println("völligegalwashiersteht"); ein, schon.

hat jemand eine erklärung dafür??

gruß
  vR34k$H0w


----------



## tfa (27. Nov 2011)

Das kann ja keiner lesen bei der Formatierung...


----------



## vR34kSH0w (27. Nov 2011)

laut den regeln hier sollte eigentlich genau das jeder lesen können:
Code Conventions for the Java(TM) Programming Language: Contents

edit: allerdings such ich hier hilfe und keine diskussion über quelltextformatierung 

daher hab ichs umformatiert:

```
import java.io.*;
import java.util.ArrayList;

public class MessageThreadTest extends Thread {
	public ArrayList <BufferedReader> br = new ArrayList <BufferedReader>();
	
	private ArrayList <String> messages = new ArrayList <String>();
	
	public void run() {
		while (true) {
			
			for (int i=0; i<br.size(); i++) {
				try {
					if (br.get(i).ready()) {
					messages.add(br.get(i).readLine());
					}
				} catch (IOException ioe) {
					System.err.println(ioe);
				}
			}
			
			for (int i=0; i<messages.size(); i++) {
				System.out.println("Message " + (i+1) + ": " + messages.get(i));
			}
		}
	}
}
```
:toll:


----------



## xehpuk (27. Nov 2011)

Nochmal kurz zur Quelltextformatierung: Wieso hast du die Code Conventions denn nicht gelesen?
Der Code im ersten Post verstößt gegen 4 - Indentation und 8 - White Space.

Was funktioniert denn nicht? Dass der Thread ununterbrochen in einer Endlosschleife läuft, ist zumindest nicht so cool.


----------



## irgendjemand (27. Nov 2011)

gut ... allerdings hält sich sein erster post hieran : NO TITLE

und das ist nun wirklich absoluter bullshit ... kein mensch der welt schreibt in irgendeiner sprache


```
for(
init,
condition,
loop
)
```

und da sag doch mal einer : man muss sich buchstabengenau an die konventionen halten ...


----------



## xehpuk (27. Nov 2011)

O je, ich kann gar nicht glauben, was ich da lese. Ist heute der 1. April? :lol:


----------



## vR34kSH0w (27. Nov 2011)

niemand sagt dass man sich dran halten MUSS habs mir mehr oder weniger angewöhnt, aber zurück zum problem:
die while schleife könnte ich durch einen timer ersetzen aber es muss ja auf jeden fall permanent nach neuen nachrichten geschaut werden.
was nicht funktioniert is ehrlichgesagt etwas bescheuert.
wie gesagt: wenn ich in zeile 11 (beim code mit der gängigen formatierung) ein system.out.println einfüge egal mit welchem inhalt geht das programm erwartungsgemäß bei br>0 in die for schleife. so wie es da steht leider nicht. br.size() hab ich mir aber ausgeben lassen - in beiden fällen 1 ???:L


----------



## JohannisderKaeufer (27. Nov 2011)

```
import java.io.*;
import java.util.ArrayList;
 
public class MessageThreadTest extends Thread {
    public ArrayList <BufferedReader> br = new ArrayList <BufferedReader>();
    
    private ArrayList <String> messages = new ArrayList <String>();
    
    public void run() {
        while (true) {
            System.out.println("1.");
            for (int i=0; i<br.size(); i++) {
                System.out.println("2.");
                try {
                    if (br.get(i).ready()) {
                      System.out.println("3.");
                      messages.add(br.get(i).readLine());
                      System.out.println("4.");
                    }
                } catch (IOException ioe) {
                    System.err.println(ioe);
                }
            }
            
            for (int i=0; i<messages.size(); i++) {
                System.out.println("Message " + (i+1) + ": " + messages.get(i));
            }
        }
    }
}
```

Was wird da wohl ausgegeben?
Ich vermute
1.
2.
3.

das readline() liest blockierend, d.h. solange kein \r oder \n eingelesen wird gibt es keine Antwort von readline().

Erst wenn das in den entsprechenden reader eingegeben wird, gibt es die Antwort von readline und ein 4. wird ausgegeben, ansonsten hängt es halt in dieser Zeile fest.

Eine Lösung könnte so aussehen, dass jedem Reader ein eigener Thread spendiert wird.


```
import java.io.*;
import java.util.ArrayList;
 
public class MessageThreadTest extends Thread {
    public ArrayList <BufferedReader> br = new ArrayList <BufferedReader>();
    
    private ArrayList <String> messages = new ArrayList <String>();
    
    public void run() {
        for(BufferedReader reader : br){
          new Thread(new Runnable(){
            public void run(){
              messages.add(br.readLine());
            }
          }).start();
        }
        while (true) {
            sleep(10000);
            for (int i=0; i<messages.size(); i++) {
                System.out.println("Message " + (i+1) + ": " + messages.get(i));
            }
        }
    }
}
```


----------



## vR34kSH0w (27. Nov 2011)

schonmal danke für die antworten...

okay die ausgabe von oben wäre:
1.
1.
1.
1.
-- -- irgendwann wird dann mal die nachricht vom client gesendet -- --
1.
2.
Message 1: [etc.]

also nicht so wirklich das was man erwarten würde 

mit dem lösungsansatz hab ich jetzt mal gearbeitet hat aber leider auch nicht funktioniert
vielleicht überseh ich ja auch was in den anderen programmteilen
hier mal der komplette quelltext...

Server

```
import java.io.*;
import java.net.*;

public class ServerTest {
	MessageThreadTest mt = new MessageThreadTest();
	ServerSocket server;
	Socket client;
	
	public ServerTest(int port) throws IOException{
		mt.start();
		
		server = new ServerSocket(port);
		
		while(true) {
			client = server.accept();
			
			mt.br.add(new BufferedReader(new InputStreamReader(client.getInputStream())));
		}
	}
	
	public static void main(String[] args) throws Exception{
		new ServerTest(1234);
	}
}
```

Client

```
import java.io.*;
import java.net.*;

public class ClientTest {
	int port;
	
	BufferedWriter bw;
	Socket socket;
	String ip;
	
	public ClientTest(String ip, int port) throws Exception {
		this.ip = ip;
		this.port = port;
		
		socket = new Socket(ip, port);
		
		bw = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
		
		bw.write("Hello, I am " + socket.getLocalSocketAddress());
		bw.newLine();
		bw.flush();
	}
	
	public static void main(String[] args) throws Exception {
		new ClientTest("10.0.1.101", 1234);
	}
}
```

der rest ist ja bekannt...


----------



## Michael... (28. Nov 2011)

wenn das mal ein Chat werden soll, dann müssen die InputStreams der verbundenen Sockets in einem jeweils separaten Thread ausgelessen werden. Es ist ja üblicherweise nicht unbedingt so, dass sich Chatteilnehmer immer in der angemeldeten Reihenfolge äußern.
Prinzip ChatServer nimmt Verbingung einens Clients an und übergibt dessen InputStream zu auslesen an einen neuen Thread. Hier kann mit z.B. readLine() blockierend auf eine Nachricht des Clients gewartet werden ohne andere Prozesse zustören. Wird eine Nachricht empfangen und per readLine() eingelesen wird dies dem ChatServer mitgeteilt und der muss dann bestimmen was damit gemacht wird, z.B. an alle Chat Teilnehmer versenden.


----------



## vR34kSH0w (28. Nov 2011)

okay kann durchaus etwas dauern bis ich dazu komme das umzuschreiben werd ich aber machen...
folgendes is wahrscheinlich ein denkfehler aber wenn doch der chatserver eine instanz des threads aufruft wie soll dieser dem chatserver dann mitteilen dass eine nachricht vorliegt. der thread hat doch eigentlich keinen zugriff auf methoden des servers !!??


----------



## Michael... (28. Nov 2011)

die Threads müssen natürlich auf die Methoden des Servers zugreifen können.
Entweder die Threads sind innere Klassen des Servers oder sie bekommen eine Referenz auf diesen übergeben.


----------



## vR34kSH0w (30. Nov 2011)

okay auf die weise hats super geklappt =)
danke für eure hilfe.

Sollte jemand ähnliche Probleme haben, hier der Quelltext:

Server

```
package tests.Server;

import java.io.*;
import java.net.*;
import java.util.*;

public class MessageServer
{
	ArrayList <String[]> messages = new ArrayList <String[]>();
	ClientHandlingThread handlingThread;
	ServerSocket server;
	Socket client;
	
	public MessageServer(int port) throws IOException
	{
		server = new ServerSocket(port);

		while(true)
		{
			client = server.accept();
			
			handlingThread = new ClientHandlingThread(
					new BufferedReader(new InputStreamReader(client.getInputStream())),
					this);
			handlingThread.start();
		}
	}
	
	public void addMessage(String userName, String message)
	{
		messages.add(new String[] {userName, message});
		
		System.out.println(userName + ": " + message);
	}
	
	public static void main(String[] args) throws IOException
	{
		new MessageServer(11112);
	}
}
```

Client

```
package tests.Server;

import java.io.*;
import java.net.*;

public class Client
{
	BufferedWriter writer;
	Socket client;
	String userName;
	
	public Client(int port, String ip, String userName) throws IOException, UnknownHostException
	{
		client = new Socket(ip, port);
		
		this.userName = userName;
		
		writer = new BufferedWriter(new OutputStreamWriter(client.getOutputStream()));
		
		sendMessage(userName);
		sendMessage("Hi, Server!");
	}
	
	public void sendMessage(String message) throws IOException
	{
		writer.write(message);
		writer.newLine();
		writer.flush();
	}
	
	public static void main(String[] args) throws Exception
	{
		new Client(11112, "10.0.1.101", "user1");
	}
}
```

Client Handling Thread

```
package tests.Server;

import java.io.*;

public class ClientHandlingThread extends Thread
{
	BufferedReader reader;
	MessageServer server;
	String userName;
	
	public ClientHandlingThread(BufferedReader reader, MessageServer server)
	{
		this.reader = reader;
		this.server = server;
	}
	
	public void run()
	{
		try
		{
			userName = reader.readLine();
		}
		catch(IOException ioe)
		{
			System.err.println(ioe);
		}
		
		while(true)
		{
			try
			{
				String message = "";
				
				if(reader.ready())
				{
					message = reader.readLine();
					server.addMessage(userName, message);
				}
			}
			catch(IOException ioe)
			{
				System.err.println(ioe);
			}
		}
	}
}
```

Für Verbesserungsvorschläge bin ich natürlich noch offen. Morgen oder übermorgen markier ich das Thema dann als erledigt =)

Danke!


----------



## Empire Phoenix (1. Dez 2011)

Du hast noch einen Fehler drinnen, du must auf das messages array synchroniziert zugreifen, da du sonst multithreading fehler bekommst. (Arraylist ist nicht Threadsafe!)


eine Möglichkeit dieses zu tun wäre um jede stelle wo das array verwendet wird:
synchronized(messages){
   mach irgetwas wobei das array sich nicht verändern darf durch andere Threads, zb add, oder ne for schleife
}

Noch so als zusatz:

Per prinzip haste egal was du mit netzwerken amcsht folgende 3 Möglichkeiten:

Socketapi:
-> 2 Threads pro client benötigt, einer zum lesenund iener zum schreiben ( denn auch das out kann blockieren, und wenn da jemand mit nem 32k modem online geht in den chat soll der ja nicht alle anderen blockieren) Dies kann derzeit noch bei dir passieren, die Lösung wäre eine ausgehende Queue pro Client, wo alle noch zu sendenden Strings drinne sind, und dann nen Thread der poll macht und die Nachricht verschickt.(Stichwort Blockingqueue)


Selector/ChannelAPI (NIO):
-> Thread zum lesen und schreiben reicht, da die api jeweils benachrichtigt wenn neue Daten zum lesen verfügbat sind, bzw geschreiben werden kann, und zudem nicht alle bytes schreiben/lesen muss, sondern stattdessen zurücklifert wieviele sie verarbeitet hat, Nachteil alles muss auf ByteBuffer ebene laufen, und man muss manuell sicherstellen, dass man die gesammte Nachricht erhalten hat, bevor man die verarbietet (in diesem Fall wohl relativ leicht lösbar von wegen Zeilenumbruch am ende jeder Nachricht)

DatagramSocket
-> UDP basiert arbeiten, hierbei muss 1 Thread zum lesen benuzt werden, geschreiben werden können belibige Datenmengen ohne verzögerung, jedoch ohne garantie dass selbige ankommen, hier muss man also wenn man Nachrichten zuverlässig übermitteln will sicherstellen, dass diese ankommen. Daher für einen Chat eher ungeeignet.


----------



## vR34kSH0w (1. Dez 2011)

wie meinst du das mit "add", die methode hab ich doch benutzt!?!?

gäbs denn andere datentypen als arraylist, die "threadsafe" sind??


----------



## vR34kSH0w (2. Dez 2011)

wenn ich jetzt alles richtig verstanden habe müsste folgendes funktionieren (hoffe ich)


```
package tests.Server;

import java.io.*;
import java.net.*;
import java.util.*;

public class MessageServer
{
	ArrayList <String[]> messages = new ArrayList <String[]>();
	ClientHandlingThread handlingThread;
	List <String[]> syncMessages = Collections.synchronizedList(messages);
	ServerSocket server;
	Socket client;
	
	public MessageServer(int port) throws IOException
	{
		server = new ServerSocket(port);

		while(true)
		{
			client = server.accept();
			
			handlingThread = new ClientHandlingThread(
					new BufferedReader(new InputStreamReader(client.getInputStream())),
					this);
			handlingThread.start();
		}
	}
	
	public void addMessage(String userName, String message)
	{
		syncMessages.add(new String[] {userName, message});
		
		System.out.println(userName + ": " + message);
	}
	
	public static void main(String[] args) throws IOException
	{
		new MessageServer(11112);
	}
}
```


----------

