客户端和服务端通讯的N种方式(四)

HTTP是大多数应用程序中常用的与服务端交互的通讯方式。在上一篇文章中我们介绍了Ophone SDK中比较简单的一种HTTP通讯API:HttpGet和HttpPost。实际上,在Ophone SDK中还有另外一套HTTP通讯API:HttpURLConnection。这套API也可以使用在基于Java的桌面或Web应用程序中。因此,如果想设计一套通用的基于HTTP的API,建议使用HttpURLConnection。通过HTTP可以传递任何形式的数据。这要比通过基于XML的WebService更灵活,传递的数据类型更广泛。例如,可以直接通过HTTP传递二进制数据,而无需对其进行编码。

HttpURLConnection类

 java.net.HttpURLConnection类是另外一种访问HTTP资源的方式。HttpURLConnection类具有完全的访问能力,可以取代HttpGet和HttpPost类。使用HttpUrlConnection访问HTTP资源可以使用如下几步:

1.  使用java.net.URL封装HTTP资源的url,并使用openConnection方法获得HttpUrlConnection对象,代码如下:

view plain copy to clipboard print ?
  1. URL url = new URL("http://www.blogjava.net/nokiaguy/archive/2009/12/14/305890.html");  
  2. HttpURLConnection httpURLConnection = (HttpURLConnection) url.openConnection();  

2.  设置请求方法,例如,GET、POST等,代码如下:

view plain copy to clipboard print ?
  1. httpURLConnection.setRequestMethod("POST");  

要注意的是,setRequestMethod方法的参数值必须大写,例如,GET、POST等。

3.  设置输入输出及其他权限。如果要下载HTTP资源或向服务端上传数据,需要使用如下的代码进行设置。

view plain copy to clipboard print ?
  1. //  下载HTTP资源,需要将setDoInput方法的参数值设为true  
  2. httpURLConnection.setDoInput(true);  
  3. //  上传数据,需要将setDoOutput方法的参数值设为true  
  4. httpURLConnection.setDoOutput(true);  

HttpURLConnection类还包含了更多的选项,例如,使用下面的代码可以禁止HttpURLConnection使用缓存。
 

view plain copy to clipboard print ?
  1. httpURLConnection.setUseCaches(false);  

4.  设置HTTP请求头。在很多情况下,要根据实际情况设置一些HTTP请求头,例如,下面的代码设置了Charset请求头的值为UTF-8。

view plain copy to clipboard print ?
  1. httpURLConnection.setRequestProperty("Charset""UTF-8");  

5.  输入和输出数据。这一步是对HTTP资源的读写操作。也就是通过InputStream和OutputStream读取和写入数据。下面的代码获得了InputStream对象和OutputStream对象。

view plain copy to clipboard print ?
  1. InputStream is = httpURLConnection.getInputStream();  
  2. OutputStream os = httpURLConnection.getOutputStream();  

至于是先读取还是先写入数据,需要根据具体情况而定。


6.  关闭输入输出流。虽然关闭输入输出流并不是必须的,在应用程序结束后,输入输出流会自动关闭。但显式关闭输入输出流是一个好习惯。关闭输入输出流的代码如下:

view plain copy to clipboard print ?
  1. Is.close();  
  2. os.close();  

上传文件

通过HttpUrlConnection可以和服务端直接进行二进制数据的交互。那么在本文给出一个上传文件的例子。通过对本例的学习,读者可以了解如何与服务端进行二进制交互。
本程序可以将手机上的文件上传到服务端。服务端程序是一个Servlet,部署完服务端程序后,启动Tomcat,在浏览器地址栏中输入如下的URL:

http://localhost:8080/upload/upload.jsp

如果在浏览器中显示如图1所示的页面,说明服务端程序已经安装成功。这个服务端程序负责接收客户端上传的文件,并将成功上传的文件保存在D:\upload目录中,如果该目录不存在,系统会自动创建该目录。读者可以使用图1所示的页面上传一个文件,观察一下效果。

图1  上传文件的页面


下面我们来实现OPhone版的文件上传客户端。浏览文件的效果如图2所示,当单击一个文件时,系统会上传该文件,上传成功后的效果如图3所示。读者可以在D:\upload目录看到上传的文件。

图2  浏览SD卡中的文件

图3 成功上传文件 


实现本例的关键是了解文件上传的原理。为了分析文件上传的原理,我们使用了HttpAnalyzer来截获图4所示的页面上传文件的HTTP请求信息。从【stream】标签页可以看到原始的HTTP请求信息,如图4所示。

图4  上传文件的HTTP请求信息


从图4可以看出,上传文件的HTTP请求信息分为如下4部分。

  • 分界符。由两部分组成:两个连字符“--”和一个任意字符串。使用浏览器上传文件一般为“-----------------数字”。分界符为单独一行。
  • 上传文件的相关信息。这些信息包括但不限于请求参数名、上传文件名、文件类型。例如,Content-Disposition: form-data; name="file"; filename="abc.jpg"。
  • 上传文件的内容。字节流形式。
  • 文件全部上传后的结束符。这个符号在图4中并没有显示出来。当上传的文件是最后一个时,在HTTP请求信息的结尾就会出现这个符号字符串。结束符和分界符类似,只是在分界符后面再加两个连字符,例如,“-----------------------------------218813199810322--”就是一个结束符。

当单击图1所示的列表中的某个文件时,会调用SD卡浏览组件的onFileItemClick事件方法,在该方法中负责上传当前单击的文件,代码如下:

view plain copy to clipboard print ?
  1. public void onFileItemClick(String filename)  
  2. {  
  3.     //  192.168.17.156是PC的IP地址,读者需要将这个IP换成自己机器的IP  
  4.     String uploadUrl = "http://192.168.17.82:8080/upload/UploadServlet";  
  5.     String end = "\r\n";  
  6.     String twoHyphens = "--";               //  两个连字符  
  7.     String boundary = "******";             //  分界符的字符串  
  8.     try  
  9.     {  
  10.         URL url = new URL(uploadUrl);  
  11.         HttpURLConnection httpURLConnection = (HttpURLConnection) url.openConnection();  
  12.         //  要想使用InputStream和OutputStream,必须使用下面两行代码  
  13.         httpURLConnection.setDoInput(true);  
  14.         httpURLConnection.setDoOutput(true);  
  15.         httpURLConnection.setUseCaches(false);  
  16.         //  设置HTTP请求方法,方法名必须大写,例如,GET、POST  
  17.         httpURLConnection.setRequestMethod("POST");  
  18.         httpURLConnection.setRequestProperty("Connection""Keep-Alive");  
  19.         httpURLConnection.setRequestProperty("Charset""UTF-8");  
  20.         //  必须在Content-Type请求头中指定分界符中的任意字符串  
  21.         httpURLConnection.setRequestProperty("Content-Type",  
  22.                 "multipart/form-data;boundary=" + boundary);  
  23.         //  获得OutputStream对象,准备上传文件  
  24.         DataOutputStream dos = new DataOutputStream(httpURLConnection.getOutputStream());  
  25.         //  设置分界符,加end表示为单独一行  
  26.         dos.writeBytes(twoHyphens + boundary + end);  
  27.         //  设置与上传文件相关的信息  
  28.         dos.writeBytes("Content-Disposition: form-data; name=\"file\"; filename=\""  
  29.                         + filename.substring(filename.lastIndexOf("/") + 1) + "\"" + end);  
  30.         //  在上传文件信息与文件内容之间必须有一个空行  
  31.         dos.writeBytes(end);  
  32.         //  开始上传文件    
  33.         FileInputStream fis = new FileInputStream(filename);  
  34.         byte[] buffer = new byte[8192]; // 8k  
  35.         int count = 0;  
  36.         //  读取文件内容,并写入OutputStream对象    
  37.         while ((count = fis.read(buffer)) != -1)  
  38.         {  
  39.             dos.write(buffer, 0, count);  
  40.         }  
  41.         fis.close();  
  42.         //  新起一行   
  43.         dos.writeBytes(end);  
  44.         //  设置结束符号(在分界符后面加两个连字符)  
  45.         dos.writeBytes(twoHyphens + boundary + twoHyphens + end);  
  46.         dos.flush();  
  47.         //  开始读取从服务端传过来的信息  
  48.         InputStream is = httpURLConnection.getInputStream();  
  49.         InputStreamReader isr = new InputStreamReader(is, "utf-8");  
  50.         BufferedReader br = new BufferedReader(isr);  
  51.         String result = br.readLine();  
  52.         Toast.makeText(this, result, Toast.LENGTH_LONG).show();  
  53.         dos.close();  
  54.         is.close();  
  55.     }  
  56.     catch (Exception e)  
  57.     {  
  58.     }  
  59. }  


在编写上面代码时应注意如下3点:

  • 在本例中分界符中的任意字符串使用了“******”,而不是浏览器使用的“---------------”。
  • 分界符中的任意字符串必须在Content-Type请求头中指定,好让服务端可以获得完整的分界符。
  • 在上传文件信息与上传文件内容之间必须有一个空行。

直接传输可序列化对象

我们曾经讲过,通过编码的方式传递可序列化的对象。但这是在WebService中。而且由于受到XML的限制,只能用这种方式传递二进制数据。但直接通过HTTP进行数据传输,就可以直接采用二进制的传输方式。例如,可以直接使用writeObject和readObject来发送或接受对象。当然,仍然可以采用编码的方式来传递对象,操作过程与WebService类似。而在这里我们主要介绍如何直接通过HTTP传递可序列化的对象。
如果服务端使用Java,那么最容易的方式就是编写一个Servlet。下面的Servlet负责接受一个Product对象,并在控制台输出Product对象中的属性值。

 

view plain copy to clipboard print ?
  1. package net.binclass;  
  2.   
  3. import java.io.IOException;  
  4. import java.io.InputStream;  
  5. import java.io.ObjectInputStream;  
  6. import javax.servlet.ServletException;  
  7. import javax.servlet.http.HttpServlet;  
  8. import javax.servlet.http.HttpServletRequest;  
  9. import javax.servlet.http.HttpServletResponse;  
  10.   
  11. public class MyServlet extends HttpServlet  
  12. {  
  13.   
  14.     @Override  
  15.     protected void service(HttpServletRequest request,  
  16.             HttpServletResponse response) throws ServletException, IOException  
  17.     {  
  18.   
  19.         InputStream is = request.getInputStream();  
  20.   
  21.         ObjectInputStream ois = new ObjectInputStream(is);  
  22.         try  
  23.         {  
  24.             //  通过ObjectInputStream对象的readObject方法获得Product对象  
  25.             Product product = (Product) ois.readObject();  
  26.             System.out.println("product.id:" + product.getId());  
  27.             System.out.println("product.name:" + product.getName());  
  28.         }  
  29.         catch (Exception e)  
  30.         {  
  31.             System.out.println(e.getMessage());  
  32.         }  
  33.   
  34.     }  
  35.   
  36. }  

Product类的代码如下:

 

view plain copy to clipboard print ?
  1. package net.binclass;  
  2. import java.io.Serializable;  
  3. public class Product implements Serializable  
  4. {  
  5.     private int id;  
  6.     private String name;  
  7.   
  8.     public int getId()  
  9.     {  
  10.         return id;  
  11.     }  
  12.   
  13.     public void setId(int id)  
  14.     {  
  15.         this.id = id;  
  16.     }  
  17.   
  18.     public String getName()  
  19.     {  
  20.         return name;  
  21.     }  
  22.   
  23.     public void setName(String name)  
  24.     {  
  25.         this.name = name;  
  26.     }  
  27. }  

下面来看一下Ophone客户端的代码。

 

view plain copy to clipboard print ?
  1. package net.binclass;  
  2.   
  3. import java.io.ObjectOutputStream;  
  4. import java.net.HttpURLConnection;  
  5. import java.net.URL;  
  6.   
  7. import android.app.Activity;  
  8. import android.os.Bundle;  
  9. import android.view.View;  
  10.   
  11. public class Main extends Activity  
  12. {  
  13.   
  14.     @Override  
  15.     public void onCreate(Bundle savedInstanceState)  
  16.     {  
  17.         super.onCreate(savedInstanceState);  
  18.         setContentView(R.layout.main);  
  19.     }  
  20.   
  21.     public void onClick_Transmit(View view)  
  22.     {  
  23.         try  
  24.         {  
  25.             URL url = new URL("http://192.168.17.82:8080/binclass/MyServlet");  
  26.             HttpURLConnection httpURLConnection = (HttpURLConnection) url  
  27.                     .openConnection();  
  28.             // 要想使用InputStream和OutputStream,必须使用下面两行代码  
  29.   
  30.             httpURLConnection.setDoOutput(true);  
  31.   
  32.             // 设置HTTP请求方法,方法名必须大写,例如,GET、POST  
  33.             httpURLConnection.setRequestMethod("POST");  
  34.             httpURLConnection.setRequestProperty("Connection""Keep-Alive");  
  35.   
  36.             ObjectOutputStream oos = new ObjectOutputStream(httpURLConnection  
  37.                     .getOutputStream());  
  38.             Product product = new Product();  
  39.             product.setId(3456);  
  40.             product.setName("OPhone 2.0手机");  
  41.             oos.writeObject(product);  
  42.             httpURLConnection.getInputStream();  
  43.   
  44.             oos.flush();  
  45.             oos.close();  
  46.   
  47.         }  
  48.         catch (Exception e)  
  49.         {  
  50.   
  51.         }  
  52.   
  53.     }  
  54. }  


上面的代码和上传文件的代码类似。只是获得了向服务端输出对象的ObjectOutputStream对象,并使用writeObject方法直接将对象传输到了服务端。这是不是很方便呢,而无需再进行字节和编码的转换。但要注意,这种方法一般只适合于服务端和客户端都使用Java来编写的情况。单击模拟器界面上的如图5所示的按钮,就会看到图6所示的控制台中输出的Product对象的属性值。

图5  传输可序列化对象的OPhone客户端

图6  Eclipse中Tomcat的Console
 

总结


本文主要介绍了HttpUrlConnection以及如何使用HttpUrlConnection来传输二进制文件。例如,上传任意的文件,以及直接传输可序列化的对象。但要注意,即使在服务端没有返回任何数据的情况下,仍然要调用HttpUrlConnection的getInputStream方法,否则客户端不会向服务端发送请求。

你可能感兴趣的:(客户端和服务端通讯的N种方式(四))