Java 对客户程序的通信过程进行了抽象,提供了通用的协议处理框架,该框架封装了 Socket,主要包括以下类:
以上类都位于 java.net 包,除 URL 类为具体类,其余的都是抽象类,对于一种具体的协议,需要创建相应的具体子类。Oracle 公司为协议处理框架提供了基于 HTTP 的实现,它们都位于 JDK 类库的 sun.net.www 包或者其子包
下例的 HtpClient 类利用 URL 类创建了一个简单的 HTTP 客户程序,先创建了一个 URL 对象,然后通过它的 openStream()
方法获得一个输入流,接下来就从这个输入流中读取服务器发送的响应结果
public class HttpClient {
public static void main(String args[]) throws IOException {
//http是协议符号
URI url = new URL("http://www.javathinker.net/hello.htm");
//接收响应结果
InputStream in = url.openStream();
ByteArrayOutputStream buffer = new ByteArrayOutputStream();
bytel] buff = new byte[1024];
int len = -l;
while((len = in.read(buff)) != -1) {
buffer.write(buff, 0, len);
}
//把字节数组转换为字符串
System.out.println(new String(buffer.toByteArray()));
}
}
URL 类的构造方法创建 URLStreamHandler 实例的流程如下:
如果在 URL 缓存已经存在这样的 URLStreamHandler
实例,则无须再创建,否则继续执行下一步
如果程序通过 URL 类的静态 setURLStreamHandlerFactory()
方法设置了 URLStreamHandlerFactory
接口的具体实现类,那么就通过这个工厂类的 createURLStreamHandler()
方法来构造 URLStreamHandler
实例,否则继续执行下一步
根据系统属性 java.prolocol.handler.pkgs
来决定 URLStreamHandler
具体子类的名字,然后对其实例化,假定运行 HttpClient 的命令为:
java -Djava.protocol.handler.pkgs=com.abc.net.www | net.javathinker.protocols HttpClient
以上命令中的 -D 选项设定系统属性,会先查找并试图实例化 com.abc.net.www.http.Handler
类,如果失败,再试图实例化 net.javathinkerprotocols.http.Handler
类,如果以上操作都失败,那么继续执行下一步
试图实例化位于 sun.net.www.prolocol
包的 sun.netwww.protocol.协议名.Handler
类,如果失败,URL 构造方法就会抛出 MalforedURLException。在本例协议名是 http,会试图实例化 sun.net.www.protocol.http.Handler
类
URL 类具有以下方法:
openConnection()
:创建并返回一个 URLConnection
对象,这个 openConnection()
方法实际上是通过调用 URLStreamHandler
类的 openConnection()
方法,来创建 URLConnection
对象openStream()
:返回用于读取服务器发送数据的输入流,该方法实际上通过调用 URLConnection
类的 getInputStream()
方法来获得输入流getContent()
:返回包装了服务器发送数据的 Java 对象,该方法实际上调用 URLConnection
类的 getContent)
方法,而 URLConnection
类的 getContent()
方法又调用了 ContentHandler
类的 getContent()
方法URLConnection 类表示客户程序与远程服务器的连接,URLConnection 有两个 boolean 类型的属性以及相应的 get 和 set 方法:
URLConnection 类提供了读取远程服务器的响应数据的一系列方法:
getHeaderField(String name)
:返回响应头中参数 name 指定的属性的值getContentType()
:返回响应正文的类型,如果无法获取响应正文的类型就返回 nullgetContentLength()
:返回响应正文的长度,如果无法获取响应正文的长度,就返回 -1getContentEncoding()
:返回响应正文的编码类型,如果无法获取响应正文的编码类型,就返回 null下例的 HtpClient 类利用 URLConnection 类来读取服务器的响应结果
public class HttpClient {
public static void main(String args[]) throws IOException {
URL url = new URL("http://www,javathinkernet/hello.htm");
URLConnection connection = url.openConnection();
//接收响应结果
System.out.printIn("正文类型:" + connection.getContentType());
System.out.printIn("正文长度:" + connection.getContentLength());
//读取响应正文
InputStream in = connection.getInputStream();
ByteArrayOutputStream buffer = new ByteArrayOutputStream();
byte[] buff = new byte[1024];
int len = -l;
while((len = in.read(buff)) != -1) {
buffer.write(buff, 0, len);
}
//把字节数组转换为字符串
System.out.println(new String(buffer.toByteArray()));
}
}
本节将为用户自定义的 ECHO 协议实现处理框架,共创建了以下类:
EchoURLConnection 类封装了一个 Socket,在 connect() 方法中创建与远程服务器连接的 Socket 对象
public class EchoURLConnection extends URLConnection {
private Socket connection = null;
public final static int DEFAULT PORT = 8000;
public EchoURLConnection(URL url) {
super(url);
}
public synchronized InputStream getInputStream() throws IOException {
if(!connected) connect();
return connection.getInputStream();
}
public synchronized OutputStream getOutputStream() throws IOException {
if(!connected) connect();
return connection.getOutputStream();
}
public String getContentType() {
return "text/plain";
}
public synchronized void connect() throws IOException {
if(!connected) {
int port = url.getPort();
if(port < 0 || port > 65535) port = DEFAULT_PORT;
this.connection = new Socket(url.getHost(), port);
this.connected = true;
}
}
public synchronized void disconnect() throws IOException {
if(connected) {
//断开连接
this.connection.close();
this.connected = false;
}
}
}
EchoURLStreamHandler 类的 openConnection()
方法负责创建一个 EchoURLConnection 对象
public class EchoURLStreamHandler extends URLStreamHandler {
public int getDefaultPort() {
return 8000;
}
protected URLConnection openConnection(URL url) throws IOException {
return new EchoURLConnection(url);
}
}
EchoURLStreamHandlerFactory 类的 createURLStreamHandle()
方法负责构造 EchoURLStreamHandler 实例
public class EchoURLStreamHandlerFactory implements URLStreamhandlerFactory {
public URLStreamHandler createURLStreamHandler(String protocol) {
if(protocol.equals("echo"))
return new EchoURLStreamHandler();
else
return null;
}
}
在客户程序中,可以通过以下方式设置 EchoURLStreamHandlerFactory
URL.setURLStreamHandlerFactory(new EchoURLStreamHandlerFactory());
URL url=new URL("echo://localhost:8000");
URLConnection 类还提供了 getContent()
方法,它有两种重载形式:
public Object getContent();
public Object getContent(Class[] classes);
第二个 getContent() 方法把服务器发送的数据优先转换为 classes 数组第一个元素指定的类型,如果转换失败,再尝试转换第二个元素指定的类型,以此类推
下例 HttpClient 演示处理服务器发送的数据
public class HttpClient {
public static void main(String args[]) throws IOException {
URL url = new URL("http://www,javathinker.net/hello.htm");
URlConnection connection = url.openConnection();
//接收响应结果
InputStream in = connection.getInputStream();
Class[] types = {String.class, InputStream.class};
Object obj = connection.getContent(types);
if(obj instanceof String) {
System.out.println(obj);
} else if(obj instanceof InputStream) {
in = (InputStream) obj;
FileOutputStream file = new FileOutputStream("data");
byte[] buff = new byte[1024];
int len = -l;
while((len = in.read(buff)) != -1) {
file.write(buff, 0 ,len);
}
System.out.println("正文保存完毕");
} else {
System.out.println("未知的响应正文类型");
}
}
}
EchoContentHandler 类负责处理 EchoServer 服务器发送的数据
public class EchoContentHandler extends ContentHandler {
/** 读取服务器发送的一行数据,把它转换为字符串对象 */
public Object getContent(URLConnection connection) throws IOException {
InputStream in = connection.getInputStream();
BufferedReader br = new BufferedReader(new InputStreamReader(in));
return br.readLine();
}
public Object getContent(URLConnection connection, Class[] classes) throws IOException {
InputStream in = connection.getInputStream();
for(int i = 0; i < classes.length; i++) {
if(classes[i] == InputStream.class) {
return in;
} else if(classes[i] == String.class) {
return getContent(connection);
}
}
return null;
}
}
第二个 getContent() 方法依次遍历 classes 参数中的元素,判断元素是否为 InputSuream 类型或 String 类型,如果是,就返回相应类型的对象,它包含了服务器发送的数据。如果 classes 参数中的元素都不是 InputStream 类型或 String 类型,就返回 null
EchoContentHandlerFactory 类的 createContentHandler() 方法负责创建一个EchoContentHandler 对象
public class EchoContentHandlerFactory implements ContentHandlerFactory {
public ContentHandler createContentHandler(String mimetype) {
if(mimetype.equals("text/plain")) {
return new EchoContentHandler();
} else {
return null;
}
}
}
在客户程序中,可以通过以下方式设置 EchoContentHandlerFactory
URLConnection.setContentHandlerFactory(new EchoContentHandlerFactory());
URL url = new URL("echo://localhost:8000");
EchoURLConnection connection = (EchoURLConnection)url.openConnection();
...
//读取服务器返回的数据,它被包装为一个字符串对象
String echoMsg = (String)connection.getContent();
public class EchoClient {
public static void main(String args[]) throws IOException {
//设置URLStreamHandlerFactory
URL.setURLStreamHandlerFactory(new EchoURLStreamHandlerFactory());
//设置ContentHandlerFactory
URLConnection.setContentHandlerFactory(new EchoContentHandlerFactory());
URL url = new URL("echo://localhost:8000");
EchoURLConnection connection = (EchoURlConnection) url.openConnection();
//允许获得输出流
connection.setDoOutput(true);
//获得输出流
PrintWriter pw = new PrintWriter(connection.getOutputStream(), true);
while(true) {
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
String msg = br.readLine();
//向服务器发送消息
pw.println(msg);
//读取服务器返回的消息
String echoMsg = (String) connection.getContent();
System.out.println(echoMsg);
if(echoMsg.equals("echo:bye")) {
//断开连接
connection.disconnect();
break;
}
}
}
}