UDP发送大型文件_不丢包[通俗易懂]

2022-09-14 11:25:07 浏览数 (1)

大家好,又见面了,我是你们的朋友全栈君。

先上图

1:如果对文件要求不高的话 ,可以使用UDP,UDP在实际测试中,丢包还是听验证的,但是效率高

2:如果文件必须完整,还是使用TCP 。Socket进行文件传输,比较稳妥

近期的项目中要是用软件升级,系统文件有600M 。一般的程序员会说,下载吗 ,直接下载安装就好了 ,我也是这样想的 ,素不知线下的网络的环境 有多差,当时一个业务员和我说,要是能实现手机发送文件给设备就好了,毕竟大家都是用手机的,不然太浪费时间了 ,因为当时用的是腾讯的Im来实现即时通讯的,利用外网来发送文件,

那么问题就来了 ,这么大 ,要多久才能发完 ,那就用局域网来发送文件吧 ,第一个想到的就是UDP来实现 ,测试中发现DUP丢包问题特别明显,当时死活都找不到原因 ,后来把发送的次数和接受的次数对比打印了一下 ,命名发送了2k次,接收端只接受了500次,OK ,问题就是发送太快了 ,那么就让发送端发慢一点,

Thread.sleep(10); 一般设置5就OK了,这个可以根据自己的设备来设定休眠的时间

这样就解决问题了 ,

源码地址 :http://pan.baidu.com/s/1i4MB40l

好的,直接看代码吧 ,

1:新建一个Service,利用Bind的形式来开启服务 ,这样不必一直在后台运行 ,service中开启线程池 ,这样降低重建线程的次数,降低内存的消耗

代码语言:javascript复制
package com.example.administrator.canchatdemo.service;

import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.os.Binder;
import android.os.IBinder;
import android.util.Log;

import com.example.administrator.canchatdemo.http.MessageReceiveRunnable;
import com.example.administrator.canchatdemo.http.MsgSendRunable;
import com.example.administrator.canchatdemo.http.UdpSend;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class SocketService extends Service {

    private static final String TAG = "message";
    private MyBinder binder = new MyBinder();
    // 设定固定数量线程的线程池
    ExecutorService executor = Executors.newFixedThreadPool(10);

    @Override
    public void onCreate() {
        super.onCreate();
        Log.i(TAG, "=======SocketService_onCreate");
    }

    public void sendMessage(String data, String ip, int port) {
        Runnable runnable = new MsgSendRunable(data, ip, port);
        executor.execute(runnable);
    }

    public void receiveMessage(Context context, int port) {
        Runnable runnable = new MessageReceiveRunnable(context, port, true);
        executor.execute(runnable);
    }


    public void sendFileMessage(String filePath, String ip, int port) {
        Runnable runnable = new UdpSend(filePath, ip, port);
        executor.execute(runnable);
    }


//    public void receiveFileMessage(int port) {
//        Runnable runnable = new UdpReceive(port);
//        executor.execute(runnable);
//    }


    public class MyBinder extends Binder {
        public SocketService getService() {
            return SocketService.this;
        }
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        return START_NOT_STICKY;
    }

    @Override
    public IBinder onBind(Intent intent) {
        return binder;
    }

    @Override
    public boolean onUnbind(Intent intent) {
        return false;
    }

    @Override
    public void onDestroy() {
        Log.i(TAG, "=======onDestroy");
        MessageReceiveRunnable.stopReceMessage(); //停止接受消息
        super.onDestroy();
    }
}

2:新建发送文件的Runnable

代码语言:javascript复制
package com.example.administrator.canchatdemo.http;

import android.util.Log;

import com.example.administrator.canchatdemo.entity.SendFileEntity;

import org.greenrobot.eventbus.EventBus;

import java.io.File;
import java.io.FileInputStream;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;

public class UdpSend implements Runnable {


    public static DatagramPacket dataPacket;


    String filePath;
    private int port;
    private String ip;

    public UdpSend(String filePath, String ip, int port) {
        this.filePath = filePath;
        this.port = port;
        this.ip = ip;
    }


    @Override
    public void run() {
        sendFile();
    }

    public void sendFile() {
        sendMessage(SendFileEntity.STATE_START, 0, "开始发送文件");
        Log.i("message", "准备发送文件");
        try {
            File file = new File(filePath);
            if (!file.exists()) {
                Log.i("message", "文件不存在");
                sendMessage(SendFileEntity.STATE_FAILED, 0, "文件不存在");
                return;
            }
            long fileSLength = file.length();
            DatagramSocket dataSocket = new DatagramSocket();
            //2,确定发送的具体的数据。
            FileInputStream in = new FileInputStream(filePath);
            byte[] buf = new byte[1024];
            int len;
            int sum = 0;
            while ((len = in.read(buf)) != -1) {
                sum  = len;
                Log.e("message", "文件传输的大小=="   sum);
                int progress = (int) (sum * 100 / fileSLength);
                dataPacket = new DatagramPacket(buf, len, InetAddress.getByName(ip), port);
                dataSocket.send(dataPacket);
                updateProgress(progress);
                Thread.sleep(10);  //延时一段时间,防止传输太快。丢包
            }
            if (dataPacket != null) {
                dataSocket.close();
            }
            sendMessage(SendFileEntity.STATE_SUCCESS, 100, "发送成功");
        } catch (Exception e) {
            sendMessage(SendFileEntity.STATE_FAILED, 0, "发送失败:"   e.toString());
            Log.i("message", "发送文件异常:"   e.toString());
        }
    }

    int lsatProgress = 0;

    private void updateProgress(int progress) {
        if (progress > lsatProgress) {
            sendMessage(SendFileEntity.STATE_PROGRESS, progress, "发送中...");
            Log.e("message", "progress=="   progress);
        }
        lsatProgress = progress;
    }

    public void sendMessage(int state, int progress, String error) {
        SendFileEntity entity = new SendFileEntity(state, progress, error);
        EventBus.getDefault().post(entity);
    }

}

3:新建接收端的Runnable

代码语言:javascript复制
package com.example.administrator.canchatdemo.http;


import android.content.Context;
import android.util.Log;

import com.example.administrator.canchatdemo.entity.ReceFileEntity;
import com.example.administrator.canchatdemo.util.NetUtil;

import org.greenrobot.eventbus.EventBus;

import java.io.File;
import java.io.FileOutputStream;
import java.net.DatagramPacket;
import java.net.DatagramSocket;

/**  * Created by Administrator on 2016/4/12.  * 接受服务  */  public class MessageReceiveRunnable implements Runnable {

    private int port;
    //停止标志
    public static boolean flag;

    private DatagramSocket da = null;

    private Context context;

    public MessageReceiveRunnable(Context context, int port, boolean flag) {
        Log.i("message", "准备接受数据,开启线程");
        this.context = context;
        this.port = port;
        this.flag = flag;
    }

    public void run() {
//        int state, int progress, String error
        sendMessage(ReceFileEntity.STATE_START, 0, "准备接收");
        try {
            if (da == null) {
                da = new DatagramSocket(port);
            }
            String filePath = "/sdcard/zzz.mp4";
            File file = new File(filePath);
            if (file.exists()) {
                file.delete();
            }
            file.createNewFile();
            FileOutputStream out = new FileOutputStream(filePath);
            Log.i("message", "接收消息的开关=="   flag);
            long sum = 0;
            int number = 0;
            while (flag) {
                number  ;
                byte[] buf = new byte[1024];
                DatagramPacket dp = new DatagramPacket(buf, buf.length);
                da.receive(dp);
                String ip = dp.getAddress().getHostAddress();
                String data = new String(dp.getData(), 0, dp.getLength());
                sum  = buf.length;
                sendMessage(ReceFileEntity.STATE_PROGRESS, sum, "接收中...");
                Log.i("message", "==receiver===接受到了消息====ip="   ip   "/"   sum   "/"   number);
                out.write(buf);
                if (!ip.contains(NetUtil.getLocalIp())) {
                    Log.i("message", "==receiver===接受到了别人发来的消息消息====ip="   ip   "ndata = "   data);
                }
            }
        } catch (Exception e) {
            sendMessage(ReceFileEntity.STATE_FAILED, 0, e.toString());
            Log.i(e.getMessage(), "服务器挂了");
        } finally {
            try {
                if (da != null)
                    da.close();
            } catch (Exception e) {
                sendMessage(ReceFileEntity.STATE_FAILED, 0, e.toString());
                e.printStackTrace();
            }
        }
    }

    public static void stopReceMessage() {
        flag = false;
    }

    public void sendMessage(int state, long progress, String error) {
        ReceFileEntity entity = new ReceFileEntity(state, progress, error);
        EventBus.getDefault().post(entity);
    }


}

4:获取IP的工具类

代码语言:javascript复制
package com.example.administrator.canchatdemo.util;

import android.util.Log;
import android.widget.Toast;

import java.net.Inet4Address;
import java.net.InetAddress;
import java.net.NetworkInterface;
import java.util.Enumeration;

/**  * Created by Administrator on 2016/4/14.  * 定义常量  */ public class NetUtil {
    //定义端口
    public static final int PORT_ALL = 51000;

    //获取255ip
    public static String getIpToAll() {
        try {
            String ip = getLocalIp();
            if (ip == null)
                return null;
            return getLocalIp().substring(0, 10)   "255";
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    //获取本地ip
    public static String getLocalIp() {
        try {
            for (Enumeration<NetworkInterface> en = NetworkInterface.getNetworkInterfaces(); en.hasMoreElements(); ) {
                NetworkInterface intf = en.nextElement();
                for (Enumeration<InetAddress> enumIpAddr = intf.getInetAddresses(); enumIpAddr.hasMoreElements(); ) {
                    InetAddress inetAddress = enumIpAddr.nextElement();
                    if (!inetAddress.isLoopbackAddress() && inetAddress instanceof Inet4Address) {
                        return inetAddress.getHostAddress().toString();
                    }
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }
}

4:新建发送,接收的对象,用来更新界面

代码语言:javascript复制
package com.example.administrator.canchatdemo.entity;

/**  * UDP文件发送状态  */  public class SendFileEntity {

    int sendState;
    String desc;
    int progress;

    public static final int STATE_START = 0;
    public static final int STATE_PROGRESS = 1;
    public static final int STATE_SUCCESS = 2;
    public static final int STATE_FAILED = 3;

    public SendFileEntity(int sendState, int progress, String desc) {
        this.sendState = sendState;
        this.progress = progress;
        this.desc = desc;
    }

    public int getProgress() {
        return progress;
    }

    public void setProgress(int progress) {
        this.progress = progress;
    }

    public int getSendTate() {
        return sendState;
    }

    public void setSendTate(int sendState) {
        this.sendState = sendState;
    }

    public String getDesc() {
        return desc;
    }

    public void setDesc(String desc) {
        this.desc = desc;
    }

    @Override
    public String toString() {
        return "SendFileEntity{"  
                "sendState="   sendState  
                ", desc='"   desc   '''  
                ", progress="   progress  
                '}';
    }
}

5:另一个对象

代码语言:javascript复制
package com.example.administrator.canchatdemo.entity;

/**  * UDP文件接受状态  */  public class ReceFileEntity {

    int sendState;
    String desc;
    long progress;

    public static final int STATE_START = 0;
    public static final int STATE_PROGRESS = 1;
    public static final int STATE_SUCCESS = 2;
    public static final int STATE_FAILED = 3;

    public ReceFileEntity(int sendState, long progress, String desc) {
        this.sendState = sendState;
        this.progress = progress;
        this.desc = desc;
    }

    public long getProgress() {
        return progress;
    }

    public void setProgress(long progress) {
        this.progress = progress;
    }

    public int getSendTate() {
        return sendState;
    }

    public void setSendTate(int sendState) {
        this.sendState = sendState;
    }

    public String getDesc() {
        return desc;
    }

    public void setDesc(String desc) {
        this.desc = desc;
    }

    @Override
    public String toString() {
        return "SendFileEntity{"  
                "sendState="   sendState  
                ", desc='"   desc   '''  
                ", progress="   progress  
                '}';
    }
}

6:测试界面的代码 ,因为我是用自己的手机来发送 ,自己的手机来接收。需要的伙伴可以修改设备IP。用多台手机来测试,代码自己小改一下就可以了

代码语言:javascript复制
package com.example.administrator.canchatdemo;

import android.app.Activity;
import android.content.ComponentName;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.Bundle;
import android.os.IBinder;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.ProgressBar;
import android.widget.TextView;

import com.example.administrator.canchatdemo.entity.ReceFileEntity;
import com.example.administrator.canchatdemo.entity.SendFileEntity;
import com.example.administrator.canchatdemo.service.SocketService;
import com.example.administrator.canchatdemo.util.NetUtil;

import org.greenrobot.eventbus.EventBus;
import org.greenrobot.eventbus.Subscribe;
import org.greenrobot.eventbus.ThreadMode;


public class TestChatActiivty extends Activity implements View.OnClickListener {

    EditText et_content;
    Button button_send, btn_jump;
    TextView tv_ip;

    private SocketService service;
    private ServiceConnection conn = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder binder) {
            SocketService.MyBinder myBinder = (SocketService.MyBinder) binder;
            service = myBinder.getService();
            service.receiveMessage(TestChatActiivty.this, NetUtil.PORT_ALL);
//            service.receiveFileMessage(NetUtil.PORT_ALL);
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {
        }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.test_main_chat);
        EventBus.getDefault().register(TestChatActiivty.this);
        initView();
    }

    TextView tv_send_state;
    TextView tv_send_progress;
    TextView tv_send_desc;


    TextView tv_rece_state;
    TextView tv_rece_progress;
    TextView tv_rece_desc;


    private void initView() {
        tv_send_state = (TextView) findViewById(R.id.tv_send_state);
        tv_send_progress = (TextView) findViewById(R.id.tv_send_progress);
        tv_send_desc = (TextView) findViewById(R.id.tv_send_desc);

        tv_rece_state = (TextView) findViewById(R.id.tv_rece_state);
        tv_rece_progress = (TextView) findViewById(R.id.tv_rece_progress);
        tv_rece_desc = (TextView) findViewById(R.id.tv_rece_desc);


        tv_ip = (TextView) findViewById(R.id.tv_ip);
        tv_ip.setText(NetUtil.getLocalIp());
        et_content = (EditText) findViewById(R.id.et_content);
        button_send = (Button) findViewById(R.id.button_send);
        button_send.setOnClickListener(this);
        btn_jump = (Button) findViewById(R.id.btn_jump);
        btn_jump.setOnClickListener(this);
    }

    String userIPToSend = "192.168.25.105";  //锤子

    // String userIPToSend = "192.168.25.114";  //小米
    @Override
    public void onClick(View view) {
        switch (view.getId()) {
            case R.id.btn_jump:
                String content = getContent();
                service.sendMessage(content, userIPToSend, NetUtil.PORT_ALL);
                break;
            case R.id.button_send:
                String filePath = "/sdcard/bbb.mp4";
                service.sendFileMessage(filePath, userIPToSend, NetUtil.PORT_ALL);
                break;
        }

    }


    @Subscribe(threadMode = ThreadMode.MAIN)
    public void sendFileNotify(SendFileEntity entity) {
        String stateShow = "";
        Log.i("down", "=====主界面消息=="   entity.toString());
        int state = entity.getSendTate();
        if (state == SendFileEntity.STATE_START) {
            stateShow = "准备发送";
        } else if (state == SendFileEntity.STATE_PROGRESS) {
            stateShow = "发送中";
        } else if (state == SendFileEntity.STATE_SUCCESS) {
            stateShow = "发送成功";
        } else if (state == SendFileEntity.STATE_FAILED) {
            stateShow = "发送失败";
        }
        tv_send_state.setText("发送状态===>"   stateShow);
        tv_send_progress.setText("发送进度===>"   entity.getProgress());
        tv_send_desc.setText("发送描述===>"   entity.getDesc());
    }


    @Subscribe(threadMode = ThreadMode.MAIN)
    public void sendFileNotify(ReceFileEntity entity) {
        Log.i("down", "=====主界面消息=="   entity.toString());
        int state = entity.getSendTate();
        String receState = "";
        if (state == ReceFileEntity.STATE_START) {
            receState = "准备接收";
        } else if (state == ReceFileEntity.STATE_PROGRESS) {
            receState = "接收中";
        } else if (state == ReceFileEntity.STATE_SUCCESS) {
            receState = "接收成功";
        } else if (state == ReceFileEntity.STATE_FAILED) {
            receState = "接收失败";
        }
        tv_rece_state.setText("接收状态===>"   receState);
        tv_rece_progress.setText("接收大小===>"   entity.getProgress());
        tv_rece_desc.setText("发送描述===>"   entity.getDesc());
    }


    public String getContent() {
        return et_content.getText().toString().trim();
    }

    @Override
    protected void onResume() {
        super.onResume();
        Intent intent = new Intent(TestChatActiivty.this, SocketService.class);
        bindService(intent, conn, BIND_AUTO_CREATE);
    }

    @Override
    protected void onStop() {
        super.onStop();
        unbindService(conn);
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        EventBus.getDefault().unregister(TestChatActiivty.this);
    }
}

UI的xml

代码语言:javascript复制
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">


    <TextView
        android:id="@ id/tv_ip"
        android:layout_width="match_parent"
        android:layout_height="50dp"
        android:gravity="center"
        android:text="ip"
        android:textColor="@color/myWhite"
        android:textSize="20sp" />

    <EditText
        android:id="@ id/et_content"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginTop="50dp"
        android:ems="10"
        android:inputType="textPersonName"
        android:text="Name"
        android:visibility="gone" />


    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center"
        android:text="发送的文件请放置根目录,命名请看代码" />

    <Button
        android:id="@ id/button_send"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginTop="20dp"
        android:text="文件消息" />


    <Button
        android:id="@ id/btn_jump"
        android:layout_width="match_parent"
        android:layout_height="50dp"
        android:text="文字消息"
        android:visibility="gone" />

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="vertical">

        <TextView
            android:id="@ id/tv_send_state"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="发送状态===> 准备发送" />

        <TextView
            android:id="@ id/tv_send_progress"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="发送进度===> 30%" />

        <TextView
            android:id="@ id/tv_send_desc"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="发送进度===> 下载中..." />

    </LinearLayout>


    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginTop="50dp"
        android:orientation="vertical">

        <TextView
            android:id="@ id/tv_rece_state"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="接受状态===> 准备发送" />

        <TextView
            android:id="@ id/tv_rece_progress"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="接收进度===> 30%" />

        <TextView
            android:id="@ id/tv_rece_desc"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="接收进度===> 下载中..." />

    </LinearLayout>


</LinearLayout>

发布者:全栈程序员栈长,转载请注明出处:https://javaforall.cn/157473.html原文链接:https://javaforall.cn

0 人点赞