• 技术文章 >Java >java教程

    Java URL自定义私有网络协议

    小葫芦小葫芦2017-01-05 17:03:42原创1330
    ——声明,脑残人士远离,本博客的核心不是if-else+前缀,而是如何通过URL协议处理框架定义私有协议

    URI与URL的区别

    URI (uniform resource identifier)统一资源标志符;URL(uniform resource location )统一资源定位符(或统一资源定位器);URI是一个相对来说更广泛的概念,URL是URI的一种,是URI命名机制的一个子集,可以说URI是抽象的,而具体要使用URL来定位资源。URI指向的一般不是物理资源路径,而是整个系统中的映射后的资源标识符。URL是Internet上用来描述信息资源的字符串,主要用在各种WWW客户程序和服务器程序上。采用URL可以用一种统一的格式来描述各种信息资源,包括文件、服务器的地址和目录等。

    一.先来序言一段

    我们习惯了http

    URL url=new URL(<a href="http://www.apptest.com:8080/test/ios.php">http://www.apptest.com:8080/test/ios.php</a>);

    我们也要习惯

    当然,我们还要让URL习惯我们

    "https", "ftp", "mailto", "telnet", "file", "ldap", "gopher", "jdbc", "rmi", "jndi", "jar", "doc", "netdoc", "nfs", "verbatim", "finger", "daytime", "systemresource"
    URL url=new URL("oschina://www.apptest.com:8080/test/ios.php");

    如果不习惯,总会出现如下异常

    java.net.MalformedURLException: unknown protocol

    在Android浏览器使用Ajax时也会不支持没有定义的过的协议。


    二.协议的自定义的理解

    协议:在编程的世界里,协议本身就是一套Input/ouput约束规则,因此,我们确切的协议应该围绕I/O展开的,所以,这里的协议可以称为I/O协议。

    协议发起方:request

    协议响应方:response

    协议成立的条件是:request和reponse认可同一套协议,并按照协议约束进行通信。


    三.自定义协议与URL的关系

    在java中,自定义协议一定需要用URL吗?

    答案是否定的。

    事实上,围绕I/O,我们的规则定义完全有我们本身掌握,并没有说离开URL地球不转了,Java要毁灭了。

    为什么使用URL类来自定义协议?

    答案是因为URL是一套成熟的协议通信处理框架。

    这里说的自定义URL协议,实质上更多的是通过已有的规则进行扩充协议。

    四.URL自定义私有协议实战

    我们知道,自定义协议需要Response 和Request,双方需要充理解对方的协议。这里为了方便起见,我们使用Http协议服务器来作为Response。

    这里我们使用了Ngnix服务器+PHP+FastCGI来构建Reponse,部署代码如下

    1.定义Response

    <?php$raw_post_data = file_get_contents('php://input', 'r');
    echo "-------\$_POST------------------\n<br/>";
    echo var_dump($_POST) . "\n";
     echo "-------php://input-------------\n<br/>";
    echo $raw_post_data . "\n<br/>";
     $rs = json_encode($_SERVER);file_put_contents('text.html',$rs);echo '写入成功';

    2.定义Request

    2.1实现URLStreamHandlerFactory工厂,主要用来产生协议处理器

    public class EchoURLStreamHandlerFactory implements URLStreamHandlerFactory {
    public URLStreamHandler createURLStreamHandler(String protocol){
    //通过这里的分流处理不同的schema请求,当然脑残人士认为这里才是核心代码,URL是一套协议处理框架,如果if-else就是核心,是不是oracle要倒闭 
    if(protocol.equals("echo") || protocol.equals("oschina"))   {
       return new EchoURLStreamHandler(); //实例化协议处理Handler 
     } 
    return null; }}

    2.2实现URLStreamHandler,主要作用是生成协议对应的连接器

    public class EchoURLStreamHandler extends URLStreamHandler {
    @Overrideprotected URLConnection openConnection(URL u) throws IOException {
    return new EchoURLConnection(u);
    //在这里我们也可以进行相应的分流}
    }

    2.3 实现URLConnection,作用是协议通信规则的自定义,这里我们使用HTTP协议作为通信规则,我们这里仿制http协议请求

    (以下才是核心代码,这里借用的http协议,当然你可以用websocket,smtp,ftp各种协议进行交互,而不是脑残人士让我承认的 if-else+URL前缀)

    public class EchoURLConnection extends URLConnection {
    private Socket connection = null;
    public final static int DEFAULT_PORT = 80;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);
    // true表示关闭Socket的缓冲,立即发送数据..其默认值为false//
    若Socket的底层实现不支持TCP_NODELAY选项,则会抛出SocketExceptionthis.connection.setTcpNoDelay(true);
    // 表示是否允许重用Socket所绑定的本地地址this.connection.setReuseAddress(true);
    // 表示接收数据时的等待超时时间,单位毫秒..其默认值为0,表示会无限等待,永远不会超时
    // 当通过Socket的输入流读数据时,如果还没有数据,就会等待
    // 超时后会抛出SocketTimeoutException,且抛出该异常后Socket仍然是连接的,可以尝试再次读数据this.connection.setSoTimeout(30000);
    // 表示当执行Socket.close()时,是否立即关闭底层的Socket
    // 这里设置为当Socket关闭后,底层Socket延迟5秒后再关闭,而5秒后所有未发送完的剩余数据也会被丢弃
    // 默认情况下,执行Socket.close()方法,该方法会立即返回,但底层的Socket实际上并不立即关闭
    // 它会延迟一段时间,直到发送完所有剩余的数据,才会真正关闭Socket,断开连接
    // Tips:当程序通过输出流写数据时,仅仅表示程序向网络提交了一批数据,由网络负责输送到接收方
    // Tips:当程序关闭Socket,有可能这批数据还在网络上传输,还未到达接收方
    // Tips:这里所说的"未发送完的剩余数据"就是指这种还在网络上传输,未被接收方接收的数据this.connection.setSoLinger(true, 5);
    // 表示发送数据的缓冲区的大小this.connection.setSendBufferSize(1024);
    // 表示接收数据的缓冲区的大小this.connection.setReceiveBufferSize(1024);
    // 表示对于长时间处于空闲状态(连接的两端没有互相传送数据)的Socket,是否要自动把它关闭,true为是
    // 其默认值为false,表示TCP不会监视连接是否有效,不活动的客户端可能会永久存在下去,而不会注意到服务器已经崩溃this.connection.setKeepAlive(true);
    // 表示是否支持发送一个字节的TCP紧急数据,socket.sendUrgentData(data)用于发送一个字节的TCP紧急数据
    // 其默认为false,即接收方收到紧急数据时不作任何处理,直接将其丢弃..若用户希望发送紧急数据,则应设其为true
    // 设为true后,接收方会把收到的紧急数据与普通数据放在同样的队列中this.connection.setOOBInline(true);
    // 该方法用于设置服务类型,以下代码请求高可靠性和最小延迟传输服务(把0x04与0x10进行位或运算)
    // Socket类用4个整数表示服务类型// 0x02:低成本(二进制的倒数第二位为1)
    // 0x04:高可靠性(二进制的倒数第三位为1)// 0x08:最高吞吐量(二进制的倒数第四位为1)
    // 0x10:最小延迟(二进制的倒数第五位为1)this.connection.setTrafficClass(0x04 | 0x10);
    // 该方法用于设定连接时间,延迟,带宽的相对重要性(该方法的三个参数表示网络传输数据的3项指标)
    // connectionTime--该参数表示用最少时间建立连接
    // latency---------该参数表示最小延迟
    // bandwidth-------该参数表示最高带宽// 可以为这些参数赋予任意整数值,这些整数之间的相对大小就决定了相应参数的相对重要性/
    // 如这里设置的就是---最高带宽最重要,其次是最小连接时间,最后是最小延迟this.connection.setPerformancePreferences(2, 1, 3);this.connected = true;StringBuilder sb = new StringBuilder();sb.append("POST " + url.getPath() + " HTTP/1.1 \r\n");
    //if(url.getPort()<0 || url.getPort()>65536){sb.append("Host:").append(url.getHost()).append("\r\n");}else{sb.append("Host:").append(url.getHost()).append(":").append(url.getPort()).append("\r\n");}sb.append("Connection:keep-alive\r\n");sb.append("Date:Fri, 22 Apr 2016 13:17:35 GMT\r\n");sb.append("Vary:Accept-Encoding\r\n");sb.append("Content-Type: application/x-www-form-urlencoded,charset=utf-8\r\n");sb.append("Content-Length: ").append("name=zhangsan&password=123456".getBytes("UTF-8").length).append("\r\n");sb.append("\r\n");this.connection.getOutputStream().write(sb.toString().getBytes("UTF-8"));}}public synchronized void disconnect() throws IOException {if (connected) {this.connection.close();this.connected = false

    ;}}}

    在这里,协议定义已经完成。

    我们测试代码如下

    尝试连接 oschina://localhost:8080/test/ios.php

    URL.setURLStreamHandlerFactory(new EchoURLStreamHandlerFactory());
    // URLConnection.setContentHandlerFactory(new EchoContentHandlerFactory());
    URL url=new URL("oschina://localhost:8080/test/ios.php");
    EchoURLConnection connection=(EchoURLConnection)url.openConnection();
    connection.setDoOutput(true);connection.setDoInput(true);
    PrintWriter pw = new PrintWriter(new OutputStreamWriter(connection.getOutputStream()));
    pw.write("name=zhangsan&password=123456");pw.flush();
     InputStream stream = connection.getInputStream();   
     int len = -1;  byte[] buf = new byte[256]; 
    while((len=stream.read(buf, 0, 256))>-1)  {
     String line = new String(buf, 0, len); 
    if(line.endsWith("\r\n0\r\n\r\n")&&len<256)   {   
         //服务器返回的是Transfer-chunked编码,\r\n0\r\n\r\n表示读取结束了,chunked编码解析:http://dbscx.iteye.com/blog/830644  line = line.substring(0, line.length()-"\r\n0\r\n\r\n".length());
     System.out.println(line); 
    break; 
    }else{  System.out.println(line);
      }
     } 
     pw.close();
    stream.close();

    运行结果

    QQ图片20170105113603.png

    结果说明,协议确实定义成功了


    当然,如上数据解析不符合我们的要求,因为是chunked编码信息,如何解析符合要求有,请移步:

    HTTP Chunked数据编码与解析算法

    五.后话,自定义mineType解析器

    java中提供了ContentHandlerFactory,用来解析mineType,我们这里制定我们自己的解析器,当然,JDK中提供的更丰富,这里所做的只是为了符合特殊需求

    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;}}

    用法很简单

    URLConnection.setContentHandlerFactory(new EchoContentHandlerFactory());

    更多Java URL自定义私有网络协议相关文章请关注PHP中文网!

    声明:本文原创发布php中文网,转载请注明出处,感谢您的尊重!如有疑问,请联系admin@php.cn处理
    专题推荐:Java URL
    上一篇:深入理解JAVA多线程之线程间的通信方式 下一篇:java解析php函数json_encode unicode 编码问题
    线上培训班

    相关文章推荐

    • 理解java8中java.util.function.*pojo反射新方法(附代码)• 浅析安卓app和微信授权登录及分享完整对接(代码分享)• 一招教你使用java快速创建Map(代码分享)• 教你一招搞定时序数据库在Spring Boot中的使用• 一文讲解Java中初始化List集合的8种方式(附代码)

    全部评论我要评论

  • 取消发布评论发送
  • 1/1

    PHP中文网