/* * File : PRUDPPacketReceiverImpl.java * Created : 20-Jan-2004 * By : parg * * Azureus - a Java Bittorrent client * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details ( see the LICENSE file ). * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ package com.aelitis.net.udp.uc.impl; /** * @author parg * */ import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.DataInputStream; import java.io.DataOutputStream; import java.io.IOException; import java.net.DatagramPacket; import java.net.DatagramSocket; import java.net.InetAddress; import java.net.InetSocketAddress; import java.net.PasswordAuthentication; import java.net.SocketTimeoutException; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.Map; import org.bouncycastle.util.encoders.Base64; import org.gudy.azureus2.core3.logging.LogAlert; import org.gudy.azureus2.core3.logging.LogEvent; import org.gudy.azureus2.core3.logging.LogIDs; import org.gudy.azureus2.core3.logging.Logger; import org.gudy.azureus2.core3.util.AEMonitor; import org.gudy.azureus2.core3.util.AESemaphore; import org.gudy.azureus2.core3.util.AEThread; import org.gudy.azureus2.core3.util.Debug; import org.gudy.azureus2.core3.util.SHA1Hasher; import org.gudy.azureus2.core3.util.SimpleTimer; import org.gudy.azureus2.core3.util.SystemTime; import org.gudy.azureus2.core3.util.TimerEvent; import org.gudy.azureus2.core3.util.TimerEventPerformer; import org.gudy.azureus2.core3.util.TimerEventPeriodic; import com.aelitis.azureus.core.impl.AzureusCoreImpl; import com.aelitis.azureus.core.networkmanager.admin.NetworkAdmin; import com.aelitis.azureus.core.networkmanager.admin.NetworkAdminPropertyChangeListener; import com.aelitis.net.udp.uc.PRUDPPacket; import com.aelitis.net.udp.uc.PRUDPPacketHandler; import com.aelitis.net.udp.uc.PRUDPPacketHandlerException; import com.aelitis.net.udp.uc.PRUDPPacketHandlerStats; import com.aelitis.net.udp.uc.PRUDPPacketReceiver; import com.aelitis.net.udp.uc.PRUDPPacketReply; import com.aelitis.net.udp.uc.PRUDPPacketRequest; import com.aelitis.net.udp.uc.PRUDPPrimordialHandler; import com.aelitis.net.udp.uc.PRUDPRequestHandler; public class PRUDPPacketHandlerImpl implements PRUDPPacketHandler { private static final LogIDs LOGID = LogIDs.NET; private final boolean TRACE_REQUESTS = false; private static final long MAX_SEND_QUEUE_DATA_SIZE = 2*1024*1024; private static final long MAX_RECV_QUEUE_DATA_SIZE = 1*1024*1024; private final int port; private DatagramSocket socket; // TODO: who needs encapsulation? public DatagramSocket getSocket() { return socket; } private PRUDPPrimordialHandler primordial_handler; private PRUDPRequestHandler request_handler; private final PRUDPPacketHandlerStatsImpl stats = new PRUDPPacketHandlerStatsImpl( this ); private final Map requests = new HashMap(); private final AEMonitor requests_mon = new AEMonitor( "PRUDPPH:req" ); private final AEMonitor send_queue_mon = new AEMonitor( "PRUDPPH:sd" ); private long send_queue_data_size; private final List[] send_queues = new List[]{ new LinkedList(),new LinkedList(),new LinkedList()}; private final AESemaphore send_queue_sem = new AESemaphore( "PRUDPPH:sq" ); private AEThread send_thread; private final AEMonitor recv_queue_mon = new AEMonitor( "PRUDPPH:rq" ); private long recv_queue_data_size; private final List recv_queue = new ArrayList(); private final AESemaphore recv_queue_sem = new AESemaphore( "PRUDPPH:rq" ); private AEThread recv_thread; private int send_delay = 0; private int receive_delay = 0; private int queued_request_timeout = 0; private long total_requests_received; private long total_requests_processed; private long total_replies; private long last_error_report; private final AEMonitor bind_address_mon = new AEMonitor( "PRUDPPH:bind" ); private InetAddress default_bind_ip; private InetAddress explicit_bind_ip; private volatile InetAddress current_bind_ip; private volatile InetAddress target_bind_ip; private volatile boolean failed; private volatile boolean destroyed; private final AESemaphore destroy_sem = new AESemaphore("PRUDPPacketHandler:destroy"); private Throwable init_error; private final LinkedList<ExternalUdpPacketHandler> externalHandlers = new LinkedList<ExternalUdpPacketHandler>(); protected PRUDPPacketHandlerImpl( int _port, InetAddress _bind_ip ) { port = _port; explicit_bind_ip = _bind_ip; default_bind_ip = NetworkAdmin.getSingleton().getSingleHomedServiceBindAddress(); calcBind(); final AESemaphore init_sem = new AESemaphore("PRUDPPacketHandler:init"); Thread t = new AEThread( "PRUDPPacketReciever:".concat(String.valueOf(port))) { @Override public void runSupport() { receiveLoop(init_sem); } }; t.setDaemon(true); t.start(); final TimerEventPeriodic[] f_ev = {null}; TimerEventPeriodic ev = SimpleTimer.addPeriodicEvent( "PRUDP:timeouts", 5000, new TimerEventPerformer() { @Override public void perform( TimerEvent event ) { if ( destroyed && f_ev[0] != null ){ f_ev[0].cancel(); } checkTimeouts(); } }); f_ev[0] = ev; init_sem.reserve(); } public void addExternalHandler(ExternalUdpPacketHandler handler) { this.externalHandlers.add(handler); } @Override public void setPrimordialHandler( PRUDPPrimordialHandler handler ) { if ( primordial_handler != null && handler != null ){ Debug.out( "Primordial handler replaced!" ); } primordial_handler = handler; } @Override public void setRequestHandler( PRUDPRequestHandler _request_handler ) { if ( request_handler != null ){ if ( _request_handler != null ){ // if we need to support this then the handler will have to be associated // with a message type map, or we chain together and give each handler // a bite at processing the message throw( new RuntimeException( "Multiple handlers per endpoint not supported" )); } } request_handler = _request_handler; } @Override public PRUDPRequestHandler getRequestHandler() { return( request_handler ); } @Override public int getPort() { return( port ); } protected void setDefaultBindAddress( InetAddress address ) { try{ bind_address_mon.enter(); default_bind_ip = address; calcBind(); }finally{ bind_address_mon.exit(); } } @Override public void setExplicitBindAddress( InetAddress address ) { try{ bind_address_mon.enter(); explicit_bind_ip = address; calcBind(); }finally{ bind_address_mon.exit(); } int loops = 0; while( current_bind_ip != target_bind_ip && !(failed || destroyed)){ if ( loops >= 100 ){ Debug.out( "Giving up on wait for bind ip change to take effect" ); break; } try{ Thread.sleep(50); loops++; }catch( Throwable e ){ break; } } } protected void calcBind() { if ( explicit_bind_ip != null ){ target_bind_ip = explicit_bind_ip; }else{ target_bind_ip = default_bind_ip; } } protected void receiveLoop( AESemaphore init_sem ) { long last_socket_close_time = 0; NetworkAdminPropertyChangeListener prop_listener = new NetworkAdminPropertyChangeListener() { @Override public void propertyChanged( String property ) { if ( property == NetworkAdmin.PR_DEFAULT_BIND_ADDRESS ){ setDefaultBindAddress( NetworkAdmin.getSingleton().getSingleHomedServiceBindAddress()); } } }; NetworkAdmin.getSingleton().addPropertyChangeListener( prop_listener ); try{ // outter loop picks up bind-ip changes while( !( failed || destroyed )){ if ( socket != null ){ try{ socket.close(); }catch( Throwable e ){ Debug.printStackTrace(e); } } InetSocketAddress address; DatagramSocket new_socket; if ( target_bind_ip == null ){ address = new InetSocketAddress("127.0.0.1",port); new_socket = new DatagramSocket( port ); }else{ address = new InetSocketAddress( target_bind_ip, port ); new_socket = new DatagramSocket( address ); } new_socket.setReuseAddress(true); // short timeout on receive so that we can interrupt a receive fairly quickly new_socket.setSoTimeout( 1000 ); // only make the socket public once fully configured socket = new_socket; current_bind_ip = target_bind_ip; // Notify other handlers for (ExternalUdpPacketHandler handler : externalHandlers) { handler.socketUpdated(socket); } init_sem.release(); if (Logger.isEnabled()) Logger.log(new LogEvent(LOGID, "PRUDPPacketReceiver: receiver established on port " + port + (current_bind_ip==null?"":(", bound to " + current_bind_ip )))); byte[] buffer = null; long successful_accepts = 0; long failed_accepts = 0; packet: while( !( failed || destroyed )){ if ( current_bind_ip != target_bind_ip ){ break; } try{ if ( buffer == null ){ buffer = new byte[PRUDPPacket.MAX_PACKET_SIZE]; } DatagramPacket packet = new DatagramPacket( buffer, buffer.length, address ); socket.receive( packet ); long receive_time = SystemTime.getCurrentTime(); successful_accepts++; failed_accepts = 0; PRUDPPrimordialHandler prim_hand = primordial_handler; if (AzureusCoreImpl.isCoreAvailable()) { // Check if the packet is an encrypted udp friend // connection or a one-hop reputation packet. for (ExternalUdpPacketHandler handler : externalHandlers) { if (handler.packetReceived(packet)) { continue packet; } } } if (buffer != null && prim_hand != null) { if ( prim_hand.packetReceived( packet )){ // primordial handlers get their own buffer as we can't guarantee // that they don't need to hang onto the data buffer = null; stats.primordialPacketReceived( packet.getLength()); } } if ( buffer != null ){ process( packet, receive_time ); } }catch( SocketTimeoutException e ){ }catch( Throwable e ){ // on vista we get periodic socket closures String message = e.getMessage(); if ( socket.isClosed() || ( message != null && message.toLowerCase().indexOf( "socket closed" ) != -1 )){ long now = SystemTime.getCurrentTime(); // can't guarantee there aren't situations where we get into a screaming // closed loop so guard against this somewhat if ( now - last_socket_close_time < 500 ){ Thread.sleep( 250 ); } last_socket_close_time = now; if (Logger.isEnabled()) Logger.log(new LogEvent(LOGID, "PRUDPPacketReceiver: recycled UDP port " + port + " after close: ok=" + successful_accepts )); break; } failed_accepts++; if (Logger.isEnabled()) Logger.log(new LogEvent(LOGID, "PRUDPPacketReceiver: receive failed on port " + port + ": ok=" + successful_accepts + ", fails=" + failed_accepts, e)); if (( failed_accepts > 100 && successful_accepts == 0 ) || failed_accepts > 1000 ){ Logger.logTextResource(new LogAlert(LogAlert.UNREPEATABLE, LogAlert.AT_ERROR, "Network.alert.acceptfail"), new String[] { "" + port, "UDP" }); // break, sometimes get a screaming loop. e.g. /* [2:01:55] DEBUG::Tue Dec 07 02:01:55 EST 2004 [2:01:55] java.net.SocketException: Socket operation on nonsocket: timeout in datagram socket peek [2:01:55] at java.net.PlainDatagramSocketImpl.peekData(Native Method) [2:01:55] at java.net.DatagramSocket.receive(Unknown Source) [2:01:55] at org.gudy.azureus2.core3.tracker.server.impl.udp.TRTrackerServerUDP.recvLoop(TRTrackerServerUDP.java:118) [2:01:55] at org.gudy.azureus2.core3.tracker.server.impl.udp.TRTrackerServerUDP$1.runSupport(TRTrackerServerUDP.java:90) [2:01:55] at org.gudy.azureus2.core3.util.AEThread.run(AEThread.java:45) */ init_error = e; failed = true; } } } } }catch( Throwable e ){ init_error = e; Logger.logTextResource(new LogAlert(LogAlert.UNREPEATABLE, LogAlert.AT_ERROR, "Tracker.alert.listenfail"), new String[] { "UDP:" + port }); Logger.log(new LogEvent(LOGID, "PRUDPPacketReceiver: " + "DatagramSocket bind failed on port " + port, e)); }finally{ init_sem.release(); destroy_sem.releaseForever(); if ( socket != null ){ try{ socket.close(); }catch( Throwable e ){ Debug.printStackTrace(e); } } NetworkAdmin.getSingleton().removePropertyChangeListener( prop_listener ); } } protected void checkTimeouts() { long now = SystemTime.getCurrentTime(); List timed_out = new ArrayList(); try{ requests_mon.enter(); Iterator it = requests.values().iterator(); while( it.hasNext()){ PRUDPPacketHandlerRequestImpl request = (PRUDPPacketHandlerRequestImpl)it.next(); long sent_time = request.getSendTime(); if ( sent_time != 0 && now - sent_time >= request.getTimeout()){ it.remove(); stats.requestTimedOut(); timed_out.add( request ); } } }finally{ requests_mon.exit(); } for (int i=0;i<timed_out.size();i++){ PRUDPPacketHandlerRequestImpl request = (PRUDPPacketHandlerRequestImpl)timed_out.get(i); if ( TRACE_REQUESTS ){ if (Logger.isEnabled()) Logger.log(new LogEvent(LOGID, LogEvent.LT_ERROR, "PRUDPPacketHandler: request timeout")); } // don't change the text of this message, it's used elsewhere try{ request.setException(new PRUDPPacketHandlerException("timed out")); }catch( Throwable e ){ Debug.printStackTrace(e); } } } protected void process( DatagramPacket dg_packet, long receive_time ) { try{ // HACK alert. Due to the form of the tracker UDP protocol (no common // header for requests and replies) we enforce a rule. All connection ids // must have their MSB set. As requests always start with the action, which // always has the MSB clear, we can use this to differentiate. byte[] packet_data = dg_packet.getData(); int packet_len = dg_packet.getLength(); // System.out.println( "received:" + packet_len ); PRUDPPacket packet; boolean request_packet; stats.packetReceived(packet_len); if ( ( packet_data[0]&0x80 ) == 0 ){ request_packet = false; packet = PRUDPPacketReply.deserialiseReply( this, (InetSocketAddress) dg_packet .getSocketAddress(), new DataInputStream(new ByteArrayInputStream( packet_data, 0, packet_len))); }else{ request_packet = true; packet = PRUDPPacketRequest.deserialiseRequest( this, new DataInputStream(new ByteArrayInputStream( packet_data, 0, packet_len))); } packet.setSerialisedSize( packet_len ); packet.setAddress( (InetSocketAddress)dg_packet.getSocketAddress()); if ( request_packet ){ total_requests_received++; // System.out.println( "Incoming from " + dg_packet.getAddress()); if ( TRACE_REQUESTS ){ Logger.log(new LogEvent(LOGID, "PRUDPPacketHandler: request packet received: " + packet.getString())); } if ( receive_delay > 0 ){ // we take the processing offline so that these incoming requests don't // interfere with replies to outgoing requests try{ recv_queue_mon.enter(); if ( recv_queue_data_size > MAX_RECV_QUEUE_DATA_SIZE ){ long now = SystemTime.getCurrentTime(); if ( now - last_error_report > 30000 ){ last_error_report = now; Debug.out( "Receive queue size limit exceeded (" + MAX_RECV_QUEUE_DATA_SIZE + "), dropping request packet [" + total_requests_received + "/" + total_requests_processed + ":" + total_replies + "]"); } }else if ( receive_delay * recv_queue.size() > queued_request_timeout ){ // by the time this request gets processed it'll have timed out // in the caller anyway, so discard it long now = SystemTime.getCurrentTime(); if ( now - last_error_report > 30000 ){ last_error_report = now; Debug.out( "Receive queue entry limit exceeded (" + recv_queue.size() + "), dropping request packet ]" + total_requests_received + "/" + total_requests_processed + ":" + total_replies + "]"); } }else{ recv_queue.add( new Object[]{ packet, new Integer( dg_packet.getLength()) }); recv_queue_data_size += dg_packet.getLength(); recv_queue_sem.release(); if ( recv_thread == null ){ recv_thread = new AEThread( "PRUDPPacketHandler:receiver" ) { @Override public void runSupport() { while( true ){ try{ recv_queue_sem.reserve(); Object[] data; try{ recv_queue_mon.enter(); data = (Object[])recv_queue.remove(0); total_requests_processed++; }finally{ recv_queue_mon.exit(); } PRUDPPacketRequest p = (PRUDPPacketRequest)data[0]; recv_queue_data_size -= ((Integer)data[1]).intValue(); PRUDPRequestHandler handler = request_handler; if ( handler != null ){ handler.process( p ); Thread.sleep( receive_delay ); } }catch( Throwable e ){ Debug.printStackTrace(e); } } } }; recv_thread.setDaemon( true ); recv_thread.start(); } } }finally{ recv_queue_mon.exit(); } }else{ PRUDPRequestHandler handler = request_handler; if ( handler != null ){ handler.process( (PRUDPPacketRequest)packet ); } } }else{ total_replies++; if ( TRACE_REQUESTS ){ Logger.log(new LogEvent(LOGID, "PRUDPPacketHandler: reply packet received: " + packet.getString())); } PRUDPPacketHandlerRequestImpl request; try{ requests_mon.enter(); if ( packet.hasContinuation()){ // don't remove the request if there are more replies to come request = (PRUDPPacketHandlerRequestImpl)requests.get(new Integer(packet.getTransactionId())); }else{ request = (PRUDPPacketHandlerRequestImpl)requests.remove(new Integer(packet.getTransactionId())); } }finally{ requests_mon.exit(); } if ( request == null ){ if ( TRACE_REQUESTS ){ Logger.log(new LogEvent(LOGID, LogEvent.LT_ERROR, "PRUDPPacketReceiver: unmatched reply received, discarding:" + packet.getString())); } }else{ request.setReply( packet, (InetSocketAddress)dg_packet.getSocketAddress(), receive_time ); } } }catch( Throwable e ){ // if someone's sending us junk we just log and continue if ( e instanceof IOException ){ // generally uninteresting }else{ Logger.log(new LogEvent(LOGID, "", e)); } } } public PRUDPPacket sendAndReceive( PRUDPPacket request_packet, InetSocketAddress destination_address ) throws PRUDPPacketHandlerException { return( sendAndReceive( null,request_packet, destination_address )); } @Override public PRUDPPacket sendAndReceive( PasswordAuthentication auth, PRUDPPacket request_packet, InetSocketAddress destination_address ) throws PRUDPPacketHandlerException { return( sendAndReceive( auth, request_packet, destination_address, PRUDPPacket.DEFAULT_UDP_TIMEOUT )); } @Override public PRUDPPacket sendAndReceive( PasswordAuthentication auth, PRUDPPacket request_packet, InetSocketAddress destination_address, long timeout ) throws PRUDPPacketHandlerException { PRUDPPacketHandlerRequestImpl request = sendAndReceive( auth, request_packet, destination_address, null, timeout, PRUDPPacketHandler.PRIORITY_MEDIUM ); return( request.getReply()); } @Override public PRUDPPacket sendAndReceive( PasswordAuthentication auth, PRUDPPacket request_packet, InetSocketAddress destination_address, long timeout, int priority ) throws PRUDPPacketHandlerException { PRUDPPacketHandlerRequestImpl request = sendAndReceive( auth, request_packet, destination_address, null, timeout, priority ); return( request.getReply()); } @Override public void sendAndReceive( PRUDPPacket request_packet, InetSocketAddress destination_address, PRUDPPacketReceiver receiver, long timeout, int priority ) throws PRUDPPacketHandlerException { sendAndReceive( null, request_packet, destination_address, receiver, timeout, priority ); } public PRUDPPacketHandlerRequestImpl sendAndReceive( PasswordAuthentication auth, PRUDPPacket request_packet, InetSocketAddress destination_address, PRUDPPacketReceiver receiver, long timeout, int priority ) throws PRUDPPacketHandlerException { if ( socket == null ){ if ( init_error != null ){ throw( new PRUDPPacketHandlerException( "Transport unavailable", init_error )); } throw( new PRUDPPacketHandlerException( "Transport unavailable" )); } try{ checkTargetAddress( destination_address ); ByteArrayOutputStream baos = new ByteArrayOutputStream(); DataOutputStream os = new DataOutputStream( baos ); request_packet.serialise(os); byte[] buffer = baos.toByteArray(); request_packet.setSerialisedSize( buffer.length ); if ( auth != null ){ //<parg_home> so <new_packet> = <old_packet> + <user_padded_to_8_bytes> + <hash> //<parg_home> where <hash> = first 8 bytes of sha1(<old_packet> + <user_padded_to_8> + sha1(pass)) //<XTF> Yes SHA1Hasher hasher = new SHA1Hasher(); String user_name = auth.getUserName(); String password = new String(auth.getPassword()); byte[] sha1_password; if ( user_name.equals( "<internal>")){ sha1_password = Base64.decode(password); }else{ sha1_password = hasher.calculateHash(password.getBytes()); } byte[] user_bytes = new byte[8]; Arrays.fill( user_bytes, (byte)0); for (int i=0;i<user_bytes.length&&i<user_name.length();i++){ user_bytes[i] = (byte)user_name.charAt(i); } hasher = new SHA1Hasher(); hasher.update( buffer ); hasher.update( user_bytes ); hasher.update( sha1_password ); byte[] overall_hash = hasher.getDigest(); //System.out.println("PRUDPHandler - auth = " + auth.getUserName() + "/" + new String(auth.getPassword())); baos.write( user_bytes ); baos.write( overall_hash, 0, 8 ); buffer = baos.toByteArray(); } DatagramPacket dg_packet = new DatagramPacket(buffer, buffer.length, destination_address ); PRUDPPacketHandlerRequestImpl request = new PRUDPPacketHandlerRequestImpl( receiver, timeout ); try{ requests_mon.enter(); requests.put( new Integer( request_packet.getTransactionId()), request ); }finally{ requests_mon.exit(); } try{ // System.out.println( "Outgoing to " + dg_packet.getAddress()); if ( send_delay > 0 && priority != PRUDPPacketHandler.PRIORITY_IMMEDIATE ){ try{ send_queue_mon.enter(); if ( send_queue_data_size > MAX_SEND_QUEUE_DATA_SIZE ){ request.sent(); // synchronous write holding lock to block senders socket.send( dg_packet ); stats.packetSent( buffer.length ); if ( TRACE_REQUESTS ){ Logger.log(new LogEvent(LOGID, "PRUDPPacketHandler: request packet sent to " + destination_address + ": " + request_packet.getString())); } Thread.sleep( send_delay ); }else{ send_queue_data_size += dg_packet.getLength(); send_queues[priority].add( new Object[]{ dg_packet, request }); if ( TRACE_REQUESTS ){ String str = ""; for (int i=0;i<send_queues.length;i++){ str += (i==0?"":",") + send_queues[i].size(); } System.out.println( "send queue sizes: " + str ); } send_queue_sem.release(); if ( send_thread == null ){ send_thread = new AEThread( "PRUDPPacketHandler:sender" ) { @Override public void runSupport() { int[] consecutive_sends = new int[send_queues.length]; while( true ){ try{ send_queue_sem.reserve(); Object[] data; int selected_priority = 0; try{ send_queue_mon.enter(); // invariant: at least one queue must have an entry for (int i=0;i<send_queues.length;i++){ List queue = send_queues[i]; int queue_size = queue.size(); if ( queue_size > 0 ){ selected_priority = i; if ( consecutive_sends[i] >= 4 || ( i < send_queues.length - 1 && send_queues[i+1].size() - queue_size > 500 )){ // too many consecutive or too imbalanced, see if there are // lower priority queues with entries consecutive_sends[i] = 0; }else{ consecutive_sends[i]++; break; } }else{ consecutive_sends[i] = 0; } } data = (Object[])send_queues[selected_priority].remove(0); }finally{ send_queue_mon.exit(); } DatagramPacket p = (DatagramPacket)data[0]; PRUDPPacketHandlerRequestImpl r = (PRUDPPacketHandlerRequestImpl)data[1]; // mark as sent before sending in case send fails // and we then rely on timeout to pick this up send_queue_data_size -= p.getLength(); r.sent(); socket.send( p ); stats.packetSent( p.getLength() ); if ( TRACE_REQUESTS ){ Logger.log(new LogEvent(LOGID, "PRUDPPacketHandler: request packet sent to " + p.getAddress())); } long delay = send_delay; if ( selected_priority == PRIORITY_HIGH ){ delay = delay/2; } Thread.sleep( delay ); }catch( Throwable e ){ // get occasional send fails, not very interesting Logger.log( new LogEvent( LOGID, LogEvent.LT_WARNING, "PRUDPPacketHandler: send failed: " + Debug.getNestedExceptionMessage(e))); } } } }; send_thread.setDaemon( true ); send_thread.start(); } } }finally{ send_queue_mon.exit(); } }else{ request.sent(); socket.send( dg_packet ); // System.out.println( "sent:" + buffer.length ); stats.packetSent( buffer.length ); if ( TRACE_REQUESTS ){ Logger.log(new LogEvent(LOGID, "PRUDPPacketHandler: " + "request packet sent to " + destination_address + ": " + request_packet.getString())); } } // if the send is ok then the request will be removed from the queue // either when a reply comes back or when it gets timed-out return( request ); }catch( Throwable e ){ // never got sent, remove it immediately try{ requests_mon.enter(); requests.remove( new Integer( request_packet.getTransactionId())); }finally{ requests_mon.exit(); } throw( e ); } }catch( PRUDPPacketHandlerException e ){ throw( e ); }catch( Throwable e ){ Logger.log(new LogEvent(LOGID,LogEvent.LT_ERROR, "PRUDPPacketHandler: sendAndReceive to " + destination_address + " failed: " + Debug.getNestedExceptionMessage(e))); throw( new PRUDPPacketHandlerException( "PRUDPPacketHandler:sendAndReceive failed", e )); } } @Override public void send( PRUDPPacket request_packet, InetSocketAddress destination_address ) throws PRUDPPacketHandlerException { if ( socket == null ){ if ( init_error != null ){ throw( new PRUDPPacketHandlerException( "Transport unavailable", init_error )); } throw( new PRUDPPacketHandlerException( "Transport unavailable" )); } try{ checkTargetAddress( destination_address ); ByteArrayOutputStream baos = new ByteArrayOutputStream(); DataOutputStream os = new DataOutputStream( baos ); request_packet.serialise(os); byte[] buffer = baos.toByteArray(); request_packet.setSerialisedSize( buffer.length ); DatagramPacket dg_packet = new DatagramPacket(buffer, buffer.length, destination_address ); // System.out.println( "Outgoing to " + dg_packet.getAddress()); if ( TRACE_REQUESTS ){ Logger.log(new LogEvent(LOGID, "PRUDPPacketHandler: reply packet sent: " + request_packet.getString())); } socket.send( dg_packet ); stats.packetSent( buffer.length ); // this is a reply to a request, no time delays considered here }catch( PRUDPPacketHandlerException e ){ throw( e ); }catch( Throwable e ){ e.printStackTrace(); Logger.log(new LogEvent(LOGID, LogEvent.LT_ERROR, "PRUDPPacketHandler: send to " + destination_address + " failed: " + Debug.getNestedExceptionMessage(e))); throw( new PRUDPPacketHandlerException( "PRUDPPacketHandler:send failed", e )); } } protected void checkTargetAddress( InetSocketAddress address ) throws PRUDPPacketHandlerException { if ( address.getPort() == 0 ){ throw( new PRUDPPacketHandlerException( "Invalid port - 0" )); } } @Override public void setDelays( int _send_delay, int _receive_delay, int _queued_request_timeout ) { send_delay = _send_delay; receive_delay = _receive_delay; // trim a bit off this limit to include processing time queued_request_timeout = _queued_request_timeout-5000; if ( queued_request_timeout < 5000 ){ queued_request_timeout = 5000; } } public long getSendQueueLength() { int res = 0; for (int i=0;i<send_queues.length;i++){ res += send_queues[i].size(); } return(res); } public long getReceiveQueueLength() { return( recv_queue.size()); } @Override public void primordialSend( byte[] buffer, InetSocketAddress target ) throws PRUDPPacketHandlerException { try{ checkTargetAddress( target ); DatagramPacket dg_packet = new DatagramPacket(buffer, buffer.length, target ); // System.out.println( "Outgoing to " + dg_packet.getAddress()); if ( TRACE_REQUESTS ){ Logger.log(new LogEvent(LOGID, "PRUDPPacketHandler: reply packet sent: " + buffer.length + " to " + target )); } socket.send( dg_packet ); stats.primordialPacketSent( buffer.length ); }catch( Throwable e ){ throw( new PRUDPPacketHandlerException( e.getMessage())); } } @Override public PRUDPPacketHandlerStats getStats() { return( stats ); } protected void destroy() { destroyed = true; destroy_sem.reserve(); } }