UDP组播丢包问题

今天UDP组播丢包问题,可把我害惨了,130个包,接收端总是只接受到121个包,稳定丢9个包,我一直以为是代码逻辑问题,但是通过130个单步调试发现,单步调试就是不丢包。

后来去复习了一下UDP。豁然开朗,UDP发送过快就是会导致丢包的,难怪我单步调试就不丢包。心累。源码如下

 

 

UDP丢包原因

一、主要丢包原因

1、接收端处理时间过长导致丢包:调用recv方法接收端收到数据后,处理数据花了一些时间,处理完后再次调用recv方法,在这二次调用间隔里,发过来的包可能丢失。对于这种情况可以修改接收端,将包接收后存入一个缓冲区,然后迅速返回继续recv。

2、发送的包巨大丢包:虽然send方法会帮你做大包切割成小包发送的事情,但包太大也不行。例如超过50K的一个udp包,不切割直接通过send方法发送也会导致这个包丢失。这种情况需要切割成小包再逐个send。

3、发送的包较大,超过接受者缓存导致丢包:包超过mtu size数倍,几个大的udp包可能会超过接收者的缓冲,导致丢包。这种情况可以设置socket接收缓冲。以前遇到过这种问题,我把接收缓冲设置成64K就解决了。

4、发送的包频率太快:虽然每个包的大小都小于mtu size 但是频率太快,例如40多个mut size的包连续发送中间不sleep,也有可能导致丢包。这种情况也有时可以通过设置socket接收缓冲解决,但有时解决不了。所以在发送频率过快的时候还是考虑sleep一下吧。

5、局域网内不丢包,公网上丢包。这个问题我也是通过切割小包并sleep发送解决的。如果流量太大,这个办法也不灵了。总之udp丢包总是会有的,如果出现了用我的方法解决不了,还有这个几个方法: 要么减小流量,要么换tcp协议传输,要么做丢包重传的工作。

 

 

二、具体问题分析

1.发送频率过高导致丢包

很多人会不理解发送速度过快为什么会产生丢包,原因就是UDP的SendTo不会造成线程阻塞,也就是说,UDP的SentTo不会像TCP中的SendTo那样,直到数据完全发送才会return回调用函数,它不保证当执行下一条语句时数据是否被发送。(SendTo方法是异步的)这样,如果要发送的数据过多或者过大,那么在缓冲区满的那个瞬间要发送的报文就很有可能被丢失。至于对“过快”的解释,作者这样说:“A few packets a second are not an issue; hundreds or thousands may be an issue.”(一秒钟几个数据包不算什么,但是一秒钟成百上千的数据包就不好办了)。 要解决接收方丢包的问题很简单,首先要保证程序执行后马上开始监听(如果数据包不确定什么时候发过来的话),其次,要在收到一个数据包后最短的时间内重新回到监听状态,其间要尽量避免复杂的操作(比较好的解决办法是使用多线程回调机制)。

2.报文过大丢包

至于报文过大的问题,可以通过控制报文大小来解决,使得每个报文的长度小于MTU。以太网的MTU通常是1500 bytes,其他一些诸如拨号连接的网络MTU值为1280 bytes,如果使用speaking这样很难得到MTU的网络,那么最好将报文长度控制在1280 bytes以下。

3.发送方丢包

发送方丢包:内部缓冲区(internal buffers)已满,并且发送速度过快(即发送两个报文之间的间隔过短);  接收方丢包:Socket未开始监听;  虽然UDP的报文长度最大可以达到64 kb,但是当报文过大时,稳定性会大大减弱。这是因为当报文过大时会被分割,使得每个分割块(翻译可能有误差,原文是fragmentation)的长度小于MTU,然后分别发送,并在接收方重新组合(reassemble),但是如果其中一个报文丢失,那么其他已收到的报文都无法返回给程序,也就无法得到完整的数据了。 

 

 

Receive.java
package com.lzj.recive;

import java.io.BufferedOutputStream;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.net.DatagramPacket;
import java.net.InetAddress;
import java.net.MulticastSocket;
import java.util.List;

public class ReceiveFile {

	private static String receivePath = "C://Users//LZJ//Desktop//";
	private static String multicastHost = "224.224.10.0";
	private InetAddress address;
	private MulticastSocket recSocket;// 发送服务多播组
	private int recPort = 9988;
	private BufferedOutputStream fos;
	private ReceiveFileDialog dialog;
	private String head = "<html><body>";
	private String body;
	private String end = "</body></html>";

	public ReceiveFile(ReceiveFileDialog dialog) {
		super();
		this.dialog = dialog;
	}

	class ReceiveThread implements Runnable {

		// 接受文件字节数组
		byte[] buf = new byte[1024 * 60];
		byte[] fileByte = new byte[1024];
		public ReceiveThread() {
			try {
				address = InetAddress.getByName(multicastHost);
				recSocket = new MulticastSocket(recPort);
				recSocket.joinGroup(address);
				recSocket.setTimeToLive(1);
			} catch (final IOException e) {
			} // end of try-catch
		}

		public void run() {
			

			try {
				while (true) {
					DatagramPacket packet = new DatagramPacket(fileByte, fileByte.length); // 待接收的数据包
					// 文件长度
					long fileSize = 0;

					// 文件包数量
					int n = 0;

					// 接收File集合
					// 客户端DatagramSocket 接收 DatagramPacket数据包(字节)
					recSocket.receive(packet);
					// 将字节数组转换成对象(反序列化)
					// 基类流 :内存流 读
					ByteArrayInputStream bais = new ByteArrayInputStream(fileByte);
					// 包装流 :对象流
					ObjectInputStream ois = new ObjectInputStream(bais);
					// 内存输入流 读取对象信息
					Object object = ois.readObject();

					body = "";
					// 向下转型 获取对象信息
					List<File> files = (List<File>) object;
					for (File file : files) {
						body += file.getName() + "<br>";
						fileSize += fileSize + file.length();
					}
					dialog.getLblNewLabel().setText(head + body + end);
					dialog.getProgressBar().setMaximum((int) (fileSize / (1024 * 60)) + 1);// 设置进度条的长度
					dialog.getProgressBar().setMinimum(0);// 设置进度条的长度
					dialog.getProgressBar().setValue(0);
					for (File file : files) {
						int fileByteSize = 0;
						int c = 0;
						fos = new BufferedOutputStream(new FileOutputStream(new File(receivePath + file.getName())));
						packet = new DatagramPacket(buf,buf.length); 
						while (true) {
							dialog.getProgressBar().setValue(++n);
							try {
								recSocket.receive(packet);// 接收数据包
							} catch (Exception e) {
								e.printStackTrace();
							}
							fileByteSize += packet.getLength();
							fos.write(packet.getData(), 0, packet.getLength());
							System.out.println("第" +(++c)+ "次写入成功" + "这次写入的大小为" + packet.getLength() +"B");
							if(fileByteSize == file.length()){
								System.out.println("文件"+file.getName()+"接收完毕");
								break;
							}

						}
//						System.out.println("文件总大小"+fileByteSize/1024+"KB");
						fos.flush();
						fos.close();
					}
					dialog.getProgressBar().setValue(dialog.getProgressBar().getMaximum());

				}
			} catch (Exception e) {
				e.printStackTrace(); 
			}

		}// end of run()
	}

}
send.java
package com.lzj.send;

import java.io.BufferedInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.net.DatagramPacket;
import java.net.InetAddress;
import java.net.MulticastSocket;
import java.net.UnknownHostException;
import java.util.List;

import javax.swing.JOptionPane;
import javax.swing.JPanel;

public class SendThread {

	private static String multicastHost = "224.224.10.0";
	private static MulticastSocket sendSocket;// 发送服务多播组
	private static InetAddress address;// 发送服务多播组
	private static int serverPort = 9988;
	private SendFileDialog dialog;
	ObjectInputStream is = null;  
    ObjectOutputStream os = null;  
	public SendThread(SendFileDialog dialog) {
		this.dialog = dialog;
	}

	static {
		try {
			address = InetAddress.getByName(multicastHost);
			sendSocket = new MulticastSocket(serverPort);
			sendSocket.joinGroup(address);
			sendSocket.setTimeToLive(1);
		} catch (UnknownHostException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		}

	}

	public void beginSend() {
		dialog.getProgressBar().setValue(0);
		SendThread1 send = new SendThread1();
		new Thread(send).start();
	}

	class SendThread1 implements Runnable {

	public void run() {
		try {
			DatagramPacket packet = null; // 声明DatagramPacket对象
			List<File> files = dialog.getFiles();
			long fileSize = 0;
			for (File file : files) {
				fileSize += file.length();
			}
			int n = 0;
			dialog.getProgressBar().setMaximum((int) (fileSize/(1024*60))+1);//设置进度条的长度
			dialog.getProgressBar().setMinimum(0);//设置进度条的长度
			dialog.getProgressBar().setValue(0);
			System.out.println(dialog.getProgressBar().getValue());
			System.out.println(dialog.getProgressBar().getMaximum());
			System.out.println(dialog.getProgressBar().getMinimum());
			
	        //对象流  写  对象输出流
	        //内存流(基类流)
	        ByteArrayOutputStream baos = new ByteArrayOutputStream();
	        //对象流(包装流 )因为传输对象
	        ObjectOutputStream oos = new ObjectOutputStream(baos);
	        //将对象写入对象流
	        oos.writeObject(files);
	        
	        //将对象转化为字节数组  因为数据包需要字节数组  该方法属于字节流  很重要!
	        byte[] buf = baos.toByteArray();
	        
	        //将字节数组的数据放入数据包
	        packet = new DatagramPacket(buf, buf.length, address, serverPort);
	        //向客户端传输数据包
	        sendSocket.send(packet );
			
			for (File file : files) {
				System.out.println("即将发送"+file.getName()+"\n");
				BufferedInputStream fis = new BufferedInputStream(new FileInputStream(file));
								
				//send file 
				byte[] data = new byte[1024*60];
				int fileByteSize = 0;
				try {
					int len = 0;
					len = fis.read(data);
					while(len!=-1){
						dialog.getProgressBar().setValue(++n);
						System.out.println("第"+n+"次发送数据包,数据包大小"+len);
						fileByteSize += len;
						packet = new DatagramPacket(data,len, address, serverPort); // 将数据打包
						sendSocket.send(packet);// 发送数据
						Thread.sleep(100);
						len = fis.read(data);
						
					}//end of while
					fis.close();//关闭文件流
					data=new String("over").getBytes();//告诉接受端发送结束
					System.out.println(file.getName()+"发送完毕,文件总大小为"+fileByteSize/1024+"KB");
					System.out.println(dialog.getProgressBar().getValue());
					int value=dialog.getProgressBar().getValue();
					if(value<dialog.getProgressBar().getMaximum()){						
						dialog.getProgressBar().setValue(dialog.getProgressBar().getMaximum());
					}
				} catch (IOException e) {
					System.out.println("发送失败:"+e.getMessage());// 输出异常信息
				}//end of try-catch	
			}}catch( Exception e){
				System.out.println("发送失败:"+e.getMessage());// 输出异常信息
			}
			
			
			
			
			
			
	}// end of run

	}// end of thread

}

 

  • 7
    点赞
  • 35
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值