NIO Klient serwer w ciągłej pętli

NIO Klient serwer w ciągłej pętli
CA
  • Rejestracja:około 9 lat
  • Ostatnio:około rok
  • Postów:80
0

Witam
Walczyłem dzisiaj z nieblokującą komunikacją. Niestety nie udało mi się spełnić mojego założenia.

Uruchamiam serwer i klienta

  1. Klient wysyła wiadomość do serwera
  2. Serwer odbiera wiadomość i odpowiada klientowi
  3. Klient odbiera wiadomość i odpowiada serwerowi
  4. No i powrót do punktu 2.

Wszystko ma odbywać się w pętli bez zamykania kanału i ponownego łączenia.
Czy wiecie gdzie popełniłem błąd?

Kod serwera:

Kopiuj

import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.net.SocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.nio.charset.Charset;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Objects;
import java.util.Set;

public final class NioServerTest implements ChannelWriter {
	
	private int port = 8080;
	private ByteBuffer bb = ByteBuffer.allocate(1000);

	public static void main(String[] args) {
		NioServerTest server = new NioServerTest();
		server.startServer();
	}
	public void startServer() {
		
		try {
			Selector selector = Selector.open();

			ServerSocketChannel ssc =  ServerSocketChannel.open();
			ssc.configureBlocking(false);

			ServerSocket ss = ssc.socket();			
			ss.bind(new InetSocketAddress(port));

			ssc.register(selector, SelectionKey.OP_ACCEPT);

			while(true) {
				//System.out.println("waiting for client connection");
				if(selector.select() > 0) {
					performIO(selector);
				}
			}
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

	public void performIO(Selector s) {
		Iterator<SelectionKey> i = s.selectedKeys().iterator();

		while(i.hasNext()) {
			try {
				SelectionKey sk = i.next();
				if(sk.isAcceptable()) {
					//System.out.println("accept client connection");
					acceptConnection(sk, s);
				}else if (sk.isReadable()) {
					//System.out.println("read from client");
					readWriteClient(sk);
                                        sk.channel().register(s, SelectionKey.OP_READ);
				} else if (sk.isWritable()) {
                                    
                                }
			}catch (IOException e) {
				e.printStackTrace();
			}
			
			i.remove();
		}
	}
	public void acceptConnection(SelectionKey sk, Selector s) throws IOException{
		ServerSocketChannel server = (ServerSocketChannel) sk.channel();
		SocketChannel sChannel = server.accept();

		sChannel.configureBlocking(false);
		sChannel.register(s, SelectionKey.OP_READ);
	}
	public void readWriteClient(SelectionKey sk) throws IOException {		

		SocketChannel schannel = (SocketChannel) sk.channel();
		bb.flip();
		bb.clear();
		
		int count = schannel.read(bb);
		if (count > 0) {
			bb.flip();
			String input = Charset.forName("UTF-8").decode(bb).toString();
			System.out.println(input);
			
			bb.flip();
			bb.clear();
			bb.put(">>> Odpowiedź serwera".getBytes());
			bb.flip();
			String inputs = Charset.forName("UTF-8").decode(bb).toString();
			System.out.println(inputs);	
			bb.rewind();
			schannel.write(bb);
		schannel.register(sk.selector(), SelectionKey.OP_READ);
			//schannel.close();
		}
	}

        
        public void prl(Object o) {
            System.out.println(o.toString());
        }        
}

Kod klienta:

Kopiuj
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.SocketChannel;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.Iterator;

public final class NioClientTest implements ChannelWriter {
	private int port = 8080;
	private String hostName = "localhost";
	private ByteBuffer bb = ByteBuffer.allocate(1000);
	
	public static void main(String[] args) {
		NioClientTest client = new NioClientTest();
		client.getResponseFromServer("Wiadomość klienta");
	}
	
	//main client method
	public void getResponseFromServer(String request) {
		try {
			//non blocking client socket
			SocketChannel sc = SocketChannel.open();
			sc.configureBlocking(false);

			InetSocketAddress addr = new InetSocketAddress(hostName, port);		 
			sc.connect(addr);			 

			while (!sc.finishConnect()) {
				System.out.println("conneting to server");
			}

			//send request
			bb.flip();
			bb.clear();
			bb.put(request.getBytes());
			bb.flip();
			sc.write(bb);

			//process response
			Selector selector = Selector.open();
			sc.register(selector, SelectionKey.OP_READ);		 
			while(true) {
				if(selector.select() > 0) {
					if(processServerResponse(selector)) {
						//return;
					}
				}
			}
		}catch (IOException e) {
			e.printStackTrace();
		}
	}

	public boolean processServerResponse(Selector s) {		
		Iterator<SelectionKey> i = s.selectedKeys().iterator();
		while(i.hasNext()) {
			try {
				SelectionKey sk = i.next();
				if (sk.isReadable()) {
					SocketChannel schannel = (SocketChannel) sk.channel();
					bb.flip();
					bb.clear();

					int count = schannel.read(bb);
					if (count > 0) {
						bb.rewind();
						String response = 
						Charset.forName("UTF-8").decode(bb).toString();
						System.out.println("response: "+response);
						
						//schannel.close();
						return true;
					}
                                        
                                        schannel.register(s, SelectionKey.OP_READ);
				} else if (sk.isWritable()) {
                                    SocketChannel channel = (SocketChannel) sk.channel();
                                    channel.write(ByteBuffer.wrap("ce".getBytes()));
                                    sk.cancel();       
                                    
                                    channel.register(s, SelectionKey.OP_READ);
                                }
				i.remove();
			}catch (IOException e) {
				e.printStackTrace();
			}
		}
		return false;
	}
        
        public void prl(Object o) {
            System.out.println(o.toString());
        }
}

Pozdrawiam

damianem
  • Rejestracja:prawie 8 lat
  • Ostatnio:3 miesiące
  • Postów:205
1

No wygląda na to, że w kliencie po odebraniu odpowiedzi od serwera brakuje wysłania następnej wiadomości.
Dodatkowo nie powinno tam być tego return bo w ten sposób omijasz usuwanie z iteratora i.remove() co jest wymagane po przetworzeniu klucza selectora.

Mam też wrażenie, że zakładasz że za każdym razem przy przyjęciu eventu OP_READ będziesz w stanie przeczytać całą wiadomość - przy tak małych wiadomościach pewnie będzie to prawda ale powinno się zakładać najbardziej pesymistyczny scenariusz, że za każdym razem możesz dostać po 1 bajcie :) Łatwiej też by było mieć osobny bufor na operacje read i write zamiast akrobacji z flipami.

edytowany 1x, ostatnio: damianem
CA
  • Rejestracja:około 9 lat
  • Ostatnio:około rok
  • Postów:80
0

Dzięki kolego. Nie wiem jak mogłem nie zwrócić uwagi na to return. To przez niego był ten problem. Teraz jest ok.
Uprościłem, więc wrzucam działający kod. Może się komuś przyda.
Serwer:

Kopiuj

import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.nio.charset.Charset;
import java.util.Iterator;

public final class NioServerTest implements ChannelWriter {
	
    private int port = 8080;
    private ByteBuffer readBuffer = ByteBuffer.allocate(1000);

    public static void main(String[] args) {
        NioServerTest server = new NioServerTest();
        server.startServer();
    }
    
    public void startServer() {
        try {
            Selector selector = Selector.open();

            ServerSocketChannel serverSocketChannel =  ServerSocketChannel.open();
            serverSocketChannel.configureBlocking(false);

            ServerSocket ss = serverSocketChannel.socket();			
            ss.bind(new InetSocketAddress(port));

            serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);

            while(true) {
                if(selector.select() > 0) {
                    listener(selector);
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    
    int i = 0;

    public void listener(Selector s) {
        Iterator<SelectionKey> iterator = s.selectedKeys().iterator();

        while (iterator.hasNext()) {
            try {
                SelectionKey sk = iterator.next();
                
                if (sk.isAcceptable()) {
                    acceptConnection(sk, s);
                } else if (sk.isReadable()) {
                    readWriteClient(sk);
                }
            } catch (IOException e) {
                    e.printStackTrace();
            }

            iterator.remove();
        }
    }
    
    public void acceptConnection(SelectionKey sk, Selector s) throws IOException{
        ServerSocketChannel server = (ServerSocketChannel) sk.channel();
        SocketChannel sChannel = server.accept();

        sChannel.configureBlocking(false);
        sChannel.register(s, SelectionKey.OP_READ);
    }
    
    public void readWriteClient(SelectionKey sk) throws IOException {		
        SocketChannel schannel = (SocketChannel) sk.channel();
        this.readBuffer.clear();

        int count = schannel.read(readBuffer);
        
        if (count > 0) {
            this.readBuffer.flip();
            String input = Charset.forName("UTF-8").decode(this.readBuffer).toString();
            System.out.println(input);

            schannel.write(ByteBuffer.wrap("Odpowiedź serwera".getBytes()));

            //schannel.close();
        }
    }
        
    public void prl(Object o) {
        System.out.println(o.toString());
    }        
}

Klient:

Kopiuj
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.SocketChannel;
import java.nio.charset.Charset;
import java.util.Iterator;

public final class NioClientTest implements ChannelWriter {
    private int port = 8080;
    private String hostName = "localhost";

    private ByteBuffer readBuffer = ByteBuffer.allocate(1000);

    public static void main(String[] args) {
        NioClientTest client = new NioClientTest();
        client.getResponseFromServer();
    }

    public void getResponseFromServer() {
        try {
            SocketChannel channel = SocketChannel.open();
            channel.configureBlocking(false);

            InetSocketAddress addr = new InetSocketAddress(hostName, port);		 
            channel.connect(addr);			 

            while (!channel.finishConnect()) {
                System.out.println("Łączenie z serwerem");
            }

            channel.write(ByteBuffer.wrap("Welcome".getBytes()));

            Selector selector = Selector.open();
            channel.register(selector, SelectionKey.OP_READ);
            
            while(true) {
                if(selector.select() > 0) {
                    processServerResponse(selector);
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public boolean processServerResponse(Selector selector) {		
        Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
        
        while(iterator.hasNext()) {
            try {
                SelectionKey selectionKey = iterator.next();
                
                if (selectionKey.isReadable()) {
                    SocketChannel channel = (SocketChannel) selectionKey.channel();
                    this.readBuffer.clear();

                    int count = channel.read(this.readBuffer);
                    
                    if (count > 0) {
                        this.readBuffer.rewind();
                        
                        String response = Charset.forName("UTF-8").decode(this.readBuffer).toString();
                        System.out.println("odpowiedź: "+response);

                        channel.write(ByteBuffer.wrap("nowa wiadomość od klienta".getBytes()));
                        //schannel.close();
                    }
                }

                iterator.remove();
            } catch (IOException e) {
                    e.printStackTrace();
            }
        }
        
        return false;
    }
        
    public void prl(Object o) {
        System.out.println(o.toString());
    }
}

CA
  • Rejestracja:około 9 lat
  • Ostatnio:około rok
  • Postów:80
0

Mam jeszcze jedno pytanie. Załóżmy, że klient po wykonaniu 10 pętli przesyłu danych kończy połączenie. Jak serwer może sprawdzić czy kanał został zamknięty? No i oczywiście jak usunąć go z selektora?

damianem
  • Rejestracja:prawie 8 lat
  • Ostatnio:3 miesiące
  • Postów:205
0

Jeśli klient zamknął połączenie i sieć była na tyle łaskawa, że taka informacja dotarła do serwera (FIN-ACK / RST), to powinieneś na nim odebrać event OP_READ i próba czytania z takiego kanału zwróci -1.

Problem w tym, że jeśli klient "umrze" nagle np. przez wyciągnięcie kabla, to druga strona nie ma jak się o tym dowiedzieć (z perspektywy serwera połączenie będzie nadal uważane za "zdrowe" tylko nieaktywne). Dlatego frameworki sieciowe mają mechanizmy, które śledzą ostatnią aktywność na danym kluczu i usuwają te, które przekroczyły określony timeout. Żeby takie sprawdzenie w wątku selectora zrobić, trzeba zmienić użycie metody .select() na wariant z małym timeoutem, bo przy nieaktwynych połączeniach nie dostajemy eventów co powoduje czekanie w .select() w nieskończoność. Można też mieć osobny wątek który będzie sprawdzał timeouty, ale to już wymaga synchronizacji (kojarzę że do którejś wersji Javy selectory miały problemy z deadlockami jeśli były używane z wielu wątków jednocześnie - selector miał w sobie wewnętrzne mechanizmy synchronizujące).

Inną opcją jest nałożenie protokołu wyższego poziomu na TCP i wysyłanie co jakiś czas wiadomości "pingujących".

Jeśli chodzi o usuwanie kanałów z selectora to z tego co pamiętam wystarczy key.cancel().

CA
  • Rejestracja:około 9 lat
  • Ostatnio:około rok
  • Postów:80
0

Ok. Najprościej zrobiłem przechwycenie błędu podczas odczytu i uznałem to za błąd z połączeniem, następnie zamknąłem kanał.
Niestety podczas zabawy z wieloma klientami wyszedł jeszcze jeden problem. Chodzi o to, że odczytuje informacje tylko od jednego klienta. Tego który połączył się pierwszy. Gdy go rozłączę dopiero pojawiają się komunikaty od następnego. Odpowiada każdemu z nich tą samą wiadomością. Bufor do odczytu inicjuję(tworzę) tuż przed samym odczytem z kanału.

CA
  • Rejestracja:około 9 lat
  • Ostatnio:około rok
  • Postów:80
0

Może pokaże jak teraz wygląda serwer

Kopiuj

import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.nio.charset.Charset;
import java.util.Iterator;
import java.util.logging.Level;
import java.util.logging.Logger;

public final class NioServerTest implements ChannelWriter {
	
    private int port = 8080;


    public static void main(String[] args) {
        NioServerTest server = new NioServerTest();
        server.startServer();
    }
    
    public void startServer() {
        try {
            Selector selector = Selector.open();

            ServerSocketChannel serverSocketChannel =  ServerSocketChannel.open();
            serverSocketChannel.configureBlocking(false);

            ServerSocket ss = serverSocketChannel.socket();			
            ss.bind(new InetSocketAddress(port));

            serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);

            while(true) {
                if(selector.select() > 0) {
                    listener(selector);
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    
    int i = 0;

    public void listener(Selector s) {
        Iterator<SelectionKey> iterator = s.selectedKeys().iterator();

        while (iterator.hasNext()) {
            SelectionKey sk = iterator.next();
            
            if (sk.isAcceptable()) {
                acceptConnection(sk, s);
            } else if (sk.isReadable()) {
                read(sk);
            }

            iterator.remove();
        }
    }
    
    public void acceptConnection(SelectionKey sk, Selector s) {
        ServerSocketChannel server = (ServerSocketChannel) sk.channel();
        SocketChannel channel = null;
        
        try {
            channel = server.accept();
            channel.configureBlocking(false);
            channel.register(s, SelectionKey.OP_READ);            
        } catch (IOException ex) {
            //Logger.getLogger(NioServerTest.class.getName()).log(Level.SEVERE, null, ex);
            err("Błąd akceptacji połączenia");
        }
    }
    
    public boolean read(SelectionKey sk) {		
        SocketChannel channel = (SocketChannel) sk.channel();
        ByteBuffer readBuffer = ByteBuffer.allocate(1000);
        readBuffer.clear();

        int count = 0;
        
        try {
            count = channel.read(readBuffer);
        } catch (IOException ex) {
            //Logger.getLogger(NioServerTest.class.getName()).log(Level.SEVERE, null, ex);
            err("Błąd odczytu. Zamknięcie gniazda.");
            
            try {channel.close();} catch (IOException ex1) {err("Błąd zamknięcia gniazda.");}
            
            return false;
        }

        if (count > 0) {
            //try {
                readBuffer.flip();
                String input = Charset.forName("UTF-8").decode(readBuffer).toString();
              //  System.out.println(input);
                
                //schannel.write(ByteBuffer.wrap("Odpowiedź serwera".getBytes()));
                this.analyse(channel, input);
                
                
                //schannel.close();
          //  } catch (IOException ex) {
                //Logger.getLogger(NioServerTest.class.getName()).log(Level.SEVERE, null, ex);
            //    err("Błąd zapisu. Zamknięcie gniazda.");
                
              //  try {schannel.close();} catch (IOException ex1) {err("Błąd zamknięcia gniazda.");}
                
               // return false;
            //}
        }
        
        return true;
    }
    
    public void analyse(SocketChannel channel, String input) {
        prl(input);
        this.send(channel, "Wiadomość od serwera");
    }
    
    public boolean send(SocketChannel channel, String message) {
        try {
            channel.write(ByteBuffer.wrap(message.getBytes()));
        } catch (IOException ex) {
            Logger.getLogger(NioServerTest.class.getName()).log(Level.SEVERE, null, ex);
            err("Błąd wysyłania");
            
            return false;
        }
        
        return true;
    }
        
    public void prl(Object o) {
        System.out.println(o.toString());
    }       
    
    public void err(String text) {   
        System.out.println("\033[0;31m >>> "+text); 
    }    
}
CA
  • Rejestracja:około 9 lat
  • Ostatnio:około rok
  • Postów:80
0

OK poprawiłem.
Napisałem klienta i serwer do przesyłania danych w postaci obiektów

Serwer:

Kopiuj

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.nio.charset.Charset;
import java.util.Iterator;
import java.util.logging.Level;
import java.util.logging.Logger;

public final class NioServerTest{
	
    public int port = 8080;
    public int client = 0;
    public ByteBuffer readBuffer = null;


    public static void main(String[] args) {
        NioServerTest server = new NioServerTest();
        server.startServer();
    }
    
    public void startServer() {
        try {
            readBuffer = ByteBuffer.allocate(1000);    
            
            Selector selector = Selector.open();

            ServerSocketChannel serverSocketChannel =  ServerSocketChannel.open();
            serverSocketChannel.configureBlocking(false);

            ServerSocket serverSocket = serverSocketChannel.socket();			
            serverSocket.bind(new InetSocketAddress(port));

            serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);

            while(true) {
                if(selector.select() > 0) {
                    Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();

                    while (iterator.hasNext()) {
                        SelectionKey selKey = iterator.next();

                        if (selKey.isAcceptable()) {
                            this.acceptConnection(selKey, selector);
                        } else if (selKey.isReadable()) {                
                            this.read(selKey);
                        }

                        iterator.remove();
                    }
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    
    public void acceptConnection(SelectionKey selKey, Selector selector) {
        ServerSocketChannel server = (ServerSocketChannel) selKey.channel();
        SocketChannel channel = null;
        
        try {
            this.client++;
            
            channel = server.accept();
            channel.configureBlocking(false);
            //add Client nr
            SelectionKey sks = channel.register(selector, SelectionKey.OP_READ);     
            sks.attach(this.client);
            
        } catch (IOException ex) {
            //Logger.getLogger(NioServerTest.class.getName()).log(Level.SEVERE, null, ex);
            err("Błąd akceptacji połączenia");
        }
    }
    
    public boolean read(SelectionKey selKey) {		
        SocketChannel channel = (SocketChannel) selKey.channel();
        int count = 0;
        
        this.readBuffer.clear();
        
        try {
            count = channel.read(this.readBuffer);
        } catch (IOException ex) {
            //Logger.getLogger(NioServerTest.class.getName()).log(Level.SEVERE, null, ex);
            err("Błąd odczytu. Zamknięcie gniazda.");
            try {channel.close();} catch (IOException ex1) {err("Błąd zamknięcia gniazda.");}
            
            return false;
        }

        if (count > 0) {
            this.readBuffer.flip();
            String input = Charset.forName("UTF-8").decode(this.readBuffer).toString();

            this.analyse(channel, this.readBuffer, selKey.attachment().toString());
        }
        
        return true;
    }
    
    public boolean send(SocketChannel channel, ByteBuffer buffer) {
        try {
            channel.write(buffer);
        } catch (IOException ex) {
            Logger.getLogger(NioServerTest.class.getName()).log(Level.SEVERE, null, ex);
            err("Błąd wysyłania");
            
            return false;
        }
        
        return true;
    }  
    
    public void analyse(SocketChannel channel, ByteBuffer buffer, String cliNr) { 
        Transport in = (Transport) this.buffToObj(buffer);
        
        prl(in.nazwa);
        
        Transport tr = new Transport("Serwer do klienta ["+cliNr+"]",1);
        this.send(channel, this.objToBBufer(tr));
    }     
    
    public Object buffToObj(ByteBuffer buffer) {
        byte[] bytes = buffer.array();
        ByteArrayInputStream in = new ByteArrayInputStream(bytes);
        ObjectInputStream is = null;
        Object obj = null;
    
        try {
            is = new ObjectInputStream(in);
            obj = is.readObject();
        } catch (IOException ex) {
            Logger.getLogger(NioServerTest.class.getName()).log(Level.SEVERE, null, ex);
        } catch (ClassNotFoundException ex) {
            Logger.getLogger(NioServerTest.class.getName()).log(Level.SEVERE, null, ex);
        }
        
        return obj;
    }
    
    public ByteBuffer objToBBufer(Object obj) {
        ByteArrayOutputStream bos = new ByteArrayOutputStream();
        ObjectOutputStream out = null;
        ByteBuffer buffer = null;
        
        try {
            out = new ObjectOutputStream(bos);   
            out.writeObject(obj);
            out.flush();
            byte[] yourBytes = bos.toByteArray();
            buffer = ByteBuffer.wrap(yourBytes);
        } catch (IOException ex) {
            Logger.getLogger(NioServerTest.class.getName()).log(Level.SEVERE, null, ex);
        } finally {
            try {
                bos.close();
            } catch (IOException ex) {
            // ignore close exception
            }
        }
        
        return buffer;
    }    
        
    public void prl(Object o) {
        System.out.println(o.toString());
    }       
    
    public void err(String text) {   
        System.out.println("\033[0;31m >>> "+text); 
    }    
}

Klient:

Kopiuj
import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.SocketChannel;
import java.util.Iterator;
import java.util.logging.Level;
import java.util.logging.Logger;

public final class NioClientTest{
    public int port = 8080;
    public String hostName = "localhost";
    public String message = "";
    public ByteBuffer readBuffer = ByteBuffer.allocate(1000);

    public static void main(String[] args) {
        NioClientTest client = new NioClientTest();
        client.startCient();
    }

    public void startCient() {
        BufferedReader reader = new BufferedReader (new InputStreamReader(System.in)) ;
        
        try {
            this.message = reader.readLine();
        } catch (IOException ex) {
            Logger.getLogger(NioClientTest.class.getName()).log(Level.SEVERE, null, ex);
        }
        
        try {
            SocketChannel channel = SocketChannel.open();
            channel.configureBlocking(false);

            InetSocketAddress address = new InetSocketAddress(hostName, port);		 
            channel.connect(address);			 

            while (!channel.finishConnect()) {
                System.out.println("Łączenie z serwerem");
            }

            Transport tr = new Transport("Wellcome",1);
            this.send(channel, this.objToBBufer(tr));

            Selector selector = Selector.open();
            channel.register(selector, SelectionKey.OP_READ);
            
            while(true) {
                if(selector.select() > 0) {
                    read(selector);
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public boolean read(Selector selector) {		
        Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
        
        while(iterator.hasNext()) {
            SocketChannel channel = null;
            
            try {
                SelectionKey selectionKey = iterator.next();
                
                if (selectionKey.isReadable()) {
                    channel = (SocketChannel) selectionKey.channel();
                    this.readBuffer.clear();

                    int count = channel.read(this.readBuffer);
                    
                    if (count > 0) {
                        this.readBuffer.rewind();
                        
                        //String input = Charset.forName("UTF-8").decode(this.readBuffer).toString();
                        
                        this.analyse(channel, this.readBuffer);
                        
                        try {
                            Thread.sleep(2000);
                        } catch (InterruptedException ex) {
                            Logger.getLogger(NioClientTest.class.getName()).log(Level.SEVERE, null, ex);
                        }
                    }
                }

                iterator.remove();
            } catch (IOException e) {
                err("Błąd odczytu. Zamknięcie gniazda.");
                try {channel.close();} catch (IOException ex1) {err("Błąd zamknięcia gniazda.");}

                return false;                
            }
        }
        
        return false;
    }

    public boolean send(SocketChannel channel, ByteBuffer buffer) {
        try {            
            channel.write(buffer);
        } catch (IOException ex) {
            Logger.getLogger(NioClientTest.class.getName()).log(Level.SEVERE, null, ex);
            err("Błąd wysyłania");
            
            return false;
        }
        
        return true;
    } 
    
    public void analyse(SocketChannel channel, ByteBuffer buffer) { 
        Transport in = (Transport) this.buffToObj(buffer);
        
        prl(in.nazwa);
        
        Transport tr = new Transport(this.message,1);
        this.send(channel, this.objToBBufer(tr));
    } 
    
    public Object buffToObj(ByteBuffer buffer) {
        byte[] bytes = buffer.array();
        ByteArrayInputStream in = new ByteArrayInputStream(bytes);
        ObjectInputStream is = null;
        Object obj = null;
    
        try {
            is = new ObjectInputStream(in);
            obj = is.readObject();
        } catch (IOException ex) {
            Logger.getLogger(NioClientTest.class.getName()).log(Level.SEVERE, null, ex);
        } catch (ClassNotFoundException ex) {
            Logger.getLogger(NioClientTest.class.getName()).log(Level.SEVERE, null, ex);
        }
        
        return obj;
    }
    
    public ByteBuffer objToBBufer(Object obj) {
        ByteArrayOutputStream bos = new ByteArrayOutputStream();
        ObjectOutputStream out = null;
        ByteBuffer buffer = null;
        
        try {
            out = new ObjectOutputStream(bos);   
            out.writeObject(obj);
            out.flush();
            byte[] yourBytes = bos.toByteArray();
            buffer = ByteBuffer.wrap(yourBytes);
        } catch (IOException ex) {
            Logger.getLogger(NioClientTest.class.getName()).log(Level.SEVERE, null, ex);
        } finally {
            try {
                bos.close();
            } catch (IOException ex) {
            // ignore close exception
            }
        }
        
        return buffer;
    }

    public void prl(String text) {
        System.out.println(text);
    }    
    
    public void err(String text) {   
        System.out.println("\033[0;31m"+getClass().getName()+":::"+Thread.currentThread().getStackTrace()[2].getMethodName()+"() >>> "+text); 
    }    
}

Klasa transportowa

Kopiuj

import java.io.Serializable;

public class Transport implements Serializable {
    
    public String nazwa = "";    
    public int ilosc = 0;
    
    public Transport() {
        
    }
    
    public Transport(String nazwa, int ilosc) {
        this.nazwa = nazwa;
        this.ilosc = ilosc;
    }
}

Klasę Transport musi zawierać zarówno klient jak i serwer.
Mam nadzieję, że komuś ułatwi to życie :)

damianem
  • Rejestracja:prawie 8 lat
  • Ostatnio:3 miesiące
  • Postów:205
0

W Twoim kodzie nadal widzę dwie rzeczy do poprawy:

  • Pisząc do asynchronicznego kanału zakładasz że wszystkie bajty zostają do niego zapisane - metoda write() zwraca liczbę faktycznie zapisanych bajtów i jeśli jest to mniej niż liczba bajtów które chcemy wysłać, to należy zapamiętać stan bufora, zarejestrować nasłuchiwanie na event OP_WRITE i dopiero gdy on przyjdzie kontynuować pisanie.
  • Analogiczna sytuacja przy czytaniu - możesz otrzymać mniej bajtów niż oczekujesz przy deserializacji - Twój protokół powinien dawać możliwość rozpoznania przez odbiorcę jak długa jest odbierana wiadomość. Najprostszy protokół który mi przychodzi do głowy to wiadomości w formie [int][message] gdzie pierwsze 4 bajty (int) to długość [message] (byte array) którym może być Twój zserializowany obiekt. Wtedy potrzebujesz stanowego dekodera (instancja per kanał) do którego wrzucasz przychodzące bajty a on wypluwa listę zdekodowanych obiektów (może być pusta jeśli nowych bajtów jest za mało żeby zdekodować jedną pełną wiadomość)
CA
  • Rejestracja:około 9 lat
  • Ostatnio:około rok
  • Postów:80
0

Jeśli dobrze rozumiem to po odczytaniu wiadomości przełączyć na OP_WRITE i zapisać gdy przyjdzie na to kolej. Jaką będę miał wtedy pewność, że zapisano wszystkie bajty?
Pobrać wartość z write() i powtórzyć w razie błędu? A co z synchronizacją klienta? Dodam dane o długości na początku tablicy, sprawdzę nie zgadza się i prośba o ponowienie przesyłu danych? Jak często występują przekłamania w odczycie i zapisie?

damianem
  • Rejestracja:prawie 8 lat
  • Ostatnio:3 miesiące
  • Postów:205
0

Rejestrować OP_WRITE powinieneś gdy write() zwraca 0 - to znak że bufor wyjściowy socketa jest zapchany i nie można pisać dalej bez blokowania. Dlatego ważne jest trzymanie stanu połączenia gdzie będzie informacja na temat tego jaką wiadomość właśnie zapisujesz i gdzie ostatnia próba się zakończyła. Otrzymanie eventu OP_WRITE oznacza, że w buforze socketa zwolniło się miejsce i można kontynuować wysyłanie od momentu w którym skończyliśmy poprzednio. Jeśli write() zwróciło pozytywną liczbę należy wyrejestrować OP_WRITE bo nie jesteśmy wtedy w sytuacji gdzie czekamy na dostępność zasobu.

Jedyną informacją na temat zapisanych bajtów jaką otrzymasz jest wynik channel.write(). To co zaproponowałem to tylko jeden z możliwych protokołów, możesz też np. dzielić wiadomości po konkretnym znaku, popularny jest znak nowej linii. Co do przekłamań na sieci to tym zajmują się niższe warstwy sieciowe i TCP, na poziomie aplikacji raczej nie powinieneś się tym przejmować. Oczywiście nigdy nie można ufać przychodzącym danym i trzeba je rozsądnie sprawdzać głównie pod względem rozmiaru. Dlatego np. serwery HTTP przyjmują pewne limity co do body requestów / URI stringa nawet jeśli nie występują one w specyfikacji. W przypadku otrzymania niewłaściwego requestu (np. takiego który by powodował alokację niebezpiecznej ilości pamięci) możesz po prostu zamknąć połączenie.

Złota zasada w NIO jest taka, że nigdy nie możesz być pewien ile danych odczytasz / zapiszesz. Jako że odczytanie / zapisanie jednej wiadomości może wymagać kilku cykli selectora, niezbędne jest trzymanie stanu który jest zapamiętany pomiędzy cyklami.

CA
  • Rejestracja:około 9 lat
  • Ostatnio:około rok
  • Postów:80
0

Czy mógłbym np w metodzie save wystawić dane do globalnego bufora, przełączyć na OP_WRITE i kiedy on nastąpi zapisać dane do gniazda? Jeśli się wszystko powiedzie przełączyć go do trybu odczytu OP_READ, jeśli nie, nie przełączać tylko powtórzyć zadanie? Niestety okazuje się, że nie rozumiem zasady działania przesyłu danych w gnieździe. Wiem, że jeśli chcę coś zapisać wrzucam to do bufora, i robię zapis. I co dzieje się dalej? Dane płyną tam jak woda w strumyku :) Na końcu kanału znajduje się bufor odczytu do którego te dane spływają. Kiedy się napełni można je odczytać. Wiadomo, że można rozpoznać ile danych zapisano bo metoda write zwraca wartość. Metoda read odczytuje dane nie wiedząc czy to całość. Ale można to rozpoznać poprzez informację wstawioną w postaci 4 bajtów wstawionych na początek tablicy (tak jak zaproponowałeś). Ale co z tym zapisywaniem i odczytem? W jaki sposób rozpoznawane są te zdarzenia? Czy związane są one z wielkością bufora zadeklarowanego podczas jego alokacji? Jak to mam rozumieć?
Czy jeżeli zadeklaruję wielkość bufora zapisu na 1000 bitów a muszę zapisać 1500 to wtedy metoda write zwróci 1000 i będę wiedział, że muszę dokończyć zapis podczas następnego zdarzenia OP_WRITE? A jeśli pomiędzy nastąpi międzyczasie OP_READ? A może przesłać wcześniej wiadomośc, że należy przygotować bufor odczytu o rozmiarze x, a następnie przesłać właściwą wiadomość? Trochę się pogubiłem. Komplikuje to bardzo działanie tego prostego programu. Jak wpłynie to na jego wydajność?

edytowany 1x, ostatnio: caprio
Kliknij, aby dodać treść...

Pomoc 1.18.8

Typografia

Edytor obsługuje składnie Markdown, w której pojedynczy akcent *kursywa* oraz _kursywa_ to pochylenie. Z kolei podwójny akcent **pogrubienie** oraz __pogrubienie__ to pogrubienie. Dodanie znaczników ~~strike~~ to przekreślenie.

Możesz dodać formatowanie komendami , , oraz .

Ponieważ dekoracja podkreślenia jest przeznaczona na linki, markdown nie zawiera specjalnej składni dla podkreślenia. Dlatego by dodać podkreślenie, użyj <u>underline</u>.

Komendy formatujące reagują na skróty klawiszowe: Ctrl+B, Ctrl+I, Ctrl+U oraz Ctrl+S.

Linki

By dodać link w edytorze użyj komendy lub użyj składni [title](link). URL umieszczony w linku lub nawet URL umieszczony bezpośrednio w tekście będzie aktywny i klikalny.

Jeżeli chcesz, możesz samodzielnie dodać link: <a href="link">title</a>.

Wewnętrzne odnośniki

Możesz umieścić odnośnik do wewnętrznej podstrony, używając następującej składni: [[Delphi/Kompendium]] lub [[Delphi/Kompendium|kliknij, aby przejść do kompendium]]. Odnośniki mogą prowadzić do Forum 4programmers.net lub np. do Kompendium.

Wspomnienia użytkowników

By wspomnieć użytkownika forum, wpisz w formularzu znak @. Zobaczysz okienko samouzupełniające nazwy użytkowników. Samouzupełnienie dobierze odpowiedni format wspomnienia, zależnie od tego czy w nazwie użytkownika znajduje się spacja.

Znaczniki HTML

Dozwolone jest używanie niektórych znaczników HTML: <a>, <b>, <i>, <kbd>, <del>, <strong>, <dfn>, <pre>, <blockquote>, <hr/>, <sub>, <sup> oraz <img/>.

Skróty klawiszowe

Dodaj kombinację klawiszy komendą notacji klawiszy lub skrótem klawiszowym Alt+K.

Reprezentuj kombinacje klawiszowe używając taga <kbd>. Oddziel od siebie klawisze znakiem plus, np <kbd>Alt+Tab</kbd>.

Indeks górny oraz dolny

Przykład: wpisując H<sub>2</sub>O i m<sup>2</sup> otrzymasz: H2O i m2.

Składnia Tex

By precyzyjnie wyrazić działanie matematyczne, użyj składni Tex.

<tex>arcctg(x) = argtan(\frac{1}{x}) = arcsin(\frac{1}{\sqrt{1+x^2}})</tex>

Kod źródłowy

Krótkie fragmenty kodu

Wszelkie jednolinijkowe instrukcje języka programowania powinny być zawarte pomiędzy obróconymi apostrofami: `kod instrukcji` lub ``console.log(`string`);``.

Kod wielolinijkowy

Dodaj fragment kodu komendą . Fragmenty kodu zajmujące całą lub więcej linijek powinny być umieszczone w wielolinijkowym fragmencie kodu. Znaczniki ``` lub ~~~ umożliwiają kolorowanie różnych języków programowania. Możemy nadać nazwę języka programowania używając auto-uzupełnienia, kod został pokolorowany używając konkretnych ustawień kolorowania składni:

```javascript
document.write('Hello World');
```

Możesz zaznaczyć również już wklejony kod w edytorze, i użyć komendy  by zamienić go w kod. Użyj kombinacji Ctrl+`, by dodać fragment kodu bez oznaczników języka.

Tabelki

Dodaj przykładową tabelkę używając komendy . Przykładowa tabelka składa się z dwóch kolumn, nagłówka i jednego wiersza.

Wygeneruj tabelkę na podstawie szablonu. Oddziel komórki separatorem ; lub |, a następnie zaznacz szablonu.

nazwisko;dziedzina;odkrycie
Pitagoras;mathematics;Pythagorean Theorem
Albert Einstein;physics;General Relativity
Marie Curie, Pierre Curie;chemistry;Radium, Polonium

Użyj komendy by zamienić zaznaczony szablon na tabelkę Markdown.

Lista uporządkowana i nieuporządkowana

Możliwe jest tworzenie listy numerowanych oraz wypunktowanych. Wystarczy, że pierwszym znakiem linii będzie * lub - dla listy nieuporządkowanej oraz 1. dla listy uporządkowanej.

Użyj komendy by dodać listę uporządkowaną.

1. Lista numerowana
2. Lista numerowana

Użyj komendy by dodać listę nieuporządkowaną.

* Lista wypunktowana
* Lista wypunktowana
** Lista wypunktowana (drugi poziom)

Składnia Markdown

Edytor obsługuje składnię Markdown, która składa się ze znaków specjalnych. Dostępne komendy, jak formatowanie , dodanie tabelki lub fragmentu kodu są w pewnym sensie świadome otaczającej jej składni, i postarają się unikać uszkodzenia jej.

Dla przykładu, używając tylko dostępnych komend, nie możemy dodać formatowania pogrubienia do kodu wielolinijkowego, albo dodać listy do tabelki - mogłoby to doprowadzić do uszkodzenia składni.

W pewnych odosobnionych przypadkach brak nowej linii przed elementami markdown również mógłby uszkodzić składnie, dlatego edytor dodaje brakujące nowe linie. Dla przykładu, dodanie formatowania pochylenia zaraz po tabelce, mogłoby zostać błędne zinterpretowane, więc edytor doda oddzielającą nową linię pomiędzy tabelką, a pochyleniem.

Skróty klawiszowe

Skróty formatujące, kiedy w edytorze znajduje się pojedynczy kursor, wstawiają sformatowany tekst przykładowy. Jeśli w edytorze znajduje się zaznaczenie (słowo, linijka, paragraf), wtedy zaznaczenie zostaje sformatowane.

  • Ctrl+B - dodaj pogrubienie lub pogrub zaznaczenie
  • Ctrl+I - dodaj pochylenie lub pochyl zaznaczenie
  • Ctrl+U - dodaj podkreślenie lub podkreśl zaznaczenie
  • Ctrl+S - dodaj przekreślenie lub przekreśl zaznaczenie

Notacja Klawiszy

  • Alt+K - dodaj notację klawiszy

Fragment kodu bez oznacznika

  • Alt+C - dodaj pusty fragment kodu

Skróty operujące na kodzie i linijkach:

  • Alt+L - zaznaczenie całej linii
  • Alt+, Alt+ - przeniesienie linijki w której znajduje się kursor w górę/dół.
  • Tab/⌘+] - dodaj wcięcie (wcięcie w prawo)
  • Shit+Tab/⌘+[ - usunięcie wcięcia (wycięcie w lewo)

Dodawanie postów:

  • Ctrl+Enter - dodaj post
  • ⌘+Enter - dodaj post (MacOS)