客户端协议处理框架

目录

一、框架的主要类

1.URL类

2.URLConnection类

3.HttpURLConnection

二、基于通信框架自定义客户端处理程序


java.net包下提供了一套客户端协议的处理框架,这个框架封装了Socket,它是在传输层之上对客户程序的通信过程进行了抽象,提供了通过的应用层协议处理框架,比如基于这个框架可以实现处理HTTP协议客户端。

一、框架的主要类

  • URL:统一资源定位器,表示客户程序要访问的远程资源;
  • URLConnection:表示客户程序与远程服务器的连接;
  • URLStreamHandler:协议处理器,主要负责创建与协议相关的URLConntecion对象;
  • ContentHandler:内容处理器,负责解析服务器发送的数据,把他转化为相应的Java对象
  • URLStreamHandlerFactory:实例化URLStreamHandler的工厂类;
  • ContentHandlerFactory:实例化ContentHandler的工厂类。

如下为上述几个类耦合的关系:

Java为协议处理框架提供了基于HTTP的实现。

1.URL类

URL类用将URL地址进行了封装,通过该URL地址可以解析出协议、主机名、端口号、定位的资源文件等等信息。我们可以通过如下方式创建一个URL对象:

URL url = new URL("URL地址")

在通过构造方法创建URL对象时,它内部会先解析出协议(protocol)、 主机名(host)、端口号(port)、需要访问的该主机上的资源以及创建一个与协议匹配的URLStreamHandler实例。创建URLStreamHandler的源码如下:

static URLStreamHandler getURLStreamHandler(String protocol) {
    //1.handlers为Hashtable<String,URLStreamHandler>
    URLStreamHandler handler = handlers.get(protocol);
    if (handler == null) {
        boolean checkedWithFactory = false;

        //2.如果已经设置了URLStreamHandlerFactory。
        if (factory != null) {
            handler = factory.createURLStreamHandler(protocol);
            checkedWithFactory = true;
        }

  
        if (handler == null) {
            String packagePrefixList = null;

            // 3.根据系统属性java.protocol.handler.pkgs决定URLStreamHandler子类的名字,并尝试对其实例化。
            //protocolPathProp = "java.protocol.handler.pkgs";
            packagePrefixList = java.security.AccessController.doPrivileged(
                    new sun.security.action.GetPropertyAction(protocolPathProp,""));
            if (packagePrefixList != "") {
                packagePrefixList += "|";
            }
            //4.拼接字符串,再下面循环中会最后做处理
            packagePrefixList += "sun.net.www.protocol";
            //上面利用"|"拼接字符串,此处将"|"作为分隔标记。
            StringTokenizer packagePrefixIter = new StringTokenizer(packagePrefixList, "|");
            //packagePrefixIter可以得到"|"分隔后的字符串(hasMoreTokens用于判断是否还有分隔出来的下一个元素)
            while (handler == null && packagePrefixIter.hasMoreTokens()) {
                //比如上述packagePrefixList若为com.abc.net.www|sun.net.www.protocol
                //则,此处遍历的packagePrefix 即为com.abc.net.www和sun.net.www.protocol
                String packagePrefix = packagePrefixIter.nextToken().trim();    
                try {
                    String clsName = packagePrefix + "." + protocol +".Handler";
                    Class<?> cls = null;
                    try {
                        cls = Class.forName(clsName);
                    } catch (ClassNotFoundException e) {
                        ClassLoader cl = ClassLoader.getSystemClassLoader();
                        if (cl != null) {
                            cls = cl.loadClass(clsName);
                        }
                    }
                    if (cls != null) {
                        handler  = (URLStreamHandler)cls.newInstance();    
                    }
                } catch (Exception e) {
                        // any number of exceptions can get thrown here
                }
            }
        }

        synchronized (streamHandlerLock) {
            URLStreamHandler handler2 = null;
            handler2 = handlers.get(protocol);
            if (handler2 != null) {
                return handler2;
            }
            if (!checkedWithFactory && factory != null) {
                handler2 = factory.createURLStreamHandler(protocol);
            }
            if (handler2 != null) {
                handler = handler2;
            }
            //将该协议对应的URLStreamHandler对象存入缓存中
            if (handler != null) {
                handlers.put(protocol, handler);
            }
        }
    }

    return handler;
}

大致流程如下:

  1. 如果在URL缓存中已经存在该协议对应的URLStreamHandler实例,则直接从缓存中获取。(该类中维护一个Hashtable<String,URLStreamHandler>用来做缓存);
  2. 如果程序已经通过URL类的静态方法setURLStreamHandlerFactory()设置了URLStreamHandlerFactory接口的具体实现类,则通过这个工厂类的createURLHandlerFactory()方法构造URLStreamHandler实例;
  3. 通过系统属性java.protocol.handler.pkgs来决定URLStreamHandler具体子类的名字,然后对其实例化。
  4. 如果失败则实例化位于sun.net.www.protocol包中的sun.net.www.protocol.协议名.Handler类,如果失败则会抛出MalformedURLException异常。(3和4是在循环内操作的)。

URL类的方法如下:

  • openConnection():创建并返回一个URLConnection对象,这个openConnection()方法实际上是通过调用 URLStreamHandler类的openConnection()方法来创建的。
  • openStream():返回用于读取服务器发送数据的输入流,该方法实际上通过调用URLConnection类的getInputStream()方法获得输入流。
  • getContent()方法:返回包装了服务器发送数据的Java对象,实际调用URLConnection的getContent()方法,而它又调用了ContentHandler类的getContent()方法。
//URL类:
public final Object getContent() throws java.io.IOException {
    return openConnection().getContent();
}
//URLConnection类:
public Object getContent() throws IOException {
    getInputStream();
    return getContentHandler().getContent(this);
}

可以看到URL中核心就是解析了URL地址,并且通过构造方法创造URLStreamHandler,上面这几个方法都是利用URLConnection来做的,我们可以只需利用URL的openConnection()方法获取URLConnection对象,然后操作该类中的方法即可,该类提供了可以向远程服务器发送数据和得到响应数据的方法。

2.URLConnection类

URLConnection类表示客户程序与远程服务器的连接。URLConnection有两个boolean类型的属性以及相应的get和set方法。

  • doInput属性:若为true,表示允许获得输入流,读取远程服务器发送得数据,该属性得默认值为true;
  • doOutput属性:若为true,表示允许获得输出流,向远程服务器发送数据。该属性得默认值为false

URLConnection类提供了读取远程服务器得响应数据得一系列方法。

  • getHeaderField(String name):返回响应头中参数name指定得属性值。
  • getContentType():返回响应正文的类型。如果无法获取响应正文的类型,则返回null。对于HTTP响应结果,在响应头中可能会包含响应正文的类型信息。
  • getContentLength():返回响应正文的长度。若无法获取,则返回“-1”.
  • getContentEncoding():返回响应正文的编码类型,若无法获取,则返回null。

使用URLConnection建议按照如下步骤:

  1. 通过在URL上调用openConnection方法创建连接对象。
  2. 设置一般请求属性和请求参数。
  3. 使用connect方法实现与远程对象的实际连接。
  4. 读取响应数据。

下面是一个客户端的简单程序示例:

public static void main(String[] args) throws IOException {
    String target = "http://localhost:9090/SpringMVC/test.jsp";
    URL url = new URL(target);
    // 1.创建连接对象
    URLConnection conn = url.openConnection();

    // 2.设置一般请求属性和请求参数。(如果没有可以不设置,但是如果需要设置则必须在connect()方法之前,否则抛出异常)
    // 设置不能使用缓存
    conn.setUseCaches(false);
    conn.setRequestProperty("Connection", "keep-alive");
    conn.setRequestProperty("Content-Type", "application/json;charset=UTF-8");
    //发送POST请求,get请求直接在URL后面拼接"name=value"字符串参数即可。
    //只不过POST请求将"name=value"字符串参数通过流发送。此处发送json请求参数,那么传输json字符串即可。
    conn.setDoOutput(true);
    OutputStream requestOut = conn.getOutputStream();
    String requestParam = JSON.toJSONString(new User("张三丰", "139929991XX"));
    requestOut.write(requestParam.getBytes("UTF-8"));
    requestOut.close();
    // 超时设置,防止网络异常情况下,可能会导致程序僵死而不继续往下执行
    // 设置连接服务器的超时时间
    conn.setConnectTimeout(30000);
    // 设置从服务器读取数据的超时时间
    conn.setReadTimeout(30000);

    // 3.实现与远程对象的实际连接。(这里也可以省略,当我们获取远程对象中的数据时内部会先建立连接发送请求)
    conn.connect();

    // 4.可以访问头字段和远程对象的内容
    // 获取响应正文类型
    String contentType = conn.getContentType();
    // 获取响应正文长度
    int len = conn.getContentLength();
    // 读取响应正文
    InputStream in = conn.getInputStream();
    byte[] bytes = new byte[1024];
    in.read(bytes);
    in.close();
    String content = new String(bytes, Charset.forName("UTF-8"));

    System.out.println("响应正文类型:" + contentType);
    System.out.println("响应正文长度:" + len);
    System.out.println("响应正文:\n" + content);
}

 服务端JSP页面如下:

<!DOCTYPE html>
<html>
	<head>
		<meta charset="UTF-8">
	<title>测试页面</title>
	</head>
	<body>
		<%
			byte[] bytes = new byte[request.getContentLength()];
			request.getInputStream().read(bytes);
			String charEncoding = request.getCharacterEncoding();
			String jsonStr = new String(bytes,charEncoding);
			JSONObject user = JSON.parseObject(jsonStr);
		%>
		<h1>姓名:<%=user.getString("name") %></h1>
		<h1>电话:<%=user.getString("phone") %></h1>
	</body>
</html>

客户端程序输出结果如下:

3.HttpURLConnection

上述URLConnection是为我们自定义通信协议处理通信连接的底层支持,如果需要自定义某个协议的客户端程序我们继承URLConnection即可,不过上述之所以能够访问Http服务器,是因为Java提供了处理HTTP协议的客户端程序。其中HttpURLConnection就是Http协议的通信连接,它可以建立与HTTP服务器的连接发送请求和接收响应。所以其实是由于底层通过sun.net.www.protocol.http.Handler类(URLStreamHandler)为我们创造了HttpURLConnection对象,所以上面的操作都是基于此对象的,每个HttpURLConnection实例只能发送一个请求,可发送GET请求和POST请求。

HttpURLConnection在URLConnection的基础上针对HTTP协议提供了更加便捷的API:

  • String getResponseMethod():获取发送请求的方法。
  • void setRequestMethod(String method):设置发送请求的方法。
  • int getResponseCode():获取服务器的响应代码。
  • String getResponseMessage():获取服务器的响应消息。

二、基于通信框架自定义客户端处理程序

首先需要实现EchoURLStreamHandlerFactory,这是URL能够解析自己定义的协议的基础,如”echo“协议。

public class EchoURLStreamHandlerFactory implements URLStreamHandlerFactory {

	@Override
	public URLStreamHandler createURLStreamHandler(String protocol) {
		if ("echo".equals(protocol)) {
			return new EchoURLStreamHandler();
		}
		return null;
	}
}
public class EchoURLStreamHandler extends URLStreamHandler {
	@Override
	protected URLConnection openConnection(URL u) throws IOException {
            return new EchoURLConnection(u);
	}
}

提前(在构造URL对象前)设置好EchoURLStreamHandlerFactory对象,构造URL对象时,会通过构造参数URL地址获取到EchoURLStreamHandler,通过它的openConnection()方法得到自定义的URLConnection连接服务器,发送请求和接收响应。自定义EchoURLConnection如下:

public class EchoURLConnection extends URLConnection {
	private Socket connection;
	public final static int DEFAULT_PORT = 54199;

	protected EchoURLConnection(URL url) {
		super(url);

	}

	@Override
	public void connect() throws IOException {
		if (this.connected) {
			return;
		}
		connection = new Socket();
		connection.connect(new InetSocketAddress(url.getHost(), url.getPort()), this.getConnectTimeout());
		this.connected = true;
	}

	@Override
	public InputStream getInputStream() throws IOException {
		synchronized (EchoURLConnection.class) {
			if (!this.connected) {
				connect();
			}
			return connection.getInputStream();
		}
	}

	@Override
	public OutputStream getOutputStream() throws IOException {
		synchronized (EchoURLConnection.class) {
			if (!this.connected) {
				connect();
			}
			return connection.getOutputStream();
		}
	}
    
        @Override
	public String getContentType() {
		return "text/plain";
	}

	// 断开连接
	public void disconnect() throws IOException {
		if (this.connected) {
			this.connected = false;
			this.connection.close();
		}
	}
}

如果需要使用URLConnection中的getContent()方法获取响应内容,那么上面的getContentType()方法重写是必要的。URLConnection中定义的getContent()方法定义如下:

public Object getContent() throws IOException {
    getInputStream();
    return getContentHandler().getContent(this); 
}

首先获取ContentHandler(),然后调用其getContent()方法。getContentHandler()的逻辑如下:

//URLConnection类:
synchronized ContentHandler getContentHandler() throws UnknownServiceException{
    //注意这里调用的是URLConnection自己的getContentType()方法.
    //该方法默认实现是通过getHeaderField()方法获取"content-type"响应头
    //而getHeaderField(String name)默认实现是返回null。所以如果不重写getContentType()方法那么下面将会抛出UnknownServiceException异常
    String contentType = stripOffParameters(getContentType());
    ContentHandler handler = null;
    if (contentType == null)
        throw new UnknownServiceException("no content-type");
    try {
        //首先从缓存中查找有没有对应的ContentHandler,若有则直接返回。
        handler = handlers.get(contentType);
        if (handler != null)
            return handler;
    } catch(Exception e) {}
    //若没有则从工厂中查找该类型对应的ContentHandler(故必须提前设置ContentHandlerFactory)
    if (factory != null)
        handler = factory.createContentHandler(contentType);
    if (handler == null) {
        try {
            handler = lookupContentHandlerClassFor(contentType);
        } catch(Exception e) {
            e.printStackTrace();
            handler = UnknownContentHandler.INSTANCE;
        }
        handlers.put(contentType, handler);
    }
    return handler;
}

EchoContentHandlerEchohContentHandlerFactory

如果不需要使用URLConnection的getContent()方法,那么这两个类也可以不实现。

public class EchohContentHandlerFactory implements ContentHandlerFactory {
    @Override
    public ContentHandler createContentHandler(String mimetype) {
        if ("text/plain".equals(mimetype)) {
	    return new EchoContentHandler();
	}
	return null;
    }
}
public class EchoContentHandler extends ContentHandler {
    @Override
    public Object getContent(URLConnection connection) throws IOException {
        //获取服务器的响应,将其转换为字符串对象
        return new DataInputStream(connection.getInputStream()).readUTF();
    }
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值