HTTP 与 Vanilla 套接字通过单跳 Wi-Fi 网络从 Android 设备上传大型二进制文件 (50-200 MB)
使用 HTTP 通过普通套接字(Android 上的 Java)通过 Wi-Fi 网络将大型(50-200 MB)文件 [文件位于 SD 卡上] 从 Android 设备发送到 Linux 服务器是否会产生大量开销。
在我当前的原型中,我使用 CherryPy-3.2.0 来实现我的 HTTP 服务器。我在 Nexus 上运行 Android 2.3.3 作为我的客户端。
目前,上传 50 MB 二进制文件需要约 100 秒**(在较慢的网络 18 Mbps* 上)和约 50 秒(在较快的 54 Mbps* 网络上)。
注意:
*我使用 WifiInfo.getLinkSpeed()
来测量网络链接速度
** 这是 HTTPClient.execute 之前和之后的时间差(postRequest)
关于其他昂贵的操作的任何其他想法,这些操作可能在除网络之外的总时间中占据很大一部分,以及如何减少这个时间,我们将不胜感激。
谢谢。
编辑 - Android 上的 HTTP 发布代码
private void doHttpPost(String fileName) throws Exception{
HttpParams httpParameters = new BasicHttpParams();
// Set the timeout in milliseconds until a connection is established.
int timeoutConnection = 9000000;
HttpConnectionParams.setConnectionTimeout(httpParameters, timeoutConnection);
// Set the default socket timeout (SO_TIMEOUT)
// in milliseconds which is the timeout for waiting for data.
int timeoutSocket = 9000000;
HttpConnectionParams.setSoTimeout(httpParameters, timeoutSocket);
HttpClient client = new DefaultHttpClient(httpParameters);
client.getParams().setParameter(ClientPNames.COOKIE_POLICY, CookiePolicy.RFC_2109);
HttpPost postRequest = new HttpPost();
postRequest.setURI(new URI("http://192.168.1.107:9999/upload/"));
MultipartEntity multiPartEntity = new MultipartEntity();
multiPartEntity.addPart("myFile", new FileBody(new File(fileName)));
postRequest.setEntity(multiPartEntity);
long before = TrafficStats.getTotalTxBytes();
long start = System.currentTimeMillis();
HttpResponse response = client.execute(postRequest);
long end = System.currentTimeMillis();
long after = TrafficStats.getTotalTxBytes();
Log.d(LOG_TAG, "HTTP Post Execution took " + (end - start) + " ms.");
if( before != TrafficStats.UNSUPPORTED && after != TrafficStats.UNSUPPORTED)
Log.d(LOG_TAG, (after-before) + " bytes transmitted to the server");
else
Log.d(LOG_TAG, "This device doesnot support Network Traffic Stats");
HttpEntity responseEntity = response.getEntity();
if (responseEntity != null) {
responseEntity.consumeContent();
Log.d(LOG_TAG, "HTTP Post Response " + response.getEntity().getContent().toString() );
}
client.getConnectionManager().shutdown();
}
编辑 2:根据此工具报告的结果,SD 卡读取速度似乎不是问题。所以它可能是 HttpClient 库或其他东西。
Is there a substantial overhead of using HTTP over plain sockets (Java on Android) to send a large (50-200 MB) file [file is on the SD card] from an Android device to a Linux server over a Wi-Fi network.
In my current prototype I'm using CherryPy-3.2.0 to implement my HTTP server. I'm running Android 2.3.3 on a Nexus one as my client.
Currently it's taking around ~100 seconds** (on slower network 18 Mbps*) and ~50 seconds (on a faster 54 Mbps*) Wi-Fi network to upload a 50 MB binary file.
NOTE:
*I'm using WifiInfo.getLinkSpeed()
to measure the network link speed
** This is the time difference before and after the HTTPClient.execute(postRequest)
Any other ideas regarding other expensive operations that may have a substantial part in the total time apart from the network and how to reduce this time would be appreciated.
Thanks.
EDIT - HTTP post code on Android
private void doHttpPost(String fileName) throws Exception{
HttpParams httpParameters = new BasicHttpParams();
// Set the timeout in milliseconds until a connection is established.
int timeoutConnection = 9000000;
HttpConnectionParams.setConnectionTimeout(httpParameters, timeoutConnection);
// Set the default socket timeout (SO_TIMEOUT)
// in milliseconds which is the timeout for waiting for data.
int timeoutSocket = 9000000;
HttpConnectionParams.setSoTimeout(httpParameters, timeoutSocket);
HttpClient client = new DefaultHttpClient(httpParameters);
client.getParams().setParameter(ClientPNames.COOKIE_POLICY, CookiePolicy.RFC_2109);
HttpPost postRequest = new HttpPost();
postRequest.setURI(new URI("http://192.168.1.107:9999/upload/"));
MultipartEntity multiPartEntity = new MultipartEntity();
multiPartEntity.addPart("myFile", new FileBody(new File(fileName)));
postRequest.setEntity(multiPartEntity);
long before = TrafficStats.getTotalTxBytes();
long start = System.currentTimeMillis();
HttpResponse response = client.execute(postRequest);
long end = System.currentTimeMillis();
long after = TrafficStats.getTotalTxBytes();
Log.d(LOG_TAG, "HTTP Post Execution took " + (end - start) + " ms.");
if( before != TrafficStats.UNSUPPORTED && after != TrafficStats.UNSUPPORTED)
Log.d(LOG_TAG, (after-before) + " bytes transmitted to the server");
else
Log.d(LOG_TAG, "This device doesnot support Network Traffic Stats");
HttpEntity responseEntity = response.getEntity();
if (responseEntity != null) {
responseEntity.consumeContent();
Log.d(LOG_TAG, "HTTP Post Response " + response.getEntity().getContent().toString() );
}
client.getConnectionManager().shutdown();
}
EDIT 2: Based on the results reported by this tool it looks like the SD card read speed is not an issue. So it may either be the HttpClient library or something else.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(3)
HTTP 连接的开销来自于它与数据一起发送的标头(这基本上是一个常量)。因此,您发送的数据越多,标头“对您造成的伤害”就越少。然而,需要考虑的更重要的方面是编码。
例如,如果您要发送非 ASCII 数据,并与
application/x-www-form-urlencoded
mime 类型配对,您将面临输入大小爆炸的风险,因为非 ASCII 字符必须被逃脱了。来自规范:
另一种选择是
multipart/form-data
,它对于二进制数据非常有效。因此,请确保您的应用程序正在使用此 MIME 类型(您甚至可以在服务器日志中检查这一点)。另一种可以大大减少上传时间的方法是压缩。如果您要上传尚未压缩的数据(大多数图像和视频格式已经压缩),请尝试在上传中添加 gzip 压缩。 另一篇文章展示了在 android 中进行设置的详细信息。
如果您的数据具有特定格式(例如图像),您可以研究适合您的数据类型的无损压缩算法(用于图像的 png、用于音频的 FLAC 等)。压缩总是以 CPU(电池)为代价,因此请记住这一点。
请记住:
在知道瓶颈之前不要对其进行优化。也许您的服务器连接速度很慢,也许您无法足够快地从 Android 文件系统读取数据以将数据推送到网络。运行一些测试,看看效果如何。
如果是我,我将不会实现直接 TCP 方法。只是我的2分钱,祝你好运!
Overhead on HTTP connection comes from the headers that it sends along with your data (which is basically a constant). So the more data you send, the less the headers 'hurt you'. However, the much more important aspect to consider is encoding.
For example, if you are sending non-ASCII data, paired with a mime type of
application/x-www-form-urlencoded
you run the risk of exploding the input size because non-ASCII characters must be escaped.From the spec:
The alternative is
multipart/form-data
which efficient for binary data. So, make sure your application is using this MIME type (you can even probably check this on your server logs).Another method which can considerably reduce your upload time is compression. If you are uploading data which isn't already compressed (most image and video formats are already compressed) try adding gzip compression to your uploads. Another post shows the details of setting this up in android.
If your data is of a specific format (say an image), you can look into lossless compression algorithms for your type of data (png for images, FLAC for audio, etc.). Compression always comes at the price of CPU (battery), so keep that in mind.
Remember:
Don't optimize something until you know its the bottleneck. Maybe your server's connection is slow, maybe you can't read from the android file system fast enough to push your data to the network. Run some tests and see what works.
If it were me, I would not implement the straight tcp approach. Just my 2 cents, good luck!
不会,通过原始套接字使用 HTTP 不会带来显着的开销。但是,这实际上取决于您如何使用 HttpClient 发送此文件。您是否在文件系统和 HttpClient 之间正确缓冲?延迟可能不是网络,而是从文件系统读取文件。事实上,您将原始链接速度提高了 3 倍,但仅降低了 2 倍。这可能意味着您的代码、服务器或文件系统中的其他地方存在一些延迟。您可以尝试从桌面客户端上传文件,以确保不是服务器导致延迟。然后通过put查看文件系统。如果一切正常,请查看您使用 HttpClient 编写的代码,看看是否可以优化。
No there is no significant overhead associated with using HTTP over raw sockets. However, it really depends on how you're using HttpClient to send this file. Are you properly buffering between the file system and HttpClient? The latency might not be the network, but reading the file from the filesystem. In fact you increased the raw link speed by 3x and only saw a reduction of 2x. That probably means there is some latency else where in your code or the server or filesystem. You might try uploading a file from a desktop client to make sure it's not the server causing the latency. Then look at the filesystem through put. If that all checks out then look at the code you've written using HttpClient and see if that could be optimized.
另请注意,在 CherryPy 3.2 中,处理请求正文的系统已完全重新设计,您可以更自由地根据请求的媒体类型实现不同的处理程序。默认情况下,CherryPy 会将您上传的字节读取到临时文件中;我假设您的代码随后将其复制到更永久的位置,这可能是对您没有用的开销(尽管有充分的安全原因使用临时文件)。另请参阅此问题,了解有关重命名临时文件的讨论。
您可以覆盖该行为;使用执行您想要的操作的
make_file
函数创建_cpreqbody.Part
的子类,然后在工具中替换cherrypy.request.body.part_class 该 URI。然后将您的代码发布到 http://tools.cherrypy.org 上,以便每个人都能受益:)
Note also in CherryPy 3.2 that the system for handling request bodies has been completely reworked, and you are much more free to implement varying handlers based on the media type of the request. By default, CherryPy will read your uploaded bytes into a temporary file; I assume your code then copies that to a more permanent location, which might be overhead that isn't useful to you (although there are good security reasons to use a temporary file). See also this question for discussion on renaming temp files.
You can override that behavior; make a subclass of
_cpreqbody.Part
with amake_file
function that does what you want, then, in a Tool, replacecherrypy.request.body.part_class
for that URI. Then post your code on http://tools.cherrypy.org so everyone can benefit :)