# Datenaustausch trotz NIO nur halb so schnell



## Kr0e (2. Jan 2009)

Hallo,
ich beschäftige mich schon eine ganze Weile inzwischen mit NIO. ICh bin dabei eine Netzwerkbibliothek zu schreiben.
Sprich Klassen, die die Kommunikation via NIO vereinfachen sollen. Ich bin nun halbwegs fertig, sprich ich habe eine funktionierende Version.

Mein Problem:

Wenn ich z.B. random Bytes (Also einfach ein 50 mb ByteBuffer z. B.) über mein Netzwerk an einen anderen Rechner schicke (100 mbit Netzwerk), dann erreiche ich leider nur 40 % der Netzbandbreite. Diese Zahl habe ich dem "TaskManager" entnommen. Wenn ich aber nun z.b. eine Datei in einen freigegebenen Ordner kopiere, so ist die Auslastung rund 85-95 % ... Nun stelle ich mir die Frage, wo der Fehler liegen kann in meiner Klassen Sammlung.

Ich könnte jetz natürlich einfach die rund 20 Klassen hier posten, aber ich denke, dass niemand Lust hat, sich in soviele Klassen einzuarbeiten, vorallem, weil ich noch keine Dokumentation habe. Deshalb beschreibe ich den Grundaufbau meines Programmes, damit (falls ich z.B. einen typischen "Anfängerfehler" gemacht habe) ihr mir vlt. helfen könnt.

Ich habe eine Mainschleife, in der select() aufgerufen wird und die jeweiligen Ereignisse ausgewertet werden. Wenn von einem channel gelesen werden kann, so geschieht dies in einem eigenen Thread. Während dessen, wird dieser Channel von OP_READ gestrichen, damit nicht parallel gelesen wird. Nachdem gelesen wurde, wird eine "onRead()" Methode innerhalb des eigenen Lesethreads aufgerufen.

Alle Thread werden in einem Threadpool ausgeführt... 
Ich verwende zur Zeit einen FixedPoolThread

Geschrieben wird in der Mainschleife, da ich mir hab sagen lassen, dass das schreiben so schnell geht, dass es keinen Geschwindigkeits (bzw. so wenig, dass es sich nicht lohnt) -vorteil bringt.

So, ich habe zur Zeit folgendes Testszenario:

1x Serverprogramm, dass auf Rechner A läuft...
1x Clientprogramm, dass auf Rechner B läuft...

Nun verschicke ich 8096 byte Blöcke. Ein Block wird erst dann gesendet, wenn die send-Methode wieder zurückkehrt, sprich es wird kein riesen Puffer im Hintergrund aufgebaut oder so, der das Programm durch Overhead verlangsamen könnte...

Gelesen wird ebenfalls in 8096 byte Blöcken... Jedes Mal bei Read in einem eigenen Thread, wird 1. gelesen, 2. eine Variable (long counter) um den Wert erhöht, der empfangen wurde... Damit man irgendwo ein Ende hat...
Nach diesen beiden Dingen,  wird der Channel wieder auf OP_READ gesetzt und der Thread wird verlassen. Da natürlich direkt wieder Daten zum empfangen da sind, wird direkt wieder in einem Thread gelesen.

Ich habe die Vermutung, dass hier evt. zuviel Rechenleistung bei drauf geht. Jedoch spricht folgendes dagegen:
Die CPU-Auslastung ist ca. bei 40-50 % Wenn also die Daten bedingt durch Overhead zu langsam sind, müsste doch die CPU 100% haben oder nicht ? 

Desweiteren benutze ich an vielen Stellen in der Mainschleife eine HashMap ... und frage oft Werte darin ab... Aber auch falls diese Listen das Programm verlangsamen würde... auch dann müsste doch die CPU hoch schnellen oder ?




Wenn ihr bis hierhin keinen Fehler erkennt, bin ich ratlos und werde nochmal das komplette Konzept überdenken !
Wenn ihr dennoch bestimmte Codepassagen sehen wollt, so sagt mir welche und ich poste sie ...


Ich bedanke mich schonmal im Vorraus, dass ihr euch meinem Problem annehmt...

Gruß Chris

PS: Frohes neues Jahr!!  :lol:


----------



## voidee (3. Jan 2009)

Ich denke nicht, dass es an einem Programmfehler liegt. 

Wie groß ist denn die "Netzbelastung", wenn du die Datei mit einem Explorer kopierst. Da bekommst du doch auch keine 100% hin (hab ich wenigstens noch nie erlebt). Über wieviele andere Rechner, Switches werden deine Packete versandt? Vielleicht liegt hier das "Problem".


----------



## voidee (3. Jan 2009)

Nachtrag: wenn die Daten zum Beispiel übers TCP/IP Protokoll versandt werden ist vermutlich - hab auf die schnelle nichts zur Nutzlast eines TCP-Packets gefunden - deine gesandte Größe 8092 Bytes zu groß für ein TCP-Packet. Versendest du tatsächlich Bytes oder einen String. Java String benötigen je Zeichen 2 Bytes.


----------



## Kr0e (3. Jan 2009)

Hi,
also wenn ich ne Datei via Windows kopiere sinds ca. 80 - 86 % manchmal nur 76 % aber nie 40 %.
Die DAten gehen über einen Switch. Ich versende Byteblöcke, Strings brauche ich nicht für die Übertragung...

Zu dem Datenpaket:

Ich habe auch eine Zahl mit 1508 bytes im Kopf (MTU - MaximumTransferUnit), die maximal auf einmal gesendet werden können. So, aber wenn ich 1508 bytes oder weniger wähle, geht die Geschwindigkeit leider noch mehr in den Keller . Habe dazu folgenden Link gefunden :

www.onlamp.com/pub/a/onlamp/2005/11/17/tcp_tuning.html

Da steht im Prinzip drin, dass die ideale Sendegröße wohl abhängig von der Verbindung ist... Sprich im Inet mit einer RTT von 50 ms nimmt man laut diesem Text wohl besser größere Puffer.

Aber meine Vermutung ist, da ich nun andere NIO-Server getestet habe udn doch das selbe geschah, dass nio nicht nur für netzwerke > 1000 Leute ausgelegt, sondern bei wenigen Usern (Bzw. vlt. sogar nur einem einzelnen) langsamer ist.

Ich stelle mir dafür einfach mal den Ablauf des Servers vor:

NIO- Variante:

- Block lesen - Channel wieder auf OP_READ setzen - select aufrufen - erneut lesen -

IO- Variante:

- Block lesen - Block lesen .....

Also, ich meine, wenn das lesen und schreiben bei kleineren Packeten derart schnell geht, dass die CPU in die Höhe schiesst, kann es ja nur daran liegen, dass iwas in einer SChleife extrem oft ausgeführt wird. Wenn aber z.b. send 10 ms oder länger dauert bzw. read... dann wird diese Schleife "abgebremst" und die Belastung geht, durch größere Packete, auf die Netzwerkkarte... Wo sie ja ansich auch hin soll!



Ich habe außerdem noch ein Problem mit folgenden Sachen:
Bei den Sockets kann man SendBufferSize und RecvBufferSize einstellen.
Wozu sind diese genau da ? ICh meine, ist das die Größe, die angibt, wieviel auf einmal
empfangen werden kann ?! Ich muss ehrlich gesagt sagen, dass mich bei den Javasockets
stört, dass man nie so genau weiß was nun im Hintergrund eigentlich abgeht.

Bei C++ ist alles etwas komplizierter, aber dafür wusste ich immer genau, wie viele Bytes wann und
wie schnell geschrieben werden ...

Danke übrigens für deine Hilfe !

Gruß Chros


----------



## Kr0e (4. Jan 2009)

Ok, ich schätze irgendwo ist ein Programmierfehler 
Oder aber das Lesen in einzelnen Threads ist die Bremse...
Ich habe jetzt mal einen extrem "schlanken" und vorallem
simplen Fileserver gemacht, der nichts weiter macht, als die DAten,
die er von einem Clienten bekommt, in eine Datei zu schreiben.
Nun bekomme ich ebenfalls spitzenwerte ... also von der Übertragungsrate her...

Server:


```
import java.io.File;
import java.io.FileOutputStream;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.ByteChannel;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.Iterator;

interface ClientAction
{
	public void onRead(ByteBuffer buffer, SelectionKey key);
	public void onLost();
}

class Client
{	
	private ByteBuffer byteBuffer = null;
	private SelectionKey key = null;
	private ClientAction tmp = null;
	
	public Client(SelectionKey key, ClientAction tmp) throws Exception
	{
		if(key == null || tmp == null)
			throw new IllegalArgumentException("key is null");
	
		byteBuffer = ByteBuffer.allocate(8192);
		
		this.tmp = tmp;
		
		ServerSocketChannel serverChannel = (ServerSocketChannel) key.channel();
		
		SocketChannel clientChannel = serverChannel.accept();
		
		clientChannel.configureBlocking(false);
		
		clientChannel.register(key.selector(), SelectionKey.OP_READ);
		
		this.key = clientChannel.register(key.selector(), SelectionKey.OP_READ);
		this.key.attach(this);
	}
	
	public void handleRead() throws Exception
	{
		int read = 0;
		
		try
		{
			read = ((ByteChannel)key.channel()).read(byteBuffer);
		}
		catch(Exception e)
		{
			e.printStackTrace();
			
			key.channel().close();
			key.cancel();
			
			tmp.onLost();
		}
		
		if(read == -1)
		{
			key.channel().close();
			key.cancel();
			
			tmp.onLost();
		}

		tmp.onRead(byteBuffer, key);
	}
}

public class AppStart implements ClientAction
{	
	private File file = new File("C:\\test.zip");

	private FileOutputStream fos = null;
	
	public void onRead(ByteBuffer buffer, SelectionKey key)
	{
		int pos = buffer.position();
		buffer.flip();
		
		//Message ?
		if(pos <= 16)
		{	
			String title = new String(buffer.array(), 0, pos);
			
			if(title.equals("READY"))
			{
				try
				{
					fos.close();
					System.out.println("Datei fertig übertragen !");
				}
				catch(Exception e)
				{
					e.printStackTrace();
				}
			}
			
			else if(title.equals("START"))
			{
				try
				{
					file.createNewFile();
					fos = new FileOutputStream(file);
					System.out.println("Datei wird übertragen !");
					
					//Bestätigung
					((SocketChannel)key.channel()).write(ByteBuffer.wrap(" ".getBytes()));
				}
				catch(Exception e)
				{
					e.printStackTrace();
				}
			}
		}
		
		//Keine Message, Daten !
		else
		{
			try
			{
				fos.write(buffer.array(), 0, pos);
			}
			catch(Exception e)
			{
				e.printStackTrace();
			}
		}
	
		buffer.clear();
	}
	
	public void onLost()
	{
		System.out.println("Client verloren");
	}
	
	public static void main(String[] args) throws Exception
	{	
		Selector selector = Selector.open();
		
		ServerSocketChannel ssc = ServerSocketChannel.open();
		ssc.configureBlocking(false);
		ssc.socket().bind(new InetSocketAddress(1234));
		
		ssc.register(selector, SelectionKey.OP_ACCEPT);
		
		System.out.println("Server offen");
		
		while(true)
		{
	        if(selector.select() == 0)
	        	continue;   
		
			Iterator<SelectionKey> selectedKeys = selector.selectedKeys().iterator();
			while(selectedKeys.hasNext()) 
	        {
	            SelectionKey key = selectedKeys.next();
	            selectedKeys.remove();
	            
	            if(!key.isValid()) 
	            	continue;
	            
	            if(key.isReadable())
	            	((Client)key.attachment()).handleRead();
	            
	            else if(key.isAcceptable())
	            {
	            	System.out.println("New Client");
	            	new Client(key, new AppStart());
	            }
	        }
		}
	}
}
```


Client

```
import java.io.File;
import java.io.FileInputStream;
import java.net.InetSocketAddress;
import java.net.Socket;


class FileClient implements Runnable
{
	private File file;
	private String host;
	private int port;
	
	public FileClient(String path, String host, int port)
	{
		this.host = host;
		this.port = port;
		file = new File(path);
	}
	
	public void run()
	{
		try
		{
			Socket c = new Socket();
			
			c.connect(new InetSocketAddress(host, port));
			
			FileInputStream fis = new FileInputStream(file);
			
			c.getOutputStream().write( "START".getBytes() );
			
			c.getInputStream().read();
			
			int read = 0;
			
			byte[] buffer = new byte[8192];
			
			while(read != -1)
			{
				read = fis.read(buffer);
				
				if(read != -1)
					c.getOutputStream().write(buffer, 0, read);
			}

			c.getOutputStream().write( "READY".getBytes() );
			
			c.close();
			fis.close();
		}
		catch(Exception e)
		{
			e.printStackTrace();
		}
	}
}

public class AppStart 
{
	public static void main(String[] args) throws Exception
	{
		new Thread(new FileClient("D:\\test.zip", "server-1", 1234)).start();
	}
}
```


So, ich habe noch eine Frage:
Ich synchronisiere relativ oft auf bestimmte Variablen und das in einer Schleife...
Aber im Normalfall muss kein Thread warten. Aber könnte es vlt sein, dass der reine
Eintritt in einen Synch-Block vlt derart viel Ressourcen benötigt ?

Und macht es eigentlich Sinn, einen NIO Clienten zu schreiben ?!


Gruß Chris


----------



## Mark.D (4. Jan 2009)

hast du schon mal benchmarks mit NIO-Libs wie xSocket  durchgeführt?

Grüße
mark


----------



## Kr0e (4. Jan 2009)

Ne, habe ich noch nicht. Benchmark mit xSockets ?, werde ich direkt mal aus probieren !

Gruß Chris


----------

