Datagram Socket Starving other threads in my UDP program

As part of the course assignment, we were tasked with adding an extra layer of reliability on top of the UDP layer that java offers to send a large image file. This should be done using the Go-Back-N protocol: http://en.wikipedia.org/wiki/Go_back_N

From what I understand, the gist of this issue relies on being able to send packets while checking if there were any acknowledgments for the old packets that would allow you to move your window.

I currently do this by having two streams: one that sends the following packets if there is room in the window; and one that continually just listens to any incoming confirmations and reacts accordingly.

My problem is that the program has to be threaded, so it is as if the two threads are acting simulatively, but in reality it seems that the ACKReceiver thread is getting a very disproportionate amount of time. From the thread dump, it appears that it "starves" the sending thread a little when it reaches the DataSocket.receive () line, blocking execution here and preventing another thread from starting at this time.

I've looked at the following question, which seems to hint that the problem is due to the fact that DatagramSocket.receive is synchronized ... but does not offer an acceptable solution to the problem:

Java Thread will not stop on IO

Here is the code for the sender part of my code, I'm relatively sure that the receiver on the other side works fine (in my opinion I didn't need to use any threads to get this to work!):

import java.io.*;
import java.net.*;
import java.nio.ByteBuffer;

public class Sender3 {
    short base = 0;
    short nextSeqNum = 0;
    DatagramPacket[] packets;
    ByteBuffer bb;
    String endSys;
    int portNum;
    String fileName;
    int retryTime;
    int windowSize;
    DatagramSocket clientSocket;
    InetAddress IPAddress;
    boolean timedOut = false;

    public Sender3(String endSys, int portNum, String fileName, int retryTime, int windowSize){
        this.endSys = endSys;
        this.portNum = portNum;
        this.fileName = fileName;
        this.retryTime = retryTime;
        this.windowSize = windowSize;
    }

    public static void main(String args[]) throws Exception{
        //Check for current arguments and assign them
        if(args.length != 5){
            System.out.println("Invalid number of arguments. Please specify: <endSystem> <portNumber> <fileName> <retryTimeout><windowSize>");
            System.exit(1);
        }

        Sender3 sendy = new Sender3(args[0], Integer.parseInt(args[1]), args[2], Integer.parseInt(args[3]), Integer.parseInt(args[4]));

        sendy.go();
    }

    private void go() throws Exception{

        clientSocket = new DatagramSocket();



        bb = ByteBuffer.allocate(2);
        byte[] picData = new byte[1021];
        byte[] sendData = new byte[1024];

        Thread.yield()
        short seqNum = 0; 
        byte[] seqBytes = new byte[2];
        byte EOFFlag = 0;
        boolean acknowledged = false;
        int lastPacketRetrys = 0;
        int resends = 0;
        IPAddress = InetAddress.getByName(endSys);

        FileInputStream imReader = new FileInputStream(new File(fileName));
        double fileSizeKb = imReader.available() / 1021.0; //We add 3 bytes to every packet, so dividing by 1021 will give us total kb sent. 
        int packetsNeeded = (int) Math.ceil(fileSizeKb);
        packets = new DatagramPacket[packetsNeeded];
        long startTime = System.currentTimeMillis();
        long endTime;
        double throughput;

        //Create array of packets to send
        for(int i = 0; i < packets.length; i++){
            if(i == packets.length - 1){
                EOFFlag = 1;
                picData = new byte[imReader.available()];
                sendData = new byte[picData.length + 3];
            }
            imReader.read(picData);
            bb.putShort((short)i);
            bb.flip();
            seqBytes = bb.array();
            bb.clear();
            System.arraycopy(seqBytes, 0, sendData, 0, seqBytes.length);
            sendData[2] = EOFFlag;
            System.arraycopy(picData, 0, sendData, 3, picData.length);
            packets[i] = new DatagramPacket((byte[])sendData.clone(), sendData.length, IPAddress, portNum);
        }

        ACKGetter ackGet = new ACKGetter();
        Thread ackThread = new Thread(ackGet);
        ackThread.start();

        //System.out.println("timeout is: " + timedOut + " base is: " + base + " packet length is: " + packets.length + " nextSeqNum: " + nextSeqNum);

        while(base != packets.length){
            if(timedOut){
                //System.out.println("Timed out waiting for acknowledgement, resending all unACKed packets in window");
                clientSocket.setSoTimeout(retryTime);
                resends++;
                if(nextSeqNum == packets.length)
                    lastPacketRetrys++;
                //Resend all packets in window
                for (int i = base; i < nextSeqNum; i++){
                //  System.out.println("Resending packets with number: " + i);
                    clientSocket.send(packets[i]);
                }
                timedOut = false;
            }

            if(nextSeqNum - base < windowSize && nextSeqNum < packets.length){
                //System.out.println("sending packet with seqNum: " + nextSeqNum);
                clientSocket.send(packets[nextSeqNum]);
                if(base == nextSeqNum){
                    clientSocket.setSoTimeout(retryTime); 
                }
                nextSeqNum++;
            }
            else{
                //Thread.yield();
            }

        }




        if(lastPacketRetrys > 10){
            System.out.println("Last packet ACK was lost (we think). So we just gave up, number of retransmissions will probably be higher");
        }
        endTime = System.currentTimeMillis();
        throughput = 1000 * fileSizeKb / (endTime - startTime);
        clientSocket.close();
        imReader.close();
        System.out.println("Number of retransmissions: " + resends);
        System.out.println("Average throughput is: " + throughput + "Kb/s");

    }


    private class ACKGetter implements Runnable {
        //Listen out for ACKs and update pointers accordingly
        DatagramPacket ackPacket;
        byte[] ackData = new byte[2];
        public void run() {
            while(base != packets.length){
                if(base != nextSeqNum){
                    try{
                        ackPacket = new DatagramPacket(ackData, ackData.length);
                        clientSocket.receive(ackPacket);
                        ackData = ackPacket.getData();
                        bb.put(ackData[0]);
                        bb.put(ackData[1]);
                        bb.flip();
                        short ack = bb.getShort();
                        bb.clear();
                        if(base <= ack){
                            //System.out.println("acknowledgement for base num: " + base + "ack num:" + ack);
                            base = (short) (ack + 1);
                            //If theres nothing left in window, stop timing, otherwise restart the timer
                            if(base == nextSeqNum){
                                clientSocket.setSoTimeout(0);
                            }
                            else{
                                clientSocket.setSoTimeout(retryTime);
                            }
                        }
                        else{
                            //System.out.println("ACK didnt change anything: " + ack);
                        }
                    }
                    catch(Exception ex){
                        timedOut = true;
                        //System.out.println("Packet timed out...resending..");
                    }
                }

                Thread.yield();


            }
        }
    }
}

      

+3


source to share


1 answer


I think you have a dead end here because the reader's thread is clientSocket.receive()

in while the sender is making the call clientSocket.setSoTimeout()

. See the following method definitions DatagramSocket

:

public synchronized void setSoTimeout(int timeout) throws SocketException {
...
public synchronized void receive(DatagramPacket p) throws IOException {

      



If you receive with a socket timeout of 0, then it receive

hangs waiting for a packet. If you issue SIGQUIT

, your JVM flushes threads and should show you a deadlock, and you can monitor stack frames to see where the sender and receiver are stuck.

To fix this, you have to stop changing the value setSoTimeout

, which sounds like very bad practice to me. I would switch to use DatagramChannel

by making the socket non-blocking and using NIO read reception. More information on how to use the pipe Selector

can be found in the NIO docs .

+5


source







All Articles