浅谈socket传输文件速率优化

socket传输文件速率优化

前言

最近有个需求,就是在需要提升换机助手的传输文件的传输速度。先来看看什么是换机助手。

浅谈socket传输文件速率优化_第1张图片
浅谈socket传输文件速率优化_第2张图片

一般厂家的换机助手都长这个样子,就是将旧手机的一些数据拷贝到新手机上去。数据一般包括:联系人,短信,壁纸,图片,音乐,视屏,文件,设置项等等。然后传输都是使用的wifi 或者wifi 直连连接后通过socket 协议去传输文件。这篇文章重在说明如何提高socket的传输速度。下面先说几个概念:

1.wifi 和wifi p2p(直连)

Wi-Fi Direct是一种全新的技术,即使在没有传统的Wi-Fi网络或Wi-Fi接入点的环境中,仍然能够在诸如智能手机和数码相机等设备间实现点对点Wi-Fi连接。

P2P是英文Peer-to-Peer(对等)的简称,又被称为“点对点”。“对等”技术,是一种网络新技术,依赖网络中参与者的计算能力和带宽,而不是把依赖都聚集在较少的几台服务器上。P2P还是英文Point to Point (点对点)的简称。搞过投屏知道miracast的,应该也知道这个。

Wi-Fi是一种可以将个人电脑、手持设备(如PDA、手机)等终端以无线方式互相连接的技术。Wi-Fi是一个无线网路通信技术的品牌,由Wi-Fi联盟(Wi-Fi Alliance)所持有。

2. 换机助手使用的传输方案

一般来说,市面上新旧手机互传文件,总共有两种方案:

  1. 使用最原始的socket 方案,旧手机作为客户端发送数据,新手机作为服务端接受数据

  2. 新手机创建热点,旧手机连接后创建一个服务器server 环境,然后新手机通过httpclient 去通过url 下载旧手机的文件

以上两种不管使用哪一种,都是需要一个手机开热点,一个去连接的,并且以上方案最终都是使用的socket 协议来传输。

1.速率优化心路历程

在拿到这个优化需求之后,总感觉这个上层这边优化不了多少空间。因为我们目前的传输速度平均为6M/s,而华为的传输速度最大可以达到70M/S,一定是华为对socket 协议底层做了优化。然后我将我们的apk放到华为手机上,然后速度没有明显的提升。这就说明跟因还在上层的实现方式。此时多年的debug 经验告诉我,这个问题,最后可能只能多开线程等来解决。但是我还是太年轻。

2.总结

1. 优先使用5G 网络

浅谈socket传输文件速率优化_第3张图片

一般来说,连接好一个wifi后,可以在wifi 详情里面看到这个,其中传输链接速度,基本就是这个wifi的最大速度了,除以8,则是最大的上传下载速度。里面现在我们看到的是86Mbps,所以使用这个网络来传输文件的话,最多也就10M/s。这个是硬性条件。不过这个传输链接速度也会实时的变化,所以我们会看到我们传输的速度也会不停的发生变化,但是基本变化不大。

如果此处使用5G 热点的话,则上面的传输链接速度则可以达到300Mbps多呢。所以传输速度也会有质的变化。下面是创建5G wifi p2p 的核心

  boolean supportWifi5G = mWifiManager.is5GHzBandSupported();
 //首先需要判断手机是否支持5Gtry {if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q && supportWifi5G) {Reflect.invokeMethod(manager, "setWifiP2pChannels", channel, 0, 48, null);} else {Reflect.invokeMethod(manager, "setWifiP2pChannels", channel, 0, 6, null);}
//设置wifi 前必须先给wifi 设置一个信道,至于什么是信道,大家可以网上查查。当时就是因为没有给5G设置这个48 信道(当然不止48有效),5G wifi 一直创建不成功。 
    
   if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q && supportWifi5G) {WifiP2pConfig.Builder builder = new WifiP2pConfig.Builder().setNetworkName("DIRECT-").setPassphrase("987654321").setGroupOperatingBand(GROUP_OWNER_BAND_5GHZ);
​      manager.createGroup(channel, builder.build(), listener);} else {
​      manager.createGroup(channel, listener);
    }
    // 然后就是设置账号和密码了,账号必须以DIRECT 开头,否则会报错。

信道介绍:https://blog.csdn.net/qq_43804080/article/details/103390070

2. 考虑使用线程池来使得性能可以利于的最大化

起初在没有使用5G wifi的时候,尝试过使用线程池来优化速度,但是发现没有什么作用,但是升级了5G WiFi之后,发现了一个问题,就是在传输一些小而多的文件时,传输速度一下子就会掉下来,例如传输1000多张图片。这是因为现在可以用的速度大了,但是每次传输一张小的文件,其余剩下的速度就会给浪费了。所以这是用5G wifi后,再同步加入遇到小而多的文件时使用线程池同时传输多张图片。这样速度就会得到大大的提升。

3. 调节缓存字节的大小

下面以一个简单的发送和接受文件的例子来说。

 private void sendFile(String path, String FileName) {
        DhcpInfo info = wifiManager.getDhcpInfo();
        String serverAddress = WIFIAdmin.intToIp(info.serverAddress);

        try {
            Socket s = new Socket();
            s.connect(new InetSocketAddress(serverAddress, PORT), 3000);
            OutputStream out = s.getOutputStream();
            //将文件名写在流的头部以#分割
            out.write((FileName + "#").getBytes());
            File file = new File(path);
            long length = file.length() / 1024 / 1024;
            String str = "客户端信息:" + s.getLocalAddress() + " P:" + s.getLocalPort() +
                    " \n服务器信息:" + s.getInetAddress() + " P:" + s.getPort() +
                    "\nserverAddress:" + serverAddress +
                    "\n 文件:" + path +
                    "\n 传输文件大小:" + length + "M";
            Message message = new Message();
            message.obj = str.toString();
            handler.sendMessage(message);
            FileInputStream inputStream = new FileInputStream(file);
            byte[] buf = new byte[1024];
            // 这里可以修改此处的缓冲区大小。速度也有有一个提升。
            int len;
            //判断是否读到文件末尾
            while ((len = inputStream.read(buf)) != -1) {
                out.write(buf, 0, len);
                Log.d("xiechangtao", "send:len:" + len);
                calculateSpeed(len);
                //将文件循环写入输出流
            }
            //告诉服务端,文件已传输完毕
            s.shutdownOutput();
            //获取从服务端反馈的信息
            BufferedReader in = new BufferedReader(new InputStreamReader(s.getInputStream()));
            String serverBack = in.readLine();
            Log.d("TAG", serverBack);
            //资源关闭
            s.close();
            inputStream.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

``

private void receiveFile() {
    try {
        int num = 0;
        ServerSocket ss = new ServerSocket(WIFIAdmin.PORT);
        while (true) {
            num++;
            Socket socket = ss.accept();
            tv_server_info.setText("有新客户端连接:" +"num:"+ socket.getInetAddress() +
                    " P:" + socket.getPort());

            InputStream in = socket.getInputStream();
            int content;
            //装载文件名的数组
            byte[] c = new byte[1024];  
            //解析流中的文件名,也就是开头的流
            for (int i = 0; (content = in.read()) != -1; i++) {
                //表示文件名已经读取完毕
                if (content == '#') {
                    break;
                }
                c[i] = (byte) content;
            }
            //将byte[]转化为字符,也就是我们需要的文件名
            String FileName = new String(c, "utf-8").trim();
            //创建一个文件,指定保存路径和刚才传输过来的文件名
            OutputStream saveFile = new FileOutputStream(new File(getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS), FileName));
            byte[] buf = new byte[1024]; // 修改此处缓冲区大小
            int len;
            //判断是否读到文件末尾
            final long startTime = System.currentTimeMillis();
            while ((len = in.read(buf)) != -1) {
                saveFile.write(buf, 0, len);
                calculateSpeed(len);
            }
            saveFile.flush();
            saveFile.close();
            //告诉发送端我已经接收完毕
            OutputStream outputStream = socket.getOutputStream();
            outputStream.write("文件接收成功".getBytes());
            final long endTime = System.currentTimeMillis();
            final long costTime = endTime - startTime;
            Message message = new Message();
            message.obj = "传输完成,总共花费了:" + costTime / 1000 + "S";
            handler.sendMessage(message);
            outputStream.flush();
            outputStream.close();
            socket.close();
        }
    } catch (Exception e) {
        e.printStackTrace();
    }
}

4. 使用socket api

以下是一些socket 的API,网上说可以提升速度,但是我有尝试过,没啥用。不过也一起放在这。

// 是否开启Nagle算法   据说这个关闭了速度可以提升1倍,但是我试几乎没有发现变化
socket.setTcpNoDelay(true);

// 设置接收发送缓冲器大小
socket.setReceiveBufferSize(64 * 1024 * 1024);
socket.setSendBufferSize(64 * 1024 * 1024);

// 设置性能参数:短链接,延迟,带宽的相对重要性
socket.setPerformancePreferences(1, 1, 0);

setTrafficClass(int trafficClass)
1) IP规定了四种服务类型,用来定性的描述服务的质量:
l 低成本:发送成本低。
l 高可靠性:保证把数据可靠的送达目的地。
l 最高吞吐量:一次可以接收或发送大批量的数据。
l 最小延迟:传输数据的速度快,把数据快速送达目的地。
2) 这四种服务类型还可以进行组合,例如,可以同时要求获得高可靠性和最小延迟。Socket类中提供了设置和读取服务类型的方法:
l 设置服务类型:public void setTrafficClass(int trafficClass) throws SocketException
l 读取服务类型:public int getTrafficClass() throws SocketException
3) Socket类用四个整数表示服务类型:
l 低成本:0x02 (二进制的倒数第二位为1)
l 高可靠性:0x04(二进制的倒数第三位为1)
l 最高吞吐量:0x08(二进制的倒数第四位为1)
l 最小延迟:0x10(二进制的倒数第五位为1)

这里再顺便附上其他的常用API

// 是否需要在长时无数据响应时发送确认数据(类似心跳包),时间大约为2小时
socket.setKeepAlive(true);

// 对于close关闭操作行为进行怎样的处理;默认为false,0
// false、0:默认情况,关闭时立即返回,底层系统接管输出流,将缓冲区内的数据发送完成
// true、0:关闭时立即返回,缓冲区数据抛弃,直接发送RST结束命令到对方,并无需经过2MSL等待
// true、200:关闭时最长阻塞200毫秒,随后按第二情况处理
socket.setSoLinger(true, 20);

// 是否让紧急数据内敛,默认false;紧急数据通过 socket.sendUrgentData(1);发送
socket.setOOBInline(true);

// 设置读取超时时间为2秒

socket.setSoTimeout(2000);

// 是否复用未完全关闭的Socket地址,对于指定bind操作后的套接字有效
socket.setReuseAddress(true);

大总结

综上,想要提升socket 的传输速度,首选应该使用5G wifi ,然后配合以线程池,传输缓冲区大小来提升速度。

你可能感兴趣的:(Android,进阶,tcp/ip,socket,速度,换机助手)