首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >数据报套接字使UDP程序中的其他线程处于饥饿状态

数据报套接字使UDP程序中的其他线程处于饥饿状态
EN

Stack Overflow用户
提问于 2012-03-17 02:30:33
回答 1查看 2.6K关注 0票数 4

作为课程作业的一部分,我们的任务是在java提供的UDP层之上添加额外的可靠性层,以便发送大型图片文件。这将使用Go-Back-N协议来完成:http://en.wikipedia.org/wiki/Go_back_N

据我所知,这个问题的症结在于能够发送数据包,同时检查是否有任何确认进入旧的数据包,这将允许您移动您的窗口。

我目前通过两个线程来做到这一点:一个线程在窗口中有空间时发送下一个数据包;另一个线程不断地侦听任何传入的确认并做出适当的反应。

我的问题是,程序应该被线程化,就好像这两个线程同时运行一样,但实际上,似乎ACKReceiver线程获得了非常不成比例的时间。从线程转储来看,当发送线程到达DataSocket.receive()行时,它似乎会让发送线程“饥饿”一段时间,阻塞执行,同时不给另一个线程运行的机会。

我看了以下问题,似乎暗示问题与DatagramSocket.receive是synchronized...but没有提供可用的解决方案的事实有关:

Java Thread won't pause on I/O operation

这是我代码的发送者部分的代码,我相对确定另一端的接收者是完全正常的(首先,我不需要使用任何线程来让它工作!):

代码语言:javascript
复制
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();


            }
        }
    }
}
EN

回答 1

Stack Overflow用户

回答已采纳

发布于 2012-03-17 02:37:47

我认为您在这里遇到了死锁,因为当发送方调用clientSocket.setSoTimeout()时,读取器线程在clientSocket.receive()中。请参阅以下DatagramSocket方法定义:

代码语言:javascript
复制
public synchronized void setSoTimeout(int timeout) throws SocketException {
...
public synchronized void receive(DatagramPacket p) throws IOException {

如果接收时套接字超时为0,则receive将挂起等待数据包。如果您发出SIGQUIT,您的JVM将转储线程并显示死锁,您可以跟踪堆栈帧以查看发送方和接收方被卡住的位置。

要解决这个问题,您应该停止更改setSoTimeout值,这在我看来是非常糟糕的做法。我将切换到使用DatagramChannel,使套接字成为非阻塞的,并使用NIO接收来执行读取。有关如何使用通道Selector的详细信息,请参阅NIO docs

票数 5
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/9742674

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档