本帖最后由 爱我吧 于 2012-12-3 23:08 编辑
前两天研究了一下串口摄像头PTC06,现在将PTC06拍摄的照片上传到服务器。
我的服务器端用的是JSP。
JSP实现文件上传还算简单了,只要做个JSP页面,放上个form,设置一下enctype和post,再加个type为file的input就可以上传了。
但是Arduino是没有form、input的之类的HTML标记的。那应该怎么实现上传呢?
其实,上传的过程,无非是客户端与服务器建立了一个通道,然后通过这个通道传送数据罢了。
那么,Arduino如何与服务器建立通道呢?
Ethernet Shield提供了EthernetClient类,可以使用这个类连接服务器。比如: - //访问服务器资源的客户端
- EthernetClient client;
复制代码
下一步就是发送数据。数据可不是随便发送的,是有规则的。例如下面的JSP页面提交时,可以看到其发送的数据:
其发送的数据格式如上图下半部分所示。这个查看HTTP头的工具叫:ieHTTPHeaders。其实,只要把这个头复制一下,放到Arduino中就能使用了。例如: -
- if(client.connect(server, 8080)) //连接服务器8080端口
- {
- Serial.println("Connect to Server");
- boundary = "---------------------------" + String(millis(), HEX);//用当前的微秒构造一个boundary
- contentLine[0] = boundary;
- contentLine[1] = "Content-Disposition: form-data; name="field1"";//上传的第一个数据的名称
- contentLine[2] = "";
- contentLine[3] = field1_value;//上传的第一个数据的值
- contentLine[4] = boundary;
- contentLine[5] = "Content-Disposition: form-data; name="field2"";//上传的第二个数据的名称
- contentLine[6] = "";
- contentLine[7] = field_value;//上传的第二个数据的值
- contentLine[8] = boundary;
- contentLine[9] = "Content-Disposition: form-data; name="field3"";//上传的第三个数据的名称
- contentLine[10] = "";
- contentLine[11] = field3_value;//上传的第三个数据的值
- contentLine[12] = boundary;
-
- if(picture_send)//如果要上传图片
- {
- contentLine[13] = "Content-Disposition: form-data; name="file"; filename="somefile.jpg"";//上传图片的名字随便起
- contentLine[14] = "Content-Type: image/pjpeg";//上传图片的类型,当然,如果上传的是其它类型的话,改为对应的type即可,则个可以在网上查
- }
- else
- {
- contentLine[13] = "Content-Disposition: form-data; name="file"; filename=""";
- contentLine[14] = "Content-Type: application/octet-stream";
- }
-
- contentLine[15] = "";
- contentLength = 0;//计算提交时内容的长度
- for(uint8_t i=0;i<CONTENT_LENGTH;i++)
- {
- contentLength += contentLine[i].length();
- }
- //额外加上图片的大小、结束符的长度和每一行的回车换行(一行2个字符)
- contentLength += ((cinema_send)?pictureLength:0) + (boundary.length() + 2) + ((CONTENT_LENGTH+2) * 2);
- client.println("POST /Security/servlet/InfoServlet HTTP/1.1");
- client.println("Accept: application/x-ms-application, image/jpeg, application/xaml+xml, image/gif, image/pjpeg, application/x-ms-xbap, */*");
- client.println("Referer: http://localhost:8080/Security/");
- client.println("Accept-Language: zh-CN");
- client.println("User-Agent: Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 6.1; WOW64; Trident/5.0; SLCC2; .NET CLR 2.0.50727; .NET CLR 3.5.30729; .NET CLR 3.0.30729; Media Center PC 6.0; .NET4.0C; .NET4.0E)");
- client.println("Content-Type: multipart/form-data; boundary=" + boundary);
- client.println("Accept-Encoding: gzip, deflate");
- client.println("Host: 172.16.1.31");
- client.println("Content-Length: " + String(contentLength, DEC));
- client.println("Connection: Keep-Alive");
- client.println("Cache-Control: no-cache");
- //先将固定的信息输出出去
- client.println();
- for(uint8_t i=0;i<CONTENT_LENGTH;i++)
- {
- client.println(contentLine[i]);
- }
- if(picture_send){
- //输出图片的信息
- while((readed = cinema->getBytes(buffer, CINEMA_BUFFER_LENGTH))>0)
- {//cinema是我自己封装的PTC06的类库
- client.write(buffer, readed);
- }
- }
- client.println();
- //输出结束boundary
- client.println(boundary + "--");
- client.println();
- Serial.println();
- }
- else
- {
- //如果不能创建连接
- client.stop();
- Serial.println("Connect Faild");
- }
复制代码
之后,在Servlet中只要对request的InputStream对象进行处理就行了。但是,不能用getParameter直接处理的,需要根据boundary依次访问,获取参数的name和参数的value。如果是上传的文件的话,则需要将其bytes写入单独的文件。下面是Servlet的doPost方法的代码:
[pre lang="JSP" line="1"]
request.setCharacterEncoding("UTF-8");
// 用于存储值的HashMap
HashMap<String, String> data = new HashMap<String, String>();
// Content-Disposition对应的K-V
String name = "";//用于获取提交时参数的名字
String filename = "";//用户获取提交时文件名
String tmpLine;//每次读取到的行
int len;//对于文件,每次读取到的长度
byte buffer[] = new byte[1024];// 1Kb,对于文件,读取时的缓存
String contentType = request.getContentType();//获取提交(请求)的type,对应着Arduino中的那个“Content-Type”,用于获取boundary
int contentLength = request.getContentLength();//获取提交(请求)的长度
String boundary = contentType.substring(contentType.indexOf("boundary=") + "boundary=".length()).trim();//把Arduino提交的boundary拿出来
String endBoudary = boundary + "--";//这是结束的boundary
// 获取客户端的输入流
ServletInputStream sis = request.getInputStream();
OutputStream os = null;
StringBuilder sb = null;
while ((len = sis.readLine(buffer, 0, buffer.length)) != -1) {//每次读取一行
tmpLine = new String(buffer, 0, len);
if (tmpLine.trim().startsWith(boundary)) {// 这是一段数据的开始,也可能是请求结束
// 完成上一个数据的读取
if (os != null) {//如果上一个数据是文件,则关闭输出流
data.put(name, filename);//存储相应的数据
os.close();
os = null;
}
if (sb != null) {//如果是简单的文本数据
data.put(name, sb.toString());//存储相应的数据
sb = null;
}
if (!tmpLine.trim().equals(endBoudary)) {//如果是新的数据
// 接着把其下一行的数据读出来
len = sis.readLine(buffer, 0, buffer.length);
tmpLine = new String(buffer, 0, len);
int start = tmpLine.indexOf("name=\"") + "name=\"".length();
int end = tmpLine.indexOf("\"", start);
name = tmpLine.substring(start, end);//获取参数的名字
if (tmpLine.indexOf("filename=\"") != -1) {//如果包含filename,则把文件名一起拿出来
start = tmpLine.indexOf("filename=\"") + "filename=\"".length();
end = tmpLine.indexOf("\"", start);
filename = tmpLine.substring(start, end);
}
// 再度一行
len = sis.readLine(buffer, 0, buffer.length);
tmpLine = new String(buffer, 0, len);
// 如果包含Content-Type,说明需要准备上传文件,否则,使用StringBuilder缓存简单数据
if (tmpLine.startsWith("Content-Type")) {
if (!filename.isEmpty()) {//如果在提交数据时,没有指定上传的文件,则filename为空字符串。这里需要进行判断的
Calendar c = Calendar.getInstance();//在服务器端构造以月、日为名字的目录结构,避免把所有的图片都放在一个文件夹下
String dir = this.getServletContext().getRealPath("/picture");
String year = String.valueOf(c.get(Calendar.YEAR));
String month = String.valueOf(c.get(Calendar.MONTH) + 1);
File d = new File(dir, year);
if (!d.exists()) {
d.mkdir();
}
d = new File(d, month);
if (!d.exists()) {
d.mkdir();
}
String fn = Toolkit.nextUUID() + filename.substring(filename.lastIndexOf("."));//这里在保存文件的时候,扩展名使用上传时文件的扩展名,即filename的扩展名
filename = "picture" + "/" + year + "/" + month + "/" + fn;
os = new FileOutputStream(new File(d, fn));
}
// 再读一个空行
sis.readLine(buffer, 0, buffer.length);
} else {
// 读出来的就是一个空行
sb = new StringBuilder();
}
}
} else {
if (os != null) {
os.write(buffer, 0, len);
}
if (sb != null) {
sb.append(tmpLine);
}
}
}[/code]
好了,这样即可以实现Arduino上传图片至服务器了。 |