HttpURLConnection 文件上传限制
一、 问题
最近在Android程序里上传向.Net服务器上传大文件的时候,发现了一个问题。当上传大文件的时候会爆出OutOfMemoryError,上传小文件则没有这种情况。
二、 猜想
之后多次试验之后发现,每次写流文件写到长度65535KB的时候,就会内存溢出。不禁让人联想到Java的65535长度限制,所以猜想可能是因为HttpURLConnection作者在封装的时候做了一定限制。
三、 验证
HttpURLConnection是通过Http协议来请求数据的,它的底层必然也是封装Socket实现的,所以我决定用底层的Socket,来实现POST请求,验证一下是否有上传限制。
四、 结果
使用Socket实现的POST上传并没有遇到上传瓶颈,成功的从服务器请求到了结果数据。
五、 总结
试验证明,HttpURLConnection的POST上传是有上传瓶颈的。可能是HttpURLConnection封装的某个地方出现了限制,或者是更底层的地方出现了限制,导致了OutOfMemoryError异常。
最后贴上Socket的Post请求实例
/**
* 发送HTTP_POST请求
*
* @see 本方法默认的连接超时和读取超时均为30秒
* @see 请求参数含有中文时,亦可直接传入本方法中,本方法内部会自动根据reqCharset参数进行
* <code>URLEncoder.encode()</code>
* @see 解码响应正文时,默认取响应头[Content-Type=text/html;
* charset=GBK]字符集,若无Content-Type,则使用UTF-8解码
* @param reqURL
* 请求地址
* @param reqParams
* 请求正文数据
* @param reqCharset
* 请求报文的编码字符集(主要针对请求参数值含中文而言)
* @return
* reqMsg-->HTTP请求完整报文,respMsg-->HTTP响应完整报文,respMsgHex-->HTTP响应的原始字节的十六进制表示
*/
public Map<String, String> sendPostRequest(String reqURL,
Map<String, String> reqParams, String reqCharset, FormFile[] files) {
StringBuilder reqData = new StringBuilder();
for (Map.Entry<String, String> entry : reqParams.entrySet()) {
try {
reqData.append(entry.getKey())
.append("=")
.append(URLEncoder.encode(entry.getValue(), reqCharset))
.append("&");
} catch (UnsupportedEncodingException e) {
System.out.println("编码字符串[" + entry.getValue()
+ "]时发生异常:系统不支持该字符集[" + reqCharset + "]");
reqData.append(entry.getKey()).append("=")
.append(entry.getValue()).append("&");
}
}
if (reqData.length() > 0) {
reqData.setLength(reqData.length() - 1); // 删除最后一个&符号
} return sendPostRequest(reqURL, reqData.toString(), reqCharset, files);
} /**
* 发送HTTP_POST请求
*
* @see you can see {@link HTTPUtil#sendPostRequest(String, Map, String)}
* @see 注意:若欲直接调用本方法,切记请求参数值含中文时,一定要对该参数值
* <code>URLEncoder.encode(value, reqCharset)</code>
* @see 注意:这里只是对key=value中的
* 'value'进行encode,而非'key='..encode完毕后,再组织成key=newValue传给本方法
*/
public Map<String, String> sendPostRequest(String reqURL, String reqData,
String reqCharset, FormFile[] files) {
int fileDataLength = 0;
if (files != null && files.length > 0) {
for (FormFile uploadFile : files) {// 得到文件类型数据的总长度
StringBuilder fileExplain = new StringBuilder();
fileExplain.append("--");
fileExplain.append(BOUNDARY);
fileExplain.append("\r\n");
fileExplain.append("Content-Disposition: form-data;name=\""
+ uploadFile.getParameterName() + "\";filename=\""
+ uploadFile.getFilname() + "\"\r\n");
fileExplain.append("Content-Type: "
+ uploadFile.getContentType() + "\r\n\r\n");
fileExplain.append("\r\n");
fileDataLength += fileExplain.length();
fileDataLength += uploadFile.getFile().length();
}
}
Map<String, String> respMap = new HashMap<String, String>();
OutputStream out = null; // 写
InputStream in = null; // 读
Socket socket = null; // 客户机
String respMsg = null;
String respCharset = "UTF-8";
StringBuilder reqMsg = new StringBuilder(); try {
URL sendURL = new URL(reqURL);
String host = sendURL.getHost();
int port = sendURL.getPort() == -1 ? 80 : sendURL.getPort();
/**
* 创建Socket
*
* @see
* --------------------------------------------------------------
* -------------------------------------
* @see 通过有参构造方法创建Socket对象时
* ,客户机就已经发出了网络连接请求,连接成功则返回Socket对象,反之抛IOException
* @see 客户端在连接服务器时,也要进行通讯,客户端也需要分配一个端口,这个端口在客户端程序中不曾指定
* @see 这时就由客户端操作系统自动分配一个空闲的端口,默认的是自动的连续分配
* @see 如服务器端一直运行着,而客户端不停的重复运行,就会发现默认分配的端口是连续分配的
* @see 即使客户端程序已经退出了,系统也没有立即重复使用先前的端口
* @see socket = new Socket(host, port);
* @see
* --------------------------------------------------------------
* -------------------------------------
* @see 不过,可以通过下面的方式显式的设定客户端的IP和Port
* @see socket = new Socket(host, port,
* InetAddress.getByName("127.0.0.1"), 8765);
* @see
* --------------------------------------------------------------
* -------------------------------------
*/
socket = new Socket();
/**
* 设置Socket属性
*/
// true表示关闭Socket的缓冲,立即发送数据..其默认值为false
// 若Socket的底层实现不支持TCP_NODELAY选项,则会抛出SocketException
socket.setTcpNoDelay(true);
// 表示是否允许重用Socket所绑定的本地地址
socket.setReuseAddress(true);
// 表示接收数据时的等待超时时间,单位毫秒..其默认值为0,表示会无限等待,永远不会超时
// 当通过Socket的输入流读数据时,如果还没有数据,就会等待
// 超时后会抛出SocketTimeoutException,且抛出该异常后Socket仍然是连接的,可以尝试再次读数据
socket.setSoTimeout(30000);
// 表示当执行Socket.close()时,是否立即关闭底层的Socket
// 这里设置为当Socket关闭后,底层Socket延迟5秒后再关闭,而5秒后所有未发送完的剩余数据也会被丢弃
// 默认情况下,执行Socket.close()方法,该方法会立即返回,但底层的Socket实际上并不立即关闭
// 它会延迟一段时间,直到发送完所有剩余的数据,才会真正关闭Socket,断开连接
// Tips:当程序通过输出流写数据时,仅仅表示程序向网络提交了一批数据,由网络负责输送到接收方
// Tips:当程序关闭Socket,有可能这批数据还在网络上传输,还未到达接收方
// Tips:这里所说的"未发送完的剩余数据"就是指这种还在网络上传输,未被接收方接收的数据
socket.setSoLinger(true, 5);
// 表示发送数据的缓冲区的大小
socket.setSendBufferSize(1024);
// 表示接收数据的缓冲区的大小
socket.setReceiveBufferSize(1024);
// 表示对于长时间处于空闲状态(连接的两端没有互相传送数据)的Socket,是否要自动把它关闭,true为是
// 其默认值为false,表示TCP不会监视连接是否有效,不活动的客户端可能会永久存在下去,而不会注意到服务器已经崩溃
socket.setKeepAlive(true);
// 表示是否支持发送一个字节的TCP紧急数据,socket.sendUrgentData(data)用于发送一个字节的TCP紧急数据
// 其默认为false,即接收方收到紧急数据时不作任何处理,直接将其丢弃..若用户希望发送紧急数据,则应设其为true
// 设为true后,接收方会把收到的紧急数据与普通数据放在同样的队列中
socket.setOOBInline(true);
// 该方法用于设置服务类型,以下代码请求高可靠性和最小延迟传输服务(把0x04与0x10进行位或运算)
// Socket类用4个整数表示服务类型
// 0x02:低成本(二进制的倒数第二位为1)
// 0x04:高可靠性(二进制的倒数第三位为1)
// 0x08:最高吞吐量(二进制的倒数第四位为1)
// 0x10:最小延迟(二进制的倒数第五位为1)
socket.setTrafficClass(0x04 | 0x10);
// 该方法用于设定连接时间,延迟,带宽的相对重要性(该方法的三个参数表示网络传输数据的3项指标)
// connectionTime--该参数表示用最少时间建立连接
// latency---------该参数表示最小延迟
// bandwidth-------该参数表示最高带宽
// 可以为这些参数赋予任意整数值,这些整数之间的相对大小就决定了相应参数的相对重要性
// 如这里设置的就是---最高带宽最重要,其次是最小连接时间,最后是最小延迟
socket.setPerformancePreferences(2, 1, 3);
/**
* 连接服务端
*/
// 客户端的Socket构造方法请求与服务器连接时,可能要等待一段时间
// 默认的Socket构造方法会一直等待下去,直到连接成功,或者出现异常
// 若欲设定这个等待时间,就要像下面这样使用不带参数的Socket构造方法,单位是毫秒
// 若超过下面设置的30秒等待建立连接的超时时间,则会抛出SocketTimeoutException
// 注意:如果超时时间设为0,则表示永远不会超时
socket.connect(new InetSocketAddress(host, port), 30000);
/**
* 构造HTTP请求报文
*/
reqMsg.append("POST ").append(sendURL.getPath())
.append(" HTTP/1.1\r\n");
reqMsg.append("Cache-Control: no-cache\r\n");
reqMsg.append("Pragma: no-cache\r\n");
reqMsg.append("User-Agent: JavaSocket/")
.append(System.getProperty("java.version")).append("\r\n");
reqMsg.append("Host: ").append(sendURL.getHost()).append("\r\n");
reqMsg.append("Accept: text/html, image/gif, image/jpeg, *; q=.2, */*; q=.2\r\n");
reqMsg.append("Connection: keep-alive\r\n");
String cookiesStr = CookieUtil.getCookie(context);
if (!StringUtil.isNullOrEmpty(cookiesStr))
reqMsg.append("Cookie:" + cookiesStr + "\r\n");
reqMsg.append(
"Content-Type: application/x-www-form-urlencoded; charset=")
.append(reqCharset).append("\r\n");
reqMsg.append("Content-Length: ")
.append(reqData.getBytes().length + fileDataLength)
.append("\r\n");
reqMsg.append("\r\n");
reqMsg.append(reqData);
/**
* 发送HTTP请求
*/
out = socket.getOutputStream();
// 这里针对getBytes()补充一下:之所以没有在该方法中指明字符集(包括上面头信息组装Content-Length的时候)
// 是因为传进来的请求正文里面不会含中文,而非中文的英文字母符号等等,其getBytes()无论是否指明字符集,得到的都是内容一样的字节数组
// 所以更建议不要直接调用本方法,而是通过sendPostRequest(String, Map<String, String>,
// String)间接调用本方法
// sendPostRequest(.., Map,
// ..)在调用本方法前,会自动对请求参数值进行URLEncoder(注意不包括key=value中的'key=')
// 而该方法的第三个参数reqCharset只是为了拼装HTTP请求头信息用的,目的是告诉服务器使用哪种字符集解码HTTP请求报文里面的中文信息
out.write(reqMsg.toString().getBytes());
// 把所有文件类型的实体数据发送出来
if (fileDataLength > 0) {
for (FormFile uploadFile : files) {
StringBuilder fileEntity = new StringBuilder();
fileEntity.append("--");
fileEntity.append(BOUNDARY);
fileEntity.append("\r\n");
fileEntity.append("Content-Disposition: form-data;name=\""
+ uploadFile.getParameterName() + "\";filename=\""
+ uploadFile.getFilname() + "\"\r\n");
fileEntity.append("Content-Type: "
+ uploadFile.getContentType() + "\r\n\r\n");
out.write(fileEntity.toString().getBytes());
byte[] buffer = new byte[1024];
int len = 0;
while ((len = uploadFile.getInStream()
.read(buffer, 0, 1024)) != -1) {
if (isStop) {
break;
}
out.write(buffer, 0, len);
}
uploadFile.getInStream().close();
out.write("\r\n".getBytes());
}
}
/**
* 接收HTTP响应
*/
in = socket.getInputStream();
// 事实上就像JDK的API所述:Closing a ByteArrayOutputStream has no effect
// 查询ByteArrayOutputStream.close()的源码会发现,它没有做任何事情,所以其close()与否是无所谓的
ByteArrayOutputStream bytesOut = new ByteArrayOutputStream();
byte[] bs = readStream(in);
bytesOut.write(bs, 0, bs.length);
System.out.println(new String(bs, "utf-8"));
/**
* 解码HTTP响应的完整报文
*/
respMsg = bytesOut.toString(respCharset);
} catch (Exception e) {
System.out.println("与[" + reqURL + "]通信遇到异常,堆栈信息如下");
e.printStackTrace();
} finally {
if (null != socket && socket.isConnected() && !socket.isClosed()) {
try {
// 此时socket的输出流和输入流也都会被关闭
// 值得注意的是:先后调用Socket的shutdownInput()和shutdownOutput()方法
// 值得注意的是:仅仅关闭了输入流和输出流,并不等价于调用Socket.close()方法
// 通信结束后,仍然要调用Socket.close()方法,因为只有该方法才会释放Socket占用的资源,如占用的本地端口等
socket.close();
} catch (IOException e) {
System.out.println("关闭客户机Socket时发生异常,堆栈信息如下");
e.printStackTrace();
}
}
}
respMap.put("reqMsg", reqMsg.toString());
respMap.put("respMsg", respMsg);
return respMap;
} public static byte[] readStream(InputStream inStream) throws Exception {
int count = 0;
while (count == 0) {
count = inStream.available();
}
byte[] b = new byte[count];
inStream.read(b);
return b;
}
上传附件类FormFile
public class FormFile {
/* 上传文件的数据 */
private InputStream inStream;
private File file;
/* 文件名称 */
private String filename;
/* 请求参数名称*/
private String parameterName;
/* 内容类型 */
private String contentType = "application/octet-stream"; public FormFile(String filname, File file, String parameterName, String contentType) {
this.filename = filname;
this.parameterName = parameterName;
this.file = file;
try {
this.inStream = new FileInputStream(file);
} catch (FileNotFoundException e) {
e.printStackTrace();
}
if(contentType!=null) this.contentType = contentType;
} public File getFile() {
return file;
} public InputStream getInStream() {
return inStream;
} public String getFilname() {
return filename;
} public void setFilname(String filname) {
this.filename = filname;
} public String getParameterName() {
return parameterName;
} public void setParameterName(String parameterName) {
this.parameterName = parameterName;
} public String getContentType() {
return contentType;
} public void setContentType(String contentType) {
this.contentType = contentType;
}
HttpURLConnection 文件上传限制的更多相关文章
- HTTP POST请求报文格式分析与Java实现文件上传
时间 2014-12-11 12:41:43 CSDN博客 原文 http://blog.csdn.net/bboyfeiyu/article/details/41863951 主题 HTTPHt ...
- ANDROID使用MULTIPARTENTITYBUILDER实现类似FORM表单提交方式的文件上传
最近在做 Android 端文件上传,要求采用 form 表单的方式提交,项目使用的 afinal 框架有文件上传功能,但是始终无法与php写的服务端对接上,无法上传成功.读源码发现:afinal 使 ...
- HttpClient文件上传下载
1 HTTP HTTP 协议可能是如今 Internet 上使用得最多.最重要的协议了,越来越多的 Java 应用程序须要直接通过 HTTP 协议来訪问网络资源. 尽管在 JDK 的 java.net ...
- 从原理角度解析Android (Java) http 文件上传
转载请标明出处:http://blog.csdn.net/lmj623565791/article/details/23781773 文件上传是我们项目中经常使用的功能,一般我们的服务器可能都是web ...
- 微信录音文件上传到服务器以及amr转化成MP3格式
微信公众号音频接口开发 根据业务需求,我们可能需要将微信录音保存到服务器,而通过微信上传语音接口上传到微信服务器的语音文件的有效期只有3天,所以需要将文件下载到我们自己的服务器. 上传语音接口 wx. ...
- 基于cxf的app文件上传接口(带回显功能)
1.SaleImpl @Override public String uploadPic(final List<Attachment> attachments) { return this ...
- 构建multipart/form-data实现文件上传
构建multipart/form-data实现文件上传 通常文件上传都是通过form表单中的file控件,并将form中的content-type设置为multipart/form-data.现在我们 ...
- 微信录音文件上传到服务器以及amr转化成MP3格式,linux上转换简单方法
微信公众号音频接口开发 根据业务需求,我们可能需要将微信录音保存到服务器,而通过微信上传语音接口上传到微信服务器的语音文件的有效期只有3天,所以需要将文件下载到我们自己的服务器. 上传语音接口 wx. ...
- Android 实现文件上传功能(upload)
文 件上传在B/S应用中是一种十分常见的功能,那么在Android平台下是否可以实现像B/S那样的文件上传功能呢?答案是肯定的.下面是一个模拟网站程 序上传文件的例子.这里只写出了Android部分的 ...
随机推荐
- 一步一步学WebSocket(二) 使用SuperWebSocket实现自己的服务端
上一篇文章,我们了解了客户端如何与服务器创建WebSocket连接.但是一个巴掌拍不响,既然是通信,就必然最少要有两个端.今天我们来看看c#如何用已有的框架实现一个WebSocket服务端. 在.Ne ...
- 自定义Java集合
一.泛型 1.在JDK1.4以前,所有的集合元素全都按照Object来存储,拿出来还要进行强制转型.由于这样的做法有太多的缺点,容易出现ClassCaseException,不安全,让人不省心,于是乎 ...
- [测]jieba分词
import jieba import os import jieba.analyse with open('src.txt', 'r') as file: data = file.read() se ...
- js event 事件冒泡和事件捕获详细介绍
. 参考: http://www.jb51.net/article/42492.htm 图: 假设一个元素div,它有一个下级元素p.<div> <p>元素</p> ...
- ffmpeg 音频转换: use ffmpeg convert the audio from stereo to mono without changing the video part
To convert the audio from stereo to mono without changing the video part, you can use FFmpeg: ffmpeg ...
- 尝试一下代码高亮。。成功的话明天写一篇blog
using System; using System.Collections; using UnityEngine; public class Time : MonoBehaviour { // Us ...
- 解决跑twoBitToFa时出现“/admin/exe/linux.x86_64/twoBitToFa: Permission denied”的问题
出现这种问题时,一般要加上以下命令: chmod ugo+x ./admin/exe/linux.x86_64/twoBitToFa 运行成功后,再将twobit格式转化为fa格式 ./admin/e ...
- [翻译]:怎样从C/C++代码中对C#进行回调
声明:网络上类似的中文博客大有存在,本人知识水平有限,业余爱好,也是为了备份收藏How to make a callback to C# from C/C++ code 本着共享知识的初衷,翻译一份给 ...
- Python模块:itertools
itertools模块:循环器 一,无穷循环器:count,cycle,repeat (1)count(5,3) #从5开始的整数循环器,每次增加3,即:5,8,11,14,17... from it ...
- [python](爬虫)如何使用正确的姿势欣赏知乎的“长得好看是怎样一种体验呢?”问答中的相片
从在知乎关注了几个大神,我发现我知乎的主页画风突变.经常会出现 ***长得好看是怎样一种体验呢? 不用***,却长得好看是一种怎样的体验? 什么样***作为头像? ... 诸如此类的问答.点进去之后发 ...