《Java核心技术卷2 高级特性》四

第四章 网络

客户端

在Java类库中,使用Socket类作为客户端

Socket s = new Socket("time-a.nist.gov",13);
InputStream in = s.getInputStream();

上述代码的功能是获取当日时间,并读取服务器返回的时间
Socket用于打开一个套接字,需要将地址和端口号传递给套接字的构造器
如果连接失败,将抛出一个UnknownHostException异常;若是其他问题,将抛出IOException异常

一旦套接字被打开,java.net.Socket类的getInputStream方法就会返回一个InputStream对象,
可以使用该对象来读取服务器返回的信息

实际上,Socket类非常简单易用,因为Java库隐藏了建立网络连接和通过连接发送数据的复杂过程

而在从套接字读取信息时,在有数据可控访问之前,读操作将会被阻塞
而麻烦的问题在于若是此时主机不可达,那么应用将需要等待很长的时间,最后会导致超时,
但这个发生超时异常的时间太长,不利于程序的及时响应
因此,Java的Socket类提供了设置超时值的方法

Socket s = new Scoket(.;..);
s.setSoTimout(10000); //time out after 10 seconds 

当你为套接字设置了超时值后,而读、写操作在没有完成之前就超过了时间限制,将会抛出SocketTimeoutException异常
当然,我们可以捕获该异常,并对超时做出反应

但实际上在这个超时问题之前还有一个超时问题,即Socke s =n new Socket(String host,int port);
会一直无限期的阻塞下去,直到建立了到达主机的初始连接为止
解决的办法就是先构造一个无连接的套接字,再使用一个超时来进行连接的方式

Socket s = new Socket();
s.connect(new InetSocketAddress(host,port),timeout);
服务器

在Java类库中,使用ServerSocket类作为服务器端
一旦启动了服务器程序,它便会等待某个客户端连接到它的端口

ServerSocket s = new ServerSocket(8189);
Socket incoming = s.accept();

其中,第一行用于建立一个负责监听端口8189的服务器
而第二行则是使程序不断地等待,直到有客户连接到这个端口
而一旦有人通过网络发送了正确的连接请求,并以此连接到了端口上,该方法会返回一个表示已经建立连接的Socket对象
可以使用这个对象来获得输入流和输出流

InputStream in = incoming.getInputStream();
OutputStream out = incoming.getOutputStream();

为了适应不同的需求,我们也可以对得到的in和out再次进行流的封装
最后,可以使用close方法来关闭连接进来的套接字

通常情况下,多个客户端同时连接到服务器上,而服务器总是不间断地运行在服务器计算机上
为此,我们需要使用线程
每当程序建立一个新的套接字连接时,即调用accept方法时,将会启动一个新的线程来处理服务器和客户端之间的连接
而主程序立即返回并等待下一个连接,因此一般的操作应为:

ServerSocket s = new ServerSocket(8189);
while(true){
    Socket incoming = s.accept();
    Runnable r = new ThreadEchoHandler(incoming);
    Thread t = new Thread(t);
    t.start();
}

ThreadEchoHandler类实现了Runnable接口,并且它的run方法中包含了服务器与客户端通信的代码
由于每一个连接都会启动一个新的线程,因而多个客户端可以同时连接到服务器了

半关闭(half-close)提供了这样一种能力:套接字连接的一段可以终止其输出,同时仍然可以接受来自另一端的输入
如下的代码演示了如何在客户端使用半关闭的方法:

try(Socket socket = new Socket(host,port))
{
    Scanner in = new Scanner(socket.getInputStream(),"UTF-8");
    PrintWriter writer = new PrintWriter(socket.getOutputStream());
    //send request data
    writer.print(...);
    writer.flush();
    socket.shutdownOutput();
    //now socket is half-closed
    //read response data
    while(in.haaNextLine()!=null){String lines = in.nextLine();...} 
}

可中断套接字

正如前面超时所说:当连接到一个套接字时,当前线程会被阻塞直到建立连接或产生超时为止
当通过套接字读写数据时,当前线程也会被阻塞知道操作成功时或产生超时为止

当然我们可以通过设置超时来阻止程序的响应时间过长,但在交互式的应用中,
需要为用户提供一个选项,用来取消那些看似不会产生结果的连接
但麻烦在于,当线程因为套接字无法响应而发生阻塞时,是无法通过interrupt来解除阻塞

但好在java.nio包提供的一个特性——SocketChannel类可以实现中断套接字的操作

SocketChannel channel = SocketChannel.open(new InetSocketAddress(host,port));

可以调用Scanner类从SocketChannel中读取信息

Scanner in = new Scanner(channel,"UTF-8");

可以通过调用静态方法channel.newOutputStream,将通道转换为输出流

OutputStream out = channel.newOutputStream(channel);

当线程正在执行打开、读取或者写入操作时,如果线程发生中断,那么这些操作不会陷入阻塞,而是以抛出异常的方式结束

获取Web数

为了在Java程序中访问Web服务器,而不只是创建套接字连接和发送HTTP请求
那么就需要了解Java中的其它的网络库

URL和URLConnection类封装了大量复杂的实现细节,这些细节涉及如何从远程站点获取信息
我们可以使用一个字符串来构造一个URL对象

URL url = new URL(ustring);

//若是想要获得该资源的内容,调用openStream方法即可
该方法将会返回一个InputStream对象

InputStream in = url.openStream();
Scanner in = new Scanner(in,"UTF-8");

如果想从某个网站获得更多的信息,那么应该使用URLConnection类
URLConnection类的一般操作步骤:
1) 调用URL类中的openConnetion方法,获得URLConnection对象

URLConnection connection = url.openConnection();

2) 使用以下方法来设置任意的请求属性
setDoInput  //默认情况下,建立的连接只产生从服务器读取信息的输入流
setDoOutput  //为了能产生输出流(向服务器提交数据),需要调用该方法,connection.setDoOutput(true);
setIfModifiesSince  //用于告诉连接你只对自某个特定日期以来被修改过的数据感兴趣
setUseCaches  //只作用于applet,命令浏览器检查它的缓存
setAllowUserInteraction  //只用于applet,用于访问有密码保护的资源时弹出对话框,以便查询用户名和口令
setRequestProperty  //用来设置对特定协议起作用的任何"name/value"对
setConnectionTimeout
setReadTimeout
3) 调用connect方法来接远程资源:

connection.connect();

4) 与服务器建立连接后,可以查询头信息
getContentType
getContentLength
getContentEncoding
getData
getExpiration
getLastModified
5) 最后,访问资源数据。使用getInputStream方法获取一个输入流以读取信息

在向Web服务器发送消息时,通常有两个命令会被用到:GET和POST
在使用GET命令时,只需将参数附在URL的结尾处即可,其一般格式为:
http://host/path?query
其中,每个参数都具有“名字=值”的形式,而这些参数之间用&字符分隔开
并且参数将使用URL编码模式进行编码

但是由于在浏览器中出现很长的查询字符串很让人郁闷,而且老式的浏览器和代理对GET请求中能够包含的字符数量做出了限制
正因为如此,POSt请求经常用来处理具有大量数据的表单
在POSt请求中,我们不会在URL上附着参数,而是从URLConnection中获得输出流,并将名字/值写入到输出流中
仍然需要将这些值进行URL编码,并用&字符将它们隔开
具体的详细过程:
1) 创建一个URLConnection对象

URL url = new URL("http://host/path");
URLConnection connection = new url.openConnection();

2) 建立一个可用于输出的连接

connection.setDoOutput(true);

3) 调用getOutputStream方法获得一个流,可以通过这个流向服务器发送数据

PrintWriter out = new PrintWriter(connection.openOutputStream(),"UTF-8");

4) 向服务器发送数据

out.print(name+"="+URLEncoder.encoder(value,"UTF-8"));

之后关闭输出流

out.close()

5) 调用getInputStream方法读取服务器的响应

猜你喜欢

转载自www.cnblogs.com/ASE265/p/12342337.html