# Nio connect von öffentlicher Ip



## DamienX (14. Sep 2009)

Hi,

jemanden ein bug, häufig gemachter Fehler etc. bekannt welcher verursacht dass man auf einen nio ServerSocketChannel via
öffentlich routbarer ip nicht connecten kann?

Ich sitze hier nun 2 Stunden und versteh die Welt nicht mehr.

Es liegt NICHT an der Firewall und nicht am forewarding! Zum connect habe ich testweise einige Programme benutzt (TCP Tester, eigene Clients) und das Ergebnis ist immer das gleiche:

Serverconnect mit hostadresse "localhost" oder "127.0.0.1" haben 1a funktioniert. Wenn ich den Umweg über eine
öffentliche ip gehe oder eine dyn dns Adresse benutze schlägt die Verbindung fehl.

Java bedankt sich mit ner "java.net.ConnectException". TCP Test Tool 2.3 mit 
"TCP connection failed verify remote address/port".

Bin mit meinem Latein am Ende.

Hier der Code (Einfachheitshalber auf eine Klasse zusammengefasst! - Entschuldigt die unschöne Form):


```
package com.mni.utils.test.experimental;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.nio.channels.spi.SelectorProvider;
import java.nio.charset.Charset;
import java.nio.charset.CharsetDecoder;
import java.nio.charset.CharsetEncoder;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;

/**
 * Experimental nio tcp server
 * 
 * @author DamienX
 *
 */
public class NioServer implements Runnable{
	
	ServerSocketChannel serverChannel;
	InetSocketAddress isa;
	int port;
	String hostname;
	Selector selector;
	boolean shutdown = false;
	
	Set<SelectionKey> blackList = new HashSet<SelectionKey>();
	
	// allocate 10KB
	ByteBuffer buffer = ByteBuffer.allocate(1024 * 10);
	
	//encoder / decoder
//	Charset charset = Charset.forName("ISO-8859-1");
	Charset charset = Charset.forName("UTF-8");
	CharsetEncoder encoder = charset.newEncoder();
	CharsetDecoder decoder = charset.newDecoder();
	
	
	public NioServer(int port, String hostname) {
		this.hostname = hostname;
		this.port = port;
		System.out.println("Building Sockeraddress...");
		isa = new InetSocketAddress(hostname, port);
		System.out.println("res: " + isa.isUnresolved());
		System.out.println("Done!");
		
		init();
	}
	
	/**
	 * Inits selector and sChannel
	 */
	private void init() {
		try {
			System.out.println("InitSelector...");
			selector = SelectorProvider.provider().openSelector();
			serverChannel = ServerSocketChannel.open();
			serverChannel.socket().bind(isa);
			serverChannel.configureBlocking(false);
			serverChannel.register(selector, SelectionKey.OP_ACCEPT);
			System.out.println("Done!");
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

	@Override
	public void run() {
		while(!shutdown){
			
			try {
				int selected = this.selector.select(5000);
				
				System.out.println("Selected: " + selected);
				int registered = this.selector.keys().size();
				
				if(selected == 0 && registered != 0){
					Iterator<SelectionKey> it = this.selector.keys().iterator();
					
					while(it.hasNext()){
						SelectionKey selKey = it.next();
						if(selKey == serverChannel.keyFor(selector)) continue;
						SocketChannel chan = (SocketChannel) selKey.channel();
						long idle = ((ConnectionInfo) selKey.attachment()).getIdleTime()/1000;
						
						System.out.println("Found pending socket: " + 
								chan.socket().getInetAddress().toString() + " Idle time: " + ((ConnectionInfo) selKey.attachment()).getIdleTime()/1000);
					
						if(idle > 60){
							selKey.cancel();
							chan.close();
							System.out.println("Client canceled -> Idle timeout!");
						}
					}
				}
				
				Iterator<SelectionKey> keys = selector.selectedKeys().iterator();
				
				if(!keys.hasNext()) System.err.println("Server is pending!");
				
				while(keys.hasNext()){
					SelectionKey key = (SelectionKey) keys.next();
					keys.remove();
					
					if(!key.isValid()){
						System.out.println("Found invalid key... ignore!");
						continue;
					}
					
					System.out.println(key.isConnectable() +"!!!");
					
					if(key.isAcceptable()){
						System.out.println("Found acceptable key... try to accept!");
						accept(key);
					}
					
					if(key.isReadable() || key.isWritable()){
						ConnectionInfo info = (ConnectionInfo) key.attachment();
						info.reset();
					}
					
					if(key.isReadable()){
						System.out.println("Found readable key... try to read!");
						SocketChannel client = (SocketChannel) key.channel();
						
						int bytes = Integer.MIN_VALUE;
						
						while(bytes != 0 && bytes != -1){
							bytes = client.read(buffer);
							System.out.println(bytes + " bytes received!");
						}						
						
						
						
						if(bytes == -1){
							key.cancel();
							client.close();
							System.out.println("Client canceled!");
							continue;
						}
						
						buffer.flip();
						CharBuffer requestBuffer = decoder.decode(buffer);
						String request = requestBuffer.toString();
						
						System.out.println("Client " + client.socket().getInetAddress().toString() +
								": " + request);
						buffer.clear();
						
						key.interestOps(SelectionKey.OP_WRITE);
					}
					
					if(key.isWritable()){
						if(blackList.contains(key)) continue;
						
						System.out.println("Found writable key... try to write!");
						buffer.clear();
						
						SocketChannel client = (SocketChannel) key.channel();
						buffer.put(encoder.encode(CharBuffer.wrap("Testrepeat!\n")));
						buffer.flip();
						
						int bytes = 0;
						int bytesToWrite = buffer.remaining();
						int zeroByteWrites = 0;
						
						System.out.println("We have " + bytesToWrite + " bytes to write!");
						
						while(buffer.hasRemaining()){
							bytes = client.write(buffer);
							System.out.println(bytes + " bytes written!");
							
							try {
								Thread.sleep(500);
							} catch (InterruptedException e) {
								e.printStackTrace();
							}
							
							//ok we had a zero write turn... could happen
							if(bytes == 0){
								zeroByteWrites++;
								System.out.println("Zero write no: " + zeroByteWrites);
								
								//ok time to worry... over 50 zero writes and the buffer is not completly
								//written.. so let an observer check the connection
								if(zeroByteWrites > 50) {
									System.out.println("Check conn via observer");
									ByteBuffer bufferCopy = ByteBuffer.allocate(buffer.capacity());
									bufferCopy.put(buffer);
									
									// add it to the blacklist so the selector can not select to write anymore
									blackList.add(key);
									new Thread(new ConnectionObserver(key, bufferCopy)).start();
									break;
								}
							}
						}
						
						buffer.clear();						
						key.interestOps(SelectionKey.OP_READ);
					}					
				}				
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
	}
	
	private void accept(SelectionKey key) {
		try {
			SocketChannel client = serverChannel.accept();
			client.configureBlocking(false);
			client.register(selector, SelectionKey.OP_READ, new ConnectionInfo());
			System.out.println("Accepted on OP_READ: " 
					+ client.socket().getInetAddress().toString());
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
	
	public void shutdown(){
		shutdown = true;
	}
	
	
	
	//Utils classes -----------------------------------------------------------------------------------
	private class ConnectionInfo {
		private long timestamp;
		
		public ConnectionInfo() {
			timestamp = System.currentTimeMillis();
		}
		
		public void reset(){
			timestamp = System.currentTimeMillis();
		}

		public long getTimestamp() {
			return timestamp;
		}
		
		public long getIdleTime() {
			return System.currentTimeMillis() - timestamp;
		}
	}
	

	private class ConnectionObserver implements Runnable{
		
		long timeout;
		long idletime;	
		SelectionKey key;
		ByteBuffer bufferRemaining;
		boolean end = false;
		
		public ConnectionObserver(SelectionKey key, ByteBuffer bufferRemaining) {
			this(key, bufferRemaining, 2000L);
		}
		
		public ConnectionObserver(SelectionKey key, ByteBuffer bufferRemaining, long timeout){
			this(key, bufferRemaining, timeout, 5);
		}
		
		public ConnectionObserver(SelectionKey key, ByteBuffer bufferRemaining,  long timeout, long idletime){
			this.key = key;
			this.bufferRemaining = bufferRemaining;
			this.timeout = timeout;
			this.idletime = idletime;
		}
		
		@Override
		public void run() {
			SocketChannel channel = (SocketChannel) key.channel();
			long time = System.currentTimeMillis();
			
			while(!end){
				try {
					System.out.println((System.currentTimeMillis() - time) + " msecs passed!");
					
					//check if we are at the timeout
					if((System.currentTimeMillis() - time) > timeout){
						//if so... we shut the connection
						key.cancel();
						channel.close();
						System.err.println("Client canceled by observer: Timeout!");
						terminate();
					} else {
						//we try to write the remaining buffer again
						System.err.println("Try to write...");
						int tmpBytes = channel.write(bufferRemaining);
						Thread.sleep(idletime);	
						
						//if we've done all the work -> go back to reading ops and terminate the observer
						if(!bufferRemaining.hasRemaining()){
							key.interestOps(SelectionKey.OP_READ);
							key.channel().register(key.selector(), SelectionKey.OP_READ);
							terminate();
						}
					}								
				} catch (IOException e1) {
					e1.printStackTrace();
				} catch (InterruptedException e) {e.printStackTrace();}
			}
		}
		
		private void terminate() {
			end = true;
		}
	}

	

	public static void main(String[] args) {
		NioServer server = new NioServer(8888, "localhost");
		new Thread(server).start();
		
	}
}
```

Bin für Anregungen dankbar.

Mfg Alex


----------



## tuxedo (14. Sep 2009)

In deinem Test bindest du den SocketServer an den Hostnamen "localhost". Dann hört der Server auch nur auf localhost/127.0.0.1 ... Du solltest die IP des Netzwerkinterfaces nehmen das Zugang zum Internet bzw. dem Router hat. Dort (Router) sollte dann natürlich auch PortForwarding entsprechend eingerichtet sein. Aber das scheinst du ja schon zu wissen/kennen.

- Alex


----------



## DamienX (14. Sep 2009)

Genau so klappt es! Hätte man drauf kommen können.
Liege ich dann mit der Vermutung richtig dass es in einer Umgebung ohne NAT/PAT 
sprich lokales Netzwerk funktioniert hätte?

Schon mal vielen dank Alex.
Mfg Alex =))


----------



## tuxedo (15. Sep 2009)

Nein. Wenn du beim erstellen des Serversockets (egal ob NIO oder IO) "localhost" oder 127.0.0.1 angibst, dann lauscht der Server ausschließlich auf localhost und kann somit von niemand anderem außer localhost Verbindungen entgegen nehmen.

Wenn du auf allen Interfaces (Also localhost + sämtliche Netzwerkkarten) lauschen willst, dann solltest du "0.0.0.0" angeben. 
Andernfalls eben ein spezielles Interface mittels Hostnamen oder IP angeben. In allen Fällen muss dieser Host oder IP auf einem lokalen Netzwerk-Device existieren.
Eine IP welche keinem lokalen Netzwerk-Device zugeordnet ist kann man nicht angeben. Ebenso kann man keinen Hostnamen angeben der zu einer IP auflöst die nicht an einem lokalen Netzwerkdevice existiert.

- Alex


----------



## DamienX (15. Sep 2009)

Danke für die Erklärung... is sollte meine Unterlagen nochmal rauskramen =(

Cheers


----------

