最近有个需求,就是在需要提升换机助手的传输文件的传输速度。先来看看什么是换机助手。
一般厂家的换机助手都长这个样子,就是将旧手机的一些数据拷贝到新手机上去。数据一般包括:联系人,短信,壁纸,图片,音乐,视屏,文件,设置项等等。然后传输都是使用的wifi 或者wifi 直连连接后通过socket 协议去传输文件。这篇文章重在说明如何提高socket的传输速度。下面先说几个概念:
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)所持有。
一般来说,市面上新旧手机互传文件,总共有两种方案:
使用最原始的socket 方案,旧手机作为客户端发送数据,新手机作为服务端接受数据
新手机创建热点,旧手机连接后创建一个服务器server 环境,然后新手机通过httpclient 去通过url 下载旧手机的文件
以上两种不管使用哪一种,都是需要一个手机开热点,一个去连接的,并且以上方案最终都是使用的socket 协议来传输。
在拿到这个优化需求之后,总感觉这个上层这边优化不了多少空间。因为我们目前的传输速度平均为6M/s,而华为的传输速度最大可以达到70M/S,一定是华为对socket 协议底层做了优化。然后我将我们的apk放到华为手机上,然后速度没有明显的提升。这就说明跟因还在上层的实现方式。此时多年的debug 经验告诉我,这个问题,最后可能只能多开线程等来解决。但是我还是太年轻。
一般来说,连接好一个wifi后,可以在wifi 详情里面看到这个,其中传输链接速度,基本就是这个wifi的最大速度了,除以8,则是最大的上传下载速度。里面现在我们看到的是86Mbps,所以使用这个网络来传输文件的话,最多也就10M/s。这个是硬性条件。不过这个传输链接速度也会实时的变化,所以我们会看到我们传输的速度也会不停的发生变化,但是基本变化不大。
如果此处使用5G 热点的话,则上面的传输链接速度则可以达到300Mbps多呢。所以传输速度也会有质的变化。下面是创建5G wifi p2p 的核心
boolean supportWifi5G = mWifiManager.is5GHzBandSupported();
//首先需要判断手机是否支持5G
try {
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
起初在没有使用5G wifi的时候,尝试过使用线程池来优化速度,但是发现没有什么作用,但是升级了5G WiFi之后,发现了一个问题,就是在传输一些小而多的文件时,传输速度一下子就会掉下来,例如传输1000多张图片。这是因为现在可以用的速度大了,但是每次传输一张小的文件,其余剩下的速度就会给浪费了。所以这是用5G wifi后,再同步加入遇到小而多的文件时使用线程池同时传输多张图片。这样速度就会得到大大的提升。
下面以一个简单的发送和接受文件的例子来说。
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();
}
}
以下是一些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 ,然后配合以线程池,传输缓冲区大小来提升速度。