Java URL自定义私有网络协议
——声明,脑残人士远离,本博客的核心不是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(http://www.apptest.com:8080/test/ios.php);
我们也要习惯
当然,我们还要让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();
运行结果
结果说明,协议确实定义成功了
当然,如上数据解析不符合我们的要求,因为是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自定义私有网络协议
-
对象的串行化 博客分类: java JavaScript数据结构网络协议thread
-
java 开发中网络编程之IP、URL详解及实例代码
-
java 开发中网络编程之IP、URL详解及实例代码
-
Java网络编程URL和URI 博客分类: Java 编程网络应用Java网络协议Scheme
-
Java网络编程URL和URI 博客分类: Java 编程网络应用Java网络协议Scheme
-
java网络编程中向指定URL发送GET POST请求示例
-
Java网络编程之URL+URLconnection使用方法示例
-
java网络编程中向指定URL发送GET POST请求示例
-
Java/Android 获取网络重定向文件的真实URL的示例代码