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部分的 ...
随机推荐
- gulp 使用介绍
gulp 使用介绍 gulp gulp 插件 gulp的配置文件gulpfile.js gulp 语法 gulp 实例 gulp的缺点 gulp gulp是基于Node.js的前端自动化构建工具,主要 ...
- Android 二维码 生成和识别(附Demo源码)
今天讲一下目前移动领域很常用的技术——二维码.现在大街小巷.各大网站都有二维码的踪迹,不管是IOS. Android.WP都有相关支持的软件.之前我就想了解二维码是如何工作,最近因为工作需要使用相关技 ...
- 几个opencv 的iOS的编译问题解决
一个iOS项目需要用到opencv,而且要支持arm64的,以前有个demo的,只支持32位的.到官网下载了最新支持64位库,结果编译无法通过. google了好久也没法解决,后来问了一个同事,找出原 ...
- 数字信号处理--Z变换,傅里叶变换,拉普拉斯变换
傅立叶变换.拉普拉斯变换.Z变换最全攻略 作者:时间:2015-07-19来源:网络 傅立叶变换.拉普拉斯变换.Z变换的联系?他们的本质和区别是什么?为什么要进行这些变换.研究的都是什么? ...
- mybatis, spring, springmvc
mybatis配置: mybatis-config.xml <configuration> <!-- 作者MyBatis博客: http://legend2011.blog.51ct ...
- chapter1 渗透测试与metasploit
网络对抗技术课程学习 chapter1 渗透测试与metasploit 一.读书笔记 二.渗透测试 通过模拟恶意攻击者的技术与方法进行攻击,挫败目标系统安全控制措施,取得访问控制权,并发现具备业务影响 ...
- ORACLE 获取程序当前位置的方法
FUNCTION f_Get_Program_Position RETURN VARCHAR2 IS l_Owner ); l_Name ); l_Lineno NUMBER; l_Type ); B ...
- Halcon学习之条形码实时扫描
dev_open_window(1,1,400,400,'blue',ThisHandle) create_bar_code_model([], [], BarCodeHandle) set_bar_ ...
- Python第一模块
一.Python简介 二.Python种类 三.Python环境 windows: 1.需要配置环境变量 2.更新:卸载重装 linux:1.常用命令: 查看默认Python版本 Python -V ...
- NIO vs. BIO
性能测试 BIO -- Blocking IO 即阻塞式IO NIO -- Non-Blocking IO, 即非阻塞式IO或异步IO 性能 -- 所谓的性能是指服务器响应客户端的能力,对于服务器我们 ...