UDP 客户端收不到字节

UDP client does not receive bytes

这个问题已经被问了很多,但到目前为止,我从以前的答案中应用的解决方案中 none 对我有所帮助。

主要目标

我正在尝试学习 UDP 连接,这是我的尝试。我想让客户端通过 UDP 在服务器上请求图片,服务器将发送它。然后客户端将使用给定的信息创建一个文件。

说明

我的主要想法是使用 "GET" 命令(不是 HTTP,只是 GET)向服务器请求图像,后跟图像名称(包括扩展名)。然后客户端等待一个答案,这是请求的图像。

问题

客户等着没来的回答

研究

我已经检查图像是否已加载到 byte[] 并且图像已发送。但是在客户端上,没有收到任何东西并停留在那里等待连接通过

来自服务器的代码

public class UDPserver {

    static DatagramSocket serverUDP;
    static DatagramPacket packet;
    static InetAddress address;
    static byte[] buffer = new byte[65507];//65507
    final static int receivingPORT = 6668;
    final static int sendingPORT = 6669; 

    public static void main(String[] args) throws SocketException, IOException, InterruptedException{

        boolean serverActive = true;

        String order = "";
        String file = "";

        //Instantiate server
        serverUDP = new DatagramSocket(receivingPORT);

        while(serverActive){            

            //Kind of packet we want to receive
            packet = new DatagramPacket(buffer, buffer.length);

            System.out.println("Server awaiting connection...");
            //Receive it
            serverUDP.receive(packet);
            System.out.println("Received packet from: " + packet.getAddress() + "/" + packet.getPort());

            //What does the packet contain?
            String msg = new String(packet.getData());
            address = packet.getAddress();
            System.out.println("Order from: " + address + "/" + receivingPORT + " says: " + msg);

            try{
                order = msg.split(" ")[0].trim();
                file = msg.split(" ")[1].trim();
            } catch (Exception e){

            }           

            switch(order){
                case("GET"):{
                    System.out.println("Sending back an image...");
                    buffer = loadImageFromServer(file);
                    packet = new DatagramPacket(buffer, buffer.length, address, sendingPORT);
                    Thread.sleep(5000);
                    serverUDP.send(packet); 
                    System.out.println("Client served");
                    break;
                }
                case("DISCONNECT"):{
                    buffer = "Server is disconnecting...".getBytes();
                    packet = new DatagramPacket(buffer, buffer.length, address, sendingPORT);
                    serverUDP.send(packet);
                    serverActive = false;
                    serverUDP.close();
                    break;
                }                
            }                   
        }        
    }

    static byte[] loadImageFromServer(String path) {

        try {
            System.out.println("Loading path: " + path);
            //Instantiate a buffer from the image for it
            BufferedImage img = ImageIO.read(UDPserver.class.getResource(path));
            //Create a byte[] stream object to handle the data
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            //Write the image data into those above with jpg format
            ImageIO.write(img, "png", baos);
            //Flush the information
            baos.flush();

            byte[] buffer = baos.toByteArray(); //Write it out on a byte string and return it
            return buffer;            

        } catch (IOException ex) {
            Logger.getLogger(UDPserver.class.getName()).log(Level.SEVERE, null, ex.fillInStackTrace());
            System.exit(-1);
        }
        return null;
    }
}

CODE 客户端

public class Client {

    static DatagramSocket clientUDP;
    static InetAddress address;
    static DatagramPacket packetSend;
    static DatagramPacket packetReceive;

    static int SIZE = 65507;
    final static int receivingPORT = 6669;
    final static int sendingPORT = 6668; 

    static byte[] buffer = new byte[SIZE];

    static Scanner scan = new Scanner(System.in);

    public static void main(String[] args) throws SocketException, UnknownHostException, IOException{


            boolean clientLoop = true;

            //Get address
            address = InetAddress.getByName("localhost");

            //Instantiate Client -> UDP
            clientUDP = new DatagramSocket();

            while(clientLoop){
                System.out.print("Enter any key and press enter");
                scan.next(); //Just to stop the loop

                //Load the buffer
                buffer = "GET imagenServidor.png".getBytes();
                //buffer = "DISCONNECT".getBytes();

                System.out.println("Buffer is ready");

                //Arm the packet
                packetSend = new DatagramPacket(buffer, buffer.length, address, sendingPORT);
                System.out.println("Packet is armed!");

                //Send the packet to the server
                clientUDP.send(packetSend);
                System.out.println("Order sent to server");

                System.out.println("Waiting an answer");

                packetReceive = new DatagramPacket(buffer, buffer.length, address, receivingPORT);
                clientUDP.receive(packetReceive);
                System.out.println("Server answered!");

                ByteArrayInputStream bais = new ByteArrayInputStream(packetReceive.getData());
                BufferedImage image = ImageIO.read(bais);
                System.out.println(image);
            }            
            clientUDP.close();
    }    
}

注意事项

首先,我觉得你在调试的时候需要学习如何使用wirshark或者tcmpdump来分析网络流,这将有助于你发现问题并解决问题。

关于你的程序,user207421提到的几个问题。我觉得用TCP比较好,但是如果你想通过这种方式学习UDP,你需要的是自己做一个slim可靠的UDP。

例如,您可能需要以下型号

  • 建立发送缓冲区和接收缓冲区,每次检查缓冲区是否为空,如果没有,send/receive并处理。(因为UDP有MTU)

  • 在每个数据报的头部添加一些额外的格式信息,包括整个报文的大小,数据报的顺序,左边的大小等(因为需要裁剪你的消息分为很多部分)

  • 搭建controller,需要有重传、重建消息等功能(因为UDP不可靠,需要检查各个部分的完整性)

希望对您有所帮助。

原因

MTU!

您正在通过 UDP 直接发送具有长缓冲区的数据包,这在大多数网络环境下可能不起作用。

通过UDP发送的数据包不能超过网络MTU,否则会被丢弃。大多数网络节点(routers/switchs/hosts...)上的网络 MTU 可能不会超过 1500,有时甚至更小。虽然有些 nods 可能会对 ip 数据包进行 sigmentation,但是当你使用 UDP 时,你不应该指望它。

建议

在此应用中改用TCP,至于:

  • 您正在发送预期完整的数据(否则将毫无用处)。

  • 你不关心拥塞控制算法。

所以只用 TCP。

根据问题的更新进行编辑

因此,由于这是一个练习,您只能在其中使用 UDP。

文件不完整就没有用,您必须确保:

  • 所有数据包都可以通过该路径。这意味着网络应该在物理上和虚拟上都连接,并且数据包大小应始终小于 MTU。
  • 如果任何数据包丢失,接收方和发送方都应该能够知道。
  • 如果任何 apckets 出问题,接收者应该能够知道。
  • 发送方应该能够缓存并重新发送尚未被接收方确认的数据包。

确保您的网络连接良好。将图像缓冲区拆分为缓冲区数组,每个缓冲区项长度小于 1000 字节(应该是安全的)。

那么让我们为此设计一个成熟但简单的协议:

    0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   | type                          | sequence number               |
   +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
   | payload ...                                                   |
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   | ...                                                           |
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

对于类型,我们可能需要:

  • 你好:0x01
  • 再见:0x02
  • 确认:0x03
  • nack: 0x04
  • 数据:0x05
  • 反馈:0x06
  • ...

序列应该是单增的。例如1, 2, 3, 4....(不必从 1 开始但可以)

它的工作原理如下:

Sender->Receiver: hello(seq=i)
Receiver->Sender: ack(seq=i)

# Sender->Receiver: hello(seq=i)
# if timeout and got no ack for seq=i

Sender->Receiver: data(seq=i+1)
Receiver->Sender: ack(seq=i+1)

# Sender->Receiver: hello(seq=i+1)
# if timeout and got no ack for seq=i+1

Sender->Receiver: data(seq=i+2)
Sender->Receiver: data(seq=i+3)
Receiver->Sender: ack(seq=i+2)
Receiver->Sender: ack(seq=i+3)

# Sender->Receiver: hello(seq=i+2)
# if timeout and got no ack for seq=i+2 or got nack for seq=i+2

Sender->Receiver: bye(seq=n)
Receiver->Sender: ack(seq=n)

# bye is not necessory