package org.webpieces.nio.impl.cm.basic.udp; import java.io.IOException; import java.net.DatagramPacket; import java.net.DatagramSocket; import java.net.InetSocketAddress; import java.net.SocketAddress; import java.net.SocketException; import java.nio.ByteBuffer; import org.webpieces.util.logging.Logger; import org.webpieces.util.logging.LoggerFactory; import org.webpieces.nio.api.channels.ChannelSession; import org.webpieces.nio.api.channels.DatagramChannel; import org.webpieces.nio.api.exceptions.NioException; import org.webpieces.nio.api.handlers.DatagramListener; import org.webpieces.nio.impl.util.ChannelSessionImpl; /** */ public class DatagramChannelImpl implements DatagramChannel { private static final Logger log = LoggerFactory.getLogger(DatagramChannelImpl.class); private ChannelSession session = new ChannelSessionImpl(); private DatagramSocket socket; private String id; private final DatagramListener listener; private ByteBuffer buffer; private ReaderThread readerThread; private boolean shutDownThread = false; private String name; public DatagramChannelImpl(String id, int bufferSize, DatagramListener dataListener) { this.id = "["+id+"] "; buffer = ByteBuffer.allocate(bufferSize); this.listener = dataListener; } public void registerForReads() { } /** * @see org.webpieces.nio.api.channels.DatagramChannel#unregisterForReads() */ public void unregisterForReads() { } /** * @see org.webpieces.nio.api.channels.DatagramChannel#getSession() */ public ChannelSession getSession() { return session; } /** * @see org.webpieces.nio.api.channels.RegisterableChannel#setReuseAddress(boolean) */ public void setReuseAddress(boolean b) { if(socket == null) throw new IllegalStateException(id+"Must bind socket before any operations can be called"); try { socket.setReuseAddress(b); } catch (SocketException e) { throw new NioException(e); } } /** * @see org.webpieces.nio.api.channels.RegisterableChannel#bind(java.net.SocketAddress) */ public void bind(SocketAddress addr) { try { socket = new DatagramSocket(addr); readerThread = new ReaderThread(); readerThread.start(); } catch(IOException e) { throw new NioException(e); } } public void setId(Object id) { } public Object getId() { return id; } /** */ public boolean isBlocking() { return true; } /** */ public void close() { if(socket == null) return; //stop the thread first!!!! if(Thread.currentThread().equals(readerThread)) { //since we are on the reader thread, we are not stuck on a DatagramPacket.receive call, just //close and return shutDownThread = true; socket.close(); return; } //otherwise, we must interrupt the call to receive shutDownThread = true; //useless on a DatagramSocket for some reason...... //readerThread.interrupt(); socket.close(); } /** */ public boolean isClosed() { if(socket == null) throw new IllegalStateException(id+"Must bind socket before any operations can be called"); return socket.isClosed(); } /** * @see org.webpieces.nio.api.channels.RegisterableChannel#isBound() */ public boolean isBound() { if(socket == null) return false; return socket.isBound(); } /** * @see org.webpieces.nio.api.channels.RegisterableChannel#getLocalAddress() */ public InetSocketAddress getLocalAddress() { if(!socket.isBound()) throw new IllegalStateException(id+"Must bind socket before any operations can be called"); log.trace(()->"get local="+socket.getLocalPort()); return new InetSocketAddress(socket.getLocalAddress(), socket.getLocalPort()); } public void write(SocketAddress addr, ByteBuffer b) { try { writeImpl(addr,b); } catch (IOException e) { throw new NioException(e); } } /** * @throws IOException * @see org.webpieces.nio.api.channels.DatagramChannel#oldWrite(java.net.SocketAddress, java.nio.ByteBuffer) */ private void writeImpl(SocketAddress addr, ByteBuffer b) throws IOException { if(socket == null) throw new IllegalStateException(id+"Must bind socket before any operations can be called"); DatagramPacket packet = new DatagramPacket(b.array(), b.position(), b.limit()-b.position(), addr); log.trace(()->"size="+(b.limit()-b.position())+" addr="+addr); socket.send(packet); } private void doThreadWork() { while(!shutDownThread) { readPackets(); } log.trace(()->id+"reader thread ending"); } private void readPackets() { InetSocketAddress fromAddr = null; try { buffer.clear(); DatagramPacket packet = new DatagramPacket(buffer.array(), buffer.remaining()); socket.receive(packet); fromAddr = (InetSocketAddress)packet.getSocketAddress(); int offset = packet.getOffset(); int len = packet.getLength(); buffer.position(offset); buffer.limit(offset+len); fireToListener(this, fromAddr, buffer); } catch(Throwable e) { //ignore an SocketException when shutDownThread. if(e instanceof SocketException && shutDownThread) return; log.error(id+"Exception processing packet", e); fireFailure(fromAddr, buffer, e); } } /** * @param fromAddr */ private void fireToListener(DatagramChannel c, InetSocketAddress fromAddr, ByteBuffer b) { try { listener.incomingData(c, fromAddr, b); if(b.remaining() > 0) { log.error(id+"Client="+listener+" did not read all the data from the buffer"); } } catch(Throwable e) { log.error(id+"Exception in client's listener", e); } } /** * @param fromAddr * @param e */ private void fireFailure(InetSocketAddress fromAddr, ByteBuffer data, Throwable e) { try { listener.failure(this, fromAddr, data, e); } catch(Throwable ee) { log.error(id+"Exception notifying client of exception", ee); } } private class ReaderThread extends Thread { /** * @see java.lang.Thread#run() */ @Override public void run() { doThreadWork(); } } /** * @see org.webpieces.nio.api.channels.RegisterableChannel#setName(java.lang.String) */ public void setName(String name) { this.name = name; } /** * @see org.webpieces.nio.api.channels.RegisterableChannel#getName() */ public String getName() { return name; } @Override public String getChannelId() { return id; } }