- 浏览: 153488 次
- 性别:
- 来自: 石家庄
文章分类
- 全部博客 (66)
- filnet pe eform (1)
- svn (1)
- ASM (3)
- 算法 k-means (1)
- FileNet unfile query sql (1)
- FileNet fetchRows fetchObjects (1)
- linux chkconfig (1)
- websphere profile linux manageprofiles (1)
- db2 linux install (1)
- IBM Tivoli Directory Server linux (1)
- download jandan.net pic (1)
- jquery picture view (1)
- db2 db2cc (1)
- FileNet (0)
- FileNet redbook (7)
- Test upload (0)
- test 博客2doc (0)
- nginx (1)
- Google-perftools (1)
- openSSL (1)
- pcre (1)
- libunwind (1)
- nginx.conf (1)
- ASM,AOP (0)
- oracle sql (1)
- webservice cxf wsdl2java (1)
- spring3 hibernate4 mutil datasource/database (1)
- java 线程 thread run (1)
- java 64进制 (0)
- java 62进制 (1)
- activemq camel spring jms (2)
- mysql5.5 Master Master Replication (1)
- centos7 hostname httpd port (1)
- centos (1)
- hostname (1)
- change http port (1)
- test (0)
- zookeeper (2)
- 无语面试 (1)
- spring+mybatis+Mysql+proc+分页 (0)
- spring+mybatis+Mysql+proc+分页+paging (2)
最新评论
-
chenchunhuis:
我们最近也在做这个事情,一楼的评论很有道理
spring3+hibernate4+maven+junit 多库/多数据源实现 -
mjs123:
你好,我执行的时候 怎么老是报 No Session foun ...
spring3+hibernate4+maven+junit 多库/多数据源实现 -
chen_bing8:
但是不知道怎么配置默认实例,用JDBC连接后没有默认实例。
linux 下 db2 TCP 服务 配置 -
chen_bing8:
非常好,照着修改成功了
linux 下 db2 TCP 服务 配置 -
yixiandave:
不喜欢写一长串char[]。。。直接写一个String然后调c ...
短ID生成基于62进制
使用Java控制UDP协议
什么是UDP协议
UDP协议的全称是用户数据报,在网络中它与TCP协议一样用于处理数据包。在OSI模型中,在第四层——传输层,处于IP协议的上一层。 UDP有不提供数据报分组、组装和不能对数据包的排序的缺点,也就是说,当报文发送之后,是无法得知其是否安全完整到达的。
为什么要使用UDP
在选择使用协议的时候,选择UDP必须要谨慎。在网络质量令人不十分满意的环境下,UDP协议数据包丢失会比较严重。但是由于UDP的特性:它不属于连接型协议,因而具有资源消耗小,处理速度快的优点,所以通常音频、视频和普通数据在传送时使用UDP较多,因为它们即使偶尔丢失一两个数据包,也不会对接收结果产生太大影响。比如我们聊天用的ICQ和OICQ就是使用的UDP协议。
在Java中操纵UDP
使用位于JDK中Java.net包下的DatagramSocket和DatagramPacket类,可以非常方便地控制用户数据报文。
在描述它们之前,必须了解位于同一个位置的InetAddress类。InetAddress实现了Java.io. Serializable接口,不允许继承。它用于描述和包装一个Internet IP地址,通过三个方法返回InetAddress实例:
getLocalhost():返回封装本地地址的实例。
getAllByName(String host):返回封装Host地址的InetAddress实例数组。
getByName(String host):返回一个封装Host地址的实例。其中,Host可以是域名或者是一个合法的IP地址。
DatagramSocket类用于创建接收和发送UDP的Socket实例。和Socket类依赖SocketImpl类一样,DatagramSocket类的实现也依靠专门为它设计的DatagramScoketImplFactory类。DatagramSocket类有3个构建器:
DatagramSocket():创建实例。这是个比较特殊的用法,通常用于客户端编程,它并没有特定监听的端口,仅仅使用一个临时的。
DatagramSocket(int port):创建实例,并固定监听Port端口的报文。
DatagramSocket(int port, InetAddress localAddr):这是个非常有用的构建器,当一台机器拥有多于一个IP地址的时候,由它创建的实例仅仅接收来自LocalAddr的报文。
值得注意的是,在创建DatagramSocket类实例时,如果端口已经被使用,会产生一个SocketException的异常抛出,并导致程序非法终止,这个异常应该注意捕获。DatagramSocket类最主要的方法有4个:
Receive(DatagramPacket d):接收数据报文到d中。receive方法产生一个“阻塞”。
Send(DatagramPacket d):发送报文d到目的地。
SetSoTimeout(int timeout):设置超时时间,单位为毫秒。
Close():关闭DatagramSocket。在应用程序退出的时候,通常会主动释放资源,关闭Socket,但是由于异常地退出可能造成资源无法回收。所以,应该在程序完成时,主动使用此方法关闭Socket,或在捕获到异常抛出后关闭Socket。
“阻塞”是一个专业名词,它会产生一个内部循环,使程序暂停在这个地方,直到一个条件触发。
DatagramPacket类用于处理报文,它将Byte数组、目标地址、目标端口等数据包装成报文或者将报文拆卸成Byte数组。应用程序在产生数据包是应该注意,TCP/IP规定数据报文大小最多包含65507个,通常主机接收548个字节,但大多数平台能够支持8192字节大小的报文。DatagramPacket类的构建器共有4个:
DatagramPacket(byte[] buf, int length, InetAddress addr, int port):从Buf数组中,取出Length长的数据创建数据包对象,目标是Addr地址,Port端口。
DatagramPacket(byte[] buf, int offset, int length, InetAddress address, int port):从Buf数组中,取出Offset开始的、Length长的数据创建数据包对象,目标是Addr地址,Port端口。
DatagramPacket(byte[] buf, int offset, int length):将数据包中从Offset开始、Length长的数据装进Buf数组。
DatagramPacket(byte[] buf, int length):将数据包中Length长的数据装进Buf数组。
DatagramPacket类最重要的方法就是getData()了,它从实例中取得报文的Byte数组编码。
★简单的实例说明
{接收数据的服务器}
byte[] buf = new byte[1000];
DatagramSocket ds = new DatagramSocket(12345);
//开始监视12345端口
DatagramPacket ip = new DatagramPacket(buf, buf.length);
//创建接收数据报的实例
while (true)
{
ds.receive(ip);
//阻塞,直到收到数据报后将数据装入IP中
System.out.println(new String(buf));
}
{发送数据的客户端}
InetAddress target = InetAddress.getByName("www.xxx.com");
//得到目标机器的地址实例
DatagramSocket ds = new DatagramSocket(9999);
//从9999端口发送数据报
String hello = "Hello, I am come in!";
//要发送的数据
byte[] buf = hello.getBytes();
//将数据转换成Byte类型
op = new DatagramPacket(buf, buf.length, target, 12345);
//将BUF缓冲区中的数据打包
ds.send(op);
//发送数据
ds.close();
//关闭连接
基于Java的UDP协议程序设计初探
在Java 中进行网络编程是相对容易的,因为J2SE中的java.net包已经对各种通信协议很好的进行了封装,本文主要讲述如何基于UDP(用户数据报)协议编写应用程序。
通常我们进行网络编程一般都是使用基于socket的TCP/IP编程,毕竟TCP/IP应用非常的广泛,比如我们浏览互联网就是基于HTTP协议、我们发送邮件是通过SMTP协议。它们都是基于TCP/IP的。TCP/IP的传输最重要的是它可以保证数据到达目的地,而UDP则不同他并不保证准确的传输,数据有可能丢失。如果有兴趣的话,读者可以参考《计算机网络》一书。
在介绍UDP编程之前有必要介绍一个重要的类InetAddress,用最简单的一句话描述这个类的作用就是:它代表了一个IP地址。这非常重要在互联网中如果知道了IP地址则意味着我们知道了通信的端点。这个类没有构造器但是有几个工厂方法,通过传递不同的参数例如IP,Hostname等来得到一个InetAddress的实例,下面的这个小例子可以得到我机器的IP地址。
import java.net.*;
public class TestNet
{
public static void main(String[] args) throws Exception
{
InetAddress ia = InetAddress.getByName("compaq");
String ipAdr = ia.getHostAddress();
System.out.println(ipAdr);
}
}
当然我的机器的名字为compaq,如果你传入localhost的话将会得到127.0.0.1。
接下来开始讲述如何使用UDP编程,这非常容易理解,我们应该首先构造一个数据报,然后把它发送出去,同时我们也可以接收数据报。在java中提供了DatagramPacket和DatagramSocket两个类来完成这样的任务,前者负责构造数据报后者负责发送和接收。看看DatagramPacket的构造器
DatagramPacket(byte[] buf, int length, InetAddress address, int port)
DatagramPacket(byte[] buf, int offset, int length, InetAddress address, int port)
DatagramPacket(byte[] buf, int offset, int length, SocketAddress address)
DatagramPacket(byte[] buf, int length, SocketAddress address)
DatagramPacket(byte[] buf, int length)
DatagramPacket(byte[] buf, int offset, int length)
其中前面四个是为构造发送的数据报而用的,因为他们有InetAddress或者SocketInetAddress作为接收端点的地址,后面一个则是为了接受数据报用的。
同样我们编写一个C/S模型的例子来说明如何使用这两个重要的类,如果对API还不熟悉请参考Java doc。下面的程序在本机构造一个时间服务器,客户端来取得时间。以前曾经写过类似的时间服务器程序,不过这个是基于UDP的编程。
import java.io.*;
import java.net.*;
import java.util.*;
public class TimeServer {
final private static int DAYTIME_PORT = 13;
public static void main(String args[]) throws
IOException {
DatagramSocket socket = new DatagramSocket(DAYTIME_PORT);
while (true) {
byte buffer[] = new byte[256];
DatagramPacket packet =new DatagramPacket(buffer, buffer.length);
socket.receive(packet);
String date = new Date().toString();
buffer = date.getBytes();
// Get response address/port
// for client from packet
InetAddress address = packet.getAddress();
int port = packet.getPort();
packet = new DatagramPacket(buffer, buffer.length, address, port);
socket.send(packet);
}
}
}
import java.io.*;
import java.net.*;
public class GetTime {
final private static int DAYTIME_PORT = 13;
public static void main(String args[]) throws
IOException {
if (args.length == 0) {
System.err.println("Please specify daytime host");
System.exit(-1);
}
String host = args[0];
byte message[] = new byte[256];
InetAddress address = InetAddress.getByName(host);
System.out.println("Checking at: " + address);
DatagramPacket packet = new DatagramPacket(message, message.length,
address, DAYTIME_PORT);
DatagramSocket socket = new DatagramSocket();
socket.send(packet);
packet =new DatagramPacket(message, message.length);
socket.receive(packet);
String time = new String(packet.getData());
System.out.println(The time at "+ host + " is: " + time);
socket.close();
}
}
Jakarta Commons HttpClient 学习笔记
虽然用telnet这样的程序都可把页面取回来,但是在与web服务器的交互中,如果涉及cookie或https或ssl等内容,一般功能相对完备的http客户端还是非常必要的。IE或NetScape等浏览器确实不错,可是如果为实现持续互动而在程序调用浏览器,我个人认为其中的工作量还是不小的,这还没考虑版权问题。最好的办法,就是能有一个开源的包,能实现http客户端的功能,供我们开发的程序调用。httpclient就是这么一个包,我相信可能有比它的实现更好的,但目前我只关注这个。:)
下面是nogoop做的功能比较表:
Features nogoop Sun JRE < 1.4.2 Sun JRE 1.4.2 Innovation Apache/Jakarta
cookies X X
plug compatible X X X X [partial]
true request output stream X X
true response input stream X X X
connection keep alive X X X X X
connection pool throttling X X
connection/request timeout X X [uns] X X
idle connection timeout X X
pipelining of requests X
alternate DNS resolution (dnsjava) X
SSL X X X X X
basic authentication X X X X X
digest authentication X X X X X
NTLM authentication X [Windows only] X
proxy authentication X X X X X
minimum JRE version 1.2 1 01年4月2日 1.2 1.2
price $499 free free free free
source available X X X
diagnostic tracing X X X
actively supported X X X X
fix turnaround fast slow slow none medium
license purchase Sun JRE Sun JRE LGPL Apache
1、HttpClient的功能
基于标准,纯正java,实现了http1.0和1.1。
在一个可扩展的OO框架内,实现了HTTP的全部方法(GET, POST,
PUT, DELETE, HEAD, OPTIONS, and TRACE)
支持HTTPS(ssl上的HTTP)的加密操作
透明地穿过HTTP代理建立连接
通过CONNECT方法,利用通过建立穿过HTTP代理的HTTPS连接
利用本地Java socket,透明地穿过SOCKS(版本5和4)代理建立连接
支持利用Basic、Digest和NTLM加密的认证
支持用于上传大文件的Multi-Part表单POST方法
插件式安全socket实现,易于使用第三方的解决方案
连接管理,支持多线程应用,支持设定单个主机总连接和最高连接数量,自动检测和关闭失效连接
直接将请求信息流送到服务器的端口
直接读取从服务器的端口送出的应答信息
支持HTTP/1.0中用KeepAlive和HTTP/1.1中用persistance设置的持久连接
直接访问由服务器送出的应答代码和头部信息
可设置连接超时时间
HttpMethods 实现Command Pattern,以允许并行请求或高效连接复用
遵循the Apache Software License协议,源码免费可得
2、预备工作
对jre1.3.*,如果要HttpClient支持https,则需要下载并安装jsse和jce.安装的步骤如下:
1)下载jsse和jce.
2)检查CLASSPATH中没有与jsse和jce相关的jar包
3)将 US_export_policy.jar、local_policy.jar、jsse.jar、jnet.jar、jce1_2_x.jar、sunjce_provider.jar、jcert.jar复制到目录:
UNIX:$JDK_HOME/jre/lib/ext
Windows:%JDK_HOME%\jre\lib\ext
4)修改下述目录下的java.security文件。
UNIX:$JDK_HOME/jre/lib/security/
Windows:%JDK_HOME%\jre\lib\security\
5)
将
#
# List of providers and their preference orders:
#
security.provider.1=sun.security.provider.Sun
security.provider.2=com.sun.rsajca.Provider
改为:
#
# List of providers and their preference orders:
#
security.provider.1=com.sun.crypto.provider.SunJCE
security.provider.2=sun.security.provider.Sun
security.provider.3=com.sun.rsajca.Provider
security.provider.4=com.sun.net.ssl.internal.ssl.Provider
HttpClient还要求安装commons-logging,下面跟httpclient一块安装。
3、取得源码
cvs -d :pserver:anoncvs@cvs.apache.org:/home/cvspublic login
password: anoncvs
cvs -d :pserver:anoncvs@cvs.apache.org:/home/cvspublic checkout jakarta-commons/logging
cvs -d :pserver:anoncvs@cvs.apache.org:/home/cvspublic checkout jakarta-commons/httpclient
编译:
cd jakarta-commons/logging
ant dist
cp dis/*.jar ../httpclient/lib/
cd ../httpclient
ant dist
4、使用HttpClient编程的基本步聚
创建 HttpClient 的一个实例.
创建某个方法(DeleteMethod,EntityEnclosingMethod,ExpectContinueMethod,GetMethod,HeadMethod,MultipartPostMethod,OptionsMethod,PostMethod,PutMethod,TraceMethod)的一个实例,一般可用要目标URL为参数。
让 HttpClient 执行这个方法.
读取应答信息.
释放连接.
处理应答.
在执行方法的过程中,有两种异常,一种是HttpRecoverableException,表示偶然性错误发生,一般再试可能成功,另一种是IOException,严重错误。
这儿有这个教程中的一个例程,可以下载。
5、认证
HttpClient三种不同的认证方案: Basic, Digest and NTLM. 这些方案可用于服务器或代理对客户端的认证,简称服务器认证或代理认证。
1)服务器认证(Server Authentication)
HttpClient处理服务器认证几乎是透明的,仅需要开发人员提供登录信息(login credentials)。登录信息保存在HttpState类的实例中,可以通过 setCredentials(String realm, Credentials cred)和getCredentials(String realm)来获取或设置。注意,设定对非特定站点访问所需要的登录信息,将realm参数置为null. HttpClient内建的自动认证,可以通过HttpMethod类的setDoAuthentication(boolean doAuthentication)方法关闭,而且这次关闭只影响HttpMethod当前的实例。
抢先认证(Preemptive Authentication)可以通过下述方法打开.
client.getState().setAuthenticationPreemptive(true);
在这种模式时,HttpClient会主动将basic认证应答信息传给服务器,即使在某种情况下服务器可能返回认证失败的应答,这样做主要是为了减少连接的建立。为使每个新建的 HttpState实例都实行抢先认证,可以如下设置系统属性。
setSystemProperty(Authenticator.PREEMPTIVE_PROPERTY, "true");
Httpclient实现的抢先认证遵循rfc2617.
2)代理认证(proxy authentication)
除了登录信息需单独存放以外,代理认证与服务器认证几乎一致。用 setProxyCredentials(String realm, Credentials cred)和 getProxyCredentials(String realm)设、取登录信息。
3)认证方案(authentication schemes)
Basic
是HTTP中规定最早的也是最兼容(?)的方案,遗憾的是也是最不安全的一个方案,因为它以明码传送用户名和密码。它要求一个UsernamePasswordCredentials实例,可以指定服务器端的访问空间或采用默认的登录信息。
Digest
是在HTTP1.1中增加的一个方案,虽然不如Basic得到的软件支持多,但还是有广泛的使用。Digest方案比Basic方案安全得多,因它根本就不通过网络传送实际的密码,传送的是利用这个密码对从服务器传来的一个随机数(nonce)的加密串。它要求一个UsernamePasswordCredentials实例,可以指定服务器端的访问空间或采用默认的登录信息。
NTLM
这是HttpClient支持的最复杂的认证协议。它M$设计的一个私有协议,没有公开的规范说明。一开始由于设计的缺陷,NTLM的安全性比Digest差,后来经过一个ServicePack补丁后,安全性则比较Digest高。NTLM需要一个NTCredentials实例. 注意,由于NTLM不使用访问空间(realms)的概念,HttpClient利用服务器的域名作访问空间的名字。还需要注意,提供给NTCredentials的用户名,不要用域名的前缀 - 如: "adrian" 是正确的,而 "DOMAIN\adrian" 则是错的.
NTLM认证的工作机制与basic和digest有很大的差别。这些差别一般由HttpClient处理,但理解这些差别有助避免在使用NTLM认证时出现错误。
从HttpClientAPI的角度来看,NTLM与其它认证方式一样的工作,差别是需要提供'NTCredentials'实例而不是'UsernamePasswordCredentials'(其实,前者只是扩展了后者)
对NTLM认证,访问空间是连接到的机器的域名,这对多域名主机会有一些麻烦.只有HttpClient连接中指定的域名才是认证用的域名。建议将realm设为null以使用默认的设置。
NTLM只是认证了一个连接而不是一请求,所以每当一个新的连接建立就要进行一次认证,且在认证的过程中保持连接是非常重要的。 因此,NTLM不能同时用于代理认证和服务器认证,也不能用于http1.0连接或服务器不支持持久连接的情况。
6、重定向
由于技术限制,以及为保证2.0发布版API的稳定,HttpClient还不能自动处重定向,但对重定向到同一主机、同一端口且采用同一协议的情况HttpClient可以支持。不能自动的处理的情况,包括需要人工交互的情况,或超出httpclient的能力。
当服务器重定向指令指到不同的主机时,HttpClient只是简单地将重定向状态码作为应答状态。所有的300到399(包含两端)的返回码,都表示是重定向应答。常见的有:
301 永久移动. HttpStatus.SC_MOVED_PERMANENTLY
302 临时移动. HttpStatus.SC_MOVED_TEMPORARILY
303 See Other. HttpStatus.SC_SEE_OTHER
307 临时重定向. HttpStatus.SC_TEMPORARY_REDIRECT
当收到简单的重定向时,程序应从HttpMethod对象中抽取新的URL并将其下载。另外,限制一下重定向次数是个好的主意,这可以避免递归循环。新的URL可以从头字段Location中抽取,如下:
String redirectLocation;
Header locationHeader = method.getResponseHeader("location");
if (locationHeader != null) {
redirectLocation = locationHeader.getValue();
} else {
// The response is invalid and did not provide the new location for
// the resource. Report an error or possibly handle the response
// like a 404 Not Found error.
}
特殊重定向:
300 多重选择. HttpStatus.SC_MULTIPLE_CHOICES
304 没有改动. HttpStatus.SC_NO T_MODIFIED
305 使用代理. HttpStatus.SC_USE_PROXY
7、字符编码(character encoding)
一个HTTP协议的请求或应答的头部(在http协议中,数据包分为两部分,一部分是头部,由一些名值对构成,一部分是主体(body),是真正传办理的数据(如HTML页面等)),必须以US-ASCII编码,这是因为头部不传数据而只描述被要传输的数据的一些信息,一个例外是cookie,它是数据但是通过头部进行传输的,所以它也要用US-ASCII编码。
HTTP数据包的主体部分,可以用任何一种方式进行编码,默认是ISO-8859-1,具体可以用头部字段Content-Type指定。可以利用 addRequestHeader方法,设定编码方式;用 getResponseCharSet取得编码方式。对HTML或XML等类型的文档,它们的本身的Content-Type也可以指定编码方式,主要区分两者的作用范围以得到正确实的解码。
URL的编码标准,由RFC1738指定为,只能是由可打印8位/字节的us-ascii字符组成,80-ff不是us-ascii字符,而00-1F是控制字符,这两个区域中用的字符都须加以编码(encoded)。
8、Cookies
HttpClient能自动管理cookie,包括允许服务器设置cookie并在需要的时候自动将cookie返回服务器,它也支持手工设置cookie后发送到服务器端。不幸的是,对如何处理cookie,有几个规范互相冲突:Netscape Cookie 草案, RFC2109, RFC2965,而且还有很大数量的软件商的cookie实现不遵循任何规范. 为了处理这种状况,HttpClient提供了策略驱动的cookie管理方式。HttpClient支持的cookie规范有:
Netscape cookie草案,是最早的cookie规范,基于rfc2109。尽管这个规范与rc2109有较大的差别,这样做可以与一些服务器兼容。
rfc2109,是w3c发布的第一个官方cookie规范。理论上讲,所有的服务器在处理cookie(版本1)时,都要遵循此规范,正因如此,HttpClient将其设为默认的规范。遗憾的是,这个规范太严格了,以致很多服务器不正确的实施了该规范或仍在作用Netscape规范。在这种情况下,应使用兼容规范。
兼容性规范,设计用来兼容尽可能多的服务器,即使它们并没有遵循标准规范。当解析cookie出现问题时,应考虑采用兼容性规范。
RFC2965规范暂时没有被HttpClient支持(在以后的版本为会加上),它定义了cookie版本2,并说明了版本1cookie的不足,RFC2965有意有久取代rfc2109.
在HttpClient中,有两种方法来指定cookie规范的使用,
HttpClient client = new HttpClient();
client.getState().setCookiePolicy(CookiePolicy.COMPATIBILITY);
这种方法设置的规范只对当前的HttpState有效,参数可取值CookiePolicy.COMPATIBILITY,CookiePolicy.NETSCAPE_DRAFT或CookiePolicy.RFC2109。
System.setProperty("apache.commons.httpclient.cookiespec", "COMPATIBILITY");
此法指的规范,对以后每个新建立的HttpState对象都有效,参数可取值"COMPATIBILITY","NETSCAPE_DRAFT"或"RFC2109"。
常有不能解析cookie的问题,但更换到兼容规范大都能解决。
9、使用HttpClient遇到问题怎么办?
用一个浏览器访问服务器,以确认服务器应答正常
如果在使代理,关掉代理试试
另找一个服务器来试试(如果运行着不同的服务器软件更好)
检查代码是否按教程中讲的思路编写
设置log级别为debug,找出问题出现的原因
打开wiretrace,来追踪客户端与服务器的通信,以确实问题出现在什么地方
用telnet或netcat手工将信息发送到服务器,适合于猜测已经找到了原因而进行试验时
将netcat以监听方式运行,用作服务器以检查httpclient如何处理应答的。
利用最新的httpclient试试,bug可能在最新的版本中修复了
向邮件列表求帮助
向bugzilla报告bug.
10、SSL
借助Java Secure Socket Extension (JSSE),HttpClient全面支持Secure Sockets Layer (SSL)或IETF Transport Layer Security (TLS)协议上的HTTP。JSSE已经jre1.4及以后的版本中,以前的版本则需要手工安装设置,具体过程参见Sun网站或本学习笔记。
HttpClient中使用SSL非常简单,参考下面两个例子:
HttpClient httpclient = new HttpClient();
GetMethod httpget = new GetMethod("https://www.verisign.com/");
httpclient.executeMethod(httpget);
System.out.println(httpget.getStatusLine().toString());
,如果通过需要授权的代理,则如下:
HttpClient httpclient = new HttpClient();
httpclient.getHostConfiguration().setProxy("myproxyhost", 8080);
httpclient.getState().setProxyCredentials("my-proxy-realm", " myproxyhost",
new UsernamePasswordCredentials("my-proxy-username", "my-proxy-password"));
GetMethod httpget = new GetMethod("https://www.verisign.com/");
httpclient.executeMethod(httpget);
System.out.println(httpget.getStatusLine().toString());
在HttpClient中定制SSL的步骤如下:
提供了一个实现了org.apache.commons.httpclient.protocol.SecureProtocolSocketFactory接口的socket factory。这个 socket factory负责打一个到服务器的端口,使用标准的或第三方的SSL函数库,并进行象连接握手等初始化操作。通常情况下,这个初始化操作在端口被创建时自动进行的。
实例化一个org.apache.commons.httpclient.protocol.Protocol对象。创建这个实例时,需要一个合法的协议类型(如https),一个定制的socket factory,和一个默认的端中号(如https的443端口).
Protocol myhttps = new Protocol("https", new MySSLSocketFactory(), 443);
然后,这个实例可被设置为协议的处理器。
HttpClient httpclient = new HttpClient();
httpclient.getHostConfiguration().setHost("www.whatever.com", 443, myhttps);
GetMethod httpget = new GetMethod("/");
httpclient.executeMethod(httpget);
通过调用Protocol.registerProtocol方法,将此定制的实例,注册为某一特定协议的默认的处理器。由此,可以很方便地定制自己的协议类型(如myhttps)。
Protocol.registerProtocol("myhttps",
new Protocol("https", new MySSLSocketFactory(), 9443));
...
HttpClient httpclient = new HttpClient();
GetMethod httpget = new GetMethod("myhttps://www.whatever.com/");
httpclient.executeMethod(httpget);
如果想用自己定制的处理器取代https默认的处理器,只需要将其注册为"https"即可。
Protocol.registerProtocol("https",
new Protocol("https", new MySSLSocketFactory(), 443));
HttpClient httpclient = new HttpClient();
GetMethod httpget = new GetMethod("https://www.whatever.com/");
httpclient.executeMethod(httpget);
已知的限制和问题
持续的SSL连接在Sun的低于1.4JVM上不能工作,这是由于JVM的bug造成。
通过代理访问服务器时,非抢先认证( Non-preemptive authentication)会失败,这是由于HttpClient的设计缺陷造成的,以后的版本中会修改。
遇到问题的处理
很多问题,特别是在jvm低于1.4时,是由jsse的安装造成的。
下面的代码,可作为最终的检测手段。
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.Writer;
import java.net.Socket; import javax.net.ssl.SSLSocketFactory; public class Test {
public static final String TARGET_HTTPS_SERVER = "www.verisign.com";
public static final int TARGET_HTTPS_PORT = 443;
public static void main(String[] args) throws Exception {
Socket socket = SSLSocketFactory.getDefault().
createSocket(TARGET_HTTPS_SERVER, TARGET_HTTPS_PORT);
try {
Writer out = new OutputStreamWriter(
socket.getOutputStream(), "ISO-8859-1");
out.write("GET / HTTP/1.1\r\n");
out.write("Host: " + TARGET_HTTPS_SERVER + ":" +
TARGET_HTTPS_PORT + "\r\n");
out.write("Agent: SSL-TEST\r\n");
out.write("\r\n");
out.flush();
BufferedReader in = new BufferedReader(
new InputStreamReader(socket.getInputStream(), "ISO-8859-1"));
String line = null;
while ((line = in.readLine()) != null) {
System.out.println(line);
}
} finally {
socket.close();
}
}
}
11、httpclient的多线程处理
使用多线程的主要目的,是为了实现并行的下载。在httpclient运行的过程中,每个http协议的方法,使用一个HttpConnection实例。由于连接是一种有限的资源,每个连接在某一时刻只能供一个线程和方法使用,所以需要确保在需要时正确地分配连接。HttpClient采用了一种类似jdbc连接池的方法来管理连接,这个管理工作由 MultiThreadedHttpConnectionManager完成。
MultiThreadedHttpConnectionManager connectionManager =
new MultiThreadedHttpConnectionManager();
HttpClient client = new HttpClient(connectionManager);
此是,client可以在多个线程中被用来执行多个方法。每次调用HttpClient.executeMethod() 方法,都会去链接管理器申请一个连接实例,申请成功这个链接实例被签出(checkout),随之在链接使用完后必须归还管理器。管理器支持两个设置: maxConnectionsPerHost 每个主机的最大并行链接数,默认为2
maxTotalConnections 客户端总并行链接最大数,默认为20
管理器重新利用链接时,采取早归还者先重用的方式(least recently used approach)。
由于是使用HttpClient的程序而不是HttpClient本身来读取应答包的主体,所以HttpClient无法决定什么时间连接不再使用了,这也就要求在读完应答包的主体后必须手工显式地调用releaseConnection()来释放申请的链接。
MultiThreadedHttpConnectionManager connectionManager = new MultiThreadedHttpConnectionManager();
HttpClient client = new HttpClient(connectionManager);
...
// 在某个线程中。
GetMethod get = new GetMethod("http://jakarta.apache.org/");
try {
client.executeMethod(get);
// print response to stdout
System.out.println(get.getResponseBodyAsStream());
} finally {
// be sure the connection is released back to the connection
// manager
get.releaseConnection();
}
对每一个HttpClient.executeMethod须有一个method.releaseConnection()与之匹配.
12、HTTP方法
HttpClient支持的HTTP方法有8种,下面分述之。
1、Options
HTTP方法Options用来向服务器发送请求,希望获得针对由请求URL(request url)标志的资源在请求/应答的通信过程可以使用的功能选项。通过这个方法,客户端可以在采取具体行动之前,就可对某一资源决定采取什么动作和/或以及一些必要条件,或者了解服务器提供的功能。这个方法最典型的应用,就是用来获取服务器支持哪些HTTP方法。
HttpClient中有一个类叫OptionsMethod,来支持这个HTTP方法,利用这个类的getAllowedMethods方法,就可以很简单地实现上述的典型应用。
OptionsMethod options = new OptionsMethod("http://jakarta.apache.org");
// 执行方法并做相应的异常处理
...
Enumeration allowedMethods = options.getAllowedMethods();
options.releaseConnection();
2、Get
HTTP方法GET用来取回请求URI(request-URI)标志的任何信息(以实体(entity)的形式),"get"这个单词本意就是”获取“的意思。如果请求URI指向的一个数据处理过程,那这个过程生成的数据,在应答中以实体的形式被返回,而不是将这个过程的代码的返回。
如果HTTP包中含有If-ModifiedSince, If-Unmodified-Since, If-Match, If-None-Match, 或 If-Range等头字段,则GET也就变成了”条件GET“,即只有满足上述字段描述的条件的实体才被取回,这样可以减少一些非必需的网络传输,或者减少为获取某一资源的多次请求(如第一次检查,第二次下载)。(一般的浏览器,都有一个临时目录,用来缓存一些网页信息,当再次浏览某个页面的时候,只下载那些修改过的内容,以加快浏览速度,就是这个道理。至于检查,则常用比GET更好的方法HEAD来实现。)如果HTTP包中含有Range头字段,那么请求URI指定的实体中,只有决定范围条件的那部分才被取回来。(用过多线程下载工具的朋友,可能比较容易理解这一点)
这个方法的典型应用,用来从web服务器下载文档。HttpClient定义了一个类叫GetMethod来支持这个方法,用GetMethod类中getResponseBody, getResponseBodyAsStream 或 getResponseBodyAsString函数就可以取到应答包包体中的文档(如HTML页面)信息。这这三个函数中,getResponseBodyAsStream通常是最好的方法,主要是因为它可以避免在处理下载的文档之前缓存所有的下载的数据。
GetMethod get = new GetMethod("http://jakarta.apache.org");
// 执行方法,并处理失败的请求.
...
InputStream in = get.getResponseBodyAsStream();
// 利用输入流来处理信息。
get.releaseConnection();
对GetMethod的最常见的不正确的使用,是没有将全部的应答主体的数据读出来。还有,必须注意要手工明确地将链接释放。
3、Head
HTTP的Head方法,与Get方法完全一致,唯一的差别是服务器不能在应答包中包含主体(message-body),而且一定不能包含主体。使用这个方法,可以使得客户无需将资源下载回就可就以得到一些关于它的基本信息。这个方法常用来检查超链的可访问性以及资源最近有没有被修改。
HTTP的head方法最典型的应用,是获取资源的基本信息。HttpClient定义了HeadMethod类支持这个方法,HeadMethod类与其它*Method类一样,用 getResponseHeaders()取回头部信息,而没有自己的特殊方法。
HeadMethod head = new HeadMethod("http://jakarta.apache.org");
// 执行方法,并处理失败的请求.
...
// 取回应答包的头字段信息.
Header[] headers = head.getResponseHeaders(); // 只取回最后修改日期字段的信息.
String lastModified = head.getResponseHeader("last-modified").getValue();
4、Post
Post在英文有“派驻”的意思,HTTP方法POST就是要求服务器接受请求包中的实体,并将其作为请求URI的下属资源。从本质上说,这意味着服务器要保存这个实体信息,而且通常由服务器端的程序进行处理。Post方法的设计意图,是要以一种统一的方式实现下列功能:
对已有的资源做评注
将信息发布到BBS、新闻组、邮件列表,或类似的文章组中
将一块数据,提交给数据处理进程
通过追加操作,来扩展一个数据库
这些都操作期待着在服务器端产生一定的“副作用”,如修改了数据库等。
HttpClient定义PostMethod类以支持该HTTP方法,在httpclient中,使用post方法有两个基本的步骤:为请求包准备数据,然后读取服务器来的应答包的信息。通过调用 setRequestBody()函数,来为请求包提供数据,它可以接收三类参数:输入流、名值对数组或字符串。至于读取应答包需要调用 getResponseBody* 那一系列的方法,与GET方法处理应答包的方法相同。
常见问题是,没有将全部应答读取(无论它对程序是否有用),或没有释放链接资源。
HttpClient Cookies
简介
HttpClient支持自动管理Cookies,允许服务端设定Cookies,并在请求时自动返回客户端的Cookies信息。客户端也可以手动的设置Cookies发送到服务端。
不幸的是,同一时间内有太多的Cookies标准: Netscape Cookie, RFC2109, RFC2965 以及大量供应商不符合规范的自定义Cookies标准。针对此,HttpClient提供Cookies管理策略驱动。这片文章意在阐述怎么样去使用不同标准的Cookies以及如何解决在使用Cookies和HttpClient时一些共同的问题。
支持的规格
以下Cookies标准,HttpClient3.1可以支持。
RFC2109
RFC2109是W3C组织第一次推出的官方Cookies标准。理论上,所有使用版本1Cookies的服务端都应该使用此标准。HttpClient已经将此标准设定为默认。
遗憾的是,许多服务端不正确的实现了标准或者仍然使用Netscape标准。所有有时感到此标准太多于严格。
RFC2109是HttpClient使用的默认Cookies协议。
RFC2965
RFC2965定义了版本2并且尝试去弥补在版本1中Cookie的RFC2109标准的缺点。RFC2965是,并规定RFC2965最终取代RFC2109.
发送RFC2965标准Cookies的服务端,将会使用Set-Cookie2 header添加到Set-Cookie Header信心中,RFC2965 Cookies是区分端口的。
Netscape标准
Netscape是最原始的Cookies规范,同时也是RFC2109的基础。尽管如此,还是在很多重要的方面与RFC2109不同,可能需要特定服务器才可以兼容。
Browser Compatibility
这种兼容性设计要求是适应尽可能多的不同的服务器,尽管不是完全按照标准来实现的。如果你遇到了解析Cookies的问题,你就可能要用到这一个规范。
有太多的web站点是用CGI脚本去实现的,而导致只有将所有的Cookies都放入Request header才可以正常的工作。这种情况下最好设置http.protocol.single-cookie-header参数为true。
Ignore Cookies
此规格忽略所有Cookie 。被用来防止HttpClient接受和发送的Cookie。
Spacifying the Specification
有俩中方式去规定使用哪种Cookies规范,每个HttpMethod实例都有HttpMethodParams,他的policy值必须使用方法CookiePolicy.registerCookieSpec()来注册。
HttpMethod method = new GetMethod();
Method.getParams().setCookiePolicy(CookiePolicy.RFC_2109);
手动处理Cookies
HttpClient的Cookie管理API可以手动处理Cookie。可以手动设置Requset的Cookie headers或是处理Response的Set-Cookie的headers或是用自动Cookie管理去代替。
HttpMethod method = new GetMethod();
Method.getParams().setCookiePolicy(CookiePolicy.IGNORE_COOKIES);
Method.setRequestHeader(“Cookie”, “special_cookie=value”);
import java.net.*;
import java.io.*;
import java.util.Properties;
import java.util.Enumeration;
/**
Http客户端程序已集成在Java语言中,可以通过URLConnection类调用。遗憾的
是,由于SUN没有公布Http客户程序的源码,它实现的细节仍是一个谜。本文根据HTTP
协议规范,用Java.net.Socket类实现一个HTTP协议客户端程序。
1.Socket类:
了解TCP/IP协议集通信的读者知道,协议间的通信是通过Socket完成的。在
Java.net包中,Socket类就是对Socket的具体实现。它通过连接到主机后,返回一个
I/O流,实现协议间的信息交换。
2 . HTTP协议
HTTP协议同其它TCP/IP协议集中的协议一样,是遵循客户/服务器模型工作的。客
户端发往服务端的信息格式如下:
------------------------------
请求方法 URL HTTP协议的版本号
提交的元信息
**空行**
实体
------------------------------
请求方法是对这次连接工作的说明,目前HTTP协议已经发展到1.1版,它包括GET、
HEAD、POST、DELETE、OPTIONS、TRACE、PUT七种。元信息是关于当前请求的信息。通
过分析元信息,可以检查实体数据是否完整,接收过程是否出错,类型是否匹配等。元
信息的引入使HTTP协议通信更加稳妥可靠。实体是请求的具体内容。
将上述报文发往Web服务器,如果成功,应答格式如下:
--------------------------------
HTTP协议的版本号 应答状态码 应答状态码说明
接收的元信息
**空行**
实体
--------------------------------
以上报文发向客户端,并且接收成功,彼此间关闭连接,完成一次握手。
下面用最常用的GET方法,来说明具体的报文应用
----------------------------------
GET http://www.youhost.com HTTP/1.0
accept: www/source; text/html; image/gif; image/jpeg; */*
User_Agent: myAgent
**空行**
-----------------------------------
这个报文是向www.youhost.com主机请求一个缺省HTML文档。客户端HTTP协议版本
号是1.0版,元信息包括可接收的文件格式,用户代理,每一段之间用回车换行符分
隔,最后以一个空行结束。发向服务器后,如果执行过程正常,服务器返回以下代码:
------------------------------------
HTTP/1.1 200 OK
Date: Tue, 14 Sep 1999 02:19:57 GMT
Server: Apache/1.2.6
Connection: close
Content-Type: text/html
**空行**
......
------------------------------------
HTTP/1.1表示这个HTTP服务器是1.1版,200是服务器对客户请求的应答状态码,OK
是对应答状态码的解释,之后是这个文档的元信息和文档正文。(相关应答状态码和元
信息的解释请参阅Inetrnet标准草案:RFC2616)。
注: 程序中只实现GET、HEAD、POST三种方法。其他几种因不常使用,暂且忽略。
*/
public class Http {
protected Socket client;
protected BufferedOutputStream sender;
protected BufferedInputStream receiver;
protected ByteArrayInputStream byteStream;
protected URL target;
private int responseCode=-1;
private String responseMessage="";
private String serverVersion="";
private Properties header = new Properties();
public Http() { }
public Http(String url) {
GET(url) ;
}
/* GET方法根据URL,会请求文件、数据库查询结果、程序运行结果等多种内容 */
public void GET(String url){
try{
checkHTTP(url);
openServer(target.getHost(),target.getPort() );
String cmd = "GET " + getURLFormat(target) + " HTTP/1.0\r\n" + getBaseHeads() + "\r\n";
sendMessage(cmd);
receiveMessage();
}
catch(ProtocolException p){
p.printStackTrace();
return;
}
catch(UnknownHostException e){
e.printStackTrace();
return;
}
catch(IOException i){
i.printStackTrace();
return;
}
}
/*
* HEAD方法只请求URL的元信息,不包括URL本身。若怀疑本机和服务器上的
* 文件相同,用这个方法检查最快捷有效。
*/
public void HEAD(String url){
try{
checkHTTP(url);
openServer(target.getHost(),target.getPort());
String cmd = "HEAD " + getURLFormat(target) + " HTTP/1.0\r\n" + getBaseHeads() + "\r\n";
sendMessage(cmd);
receiveMessage();
}
catch(ProtocolException p){
p.printStackTrace();
return;
}
catch(UnknownHostException e){
e.printStackTrace();
return;
}
catch(IOException i){
i.printStackTrace();
return;
}
}
/*
* POST方法是向服务器传送数据,以便服务器做出相应的处理。例如网页上常用的
* 提交表格。
*/
public void POST(String url,String content) {
try{
checkHTTP(url);
openServer(target.getHost(),target.getPort() );
String cmd = "POST " + getURLFormat(target) + " HTTP/1.0\r\n" + getBaseHeads();
cmd += "Content-type: application/x-www-form-urlencoded\r\n";
cmd += "Content-length: " + content.length() + "\r\n\r\n";
cmd += content + "\r\n";
sendMessage(cmd);
receiveMessage();
}
catch(ProtocolException p){
p.printStackTrace();
return;
}
catch(UnknownHostException e){
e.printStackTrace();
return;
}
catch(IOException i){
i.printStackTrace();
return;
}
}
protected void checkHTTP(String url) throws ProtocolException {
try{
URL target = new URL(url);
if(target == null || !target.getProtocol().toUpperCase().equals("HTTP")){
throw new ProtocolException("这不是HTTP协议");
}
this.target = target;
}
catch(MalformedURLException m) {
throw new ProtocolException("协议格式错误");
}
}
/*
* 与Web服务器连接。若找不到Web服务器,InetAddress会引发UnknownHostException
* 异常。若Socket连接失败,会引发IOException异常。
*/
protected void openServer(String host,int port) throws UnknownHostException,IOException {
header.clear();
responseMessage="";
responseCode=-1;
if(client!=null){
closeServer();
}
if(byteStream != null){
byteStream.close();
byteStream=null;
}
InetAddress address = InetAddress.getByName(host);
client = new Socket(address,port==-1 80:port);
client.setSoTimeout(5000);
sender = new BufferedOutputStream(client.getOutputStream());
receiver = new BufferedInputStream(client.getInputStream());
}
/* 关闭与Web服务器的连接 */
protected void closeServer() throws IOException {
if(client==null){
return;
}
try{
client.close();
sender.close();
receiver.close();
}
catch(IOException i){
throw i;
}
client=null;
sender=null;
receiver=null;
}
protected String getURLFormat(URL target) {
String spec = "http://" + target.getHost();
if(target.getPort()!=-1){
spec+=":"+target.getPort();
}
return spec+=target.getFile();
}
/* 向Web服务器传送数据 */
protected void sendMessage(String data) throws IOException{
sender.write(data.getBytes(),0,data.length());
sender.flush();
}
/* 接收来自Web服务器的数据 */
protected void receiveMessage() throws IOException{
byte data[] = new byte[1024];
int count=0;
int word=-1;
// 解析第一行
while( (word=receiver.read())!=-1 ) {
if(word=='\r'||word=='\n') {
word=receiver.read();
if(word=='\n') word=receiver.read();
break;
}
if(count == data.length) data = addCapacity(data);
data[count++]=(byte)word;
}
String message = new String(data,0,count);
int mark = message.indexOf(32);
serverVersion = message.substring(0,mark);
while( mark
responseCode = Integer.parseInt(message.substring(mark+1,mark+=4));
responseMessage = message.substring(mark,message.length()).trim();
// 应答状态码和处理请读者添加
switch(responseCode) {
case 400:
throw new IOException("错误请求");
case 404:
throw new FileNotFoundException( getURLFormat(target) );
case 503:
throw new IOException("服务器不可用" );
}
if(word==-1) throw new ProtocolException("信息接收异常终止");
int symbol=-1;
count=0;
// 解析元信息
while( word!='\r' && word!='\n' && word>-1) {
if(word=='\t') word=32;
if(count==data.length) data = addCapacity(data);
data[count++] = (byte)word;
parseLine: {
while( (symbol=receiver.read()) >-1 ) {
switch(symbol) {
case '\t':
symbol=32; break;
case '\r':
case '\n':
word = receiver.read();
if( symbol=='\r' && word=='\n') {
word=receiver.read();
if(word=='\r') word=receiver.read();
}
if( word=='\r' || word=='\n' || word>32) break parseLine;
symbol=32; break;
}
if(count==data.length) data = addCapacity(data);
data[count++] = (byte)symbol;
}
word=-1;
}
message = new String(data,0,count);
mark = message.indexOf(':');
String key = null;
if(mark>0) key = message.substring(0,mark);
mark++;
while( mark
<="32"> String value = message.substring(mark,message.length() );
header.put(key,value);
count=0;
}
// 获得正文数据
while( (word=receiver.read())!=-1) {
if(count == data.length) data = addCapacity(data);
data[count++] = (byte)word;
}
if(count>0) byteStream = new ByteArrayInputStream(data,0,count);
data=null;
closeServer();
}
public String getResponseMessage() {
return responseMessage;
}
public int getResponseCode() {
return responseCode;
}
public String getServerVersion() {
return serverVersion;
}
public InputStream getInputStream() {
return byteStream;
}
public synchronized String getHeaderKey(int i) {
if(i>=header.size()){
return null;
}
Enumeration enum = header.propertyNames();
String key = null;
for(int j=0; j<=i; j++){
key = (String)enum.nextElement();
}
return key;
}
public synchronized String getHeaderValue(int i) {
if(i>=header.size()) return null;
return header.getProperty(getHeaderKey(i));
}
public synchronized String getHeaderValue(String key) {
return header.getProperty(key);
}
protected String getBaseHeads() {
String inf = "User-Agent: IcewolfHttp/1.0\r\nAccept: www/source; text/html; image/gif; */*\r\n";
return inf;
}
private byte[] addCapacity(byte rece[]){
byte temp[] = new byte[rece.length+1024];
System.arraycopy(rece,0,temp,0,rece.length);
return temp;
}
public static void main(String [] args){
/*
Http p = new Http();
p.GET("http://www.google.com/search q=java+book");
byte[] bGets = new byte[1024];
ByteArrayInputStream pS = (ByteArrayInputStream)p.getInputStream();
int offset = 0;
while((offset=pS.read(bGets,0,1024)) != -1){
System.out.print(new String(bGets,0,offset));
}
*/
String target = "http://www.google.com/search q=java+book";
try {
URL url = new URL(target);
HttpURLConnection pconn = (HttpURLConnection)url.openConnection();
//HttpURLConnection.setFollowRedirects(true);
//pconn.setInstanceFollowRedirects(true);
pconn.addRequestProperty("User-Agent","IcewolfHttp/1.0");
pconn.addRequestProperty("Accept","www/source; text/html; image/gif; */*");
pconn.connect();
System.out.println("Connect status:"+pconn.getResponseCode());
//if(HttpURLConnection.HTTP_ACCEPTED == pconn.getResponseCode())
//InputStream in = url.openConnection();
InputStream in = pconn.getInputStream();
System.out.println("Get status:"+pconn.getResponseCode());
BufferedInputStream buff = new BufferedInputStream(in);
Reader r = new InputStreamReader(buff);
int c = 0;
while ( (c = r.read()) != -1){
System.out.print((char)c);
}
buff.close();
in.close();
pconn.disconnect();
} catch (MalformedURLException mfe) {
System.err.println(target + " is not a parsable URL");
} catch (IOException ioe) {
System.err.println(ioe);
}
}
}
package service;
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
public class CyclicBarrierTaskScheduler implements Runnable {
private CyclicBarrier cyclicBarrier;
private int batchTaskNumbers;
private int realtimeTaskNumbers;
// you can set an ExecutorService extenally
private ExecutorService executor = Executors.newFixedThreadPool(10);
public void run() {
// pre-validate on states of current object
cyclicBarrier = new CyclicBarrier(getBatchTaskNumbers(),
new Runnable() {
public void run() {
System.out.println("B");
// for (int i = 0; i < getRealtimeTaskNumbers(); i++) {
// getExecutor().execute(new RealtimeTask());
// }
}
});
for (int i = 0; i < getBatchTaskNumbers(); i++) {
getExecutor().execute(new Runnable() {
public void run() {
new BatchTask().run();
try {
getCyclicBarrier().await();
} catch (InterruptedException e) {
e.printStackTrace(); // process exception as per your
// need
} catch (BrokenBarrierException e) {
e.printStackTrace(); // process exception as per your
// need
}
}
});
}
}
public void shutdown() {
if (getExecutor() != null) {
getExecutor().shutdown();
try {
getExecutor().awaitTermination(Integer.MAX_VALUE,
TimeUnit.SECONDS);
} catch (InterruptedException e) {
e.printStackTrace(); // process exception as per your need
}
}
}
public CyclicBarrier getCyclicBarrier() {
return cyclicBarrier;
}
public int getBatchTaskNumbers() {
return batchTaskNumbers;
}
public void setBatchTaskNumbers(int batchTaskNumbers) {
this.batchTaskNumbers = batchTaskNumbers;
}
public int getRealtimeTaskNumbers() {
return realtimeTaskNumbers;
}
public void setRealtimeTaskNumbers(int realtimeTaskNumbers) {
this.realtimeTaskNumbers = realtimeTaskNumbers;
}
public ExecutorService getExecutor() {
return executor;
}
public void setExecutor(ExecutorService executor) {
this.executor = executor;
}
public static void main(String[] args) {
CyclicBarrierTaskScheduler taskScheduler = new CyclicBarrierTaskScheduler();
taskScheduler.setBatchTaskNumbers(10);
taskScheduler.setRealtimeTaskNumbers(15);
try {
taskScheduler.run();
} finally {
// taskScheduler.shutdown();
}
}
}
客户端:
Java代码
package com.wlh.test;
import java.io.*;
import java.net.*;
public class talkclient
{
public static void main(String args[])
{
try
{
Socket socket = new Socket("192.168.13.123",9999);
BufferedReader sin = new BufferedReader(new InputStreamReader(System.in));
PrintWriter os = new PrintWriter(socket.getOutputStream());
BufferedReader is = new BufferedReader(new InputStreamReader(socket.getInputStream()));
String readline;
while (!"bye".equals(readline=sin.readLine())) {
//==============写服务端==================//
os.println(readline);
os.flush();
//==============读服务端==================//
System.out.println("服务端反馈:"+is.readLine());
}
os.close();
is.close();
socket.close();
}catch(Exception e)
{
System.out.println("Error" + e);
}
}
}
package com.wlh.test;
import java.io.*;
import java.net.*;
public class talkclient
{
public static void main(String args[])
{
try
{
Socket socket = new Socket("192.168.13.123",9999);
BufferedReader sin = new BufferedReader(new InputStreamReader(System.in));
PrintWriter os = new PrintWriter(socket.getOutputStream());
BufferedReader is = new BufferedReader(new InputStreamReader(socket.getInputStream()));
String readline;
while (!"bye".equals(readline=sin.readLine())) {
//==============写服务端==================//
os.println(readline);
os.flush();
//==============读服务端==================//
System.out.println("服务端反馈:"+is.readLine());
}
os.close();
is.close();
socket.close();
}catch(Exception e)
{
System.out.println("Error" + e);
}
}
}
服务端:负责接收多个客户端的连接 ,然后交给多线程去处理
Java代码
package com.wlh.test;
import java.io.*;
import java.net.*;
//引入了无关的类
import java.applet.Applet;
public class talkserver {
static int clientnum = 0; // 静态成员变量,记录当前客户的个数
public static void main(String args[]) {
ServerSocket server = null;
try {
server = new ServerSocket(9999);
} catch (IOException e1) {
e1.printStackTrace();
}
while(true){
try {
Socket socket = server.accept();
System.out.println("一个客户端连上来了....");
ServerThread thread=new ServerThread(socket);
//thread.start();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
package com.wlh.test;
import java.io.*;
import java.net.*;
//引入了无关的类
import java.applet.Applet;
public class talkserver {
static int clientnum = 0; // 静态成员变量,记录当前客户的个数
public static void main(String args[]) {
ServerSocket server = null;
try {
server = new ServerSocket(9999);
} catch (IOException e1) {
e1.printStackTrace();
}
while(true){
try {
Socket socket = server.accept();
System.out.println("一个客户端连上来了....");
ServerThread thread=new ServerThread(socket);
//thread.start();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
多线程程序:
Java代码
package com.wlh.test;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.Socket;
/**
* 服务器端逻辑线程
*/
public class ServerThread extends Thread {
Socket socket;
BufferedReader br=null;
PrintWriter os=null;
public ServerThread(Socket socket) {
this.socket = socket;
start(); //启动线程
}
public void run() {
BufferedReader is=null;
PrintWriter os=null;
try {
boolean flag=true;
//ServerSocket server = null;
//Socket socket = null;
//ServerSocket server = new ServerSocket(9999);
System.out.println("server listen on port 9999...");
//socket = server.accept();
is = new BufferedReader(new InputStreamReader(socket.getInputStream()));
os = new PrintWriter(socket.getOutputStream());
while(flag){
System.out.println("-------------------");
//多线程
//new ServerThread(socket, clientnum).start();
//标准输入端读取一行
BufferedReader sin = new BufferedReader(new InputStreamReader(System.in));
String str;
String reply;
while(!"".equals(str=is.readLine())){
System.out.println(".....start...........");
System.out.println(str); //每写一行,就等着从客户端读进一行,
//====向客户端反馈读到的信息=====//
os.println("服务端已经收到:"+str);
os.flush();
//=======从控制台读取信息==========//
//reply=sin.readLine();
//os.println("服务端 说:"+reply); //写到客户端
//os.flush();
System.out.println("......end.........");
}
System.out.println("out of while...");
//String line=sin.readLine();
os.println("server AAAAAAAAAAAAA"); //写到客户端
os.flush();
}
//****流如果关闭,socket也将会关闭,所以如果想在一个socket连接之内通讯*****//,
//***则应该在while之外关闭socket****//
//server.close();
} catch (Exception e) {
System.out.println("Error" + e);
}finally{
try {
is.close();
os.close();
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
/**
* 关闭流和连接
*/
private void close() {
try {
//关闭流和连接
os.close();
br.close();
socket.close();
} catch (Exception e) {
}
}
}
java并发编程-构建块
Executor框架是指java 5中引入的一系列并发库中与executor相关的一些功能类,其中包括线程池,Executor,Executors,ExecutorService,CompletionService,Future,Callable等。他们的关系为:
并发编程的一种编程方式是把任务拆分为一些列的小任务,即Runnable,然后在提交给一个Executor执行,Executor.execute(Runnalbe) 。Executor在执行时使用内部的线程池完成操作。
一、创建线程池
Executors类,提供了一系列工厂方法用于创先线程池,返回的线程池都实现了ExecutorService接口。
public static ExecutorService newFixedThreadPool(int nThreads)
创建固定数目线程的线程池。
public static ExecutorService newCachedThreadPool()
创建一个可缓存的线程池,调用execute 将重用以前构造的线程(如果线程可用)。如果现有线程没有可用的,则创建一个新线程并添加到池中。终止并从缓存中移除那些已有 60 秒钟未被使用的线程。
public static ExecutorService newSingleThreadExecutor()
创建一个单线程化的Executor。
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize)
创建一个支持定时及周期性的任务执行的线程池,多数情况下可用来替代Timer类。
Java代码
Executor executor = Executors.newFixedThreadPool(10);
Runnable task = new Runnable() {
@Override
public void run() {
System.out.println("task over");
}
};
executor.execute(task);
executor = Executors.newScheduledThreadPool(10);
ScheduledExecutorService scheduler = (ScheduledExecutorService) executor;
scheduler.scheduleAtFixedRate(task, 10, 10, TimeUnit.SECONDS);
Executor executor = Executors.newFixedThreadPool(10);
Runnable task = new Runnable() {
@Override
public void run() {
System.out.println("task over");
}
};
executor.execute(task);
executor = Executors.newScheduledThreadPool(10);
ScheduledExecutorService scheduler = (ScheduledExecutorService) executor;
scheduler.scheduleAtFixedRate(task, 10, 10, TimeUnit.SECONDS); 二、ExecutorService与生命周期
ExecutorService扩展了Executor并添加了一些生命周期管理的方法。一个Executor的生命周期有三种状态,运行 ,关闭 ,终止 。Executor创建时处于运行状态。当调用ExecutorService.shutdown()后,处于关闭状态,isShutdown()方法返回true。这时,不应该再想Executor中添加任务,所有已添加的任务执行完毕后,Executor处于终止状态,isTerminated()返回true。
如果Executor处于关闭状态,往Executor提交任务会抛出unchecked exception RejectedExecutionException。
Java代码
ExecutorService executorService = (ExecutorService) executor;
while (!executorService.isShutdown()) {
try {
executorService.execute(task);
} catch (RejectedExecutionException ignored) {
}
}
executorService.shutdown();
ExecutorService executorService = (ExecutorService) executor;
while (!executorService.isShutdown()) {
try {
executorService.execute(task);
} catch (RejectedExecutionException ignored) {
}
}
executorService.shutdown();
三、使用Callable,Future返回结果
Future<V>代表一个异步执行的操作,通过get()方法可以获得操作的结果,如果异步操作还没有完成,则,get()会使当前线程阻塞。FutureTask<V>实现了Future<V>和Runable<V>。Callable代表一个有返回值得操作。
Java代码
Callable<Integer> func = new Callable<Integer>(){
public Integer call() throws Exception {
System.out.println("inside callable");
Thread.sleep(1000);
return new Integer(8);
}
};
FutureTask<Integer> futureTask = new FutureTask<Integer>(func);
Thread newThread = new Thread(futureTask);
newThread.start();
try {
System.out.println("blocking here");
Integer result = futureTask.get();
System.out.println(result);
} catch (InterruptedException ignored) {
} catch (ExecutionException ignored) {
}
Callable<Integer> func = new Callable<Integer>(){
public Integer call() throws Exception {
System.out.println("inside callable");
Thread.sleep(1000);
return new Integer(8);
}
};
FutureTask<Integer> futureTask = new FutureTask<Integer>(func);
Thread newThread = new Thread(futureTask);
newThread.start();
try {
System.out.println("blocking here");
Integer result = futureTask.get();
System.out.println(result);
} catch (InterruptedException ignored) {
} catch (ExecutionException ignored) {
} ExecutoreService提供了submit()方法,传递一个Callable,或Runnable,返回Future。如果Executor后台线程池还没有完成Callable的计算,这调用返回Future对象的get()方法
什么是UDP协议
UDP协议的全称是用户数据报,在网络中它与TCP协议一样用于处理数据包。在OSI模型中,在第四层——传输层,处于IP协议的上一层。 UDP有不提供数据报分组、组装和不能对数据包的排序的缺点,也就是说,当报文发送之后,是无法得知其是否安全完整到达的。
为什么要使用UDP
在选择使用协议的时候,选择UDP必须要谨慎。在网络质量令人不十分满意的环境下,UDP协议数据包丢失会比较严重。但是由于UDP的特性:它不属于连接型协议,因而具有资源消耗小,处理速度快的优点,所以通常音频、视频和普通数据在传送时使用UDP较多,因为它们即使偶尔丢失一两个数据包,也不会对接收结果产生太大影响。比如我们聊天用的ICQ和OICQ就是使用的UDP协议。
在Java中操纵UDP
使用位于JDK中Java.net包下的DatagramSocket和DatagramPacket类,可以非常方便地控制用户数据报文。
在描述它们之前,必须了解位于同一个位置的InetAddress类。InetAddress实现了Java.io. Serializable接口,不允许继承。它用于描述和包装一个Internet IP地址,通过三个方法返回InetAddress实例:
getLocalhost():返回封装本地地址的实例。
getAllByName(String host):返回封装Host地址的InetAddress实例数组。
getByName(String host):返回一个封装Host地址的实例。其中,Host可以是域名或者是一个合法的IP地址。
DatagramSocket类用于创建接收和发送UDP的Socket实例。和Socket类依赖SocketImpl类一样,DatagramSocket类的实现也依靠专门为它设计的DatagramScoketImplFactory类。DatagramSocket类有3个构建器:
DatagramSocket():创建实例。这是个比较特殊的用法,通常用于客户端编程,它并没有特定监听的端口,仅仅使用一个临时的。
DatagramSocket(int port):创建实例,并固定监听Port端口的报文。
DatagramSocket(int port, InetAddress localAddr):这是个非常有用的构建器,当一台机器拥有多于一个IP地址的时候,由它创建的实例仅仅接收来自LocalAddr的报文。
值得注意的是,在创建DatagramSocket类实例时,如果端口已经被使用,会产生一个SocketException的异常抛出,并导致程序非法终止,这个异常应该注意捕获。DatagramSocket类最主要的方法有4个:
Receive(DatagramPacket d):接收数据报文到d中。receive方法产生一个“阻塞”。
Send(DatagramPacket d):发送报文d到目的地。
SetSoTimeout(int timeout):设置超时时间,单位为毫秒。
Close():关闭DatagramSocket。在应用程序退出的时候,通常会主动释放资源,关闭Socket,但是由于异常地退出可能造成资源无法回收。所以,应该在程序完成时,主动使用此方法关闭Socket,或在捕获到异常抛出后关闭Socket。
“阻塞”是一个专业名词,它会产生一个内部循环,使程序暂停在这个地方,直到一个条件触发。
DatagramPacket类用于处理报文,它将Byte数组、目标地址、目标端口等数据包装成报文或者将报文拆卸成Byte数组。应用程序在产生数据包是应该注意,TCP/IP规定数据报文大小最多包含65507个,通常主机接收548个字节,但大多数平台能够支持8192字节大小的报文。DatagramPacket类的构建器共有4个:
DatagramPacket(byte[] buf, int length, InetAddress addr, int port):从Buf数组中,取出Length长的数据创建数据包对象,目标是Addr地址,Port端口。
DatagramPacket(byte[] buf, int offset, int length, InetAddress address, int port):从Buf数组中,取出Offset开始的、Length长的数据创建数据包对象,目标是Addr地址,Port端口。
DatagramPacket(byte[] buf, int offset, int length):将数据包中从Offset开始、Length长的数据装进Buf数组。
DatagramPacket(byte[] buf, int length):将数据包中Length长的数据装进Buf数组。
DatagramPacket类最重要的方法就是getData()了,它从实例中取得报文的Byte数组编码。
★简单的实例说明
{接收数据的服务器}
byte[] buf = new byte[1000];
DatagramSocket ds = new DatagramSocket(12345);
//开始监视12345端口
DatagramPacket ip = new DatagramPacket(buf, buf.length);
//创建接收数据报的实例
while (true)
{
ds.receive(ip);
//阻塞,直到收到数据报后将数据装入IP中
System.out.println(new String(buf));
}
{发送数据的客户端}
InetAddress target = InetAddress.getByName("www.xxx.com");
//得到目标机器的地址实例
DatagramSocket ds = new DatagramSocket(9999);
//从9999端口发送数据报
String hello = "Hello, I am come in!";
//要发送的数据
byte[] buf = hello.getBytes();
//将数据转换成Byte类型
op = new DatagramPacket(buf, buf.length, target, 12345);
//将BUF缓冲区中的数据打包
ds.send(op);
//发送数据
ds.close();
//关闭连接
基于Java的UDP协议程序设计初探
在Java 中进行网络编程是相对容易的,因为J2SE中的java.net包已经对各种通信协议很好的进行了封装,本文主要讲述如何基于UDP(用户数据报)协议编写应用程序。
通常我们进行网络编程一般都是使用基于socket的TCP/IP编程,毕竟TCP/IP应用非常的广泛,比如我们浏览互联网就是基于HTTP协议、我们发送邮件是通过SMTP协议。它们都是基于TCP/IP的。TCP/IP的传输最重要的是它可以保证数据到达目的地,而UDP则不同他并不保证准确的传输,数据有可能丢失。如果有兴趣的话,读者可以参考《计算机网络》一书。
在介绍UDP编程之前有必要介绍一个重要的类InetAddress,用最简单的一句话描述这个类的作用就是:它代表了一个IP地址。这非常重要在互联网中如果知道了IP地址则意味着我们知道了通信的端点。这个类没有构造器但是有几个工厂方法,通过传递不同的参数例如IP,Hostname等来得到一个InetAddress的实例,下面的这个小例子可以得到我机器的IP地址。
import java.net.*;
public class TestNet
{
public static void main(String[] args) throws Exception
{
InetAddress ia = InetAddress.getByName("compaq");
String ipAdr = ia.getHostAddress();
System.out.println(ipAdr);
}
}
当然我的机器的名字为compaq,如果你传入localhost的话将会得到127.0.0.1。
接下来开始讲述如何使用UDP编程,这非常容易理解,我们应该首先构造一个数据报,然后把它发送出去,同时我们也可以接收数据报。在java中提供了DatagramPacket和DatagramSocket两个类来完成这样的任务,前者负责构造数据报后者负责发送和接收。看看DatagramPacket的构造器
DatagramPacket(byte[] buf, int length, InetAddress address, int port)
DatagramPacket(byte[] buf, int offset, int length, InetAddress address, int port)
DatagramPacket(byte[] buf, int offset, int length, SocketAddress address)
DatagramPacket(byte[] buf, int length, SocketAddress address)
DatagramPacket(byte[] buf, int length)
DatagramPacket(byte[] buf, int offset, int length)
其中前面四个是为构造发送的数据报而用的,因为他们有InetAddress或者SocketInetAddress作为接收端点的地址,后面一个则是为了接受数据报用的。
同样我们编写一个C/S模型的例子来说明如何使用这两个重要的类,如果对API还不熟悉请参考Java doc。下面的程序在本机构造一个时间服务器,客户端来取得时间。以前曾经写过类似的时间服务器程序,不过这个是基于UDP的编程。
import java.io.*;
import java.net.*;
import java.util.*;
public class TimeServer {
final private static int DAYTIME_PORT = 13;
public static void main(String args[]) throws
IOException {
DatagramSocket socket = new DatagramSocket(DAYTIME_PORT);
while (true) {
byte buffer[] = new byte[256];
DatagramPacket packet =new DatagramPacket(buffer, buffer.length);
socket.receive(packet);
String date = new Date().toString();
buffer = date.getBytes();
// Get response address/port
// for client from packet
InetAddress address = packet.getAddress();
int port = packet.getPort();
packet = new DatagramPacket(buffer, buffer.length, address, port);
socket.send(packet);
}
}
}
import java.io.*;
import java.net.*;
public class GetTime {
final private static int DAYTIME_PORT = 13;
public static void main(String args[]) throws
IOException {
if (args.length == 0) {
System.err.println("Please specify daytime host");
System.exit(-1);
}
String host = args[0];
byte message[] = new byte[256];
InetAddress address = InetAddress.getByName(host);
System.out.println("Checking at: " + address);
DatagramPacket packet = new DatagramPacket(message, message.length,
address, DAYTIME_PORT);
DatagramSocket socket = new DatagramSocket();
socket.send(packet);
packet =new DatagramPacket(message, message.length);
socket.receive(packet);
String time = new String(packet.getData());
System.out.println(The time at "+ host + " is: " + time);
socket.close();
}
}
Jakarta Commons HttpClient 学习笔记
虽然用telnet这样的程序都可把页面取回来,但是在与web服务器的交互中,如果涉及cookie或https或ssl等内容,一般功能相对完备的http客户端还是非常必要的。IE或NetScape等浏览器确实不错,可是如果为实现持续互动而在程序调用浏览器,我个人认为其中的工作量还是不小的,这还没考虑版权问题。最好的办法,就是能有一个开源的包,能实现http客户端的功能,供我们开发的程序调用。httpclient就是这么一个包,我相信可能有比它的实现更好的,但目前我只关注这个。:)
下面是nogoop做的功能比较表:
Features nogoop Sun JRE < 1.4.2 Sun JRE 1.4.2 Innovation Apache/Jakarta
cookies X X
plug compatible X X X X [partial]
true request output stream X X
true response input stream X X X
connection keep alive X X X X X
connection pool throttling X X
connection/request timeout X X [uns] X X
idle connection timeout X X
pipelining of requests X
alternate DNS resolution (dnsjava) X
SSL X X X X X
basic authentication X X X X X
digest authentication X X X X X
NTLM authentication X [Windows only] X
proxy authentication X X X X X
minimum JRE version 1.2 1 01年4月2日 1.2 1.2
price $499 free free free free
source available X X X
diagnostic tracing X X X
actively supported X X X X
fix turnaround fast slow slow none medium
license purchase Sun JRE Sun JRE LGPL Apache
1、HttpClient的功能
基于标准,纯正java,实现了http1.0和1.1。
在一个可扩展的OO框架内,实现了HTTP的全部方法(GET, POST,
PUT, DELETE, HEAD, OPTIONS, and TRACE)
支持HTTPS(ssl上的HTTP)的加密操作
透明地穿过HTTP代理建立连接
通过CONNECT方法,利用通过建立穿过HTTP代理的HTTPS连接
利用本地Java socket,透明地穿过SOCKS(版本5和4)代理建立连接
支持利用Basic、Digest和NTLM加密的认证
支持用于上传大文件的Multi-Part表单POST方法
插件式安全socket实现,易于使用第三方的解决方案
连接管理,支持多线程应用,支持设定单个主机总连接和最高连接数量,自动检测和关闭失效连接
直接将请求信息流送到服务器的端口
直接读取从服务器的端口送出的应答信息
支持HTTP/1.0中用KeepAlive和HTTP/1.1中用persistance设置的持久连接
直接访问由服务器送出的应答代码和头部信息
可设置连接超时时间
HttpMethods 实现Command Pattern,以允许并行请求或高效连接复用
遵循the Apache Software License协议,源码免费可得
2、预备工作
对jre1.3.*,如果要HttpClient支持https,则需要下载并安装jsse和jce.安装的步骤如下:
1)下载jsse和jce.
2)检查CLASSPATH中没有与jsse和jce相关的jar包
3)将 US_export_policy.jar、local_policy.jar、jsse.jar、jnet.jar、jce1_2_x.jar、sunjce_provider.jar、jcert.jar复制到目录:
UNIX:$JDK_HOME/jre/lib/ext
Windows:%JDK_HOME%\jre\lib\ext
4)修改下述目录下的java.security文件。
UNIX:$JDK_HOME/jre/lib/security/
Windows:%JDK_HOME%\jre\lib\security\
5)
将
#
# List of providers and their preference orders:
#
security.provider.1=sun.security.provider.Sun
security.provider.2=com.sun.rsajca.Provider
改为:
#
# List of providers and their preference orders:
#
security.provider.1=com.sun.crypto.provider.SunJCE
security.provider.2=sun.security.provider.Sun
security.provider.3=com.sun.rsajca.Provider
security.provider.4=com.sun.net.ssl.internal.ssl.Provider
HttpClient还要求安装commons-logging,下面跟httpclient一块安装。
3、取得源码
cvs -d :pserver:anoncvs@cvs.apache.org:/home/cvspublic login
password: anoncvs
cvs -d :pserver:anoncvs@cvs.apache.org:/home/cvspublic checkout jakarta-commons/logging
cvs -d :pserver:anoncvs@cvs.apache.org:/home/cvspublic checkout jakarta-commons/httpclient
编译:
cd jakarta-commons/logging
ant dist
cp dis/*.jar ../httpclient/lib/
cd ../httpclient
ant dist
4、使用HttpClient编程的基本步聚
创建 HttpClient 的一个实例.
创建某个方法(DeleteMethod,EntityEnclosingMethod,ExpectContinueMethod,GetMethod,HeadMethod,MultipartPostMethod,OptionsMethod,PostMethod,PutMethod,TraceMethod)的一个实例,一般可用要目标URL为参数。
让 HttpClient 执行这个方法.
读取应答信息.
释放连接.
处理应答.
在执行方法的过程中,有两种异常,一种是HttpRecoverableException,表示偶然性错误发生,一般再试可能成功,另一种是IOException,严重错误。
这儿有这个教程中的一个例程,可以下载。
5、认证
HttpClient三种不同的认证方案: Basic, Digest and NTLM. 这些方案可用于服务器或代理对客户端的认证,简称服务器认证或代理认证。
1)服务器认证(Server Authentication)
HttpClient处理服务器认证几乎是透明的,仅需要开发人员提供登录信息(login credentials)。登录信息保存在HttpState类的实例中,可以通过 setCredentials(String realm, Credentials cred)和getCredentials(String realm)来获取或设置。注意,设定对非特定站点访问所需要的登录信息,将realm参数置为null. HttpClient内建的自动认证,可以通过HttpMethod类的setDoAuthentication(boolean doAuthentication)方法关闭,而且这次关闭只影响HttpMethod当前的实例。
抢先认证(Preemptive Authentication)可以通过下述方法打开.
client.getState().setAuthenticationPreemptive(true);
在这种模式时,HttpClient会主动将basic认证应答信息传给服务器,即使在某种情况下服务器可能返回认证失败的应答,这样做主要是为了减少连接的建立。为使每个新建的 HttpState实例都实行抢先认证,可以如下设置系统属性。
setSystemProperty(Authenticator.PREEMPTIVE_PROPERTY, "true");
Httpclient实现的抢先认证遵循rfc2617.
2)代理认证(proxy authentication)
除了登录信息需单独存放以外,代理认证与服务器认证几乎一致。用 setProxyCredentials(String realm, Credentials cred)和 getProxyCredentials(String realm)设、取登录信息。
3)认证方案(authentication schemes)
Basic
是HTTP中规定最早的也是最兼容(?)的方案,遗憾的是也是最不安全的一个方案,因为它以明码传送用户名和密码。它要求一个UsernamePasswordCredentials实例,可以指定服务器端的访问空间或采用默认的登录信息。
Digest
是在HTTP1.1中增加的一个方案,虽然不如Basic得到的软件支持多,但还是有广泛的使用。Digest方案比Basic方案安全得多,因它根本就不通过网络传送实际的密码,传送的是利用这个密码对从服务器传来的一个随机数(nonce)的加密串。它要求一个UsernamePasswordCredentials实例,可以指定服务器端的访问空间或采用默认的登录信息。
NTLM
这是HttpClient支持的最复杂的认证协议。它M$设计的一个私有协议,没有公开的规范说明。一开始由于设计的缺陷,NTLM的安全性比Digest差,后来经过一个ServicePack补丁后,安全性则比较Digest高。NTLM需要一个NTCredentials实例. 注意,由于NTLM不使用访问空间(realms)的概念,HttpClient利用服务器的域名作访问空间的名字。还需要注意,提供给NTCredentials的用户名,不要用域名的前缀 - 如: "adrian" 是正确的,而 "DOMAIN\adrian" 则是错的.
NTLM认证的工作机制与basic和digest有很大的差别。这些差别一般由HttpClient处理,但理解这些差别有助避免在使用NTLM认证时出现错误。
从HttpClientAPI的角度来看,NTLM与其它认证方式一样的工作,差别是需要提供'NTCredentials'实例而不是'UsernamePasswordCredentials'(其实,前者只是扩展了后者)
对NTLM认证,访问空间是连接到的机器的域名,这对多域名主机会有一些麻烦.只有HttpClient连接中指定的域名才是认证用的域名。建议将realm设为null以使用默认的设置。
NTLM只是认证了一个连接而不是一请求,所以每当一个新的连接建立就要进行一次认证,且在认证的过程中保持连接是非常重要的。 因此,NTLM不能同时用于代理认证和服务器认证,也不能用于http1.0连接或服务器不支持持久连接的情况。
6、重定向
由于技术限制,以及为保证2.0发布版API的稳定,HttpClient还不能自动处重定向,但对重定向到同一主机、同一端口且采用同一协议的情况HttpClient可以支持。不能自动的处理的情况,包括需要人工交互的情况,或超出httpclient的能力。
当服务器重定向指令指到不同的主机时,HttpClient只是简单地将重定向状态码作为应答状态。所有的300到399(包含两端)的返回码,都表示是重定向应答。常见的有:
301 永久移动. HttpStatus.SC_MOVED_PERMANENTLY
302 临时移动. HttpStatus.SC_MOVED_TEMPORARILY
303 See Other. HttpStatus.SC_SEE_OTHER
307 临时重定向. HttpStatus.SC_TEMPORARY_REDIRECT
当收到简单的重定向时,程序应从HttpMethod对象中抽取新的URL并将其下载。另外,限制一下重定向次数是个好的主意,这可以避免递归循环。新的URL可以从头字段Location中抽取,如下:
String redirectLocation;
Header locationHeader = method.getResponseHeader("location");
if (locationHeader != null) {
redirectLocation = locationHeader.getValue();
} else {
// The response is invalid and did not provide the new location for
// the resource. Report an error or possibly handle the response
// like a 404 Not Found error.
}
特殊重定向:
300 多重选择. HttpStatus.SC_MULTIPLE_CHOICES
304 没有改动. HttpStatus.SC_NO T_MODIFIED
305 使用代理. HttpStatus.SC_USE_PROXY
7、字符编码(character encoding)
一个HTTP协议的请求或应答的头部(在http协议中,数据包分为两部分,一部分是头部,由一些名值对构成,一部分是主体(body),是真正传办理的数据(如HTML页面等)),必须以US-ASCII编码,这是因为头部不传数据而只描述被要传输的数据的一些信息,一个例外是cookie,它是数据但是通过头部进行传输的,所以它也要用US-ASCII编码。
HTTP数据包的主体部分,可以用任何一种方式进行编码,默认是ISO-8859-1,具体可以用头部字段Content-Type指定。可以利用 addRequestHeader方法,设定编码方式;用 getResponseCharSet取得编码方式。对HTML或XML等类型的文档,它们的本身的Content-Type也可以指定编码方式,主要区分两者的作用范围以得到正确实的解码。
URL的编码标准,由RFC1738指定为,只能是由可打印8位/字节的us-ascii字符组成,80-ff不是us-ascii字符,而00-1F是控制字符,这两个区域中用的字符都须加以编码(encoded)。
8、Cookies
HttpClient能自动管理cookie,包括允许服务器设置cookie并在需要的时候自动将cookie返回服务器,它也支持手工设置cookie后发送到服务器端。不幸的是,对如何处理cookie,有几个规范互相冲突:Netscape Cookie 草案, RFC2109, RFC2965,而且还有很大数量的软件商的cookie实现不遵循任何规范. 为了处理这种状况,HttpClient提供了策略驱动的cookie管理方式。HttpClient支持的cookie规范有:
Netscape cookie草案,是最早的cookie规范,基于rfc2109。尽管这个规范与rc2109有较大的差别,这样做可以与一些服务器兼容。
rfc2109,是w3c发布的第一个官方cookie规范。理论上讲,所有的服务器在处理cookie(版本1)时,都要遵循此规范,正因如此,HttpClient将其设为默认的规范。遗憾的是,这个规范太严格了,以致很多服务器不正确的实施了该规范或仍在作用Netscape规范。在这种情况下,应使用兼容规范。
兼容性规范,设计用来兼容尽可能多的服务器,即使它们并没有遵循标准规范。当解析cookie出现问题时,应考虑采用兼容性规范。
RFC2965规范暂时没有被HttpClient支持(在以后的版本为会加上),它定义了cookie版本2,并说明了版本1cookie的不足,RFC2965有意有久取代rfc2109.
在HttpClient中,有两种方法来指定cookie规范的使用,
HttpClient client = new HttpClient();
client.getState().setCookiePolicy(CookiePolicy.COMPATIBILITY);
这种方法设置的规范只对当前的HttpState有效,参数可取值CookiePolicy.COMPATIBILITY,CookiePolicy.NETSCAPE_DRAFT或CookiePolicy.RFC2109。
System.setProperty("apache.commons.httpclient.cookiespec", "COMPATIBILITY");
此法指的规范,对以后每个新建立的HttpState对象都有效,参数可取值"COMPATIBILITY","NETSCAPE_DRAFT"或"RFC2109"。
常有不能解析cookie的问题,但更换到兼容规范大都能解决。
9、使用HttpClient遇到问题怎么办?
用一个浏览器访问服务器,以确认服务器应答正常
如果在使代理,关掉代理试试
另找一个服务器来试试(如果运行着不同的服务器软件更好)
检查代码是否按教程中讲的思路编写
设置log级别为debug,找出问题出现的原因
打开wiretrace,来追踪客户端与服务器的通信,以确实问题出现在什么地方
用telnet或netcat手工将信息发送到服务器,适合于猜测已经找到了原因而进行试验时
将netcat以监听方式运行,用作服务器以检查httpclient如何处理应答的。
利用最新的httpclient试试,bug可能在最新的版本中修复了
向邮件列表求帮助
向bugzilla报告bug.
10、SSL
借助Java Secure Socket Extension (JSSE),HttpClient全面支持Secure Sockets Layer (SSL)或IETF Transport Layer Security (TLS)协议上的HTTP。JSSE已经jre1.4及以后的版本中,以前的版本则需要手工安装设置,具体过程参见Sun网站或本学习笔记。
HttpClient中使用SSL非常简单,参考下面两个例子:
HttpClient httpclient = new HttpClient();
GetMethod httpget = new GetMethod("https://www.verisign.com/");
httpclient.executeMethod(httpget);
System.out.println(httpget.getStatusLine().toString());
,如果通过需要授权的代理,则如下:
HttpClient httpclient = new HttpClient();
httpclient.getHostConfiguration().setProxy("myproxyhost", 8080);
httpclient.getState().setProxyCredentials("my-proxy-realm", " myproxyhost",
new UsernamePasswordCredentials("my-proxy-username", "my-proxy-password"));
GetMethod httpget = new GetMethod("https://www.verisign.com/");
httpclient.executeMethod(httpget);
System.out.println(httpget.getStatusLine().toString());
在HttpClient中定制SSL的步骤如下:
提供了一个实现了org.apache.commons.httpclient.protocol.SecureProtocolSocketFactory接口的socket factory。这个 socket factory负责打一个到服务器的端口,使用标准的或第三方的SSL函数库,并进行象连接握手等初始化操作。通常情况下,这个初始化操作在端口被创建时自动进行的。
实例化一个org.apache.commons.httpclient.protocol.Protocol对象。创建这个实例时,需要一个合法的协议类型(如https),一个定制的socket factory,和一个默认的端中号(如https的443端口).
Protocol myhttps = new Protocol("https", new MySSLSocketFactory(), 443);
然后,这个实例可被设置为协议的处理器。
HttpClient httpclient = new HttpClient();
httpclient.getHostConfiguration().setHost("www.whatever.com", 443, myhttps);
GetMethod httpget = new GetMethod("/");
httpclient.executeMethod(httpget);
通过调用Protocol.registerProtocol方法,将此定制的实例,注册为某一特定协议的默认的处理器。由此,可以很方便地定制自己的协议类型(如myhttps)。
Protocol.registerProtocol("myhttps",
new Protocol("https", new MySSLSocketFactory(), 9443));
...
HttpClient httpclient = new HttpClient();
GetMethod httpget = new GetMethod("myhttps://www.whatever.com/");
httpclient.executeMethod(httpget);
如果想用自己定制的处理器取代https默认的处理器,只需要将其注册为"https"即可。
Protocol.registerProtocol("https",
new Protocol("https", new MySSLSocketFactory(), 443));
HttpClient httpclient = new HttpClient();
GetMethod httpget = new GetMethod("https://www.whatever.com/");
httpclient.executeMethod(httpget);
已知的限制和问题
持续的SSL连接在Sun的低于1.4JVM上不能工作,这是由于JVM的bug造成。
通过代理访问服务器时,非抢先认证( Non-preemptive authentication)会失败,这是由于HttpClient的设计缺陷造成的,以后的版本中会修改。
遇到问题的处理
很多问题,特别是在jvm低于1.4时,是由jsse的安装造成的。
下面的代码,可作为最终的检测手段。
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.Writer;
import java.net.Socket; import javax.net.ssl.SSLSocketFactory; public class Test {
public static final String TARGET_HTTPS_SERVER = "www.verisign.com";
public static final int TARGET_HTTPS_PORT = 443;
public static void main(String[] args) throws Exception {
Socket socket = SSLSocketFactory.getDefault().
createSocket(TARGET_HTTPS_SERVER, TARGET_HTTPS_PORT);
try {
Writer out = new OutputStreamWriter(
socket.getOutputStream(), "ISO-8859-1");
out.write("GET / HTTP/1.1\r\n");
out.write("Host: " + TARGET_HTTPS_SERVER + ":" +
TARGET_HTTPS_PORT + "\r\n");
out.write("Agent: SSL-TEST\r\n");
out.write("\r\n");
out.flush();
BufferedReader in = new BufferedReader(
new InputStreamReader(socket.getInputStream(), "ISO-8859-1"));
String line = null;
while ((line = in.readLine()) != null) {
System.out.println(line);
}
} finally {
socket.close();
}
}
}
11、httpclient的多线程处理
使用多线程的主要目的,是为了实现并行的下载。在httpclient运行的过程中,每个http协议的方法,使用一个HttpConnection实例。由于连接是一种有限的资源,每个连接在某一时刻只能供一个线程和方法使用,所以需要确保在需要时正确地分配连接。HttpClient采用了一种类似jdbc连接池的方法来管理连接,这个管理工作由 MultiThreadedHttpConnectionManager完成。
MultiThreadedHttpConnectionManager connectionManager =
new MultiThreadedHttpConnectionManager();
HttpClient client = new HttpClient(connectionManager);
此是,client可以在多个线程中被用来执行多个方法。每次调用HttpClient.executeMethod() 方法,都会去链接管理器申请一个连接实例,申请成功这个链接实例被签出(checkout),随之在链接使用完后必须归还管理器。管理器支持两个设置: maxConnectionsPerHost 每个主机的最大并行链接数,默认为2
maxTotalConnections 客户端总并行链接最大数,默认为20
管理器重新利用链接时,采取早归还者先重用的方式(least recently used approach)。
由于是使用HttpClient的程序而不是HttpClient本身来读取应答包的主体,所以HttpClient无法决定什么时间连接不再使用了,这也就要求在读完应答包的主体后必须手工显式地调用releaseConnection()来释放申请的链接。
MultiThreadedHttpConnectionManager connectionManager = new MultiThreadedHttpConnectionManager();
HttpClient client = new HttpClient(connectionManager);
...
// 在某个线程中。
GetMethod get = new GetMethod("http://jakarta.apache.org/");
try {
client.executeMethod(get);
// print response to stdout
System.out.println(get.getResponseBodyAsStream());
} finally {
// be sure the connection is released back to the connection
// manager
get.releaseConnection();
}
对每一个HttpClient.executeMethod须有一个method.releaseConnection()与之匹配.
12、HTTP方法
HttpClient支持的HTTP方法有8种,下面分述之。
1、Options
HTTP方法Options用来向服务器发送请求,希望获得针对由请求URL(request url)标志的资源在请求/应答的通信过程可以使用的功能选项。通过这个方法,客户端可以在采取具体行动之前,就可对某一资源决定采取什么动作和/或以及一些必要条件,或者了解服务器提供的功能。这个方法最典型的应用,就是用来获取服务器支持哪些HTTP方法。
HttpClient中有一个类叫OptionsMethod,来支持这个HTTP方法,利用这个类的getAllowedMethods方法,就可以很简单地实现上述的典型应用。
OptionsMethod options = new OptionsMethod("http://jakarta.apache.org");
// 执行方法并做相应的异常处理
...
Enumeration allowedMethods = options.getAllowedMethods();
options.releaseConnection();
2、Get
HTTP方法GET用来取回请求URI(request-URI)标志的任何信息(以实体(entity)的形式),"get"这个单词本意就是”获取“的意思。如果请求URI指向的一个数据处理过程,那这个过程生成的数据,在应答中以实体的形式被返回,而不是将这个过程的代码的返回。
如果HTTP包中含有If-ModifiedSince, If-Unmodified-Since, If-Match, If-None-Match, 或 If-Range等头字段,则GET也就变成了”条件GET“,即只有满足上述字段描述的条件的实体才被取回,这样可以减少一些非必需的网络传输,或者减少为获取某一资源的多次请求(如第一次检查,第二次下载)。(一般的浏览器,都有一个临时目录,用来缓存一些网页信息,当再次浏览某个页面的时候,只下载那些修改过的内容,以加快浏览速度,就是这个道理。至于检查,则常用比GET更好的方法HEAD来实现。)如果HTTP包中含有Range头字段,那么请求URI指定的实体中,只有决定范围条件的那部分才被取回来。(用过多线程下载工具的朋友,可能比较容易理解这一点)
这个方法的典型应用,用来从web服务器下载文档。HttpClient定义了一个类叫GetMethod来支持这个方法,用GetMethod类中getResponseBody, getResponseBodyAsStream 或 getResponseBodyAsString函数就可以取到应答包包体中的文档(如HTML页面)信息。这这三个函数中,getResponseBodyAsStream通常是最好的方法,主要是因为它可以避免在处理下载的文档之前缓存所有的下载的数据。
GetMethod get = new GetMethod("http://jakarta.apache.org");
// 执行方法,并处理失败的请求.
...
InputStream in = get.getResponseBodyAsStream();
// 利用输入流来处理信息。
get.releaseConnection();
对GetMethod的最常见的不正确的使用,是没有将全部的应答主体的数据读出来。还有,必须注意要手工明确地将链接释放。
3、Head
HTTP的Head方法,与Get方法完全一致,唯一的差别是服务器不能在应答包中包含主体(message-body),而且一定不能包含主体。使用这个方法,可以使得客户无需将资源下载回就可就以得到一些关于它的基本信息。这个方法常用来检查超链的可访问性以及资源最近有没有被修改。
HTTP的head方法最典型的应用,是获取资源的基本信息。HttpClient定义了HeadMethod类支持这个方法,HeadMethod类与其它*Method类一样,用 getResponseHeaders()取回头部信息,而没有自己的特殊方法。
HeadMethod head = new HeadMethod("http://jakarta.apache.org");
// 执行方法,并处理失败的请求.
...
// 取回应答包的头字段信息.
Header[] headers = head.getResponseHeaders(); // 只取回最后修改日期字段的信息.
String lastModified = head.getResponseHeader("last-modified").getValue();
4、Post
Post在英文有“派驻”的意思,HTTP方法POST就是要求服务器接受请求包中的实体,并将其作为请求URI的下属资源。从本质上说,这意味着服务器要保存这个实体信息,而且通常由服务器端的程序进行处理。Post方法的设计意图,是要以一种统一的方式实现下列功能:
对已有的资源做评注
将信息发布到BBS、新闻组、邮件列表,或类似的文章组中
将一块数据,提交给数据处理进程
通过追加操作,来扩展一个数据库
这些都操作期待着在服务器端产生一定的“副作用”,如修改了数据库等。
HttpClient定义PostMethod类以支持该HTTP方法,在httpclient中,使用post方法有两个基本的步骤:为请求包准备数据,然后读取服务器来的应答包的信息。通过调用 setRequestBody()函数,来为请求包提供数据,它可以接收三类参数:输入流、名值对数组或字符串。至于读取应答包需要调用 getResponseBody* 那一系列的方法,与GET方法处理应答包的方法相同。
常见问题是,没有将全部应答读取(无论它对程序是否有用),或没有释放链接资源。
HttpClient Cookies
简介
HttpClient支持自动管理Cookies,允许服务端设定Cookies,并在请求时自动返回客户端的Cookies信息。客户端也可以手动的设置Cookies发送到服务端。
不幸的是,同一时间内有太多的Cookies标准: Netscape Cookie, RFC2109, RFC2965 以及大量供应商不符合规范的自定义Cookies标准。针对此,HttpClient提供Cookies管理策略驱动。这片文章意在阐述怎么样去使用不同标准的Cookies以及如何解决在使用Cookies和HttpClient时一些共同的问题。
支持的规格
以下Cookies标准,HttpClient3.1可以支持。
RFC2109
RFC2109是W3C组织第一次推出的官方Cookies标准。理论上,所有使用版本1Cookies的服务端都应该使用此标准。HttpClient已经将此标准设定为默认。
遗憾的是,许多服务端不正确的实现了标准或者仍然使用Netscape标准。所有有时感到此标准太多于严格。
RFC2109是HttpClient使用的默认Cookies协议。
RFC2965
RFC2965定义了版本2并且尝试去弥补在版本1中Cookie的RFC2109标准的缺点。RFC2965是,并规定RFC2965最终取代RFC2109.
发送RFC2965标准Cookies的服务端,将会使用Set-Cookie2 header添加到Set-Cookie Header信心中,RFC2965 Cookies是区分端口的。
Netscape标准
Netscape是最原始的Cookies规范,同时也是RFC2109的基础。尽管如此,还是在很多重要的方面与RFC2109不同,可能需要特定服务器才可以兼容。
Browser Compatibility
这种兼容性设计要求是适应尽可能多的不同的服务器,尽管不是完全按照标准来实现的。如果你遇到了解析Cookies的问题,你就可能要用到这一个规范。
有太多的web站点是用CGI脚本去实现的,而导致只有将所有的Cookies都放入Request header才可以正常的工作。这种情况下最好设置http.protocol.single-cookie-header参数为true。
Ignore Cookies
此规格忽略所有Cookie 。被用来防止HttpClient接受和发送的Cookie。
Spacifying the Specification
有俩中方式去规定使用哪种Cookies规范,每个HttpMethod实例都有HttpMethodParams,他的policy值必须使用方法CookiePolicy.registerCookieSpec()来注册。
HttpMethod method = new GetMethod();
Method.getParams().setCookiePolicy(CookiePolicy.RFC_2109);
手动处理Cookies
HttpClient的Cookie管理API可以手动处理Cookie。可以手动设置Requset的Cookie headers或是处理Response的Set-Cookie的headers或是用自动Cookie管理去代替。
HttpMethod method = new GetMethod();
Method.getParams().setCookiePolicy(CookiePolicy.IGNORE_COOKIES);
Method.setRequestHeader(“Cookie”, “special_cookie=value”);
import java.net.*;
import java.io.*;
import java.util.Properties;
import java.util.Enumeration;
/**
Http客户端程序已集成在Java语言中,可以通过URLConnection类调用。遗憾的
是,由于SUN没有公布Http客户程序的源码,它实现的细节仍是一个谜。本文根据HTTP
协议规范,用Java.net.Socket类实现一个HTTP协议客户端程序。
1.Socket类:
了解TCP/IP协议集通信的读者知道,协议间的通信是通过Socket完成的。在
Java.net包中,Socket类就是对Socket的具体实现。它通过连接到主机后,返回一个
I/O流,实现协议间的信息交换。
2 . HTTP协议
HTTP协议同其它TCP/IP协议集中的协议一样,是遵循客户/服务器模型工作的。客
户端发往服务端的信息格式如下:
------------------------------
请求方法 URL HTTP协议的版本号
提交的元信息
**空行**
实体
------------------------------
请求方法是对这次连接工作的说明,目前HTTP协议已经发展到1.1版,它包括GET、
HEAD、POST、DELETE、OPTIONS、TRACE、PUT七种。元信息是关于当前请求的信息。通
过分析元信息,可以检查实体数据是否完整,接收过程是否出错,类型是否匹配等。元
信息的引入使HTTP协议通信更加稳妥可靠。实体是请求的具体内容。
将上述报文发往Web服务器,如果成功,应答格式如下:
--------------------------------
HTTP协议的版本号 应答状态码 应答状态码说明
接收的元信息
**空行**
实体
--------------------------------
以上报文发向客户端,并且接收成功,彼此间关闭连接,完成一次握手。
下面用最常用的GET方法,来说明具体的报文应用
----------------------------------
GET http://www.youhost.com HTTP/1.0
accept: www/source; text/html; image/gif; image/jpeg; */*
User_Agent: myAgent
**空行**
-----------------------------------
这个报文是向www.youhost.com主机请求一个缺省HTML文档。客户端HTTP协议版本
号是1.0版,元信息包括可接收的文件格式,用户代理,每一段之间用回车换行符分
隔,最后以一个空行结束。发向服务器后,如果执行过程正常,服务器返回以下代码:
------------------------------------
HTTP/1.1 200 OK
Date: Tue, 14 Sep 1999 02:19:57 GMT
Server: Apache/1.2.6
Connection: close
Content-Type: text/html
**空行**
......
------------------------------------
HTTP/1.1表示这个HTTP服务器是1.1版,200是服务器对客户请求的应答状态码,OK
是对应答状态码的解释,之后是这个文档的元信息和文档正文。(相关应答状态码和元
信息的解释请参阅Inetrnet标准草案:RFC2616)。
注: 程序中只实现GET、HEAD、POST三种方法。其他几种因不常使用,暂且忽略。
*/
public class Http {
protected Socket client;
protected BufferedOutputStream sender;
protected BufferedInputStream receiver;
protected ByteArrayInputStream byteStream;
protected URL target;
private int responseCode=-1;
private String responseMessage="";
private String serverVersion="";
private Properties header = new Properties();
public Http() { }
public Http(String url) {
GET(url) ;
}
/* GET方法根据URL,会请求文件、数据库查询结果、程序运行结果等多种内容 */
public void GET(String url){
try{
checkHTTP(url);
openServer(target.getHost(),target.getPort() );
String cmd = "GET " + getURLFormat(target) + " HTTP/1.0\r\n" + getBaseHeads() + "\r\n";
sendMessage(cmd);
receiveMessage();
}
catch(ProtocolException p){
p.printStackTrace();
return;
}
catch(UnknownHostException e){
e.printStackTrace();
return;
}
catch(IOException i){
i.printStackTrace();
return;
}
}
/*
* HEAD方法只请求URL的元信息,不包括URL本身。若怀疑本机和服务器上的
* 文件相同,用这个方法检查最快捷有效。
*/
public void HEAD(String url){
try{
checkHTTP(url);
openServer(target.getHost(),target.getPort());
String cmd = "HEAD " + getURLFormat(target) + " HTTP/1.0\r\n" + getBaseHeads() + "\r\n";
sendMessage(cmd);
receiveMessage();
}
catch(ProtocolException p){
p.printStackTrace();
return;
}
catch(UnknownHostException e){
e.printStackTrace();
return;
}
catch(IOException i){
i.printStackTrace();
return;
}
}
/*
* POST方法是向服务器传送数据,以便服务器做出相应的处理。例如网页上常用的
* 提交表格。
*/
public void POST(String url,String content) {
try{
checkHTTP(url);
openServer(target.getHost(),target.getPort() );
String cmd = "POST " + getURLFormat(target) + " HTTP/1.0\r\n" + getBaseHeads();
cmd += "Content-type: application/x-www-form-urlencoded\r\n";
cmd += "Content-length: " + content.length() + "\r\n\r\n";
cmd += content + "\r\n";
sendMessage(cmd);
receiveMessage();
}
catch(ProtocolException p){
p.printStackTrace();
return;
}
catch(UnknownHostException e){
e.printStackTrace();
return;
}
catch(IOException i){
i.printStackTrace();
return;
}
}
protected void checkHTTP(String url) throws ProtocolException {
try{
URL target = new URL(url);
if(target == null || !target.getProtocol().toUpperCase().equals("HTTP")){
throw new ProtocolException("这不是HTTP协议");
}
this.target = target;
}
catch(MalformedURLException m) {
throw new ProtocolException("协议格式错误");
}
}
/*
* 与Web服务器连接。若找不到Web服务器,InetAddress会引发UnknownHostException
* 异常。若Socket连接失败,会引发IOException异常。
*/
protected void openServer(String host,int port) throws UnknownHostException,IOException {
header.clear();
responseMessage="";
responseCode=-1;
if(client!=null){
closeServer();
}
if(byteStream != null){
byteStream.close();
byteStream=null;
}
InetAddress address = InetAddress.getByName(host);
client = new Socket(address,port==-1 80:port);
client.setSoTimeout(5000);
sender = new BufferedOutputStream(client.getOutputStream());
receiver = new BufferedInputStream(client.getInputStream());
}
/* 关闭与Web服务器的连接 */
protected void closeServer() throws IOException {
if(client==null){
return;
}
try{
client.close();
sender.close();
receiver.close();
}
catch(IOException i){
throw i;
}
client=null;
sender=null;
receiver=null;
}
protected String getURLFormat(URL target) {
String spec = "http://" + target.getHost();
if(target.getPort()!=-1){
spec+=":"+target.getPort();
}
return spec+=target.getFile();
}
/* 向Web服务器传送数据 */
protected void sendMessage(String data) throws IOException{
sender.write(data.getBytes(),0,data.length());
sender.flush();
}
/* 接收来自Web服务器的数据 */
protected void receiveMessage() throws IOException{
byte data[] = new byte[1024];
int count=0;
int word=-1;
// 解析第一行
while( (word=receiver.read())!=-1 ) {
if(word=='\r'||word=='\n') {
word=receiver.read();
if(word=='\n') word=receiver.read();
break;
}
if(count == data.length) data = addCapacity(data);
data[count++]=(byte)word;
}
String message = new String(data,0,count);
int mark = message.indexOf(32);
serverVersion = message.substring(0,mark);
while( mark
responseCode = Integer.parseInt(message.substring(mark+1,mark+=4));
responseMessage = message.substring(mark,message.length()).trim();
// 应答状态码和处理请读者添加
switch(responseCode) {
case 400:
throw new IOException("错误请求");
case 404:
throw new FileNotFoundException( getURLFormat(target) );
case 503:
throw new IOException("服务器不可用" );
}
if(word==-1) throw new ProtocolException("信息接收异常终止");
int symbol=-1;
count=0;
// 解析元信息
while( word!='\r' && word!='\n' && word>-1) {
if(word=='\t') word=32;
if(count==data.length) data = addCapacity(data);
data[count++] = (byte)word;
parseLine: {
while( (symbol=receiver.read()) >-1 ) {
switch(symbol) {
case '\t':
symbol=32; break;
case '\r':
case '\n':
word = receiver.read();
if( symbol=='\r' && word=='\n') {
word=receiver.read();
if(word=='\r') word=receiver.read();
}
if( word=='\r' || word=='\n' || word>32) break parseLine;
symbol=32; break;
}
if(count==data.length) data = addCapacity(data);
data[count++] = (byte)symbol;
}
word=-1;
}
message = new String(data,0,count);
mark = message.indexOf(':');
String key = null;
if(mark>0) key = message.substring(0,mark);
mark++;
while( mark
<="32"> String value = message.substring(mark,message.length() );
header.put(key,value);
count=0;
}
// 获得正文数据
while( (word=receiver.read())!=-1) {
if(count == data.length) data = addCapacity(data);
data[count++] = (byte)word;
}
if(count>0) byteStream = new ByteArrayInputStream(data,0,count);
data=null;
closeServer();
}
public String getResponseMessage() {
return responseMessage;
}
public int getResponseCode() {
return responseCode;
}
public String getServerVersion() {
return serverVersion;
}
public InputStream getInputStream() {
return byteStream;
}
public synchronized String getHeaderKey(int i) {
if(i>=header.size()){
return null;
}
Enumeration enum = header.propertyNames();
String key = null;
for(int j=0; j<=i; j++){
key = (String)enum.nextElement();
}
return key;
}
public synchronized String getHeaderValue(int i) {
if(i>=header.size()) return null;
return header.getProperty(getHeaderKey(i));
}
public synchronized String getHeaderValue(String key) {
return header.getProperty(key);
}
protected String getBaseHeads() {
String inf = "User-Agent: IcewolfHttp/1.0\r\nAccept: www/source; text/html; image/gif; */*\r\n";
return inf;
}
private byte[] addCapacity(byte rece[]){
byte temp[] = new byte[rece.length+1024];
System.arraycopy(rece,0,temp,0,rece.length);
return temp;
}
public static void main(String [] args){
/*
Http p = new Http();
p.GET("http://www.google.com/search q=java+book");
byte[] bGets = new byte[1024];
ByteArrayInputStream pS = (ByteArrayInputStream)p.getInputStream();
int offset = 0;
while((offset=pS.read(bGets,0,1024)) != -1){
System.out.print(new String(bGets,0,offset));
}
*/
String target = "http://www.google.com/search q=java+book";
try {
URL url = new URL(target);
HttpURLConnection pconn = (HttpURLConnection)url.openConnection();
//HttpURLConnection.setFollowRedirects(true);
//pconn.setInstanceFollowRedirects(true);
pconn.addRequestProperty("User-Agent","IcewolfHttp/1.0");
pconn.addRequestProperty("Accept","www/source; text/html; image/gif; */*");
pconn.connect();
System.out.println("Connect status:"+pconn.getResponseCode());
//if(HttpURLConnection.HTTP_ACCEPTED == pconn.getResponseCode())
//InputStream in = url.openConnection();
InputStream in = pconn.getInputStream();
System.out.println("Get status:"+pconn.getResponseCode());
BufferedInputStream buff = new BufferedInputStream(in);
Reader r = new InputStreamReader(buff);
int c = 0;
while ( (c = r.read()) != -1){
System.out.print((char)c);
}
buff.close();
in.close();
pconn.disconnect();
} catch (MalformedURLException mfe) {
System.err.println(target + " is not a parsable URL");
} catch (IOException ioe) {
System.err.println(ioe);
}
}
}
package service;
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
public class CyclicBarrierTaskScheduler implements Runnable {
private CyclicBarrier cyclicBarrier;
private int batchTaskNumbers;
private int realtimeTaskNumbers;
// you can set an ExecutorService extenally
private ExecutorService executor = Executors.newFixedThreadPool(10);
public void run() {
// pre-validate on states of current object
cyclicBarrier = new CyclicBarrier(getBatchTaskNumbers(),
new Runnable() {
public void run() {
System.out.println("B");
// for (int i = 0; i < getRealtimeTaskNumbers(); i++) {
// getExecutor().execute(new RealtimeTask());
// }
}
});
for (int i = 0; i < getBatchTaskNumbers(); i++) {
getExecutor().execute(new Runnable() {
public void run() {
new BatchTask().run();
try {
getCyclicBarrier().await();
} catch (InterruptedException e) {
e.printStackTrace(); // process exception as per your
// need
} catch (BrokenBarrierException e) {
e.printStackTrace(); // process exception as per your
// need
}
}
});
}
}
public void shutdown() {
if (getExecutor() != null) {
getExecutor().shutdown();
try {
getExecutor().awaitTermination(Integer.MAX_VALUE,
TimeUnit.SECONDS);
} catch (InterruptedException e) {
e.printStackTrace(); // process exception as per your need
}
}
}
public CyclicBarrier getCyclicBarrier() {
return cyclicBarrier;
}
public int getBatchTaskNumbers() {
return batchTaskNumbers;
}
public void setBatchTaskNumbers(int batchTaskNumbers) {
this.batchTaskNumbers = batchTaskNumbers;
}
public int getRealtimeTaskNumbers() {
return realtimeTaskNumbers;
}
public void setRealtimeTaskNumbers(int realtimeTaskNumbers) {
this.realtimeTaskNumbers = realtimeTaskNumbers;
}
public ExecutorService getExecutor() {
return executor;
}
public void setExecutor(ExecutorService executor) {
this.executor = executor;
}
public static void main(String[] args) {
CyclicBarrierTaskScheduler taskScheduler = new CyclicBarrierTaskScheduler();
taskScheduler.setBatchTaskNumbers(10);
taskScheduler.setRealtimeTaskNumbers(15);
try {
taskScheduler.run();
} finally {
// taskScheduler.shutdown();
}
}
}
客户端:
Java代码
package com.wlh.test;
import java.io.*;
import java.net.*;
public class talkclient
{
public static void main(String args[])
{
try
{
Socket socket = new Socket("192.168.13.123",9999);
BufferedReader sin = new BufferedReader(new InputStreamReader(System.in));
PrintWriter os = new PrintWriter(socket.getOutputStream());
BufferedReader is = new BufferedReader(new InputStreamReader(socket.getInputStream()));
String readline;
while (!"bye".equals(readline=sin.readLine())) {
//==============写服务端==================//
os.println(readline);
os.flush();
//==============读服务端==================//
System.out.println("服务端反馈:"+is.readLine());
}
os.close();
is.close();
socket.close();
}catch(Exception e)
{
System.out.println("Error" + e);
}
}
}
package com.wlh.test;
import java.io.*;
import java.net.*;
public class talkclient
{
public static void main(String args[])
{
try
{
Socket socket = new Socket("192.168.13.123",9999);
BufferedReader sin = new BufferedReader(new InputStreamReader(System.in));
PrintWriter os = new PrintWriter(socket.getOutputStream());
BufferedReader is = new BufferedReader(new InputStreamReader(socket.getInputStream()));
String readline;
while (!"bye".equals(readline=sin.readLine())) {
//==============写服务端==================//
os.println(readline);
os.flush();
//==============读服务端==================//
System.out.println("服务端反馈:"+is.readLine());
}
os.close();
is.close();
socket.close();
}catch(Exception e)
{
System.out.println("Error" + e);
}
}
}
服务端:负责接收多个客户端的连接 ,然后交给多线程去处理
Java代码
package com.wlh.test;
import java.io.*;
import java.net.*;
//引入了无关的类
import java.applet.Applet;
public class talkserver {
static int clientnum = 0; // 静态成员变量,记录当前客户的个数
public static void main(String args[]) {
ServerSocket server = null;
try {
server = new ServerSocket(9999);
} catch (IOException e1) {
e1.printStackTrace();
}
while(true){
try {
Socket socket = server.accept();
System.out.println("一个客户端连上来了....");
ServerThread thread=new ServerThread(socket);
//thread.start();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
package com.wlh.test;
import java.io.*;
import java.net.*;
//引入了无关的类
import java.applet.Applet;
public class talkserver {
static int clientnum = 0; // 静态成员变量,记录当前客户的个数
public static void main(String args[]) {
ServerSocket server = null;
try {
server = new ServerSocket(9999);
} catch (IOException e1) {
e1.printStackTrace();
}
while(true){
try {
Socket socket = server.accept();
System.out.println("一个客户端连上来了....");
ServerThread thread=new ServerThread(socket);
//thread.start();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
多线程程序:
Java代码
package com.wlh.test;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.Socket;
/**
* 服务器端逻辑线程
*/
public class ServerThread extends Thread {
Socket socket;
BufferedReader br=null;
PrintWriter os=null;
public ServerThread(Socket socket) {
this.socket = socket;
start(); //启动线程
}
public void run() {
BufferedReader is=null;
PrintWriter os=null;
try {
boolean flag=true;
//ServerSocket server = null;
//Socket socket = null;
//ServerSocket server = new ServerSocket(9999);
System.out.println("server listen on port 9999...");
//socket = server.accept();
is = new BufferedReader(new InputStreamReader(socket.getInputStream()));
os = new PrintWriter(socket.getOutputStream());
while(flag){
System.out.println("-------------------");
//多线程
//new ServerThread(socket, clientnum).start();
//标准输入端读取一行
BufferedReader sin = new BufferedReader(new InputStreamReader(System.in));
String str;
String reply;
while(!"".equals(str=is.readLine())){
System.out.println(".....start...........");
System.out.println(str); //每写一行,就等着从客户端读进一行,
//====向客户端反馈读到的信息=====//
os.println("服务端已经收到:"+str);
os.flush();
//=======从控制台读取信息==========//
//reply=sin.readLine();
//os.println("服务端 说:"+reply); //写到客户端
//os.flush();
System.out.println("......end.........");
}
System.out.println("out of while...");
//String line=sin.readLine();
os.println("server AAAAAAAAAAAAA"); //写到客户端
os.flush();
}
//****流如果关闭,socket也将会关闭,所以如果想在一个socket连接之内通讯*****//,
//***则应该在while之外关闭socket****//
//server.close();
} catch (Exception e) {
System.out.println("Error" + e);
}finally{
try {
is.close();
os.close();
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
/**
* 关闭流和连接
*/
private void close() {
try {
//关闭流和连接
os.close();
br.close();
socket.close();
} catch (Exception e) {
}
}
}
java并发编程-构建块
Executor框架是指java 5中引入的一系列并发库中与executor相关的一些功能类,其中包括线程池,Executor,Executors,ExecutorService,CompletionService,Future,Callable等。他们的关系为:
并发编程的一种编程方式是把任务拆分为一些列的小任务,即Runnable,然后在提交给一个Executor执行,Executor.execute(Runnalbe) 。Executor在执行时使用内部的线程池完成操作。
一、创建线程池
Executors类,提供了一系列工厂方法用于创先线程池,返回的线程池都实现了ExecutorService接口。
public static ExecutorService newFixedThreadPool(int nThreads)
创建固定数目线程的线程池。
public static ExecutorService newCachedThreadPool()
创建一个可缓存的线程池,调用execute 将重用以前构造的线程(如果线程可用)。如果现有线程没有可用的,则创建一个新线程并添加到池中。终止并从缓存中移除那些已有 60 秒钟未被使用的线程。
public static ExecutorService newSingleThreadExecutor()
创建一个单线程化的Executor。
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize)
创建一个支持定时及周期性的任务执行的线程池,多数情况下可用来替代Timer类。
Java代码
Executor executor = Executors.newFixedThreadPool(10);
Runnable task = new Runnable() {
@Override
public void run() {
System.out.println("task over");
}
};
executor.execute(task);
executor = Executors.newScheduledThreadPool(10);
ScheduledExecutorService scheduler = (ScheduledExecutorService) executor;
scheduler.scheduleAtFixedRate(task, 10, 10, TimeUnit.SECONDS);
Executor executor = Executors.newFixedThreadPool(10);
Runnable task = new Runnable() {
@Override
public void run() {
System.out.println("task over");
}
};
executor.execute(task);
executor = Executors.newScheduledThreadPool(10);
ScheduledExecutorService scheduler = (ScheduledExecutorService) executor;
scheduler.scheduleAtFixedRate(task, 10, 10, TimeUnit.SECONDS); 二、ExecutorService与生命周期
ExecutorService扩展了Executor并添加了一些生命周期管理的方法。一个Executor的生命周期有三种状态,运行 ,关闭 ,终止 。Executor创建时处于运行状态。当调用ExecutorService.shutdown()后,处于关闭状态,isShutdown()方法返回true。这时,不应该再想Executor中添加任务,所有已添加的任务执行完毕后,Executor处于终止状态,isTerminated()返回true。
如果Executor处于关闭状态,往Executor提交任务会抛出unchecked exception RejectedExecutionException。
Java代码
ExecutorService executorService = (ExecutorService) executor;
while (!executorService.isShutdown()) {
try {
executorService.execute(task);
} catch (RejectedExecutionException ignored) {
}
}
executorService.shutdown();
ExecutorService executorService = (ExecutorService) executor;
while (!executorService.isShutdown()) {
try {
executorService.execute(task);
} catch (RejectedExecutionException ignored) {
}
}
executorService.shutdown();
三、使用Callable,Future返回结果
Future<V>代表一个异步执行的操作,通过get()方法可以获得操作的结果,如果异步操作还没有完成,则,get()会使当前线程阻塞。FutureTask<V>实现了Future<V>和Runable<V>。Callable代表一个有返回值得操作。
Java代码
Callable<Integer> func = new Callable<Integer>(){
public Integer call() throws Exception {
System.out.println("inside callable");
Thread.sleep(1000);
return new Integer(8);
}
};
FutureTask<Integer> futureTask = new FutureTask<Integer>(func);
Thread newThread = new Thread(futureTask);
newThread.start();
try {
System.out.println("blocking here");
Integer result = futureTask.get();
System.out.println(result);
} catch (InterruptedException ignored) {
} catch (ExecutionException ignored) {
}
Callable<Integer> func = new Callable<Integer>(){
public Integer call() throws Exception {
System.out.println("inside callable");
Thread.sleep(1000);
return new Integer(8);
}
};
FutureTask<Integer> futureTask = new FutureTask<Integer>(func);
Thread newThread = new Thread(futureTask);
newThread.start();
try {
System.out.println("blocking here");
Integer result = futureTask.get();
System.out.println(result);
} catch (InterruptedException ignored) {
} catch (ExecutionException ignored) {
} ExecutoreService提供了submit()方法,传递一个Callable,或Runnable,返回Future。如果Executor后台线程池还没有完成Callable的计算,这调用返回Future对象的get()方法
相关推荐
在本章"数据源-第1章.rar"中,我们将深入探讨如何利用数据透视表来提升办公效率,并针对初学者提供实用的学习指南。 首先,我们要理解什么是数据源。在数据分析的语境下,数据源是指包含所需信息的原始数据集,可以...
杂乱无章。 - C. 非常宜人。 - D. 地狱般的地方。 5. **题干**:“关于某件物品的价格。” - **选项**: - A. 他在非常低的价格下得到了相机。 - B. 相机非常昂贵。 - C. 这台相机一文不值。 - D. 他不喜欢...
1. 成语纠错 - 直截了当 - 焕然一新 - 道貌岸然 - 既往不咎 - 别出心裁 - 礼尚往来 - 难以名状 - 色厉内荏 - 如火如荼 - 因地制宜 - 推心置腹 - 纷至沓来 - 原形毕露 - 谈笑风生 - 委曲求全 - ...
1. 地面管理: - 办公区的楼道必须保持畅通,禁止堆放杂物,违者每发现一次罚款10元。 - 办公室地面每日需清扫,确保无垃圾、无杂物、无污渍、无烟头。任何违反此规定的,将根据情况给予相应处罚。 2. 桌椅与物品...
这款插件的核心目标是将杂乱无章的源代码转换为整洁、一致的格式。它支持多种编程语言,包括但不限于HTML、CSS、JavaScript、Python、PHP、Java、C++、Ruby等。通过自动缩进、添加或删除空格、调整括号位置等方式,...
RO---S---SCL---SCR---SEC---SHA---SL---SN---SP---SPL---ORBIT---PRE---PRINT---SI---SU---TO---TOR---TR---UC---UNI---V---VP---W---X---XA---XB---XC---XL---XR---Z---”这部分可能是由于OCR错误而显得杂乱无章,...
1. **磁化和退磁**: - 磁化是指使原本无磁性的物质(如铁、钴、镍)获得磁性的过程。在微观层面,物质由许多称为磁畴的小区域组成,每个磁畴有自己的磁化方向。 - 当磁畴的磁化方向杂乱无章时,物体对外不显现...
根据提供的文件【标题】:"1题库-选择题-计算机基础-word-c语言.pdf"和【描述】:"1题库-选择题-计算机基础-word-c语言.pdf"以及【标签】:"技术及资料",结合【部分内容】中的断断续续的内容,我们可以提炼出以下知识...
### 硬件知识点概述 #### 一、旁路电容与去耦电容 - **定义**: 旁路电容和去耦电容主要用于滤除电源中的噪声,确保电源的稳定性。 - **功能差异**: - **旁路电容**: 主要用于消除高频噪声,减少对后续电路的影响...
杂乱无章的意思和造句参考.doc
这种方法强调通过视觉化的方式,将看似杂乱无章的数据分类,有助于团队成员共同理解问题的本质,从而制定有效的解决方案。 在使用亲和图时,通常遵循以下步骤: 1. **确定问题焦点**:首先,确定需要解决的主要...
它能快速地将杂乱无章的Java代码整理得井井有条,遵循Google的Java编程风格指南。 在Java开发中,代码格式的一致性至关重要,因为这直接影响到代码的可读性和维护性。`google-java-format` 可以作为一个预提交检查...
【PPT素材模板25套-1】是一个包含25个不同设计风格的PowerPoint演示文稿模板的压缩包资源,适用于个人或企业制作专业、美观的PPT演示。这个集合提供了丰富的图形、图表、背景、过渡效果和配色方案,以满足不同场合和...
然而,原始的JSON数据通常以纯文本形式展示,对于大型、结构复杂的数据,直接查看可能会显得杂乱无章,不易于理解和分析。这时,我们就需要借助工具来美化和格式化JSON数据,使其更易于阅读。 "jQuery-Plugin-For-...
1-AGO方法的优势在于它可以通过累加原始数据序列,过滤掉一些随机的扰动因素,从而使原本杂乱无章的数据变得规律化,便于进一步的分析和建模。 本文中,作者陈团结和钱振东通过分析证明了1-AGO方法能够有效地消除...
1. **Flutter基础知识**:包括Widget树、BuildContext、StatefulWidget与 StatelessWidget的区别、动画实现、路由跳转等。 2. **Dart语言特性**:如异步编程(async/await)、dart:io库用于网络请求、dart:convert...
其中,“MOC”、“VL”、“VM”等可能是引脚的命名,“1”和“2”等数字可能表示引脚号或者网络号,“MOC”、“VH”、“VL”、“VC”等可能代表不同的电压级别或者信号类型。 由于文档内容杂乱无章,我们难以确定其...
Platzi网站开发学院-Huddle登陆页面,包含单个入门部分欢迎 :waving_hand: 您每周都会遇到的挑战使您可以提高现实工作流程中的技能。 你准备好了吗?...在哪里找到所有东西? 您的任务是使用/design文件夹中的设计来...
1. **亲密性原则**:将相关联的元素组织在一起,使页面的逻辑结构清晰。例如,当你有多个点需要阐述时,不要将它们散落在各处,而是应该将同类信息归类,形成视觉上的群组。可以使用表格、列表或图形来整合内容,...
前端导师-弯曲截面的杂乱无章的登陆页面 欢迎! :waving_hand: 感谢您检查此前端编码挑战。 挑战可帮助您构建现实的项目,从而提高您的编码技能。 为了应对这一挑战,您需要对HTML和CSS有基本的了解。 挑战 您...