第13章 Java网络编程

Java作为一种网络上的编程语言,用其开发网络软件特别便利,它提供了丰富的网络功能,这些功能封装在java.net包中。

Java的网络编程主要有三类:一是利用URL(Uniform Resource Locator,统一资源定位器)来获取网络上的资源以及将本地的数据传送到网络的另一端;第二种是通过Socket(套接字)在客户机与服务器之间建立一个连接通道,来进行数据的传输与通信,此功能通常用于面向连接的通信;第三种是基于UDP数据报的网络编程,用于建立一种非面向连接、传输数据到达目的地的顺序和时间不可靠的连接方式。

本章实例介绍如何获得IP地址和域名、提取URL信息、Socket通信、基于UDP的C/S程序、多客户连接的Socket通信以及代理服务器和文件传输模拟等。

实例110 获取I P地址和域名

IP地址在计算机内部的表现形式是一个32位的二进制数,实现表现为一个四点格式的数据,由点号(.)将数据分为4个数字,如202.106.0.20,每个数字代表一个8位的二进制数。32位的二进制数的IP地址对用户来说不方便记忆和使用,为此引进了字符形式的IP地址,即域名,它用来惟一标识因特网上的主机或路由器,域名只是一个逻辑概念,并不反映出计算机所在的物理地点。本实例介绍如何获取IP地址和域名以及远程服务器的IP地址。

技术要点

获取IP地址和域名的技术要点如下:

• 通过java.net.InetAddress类的静态方法getLocalHost()能够获得本机的网络地址信息,也是一个InetAddress对象。

• InetAddress类的getHostAddress()方法,能获取该网络地址的IP信息。getHostName()方法获取网络地址的域名或者机器名。静态方法getByName()根据域名获得该域名代表的主机的网络地址信息,返回的是一个InetAddress对象。

• 当一个主机配置多个网络地址时,可以通过InetAddress类的getAllByName()静态方法根据域名获得该域名代表的主机的所有网络地址信息,返回的是InetAddress对象。

实现步骤

(1)新建一个类名为TextGetIPAndDomain.java。

(2)代码如下所示:

package com.zf.s13;                                    //创建一个包
import java.net.InetAddress;                           //引入类
import java.net.UnknownHostException;
public class TextGetIPAndDomain {                           //操作获取IP地址和域名的类
    public static void getLocalIP() {                       //获取本机的IP地址
          try {
              InetAddress addr = InetAddress.getLocalHost();//创建本地主机IP地址对象
              String hostAddr=addr.getHostAddress();        //获取IP地址
              String hostName=addr.getHostName();           //获取本地机器名
              System.out.println("本地IP地址:" +hostAddr);
              System.out.println("本地机器名:" +hostName);
          } catch (UnknownHostException e) {                //捕获未知主机异常
              System.out.println("不能获得主机IP地址:"+e.getMessage());
              System.exit(1);
          }
    }
    public static void getIPByName(String hostName){        //根据域名获得主机的IP地址
          InetAddress addr;
          try {
              addr = InetAddress.getByName(hostName);       //根据域名创建主机地址对象
              String hostAddr=addr.getHostAddress();        //获取主机IP地址
              System.out.println("域名为:"+hostName+"的主机IP地址:"+hostAddr);
          } catch (UnknownHostException e) {                //捕获未知主机异常
              System.out.println("不能根据域名获取主机IP地址:"+e.getMessage());
              System.exit(1);
          }
    }
    public static void getAllIPByName(String hostName){//根据域名获得主机所有的IP地址
          InetAddress[] addrs;
          try {
              addrs = InetAddress.getAllByName(hostName);//根据域名创建主机地址对象
              String[] ips = new String[addrs.length];
              System.out.println("域名为"+hostName+"的主机所有的IP地址为:");
              for (int i = 0; i < addrs.length; i++) {
                    ips[i] = addrs[i].getHostAddress(); //获取主机IP地址
                    System.out.println(ips[i]);
              }
          } catch (UnknownHostException e) {            //捕获未知主机异常
              System.out.println("不能根据域名获取主机所有IP地址:"+e.getMessage());
              System.exit(1);
          }
    }
    public static void main(String[] args) {            //Java程序主入口处
          getLocalIP();                                 //调用方法获得本机的IP地址
          String hostName="www.sohu.com";               //搜狐域名
          getIPByName(hostName);                        //获取搜狐的主机IP地址
          getAllIPByName(hostName);                     //获取搜狐域名主机所有的IP地址
    }
}

(3)运行结果如下所示:

本地IP地址:192.168.2.103
本地机器名:zf
域名为:www.sohu.com的主机IP地址:61.135.179.155
域名为www.sohu.com的主机所有的IP地址为:
61.135.179.155
61.135.179.160
61.135.179.184
61.135.179.190
61.135.133.37
61.135.133.38
61.135.133.88
61.135.133.89

源程序解读

(1)getLocalIP()方法获得本机的IP地址,通过InetAddress类的静态方法getLocalHost()获得本机的网络地址信息,getHostAddress()方法获得该网络地址信息的IP地址;getHostName ()方法获得本机的机器名。

(2)getIPByName()方法根据域名获得主机的IP地址,使用了InetAddress的静态方法getByName()、getAllIPByName()方法根据域名获得主机的所有IP地址,使用了InetAddress类的getAllByName()方法获得IP网络地址数组集合,运用循环遍历将所有IP地址输出。

实例111 获取网络资源(URL)

URL(Uniform Resource Locator)是统一资源定位器的简称,一般表现为字符串形式,表示Internet上的某一资源的地址,是Internet中对网络资源进行统一定位和管理的标识,利用URL就可以获取网络上的资源。本实例介绍从URL中提取资源信息,其中包括URL的主机地址、端口、协议以及它所引用的资源信息。

技术要点

运用URL获取网络上的资源信息的技术要点如下:

• Java.net.URL的格式一般可以分为三个部分:协议名,指访问网络主机上的资源所用的协议;主机名,指资源所在的主机的名称、IP地址或者域名;文件路径和文件名,指主机资源的具体地址。第一部分与第二部分之间用“://”分隔,第二部分与第三部分之间用“/”分隔,第三部分可省略,但第一、二部分不可缺。

• Java使用URL的目的就是从网络上获得声音、图像、HTML文档以及文件数据等资源,以便对资源进行处理。URL类提供get()方法能够获取URL的主机地址、端口、协议等信息。

• 如果URL协议是HTTP,可以获取请求的方法、响应消息等。URL类打开URL的openConnection()方法可以得到一个URLConnection,该对象可以获得URL引用的资源信息,包括内容长度与类型、编码方式等。

实现步骤

(1)创建一个类名为TextURL.java。

(2)代码如下所示:

package com.zf.s13;                                    //创建一个包
import java.io.IOException;                            //引入类
import java.io.*;
import java.net.URL;
import java.net.URLConnection;
public class TextURL {                                 //操作从URL中获取网络资源的类
                                                       //获取URL指定的资源
    public static void getImageResourcByURL(String imagesFile) throws IOException {
          URL url = new URL(imagesFile);
          Object obj = url.getContent();               //获得此URL的内容
          System.out.println(obj.getClass().getName());//显示名称
    }
                                                       //获取URL指定的资源
    public static void getHtmlResourceByURL(String htmlFile) throws IOException {
          URL url = new URL(htmlFile);
          URLConnection uc = url.openConnection();     //创建远程对象连接对象
          InputStream in = uc.getInputStream();        //打开的连接读取的输入流
          int c;
          while ((c = in.read()) != -1){               //循环读取资源信息
              System.out.print((char)c);
          }
          System.out.println();
          in.close();
    }
                                                       //读取URL指定的网页内容
    public static void getHTMLResource(String htmlFile) throws IOException {
          URL url = new URL(htmlFile);                 //创建URL对象
          Reader reader = new InputStreamReader(new BufferedInputStream(
                    url.openStream()));                //打开URL连接创建一个读对象
          int c;
          while ((c = reader.read()) != -1) {          //循环读取资源信息
              System.out.print((char) c);
          }
          System.out.println();
          reader.close();
    }
                                                       //读取URL指定的网页内容
    public static void getResourceOfHTML(String htmlFile) throws IOException {
          URL url = new URL(htmlFile);
          InputStream in = url.openStream();           //打开URL连接创建输入流
          int c;
          while ((c = in.read()) != -1){               //循环读取资源信息
              System.out.print((char)c);
          }
          System.out.println();
          in.close();
    }
                                                       //Java所支持的URL类型
    public static void supportURLType(String host,String file) {
          String[] schemes = { "http", "https", "ftp", "mailto", "telnet",
                    "file", "ldap", "gopher", "jdbc", "rmi", "jndi", "jar", "doc",
                    "netdoc", "nfs", "verbatim", "finger", "daytime",
                    "systemresource" };                //创建URL类型数组
          for (int i = 0; i < schemes.length; i++) {//遍历数组判断是否是Java支持的URL类型
                try {
                    URL u = new URL(schemes[i], host, file);
                    System.out.println(schemes[i] + "是Java所支持的URL类型\r\n");
                } catch (Exception ex) {
                    System.out.println(schemes[i] + "不是Java所支持的URL类型\r\n");
                }
          }
      }
      public static void main(String[] args) throws IOException {//Java程序主入口处
          String imageFile="http://localhost:8080/Demo/001.jpg";
          String htmlFile="http://localhost:8080/Demo/index.jsp";
          String host="http://localhost:8080/Demo";
          String file="/index.html";
          System.out.println("1.获取URL指定的图像资源信息");
          getImageResourcByURL(imageFile);
          System.out.println("2.获取URL指定的HTML网页资源信息");
          getHtmlResourceByURL(htmlFile);
          System.out.println("3.根据URL创建读对象读取网页内容");
          getHTMLResource(htmlFile);
          System.out.println("4.根据URL创建输入流读取网页内容");
          getResourceOfHTML(htmlFile);
          System.out.println("5.判断Java所支持的URL类型 ");
          supportURLType(host,file);
      }
}

(3)运行结果如下所示:

1.获取URL指定的图像资源信息
sun.awt.image.URLImageSource
2.获取URL指定的HTML网页资源信息
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
    <head>
          <title>HTML File TO XML File</title>
    </head>
    <body>
......
3.根据URL创建读对象读取网页内容
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
    <head>
          <title>HTML File TO XML File</title>
    </head>
    <body>
......
4.根据URL创建输入流读取网页内容
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
    <head>
          <title>HTML File TO XML File</title>
    </head>
......
5.判断Java所支持的URL类型
http是Java所支持的URL类型
https是Java所支持的URL类型
ftp是Java所支持的URL类型
mailto是Java所支持的URL类型
telnet不是Java所支持的URL类型
file是Java所支持的URL类型
ldap不是Java所支持的URL类型
gopher是Java所支持的URL类型
jdbc不是Java所支持的URL类型
rmi不是Java所支持的URL类型
jndi不是Java所支持的URL类型
jar是Java所支持的URL类型
doc不是Java所支持的URL类型
netdoc是Java所支持的URL类型
nfs不是Java所支持的URL类型
verbatim不是Java所支持的URL类型
finger不是Java所支持的URL类型
daytime不是Java所支持的URL类型
systemresource不是Java所支持的URL类型

源程序解读

(1)getImageResourcByURL()方法创建URL对象来获取网络上的资源。getContent()方法获取网络资源内容,getName()方法获取资源内容类的名称。

(2)getHtmlResourceByURL()方法创建URL对象,getConnection()方法创建远程对象连接,getInputStream()方法打开连接读取输入流,循环读取输入流中的资源信息,读完后释放资源。

(3)getHTMLResource()方法创建URL对象,创建读对象封装URL对象通过方法openStream()打开的流,循环输出读取的流信息。

(4)getResourceOfHTML()方法根据URL对象的openStream()方法打开URL连接的流信息创建输入流。循环输出读取的流信息。

(5)supportURLType()方法创建一个一维数组包含需要判断的URL类型。循环根据URL对象的参数判断哪种是Java支持的URL类型。

实例112 FTP文件传输模拟

FTP(File Transfer Protocol)是文件传输协议的简称,用于Internet上的控制文件的双向传输。同时它也是一个应用程序(Application)。用户可以通过它把自己的PC机与世界各地所有运行FTP协议的服务器相连,访问服务器上的大量程序和信息。本实例介绍访问FTP服务器,对FTP服务器中的文件进行上传和下载操作。

技术要点

在FTP服务器上进行文件传输操作的技术要点如下:

• FTP的传输有两种方式:ASCII传输模式和二进制传输模式。其中ASCII传输模式是ftp自动调整文件的内容以便于把文件解释成其他计算机存储文本文件的格式。二进制传输模式中,保存文件的位序,以便原始和拷贝的是逐位一一对应的。如果在ASCII方式下传输二进制文件,即使不需要也仍会转译。这会使传输稍微变慢,也会损坏数据,使文件变得不能用。在大多数计算机上,ASCII方式一般假设每一字符的第一有效位无意义,因为ASCII字符组合不使用它。如果传输二进制文件,所有的位都是重要的。如果知道这两台机器是同样的,则二进制方式对文本文件和数据文件都是有效的。

• 启动FTP服务器,用户需要在浏览器地址栏中输入如下格式的url地址:ftp://[用户名:口令@]ftp服务器域名[:端口号],如ftp://USER:123@192.168.2.103。在CMD命令行下也可以用上述方法连接,通过put命令和get命令达到上传和下载的目的,通过ls命令列出目录。

• 将一个客户端转变成FTP服务器需要下载server-u工具,安装完server-u工具,客户端就可以作为FTP服务器访问了。

实现步骤

(1)新建一个类名为TextFTPFileTran.java。

(2)代码如下所示:

package com.zf.s13;                                 //创建一个包
import sun.net.ftp.*;                               //引入类
import sun.net.*;
import java.io.*;
public class TextFTPFileTran {                      //操作模拟FTP文件传输的类
    private TelnetInputStream getfile;
    private TelnetOutputStream putfile;
    private String info;
    private FtpClient ftp = null;
    public void loginServer() {                     //访问FTP服务器
          try {
              ftp = new FtpClient("192.168.2.103"); //与服务器建立连接,IP为192.168.2.103
          } catch (IOException e) {                 //捕获异常
              System.out.println("不能与指定服务器进行连接:" + e.getMessage());
          }
          info = ftp.getResponseString();           //获取服务器响应的信息
          System.out.println("与FTP服务器进行连接:" + info);
          try {
              ftp.login("USER", "123456");          //登录到Ftp服务器
          } catch (IOException e) {                 //捕获异常
              System.out.println("用户没有权限访问服务器或没有该用户:"+ e.getMessage());
          }
          info = ftp.getResponseString();           //获取服务器响应的信息
          System.out.println("登录FTP服务器:" + info);
    }
    public void downLoadText()throws IOException{//下载文本文件的代码
          getfile = ftp.get("poems.txt");           //获得FTP服务器指定目录下的文本文件
          info = ftp.getResponseString();           //获取服务器响应的信息
          System.out.println("获得FTP服务器上的poems.txt文件:" + info);
          ftp.ascii();                                  //上传下载文本文件要标志
          info = ftp.getResponseString();               //获取服务器响应的信息
          System.out.println("下载文本文件:" + info);
                                                        //创建输入流读对象
          InputStreamReader isr = new InputStreamReader(getfile);
          BufferedReader b = new BufferedReader(isr);   //创建缓冲读对象
          BufferedWriter writer = new BufferedWriter(new FileWriter(new File(
                    "F:/temp/poems1.txt")));            //创建缓冲写对象
          while (true) {//循环将FTP服务器上的文本文件写入本地文件中
              String s = b.readLine();                  //获取每行记录
              if (s == null)
                    break;
              else
                    writer.write(s);                    //写入文件中
              writer.flush();                           //刷新,只有刷新才能写入文件成功
              writer.newLine();                         //换行
          }
          writer.close();                               //释放资源
          isr.close();
          b.close();
          getfile.close();
          System.out.println("下载文本文件poems.txt到本地F:/temp/poems1.txt成功");
      }
      public void downLoadBinary() throws Exception {   //下载二进制文件的代码
          ftp.binary();                                 //下载上传二进制文件标志
          info = ftp.getResponseString();               //获取服务器响应的信息
          System.out.println("下载二进制文件:" + info);
          getfile = ftp.get("poem.jar");           //获取FTP服务器指定目录下的jar文件
          info = ftp.getResponseString();               //获取服务器响应的信息
          System.out.println("获得FTP服务器上的poem.jar文件:" + info);
          FileOutputStream output = new FileOutputStream(//创建文件输出流
                    new File("F:/poem1.jar"));
          while (true) {//循环将FTP服务器上的jar文件写入到本地文件
              int i = getfile.read();                   //读取jar文件信息
              if (i == -1)
                    break;
              else {
                    output.write((byte) i);             //按字节写入文件中
                    output.flush();                     //刷新,只有刷新才能写入文件成功
              }
          }
          getfile.close();                              //释放资源
          output.close();
          System.out.println("下载二进制文件poem.jar到本地F:/temp/poem1.jar成功");
      }
      public void uploadText() throws Exception {       //上传文本文件的代码
          ftp.ascii();                                  //上传下载文本文件要标志
          info = ftp.getResponseString();               //获取服务器响应的信息
          System.out.println("上传文本文件:" + info);
          putfile = ftp.put("poem.html"); //获取FTP服务器端指定目录下的html文件
          info = ftp.getResponseString();               //获取服务器响应的信息
          System.out.println("设置上传到FTP服务器上的文件poem.html:" + info);
          BufferedReader fis = new BufferedReader(     //创建缓冲读对象
                    new FileReader("F:/poem.html"));
          BufferedWriter fos = new BufferedWriter(     //创建缓冲写对象
                    new OutputStreamWriter(putfile));
          while (true) {//循环将本地html文件写到FTP服务器的文件中
              String i = fis.readLine();               //读取每行记录
              if (i == null)
                    break;
              else {
                    fos.write(i);                         //写入文件中
                    fos.flush();                          //刷新,只有刷新才能写入文件成功
                    fos.newLine();                        //换行
              }
          }
          putfile.close();
          fis.close();
          fos.close();
          System.out.println("上传本地文本文件F:/poem.html到FTP服务器成功,名为poem.html");
      }
      public void updateDirUpload() throws Exception {//更改FTP服务器指定当前目录并上传文件
          TelnetInputStream in = null;
          try {
              in = ftp.list();                          //获得当前目录列表
          } catch (IOException e) {                     //捕获异常
              System.out.println("无法获取目录列表:" + e.getMessage());
          }
          info = ftp.getResponseString();               //获取服务器响应的信息
          System.out.println("获得当前目录列表" + info);
          try {
              int   ch;
              StringBuffer   buf   =   new   StringBuffer();
              in=   ftp.list(); //得到主机端当前目录下所有文件和目录的输入数据流
              while((ch=in.read())>=0){                 //从输入流中读取数据
                    buf.append((char)ch);               //保存数据到缓冲区
              }
              in.close();                               //释放资源
              System.out.println(buf);
          } catch (IOException e) {                     //捕获异常
              System.out.println("读取目录列表信息:" + e.getMessage());
          }
          System.out.println("----"+info);
          try {
              ftp.cd("show");                           //改变当前目录,进入show目录下
          } catch (IOException e) {                     //捕获异常
              System.out.println("无法改变当前目录:" + e.getMessage());
          }
          info = ftp.getResponseString();               //获取服务器响应的信息
          System.out.println("改变当前目录为show" + info);
          ftp.binary();                                 //下载上传二进制文件标志
          putfile = ftp.put("poem2.html"); //上传到FTP服务器的文件,名为poem2.html
          info = ftp.getResponseString();               //获取服务器响应的信息
          System.out.println("上传到FTP服务器的文件名为poem2.html" + info);
                                                  //创建文件输入流
          FileInputStream fs = new FileInputStream("F:\\poem.html");
          while (true) {//循环将本地html文件上传到FTP服务器中的show目录下
              int i = fs.read();                        //读取html文件信息
              if (i == -1)
                    break;
              else {
                    putfile.write((byte) i);            //按字节写入文件中
                    putfile.flush();                    //刷新,只有刷新才能写入文件成功
              }
          }
          putfile.close();                              //释放资源
          fs.close();
          System.out.println("本地文件F:\\poem.html上传到FTP服务器上的目录show下,名为
poem2.html成功");
      }
      public static void main(String[] args) throws Exception {//Java程序主入口处
          TextFTPFileTran text = new TextFTPFileTran();//实例化对象
          text.loginServer();                           //调用方法访问FTP服务器
          text.downLoadText();                          //调用方法下载文本文件
          text.downLoadBinary();                        //调用方法下载二进制文件
          text.uploadText();                            //调用方法上传文本文件
          text.updateDirUpload();                 //调用方法改变当前目录上传二进制文件
      }
}

(3)运行结果如下所示:

与FTP服务器进行连接:220 Serv-U FTP Server v8.0 ready...
登录FTP服务器:230 User logged in, proceed.
获得FTP服务器上poems.txt文件:150 Opening ASCII mode data connection for poems.txt(305 Bytes).
下载文本文件:200 Type set to A.
下载文本文件poems.txt到本地F:/temp/poems1.txt成功
下载二进制文件:200 Type set to I.
获得FTP服务器上的poem.jar文件:150 Opening BINARY mode data connection for poem.jar(1104 Bytes).
下载二进制文件poem.jar到本地F:/temp/poem1.jar成功
上传文本文件:200 Type set to A.
设置上传到FTP服务器上的文件poem.html:150 Opening ASCII mode data connection for poem.html.
上传本地文本文件F:/poem.html到FTP服务器成功,名为poem.html
获得当前目录列表150 Opening ASCII mode data connection for /bin/ls.
-rw-rw-rw-   1 user     group         544 Apr 15 14:26 poem.html
-rw-rw-rw-   1 user     group        1104 Apr 1  09:01 poem.jar
-rw-rw-rw-   1 user     group         305 Apr 15 13:35 poems.txt
-rw-rw-rw-   1 user     group         305 Apr 15 14:26 poems1.txt
drw-rw-rw-   1 user     group           0 Apr 15 14:26 show
----150 Opening ASCII mode data connection for /bin/ls.
改变当前目录为show250 Directory changed to /show
上传到FTP服务器的文件名为poem2.html150 Opening BINARY mode data connection for poem2.html.
本地文件F:\poem.html上传到FTP服务器上的目录show下,名为poem2.html成功

源程序解读

(1)loginServer()方法创建FTP客户端与FTP服务器建立连接。getResponseString()方法获取服务器响应的信息,FtpClient的login()方法通过用户名和密码登录到FTP服务器可以访问到FTP服务器上指定的目录信息。

(2)downLoadText()方法将FTP服务器上的文本文件下载到本地。FtpClient类的get()方法获得FTP服务器上指定目录下的文本文件。FtpClient类ascii()方法是FTP自动调整文件的内容解释成本地计算机文本文件的格式。创建缓冲写流封装输入读流对象,循环将FTP服务器上指定的文本文件按每行读取到本地文件中。缓冲写对象的write()是将内容写入缓冲,flush()方法刷新将内容保存到文件中。

(3)downLoadBinary()方法将FTP服务器上的二进制文件下载到本地。FtpClient类的binary()方法是二进制传输模式,保存文件的位序,以便原始和拷贝的是逐位一一对应。创建文件输出流,循环将FTP服务器上的jar文件写入到本地文件中。

(4)uploadText()方法将本地文本文件上传到FTP服务器。创建缓冲写对象封装缓冲读流,循环将本地HTML网页文件按每行读取到FTP服务器的文件中。

(5)updateDirUpload()方法更改FTP服务器指定操作的目录并在新的目录下上传HTML网页文件。FtpClient类的list()方法获得FTP服务器当前目录的列表,循环将列表信息读到缓冲区输出。FtpClient类的cd()方法是改变当前目录到指定目录,put()方法将本地文件上传到FTP服务器的文件重新命名。创建文件输入流循环将本地文件上传到FTP服务器新指定的目录下。

实例113 自制浏览器

We b浏览器可以查看网络上的资源以及带有前进、后退、刷新和收藏网页等多种功能。本实例介绍如何自制一个浏览器实现查看网页、前进、后退、刷新以及创建新窗口和关闭所有窗口时触发关闭应用程序的简单功能。

技术要点

实现自制浏览器的技术要点如下:

• javax.swing.JEditorPane文本控件支持纯文本、HTML文本、RTF文本的显示,可以用它来显示HTML网页。为了使得网页上的超链接生效,必须通过addHyperlinkListener()方法为JEditorPane注册超链接事件处理器。

• JEditorPane的setPage方法加载URL,当加载完毕后,触发PropertyChangeListener事件,通过addPropertyChangeListener()方法为JEditorPane注册属性改变事件处理器。

实现步骤

(1)创建一个类名为TextWebBrowser.java。

(2)代码如下所示:

package com.zf.s13;                                    //创建一个包
import java.awt.BorderLayout;                          //引入类
import java.awt.event.*;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.io.File;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import javax.swing.*;
import javax.swing.event.HyperlinkEvent;
import javax.swing.event.HyperlinkListener;
import javax.swing.filechooser.FileFilter;
public class TextWebBrowser extends JFrame implements HyperlinkListener,
          PropertyChangeListener {                //实现Web浏览器,支持HTML页面的显示
    private static final long serialVersionUID = 1L;
    JEditorPane Pane;                                  //显示HTML的面板
    JLabel messageLine;                                //最底下的状态栏
    JTextField url;                                    //网址URL输入栏
    JFileChooser fileChooser;                          //文件选择器
    JButton back;                                      //前进按钮
    JButton forward;                                   //后退按钮
    java.util.List historyReport = new ArrayList();    //保存历史记录的列表
    int current = -1;                                  //当前页面在历史记录列表中的位置
    public static final int maxHistory = 50;           //当超过50时消除历史记录
    static int count = 0;                              //当前已经打开的浏览器窗口数
    static boolean exit = false;
    String home = "http://www.baidu.com";              //默认的主页
    public TextWebBrowser() {                          //默认构造方法
          super("TextWebBrowser");
          Pane = new JEditorPane();                    //新建面板
          Pane.setEditable(false);                     //不可编辑
          Pane.addHyperlinkListener(this);             //注册事件处理器,用于超链接事件
          Pane.addPropertyChangeListener(this);        //用于处理属性改变事件
          this.getContentPane().add(new JScrollPane(Pane),//将面板放入主窗口中
                    BorderLayout.CENTER);              //面板居中
          messageLine = new JLabel(" ");               //创建状态栏
          this.getContentPane().add(messageLine, BorderLayout.SOUTH);
          this.initMenu();                             //调用方法初始化菜单
          this.initToolbar();                          //调用方法初始化工具栏
          TextWebBrowser.count++;
                                                       //当关闭窗口时,触发事件
          this.addWindowListener(new WindowAdapter() {
                        public void windowClosing(WindowEvent e) {
                              close();
                        }
                    });
    }
    private void initMenu() {                          //初始化菜单栏
          JMenu fileMenu = new JMenu("文件");          //创建文件菜单项
          fileMenu.setMnemonic('F');                   //设置快捷键
          JMenuItem newMenuItem = new JMenuItem("新建");//创建新建项
          newMenuItem.setMnemonic('N');                //设置快捷键
                                                       //选择新建触发打开窗口事件
          newMenuItem.addActionListener(new ActionListener() {
                        public void actionPerformed(ActionEvent e) {
                              newBrowser();            //调用打开新的窗口方法
                        }
                    });
                                                       //创建打开项
          JMenuItem openMenuItem = new JMenuItem("打开");
          openMenuItem.setMnemonic('O');               //设置快捷键
                                                       //选择打开触发打开窗口事件
          openMenuItem.addActionListener(new ActionListener() {
                        public void actionPerformed(ActionEvent e) {
                              openLocalPage();         //调用打开文件方法
                        }
                    });
          JMenuItem closeMenuItem = new JMenuItem("关闭");     //创建关闭项
          closeMenuItem.setMnemonic('C');              //设置快捷键
                                                       //选择关闭触发打开窗口事件
          closeMenuItem.addActionListener(new ActionListener() {
                        public void actionPerformed(ActionEvent e) {
                              close();                 //调用关闭窗口方法
                        }
                    });
                                                       //创建退出项
          JMenuItem exitMenuItem = new JMenuItem("退出");
          exitMenuItem.setMnemonic('E');               //设置快捷键
                                                       //选择退出触发打开窗口事件
          exitMenuItem.addActionListener(new ActionListener() {
                        public void actionPerformed(ActionEvent e) {
                              exit();                  //调用退出方法
                        }
                    });
          fileMenu.add(newMenuItem);                   //将新建项添加到文件菜单下
          fileMenu.add(openMenuItem);                  //将打开项添加到文件菜单下
          fileMenu.add(closeMenuItem);                 //将关闭项添加到文件菜单下
          fileMenu.add(exitMenuItem);                  //将退出项添加到文件菜单下
          JMenuBar menuBar = new JMenuBar();           //创建菜单栏
          menuBar.add(fileMenu);                       //将文件菜单放入菜单栏
          this.setJMenuBar(menuBar);                   //设置菜单栏到主窗口
      }
      private void initToolbar() {                     //初始化工具栏
          back = new JButton("后退");                  //创建后退按钮
          back.setEnabled(false);                      //不可用
                                                       //选择后退触发后退事件
          back.addActionListener(new ActionListener() {
                        public void actionPerformed(ActionEvent e) {
                              back();                  //调用后退方法
                        }
                    });
          forward = new JButton("前进");               //创建前进按钮
          forward.setEnabled(false);                   //不可用
                                                       //选择前进触发前进事件
          forward.addActionListener(new ActionListener() {
                        public void actionPerformed(ActionEvent e) {
                              forward();               //调用前进方法
                        }
                    });
          JButton refreshButton = new JButton("刷新"); //创建刷新按钮
                                                       //选择刷新触发刷新事件
          refreshButton.addActionListener(new ActionListener() {
                        public void actionPerformed(ActionEvent e) {
                              reload();                //调用刷新方法
                        }
                    });
          JToolBar toolbar = new JToolBar();           //创建工具栏
          toolbar.add(back);                           //将后退按钮添加到工具栏
          toolbar.add(forward);                        //将前进按钮添加到工具栏
          toolbar.add(refreshButton);                  //将刷新按钮添加到工具栏
          url = new JTextField();                      //创建文本框
                                                       //输入地址回车触发事件
          url.addActionListener(new ActionListener() {
                        public void actionPerformed(ActionEvent e) {
                              displayPage(url.getText());
                        }
                    });
          toolbar.add(new JLabel("      地址:"));      //添加地址标签
          toolbar.add(url);                           //将文本框添加到工具栏
                                                      //将工具栏放在主窗口的南部
          this.getContentPane().add(toolbar, BorderLayout.NORTH);
    }
                                                      //当所有窗口关闭时浏览器退出
    public static void closeWindowWhenAllExit(boolean b) {
          exit = b;
    }
    public void setHome(String home) {                //设置主页
          this.home = home;
    }
    public String getHome() {
          return home;
    }
    private boolean visitURL(URL source) {            //访问网址URL
          try {
              String href = source.toString();        //获取网址
              Pane.setPage(source);                   //设置待访问的URL
              this.setTitle(href);             //页面打开后,将浏览器窗口的标题设为URL
              url.setText(href);                      //网址输入框的内容也设置为URL
              return true;
          } catch (IOException ex) {
              messageLine.setText("不能打开页面:" + ex.getMessage());
              return false;
          }
    }
    public void displayPage(URL url) {         //打开URL指定的页面,URL放入历史列表中
        if (visitURL(url)) {                          //访问页面是否成功
              historyReport.add(url);                 //URL放入历史列表中
              int numentries = historyReport.size();
              if (numentries > maxHistory + 10) {
                  historyReport = historyReport.subList(numentries - maxHistory,
                            numentries);
                  numentries = maxHistory;
              }
              current = numentries - 1;
              if (current > 0) {                       //不是当前页
                  back.setEnabled(true);               //允许使用后退按钮
              }
        }
    }
    public void displayPage(String href) {             //浏览器打开指定页面
        try {
              if (!href.startsWith("http://")) {       //默认为HTTP协议
                  href = "http://" + href;
              }
              displayPage(new URL(href));              //调用方法
        } catch (MalformedURLException ex) {
              messageLine.setText("错误的网址: " + href);
        }
    }
    public void openLocalPage() {                      //打开本地文件
        if (fileChooser == null) {
              fileChooser = new JFileChooser();        //创建文件选择器
              FileFilter filter = new FileFilter() {//文件过滤器限制只接受HTML和HTM文件
                  public boolean accept(File f) {
                        String fn = f.getName();
                        if (fn.endsWith(".html") || fn.endsWith(".htm")) {
                            return true;
                        } else {
                            return false;
                        }
                  }
                  public String getDescription() {
                        return "HTML Files";
                  }
              };
              fileChooser.setFileFilter(filter);
              fileChooser.addChoosableFileFilter(filter); //只允许选择HTML和HTM文件
        }
        int result = fileChooser.showOpenDialog(this);         //打开文件选择器
        if (result == JFileChooser.APPROVE_OPTION) {           //选择确定按钮
              File selectedFile=fileChooser.getSelectedFile(); //获得选择的文件
              try {
                  displayPage(selectedFile.toURL());
              } catch (MalformedURLException e) {
                  e.printStackTrace();
              }
          }
    }
    public void back() {                               //后退方法
          if (current > 0) {
              visitURL((URL) historyReport.get(--current));   //访问前一页
          }
          back.setEnabled((current > 0));              //当前页下标>0,可后退
                                                       //下标不是最后一页允许前进
          forward.setEnabled((current < historyReport.size() - 1));
    }
    public void forward() {                            //前进方法
          if (current < historyReport.size() - 1) {
              visitURL((URL) historyReport.get(++current));
          }
          back.setEnabled((current > 0));              //当前页面下标>0,可后退
                                                       //当前页面下标不是最后,可前进
          forward.setEnabled((current < historyReport.size() - 1));
    }
    public void reload() {                             //重新加载页面
          if (current != -1) {                         //显示空白页
              Pane.setDocument(new javax.swing.text.html.HTMLDocument());
              visitURL((URL) historyReport.get(current));//访问当前页
          }
    }
    public void home() {                               //显示主页方法
          displayPage(getHome());
    }
    public void newBrowser() {                         //打开新的浏览器窗口
          TextWebBrowser b = new TextWebBrowser();
          b.setSize(this.getWidth(), this.getHeight());//窗口与当前窗口一样大
          b.setVisible(true);
    }
    public void close() {                              //关闭当前窗口
          this.setVisible(false);                      //隐藏当前窗口,销毁窗口中的组件
          this.dispose();
          synchronized (TextWebBrowser.class) {
              TextWebBrowser.count--;
              if ((count == 0) && exit) {
                  System.exit(0);
              }
          }
    }
    public void exit() {                               //退出窗口程序
          if ((JOptionPane.showConfirmDialog(this, "你确定退出Web浏览器?", "退出",
                JOptionPane.YES_NO_OPTION) == JOptionPane.YES_OPTION)) {//判断是否退出
              System.exit(0);
          }
    }
    public void hyperlinkUpdate(HyperlinkEvent e) {          //处理超链接事件
        HyperlinkEvent.EventType type = e.getEventType();    //获取事件类型
        if (type ==HyperlinkEvent.EventType.ACTIVATED) {     //单击超链接
              displayPage(e.getURL());
        }
        else if (type == HyperlinkEvent.EventType.ENTERED) { //鼠标移动到超链接
              messageLine.setText(e.getURL().toString());    //状态栏定值
        }
        else if (type ==HyperlinkEvent.EventType.EXITED) {   //鼠标离开超链接
              messageLine.setText(" ");                      //状态栏定值
        }
    }
    public void propertyChange(PropertyChangeEvent e) {      //处理属性改变事件
    }
    public static void main(String[] args) throws IOException {//Java程序主入口处
        //设置浏览器,当所有浏览器窗口都被关闭时,退出应用程序
        TextWebBrowser.closeWindowWhenAllExit(true);
        TextWebBrowser browser = new TextWebBrowser();    //创建一个浏览器窗口
        browser.setSize(500, 400);                        //设置浏览器窗口的默认大小
        browser.setVisible(true);                         //显示窗口
        browser.displayPage(browser.getHome());           //打开主页
    }
}

(3)运行结果如下所示:

源程序解读

(1)程序在类的构造方法中首先为JEditorPane注册事件处理器,setEditable()方法设置JEditorPane不可修改;调用initMenu()方法初始化菜单栏,setMnemonic()方法为菜单和菜单项设置快捷键;调用initToolbar()方法初始化工具栏,在工具栏上添加了控制按钮、标签以及文本输入框,并为每个按钮添加了事件触发处理器,当用户单击按钮时触发事件。当用户在文本框中输出网址并按回车键时触发事件读取网址的信息,当窗口关闭时触发事件,调用close()方法,该方法判断当前浏览器的窗口数,如果待关闭的窗口是最后一个,则退出程序,否则,只关闭窗口不退出程序。

(2)visit()方法用于打开URL。JEditorPane的setPage()方法打开URL,当URL完全打开时,将当前浏览器窗口的标题和网址输入框的内容设置为URL。

(3)displayPage()方法显示网页。通过visit()方法打开URL,再将URL放入到历史列表中,来实现“前进”和“后退”功能。根据当前网页在历史列表中的位置,决定这两个按钮是否可用,通过JButton的setEnabled()方法设置按钮是否可用。

(4)openLocalPage方法实现打开本地文件的功能。通过JFileChooser打开本地文件的选择器,getSelectedFile()方法获得被选择的文件对象,File的toURL()方法即可获得文件的URL,调用displayPage()方法,便在浏览器中打开了本地的HTML文件。

(5)hyperlinkUpdate()方法实现了HyperlinkListener接口,用于处理HTML文档中的超链接事件,方法的参数为HyperlinkEvent对象,getURL()方法获得与事件相关的超链接URL。用户单击了一个超链接,产生的事件类型是HyperlinkEvent.EventType. ACTIVATED常量,通过displayPage()方法打开该链接;如果用户鼠标停留在超链接上,则产生的事件类型为HyperlinkEvent.EventType.ENTERED,此时将链接的URL显示在状态栏的标签中;如果用户鼠标离开超链接,则产生的事件类型为HyperlinkEvent.EventType. EXITED,此时将状态栏的标签的文本设为空。

实例114 点对点通信(Socket基于TCP协议)

使用Socket进行通信,需要有客户机和服务器。客户机和服务器可以在同一台机器上,但客户机和服务器处理的信息及信息处理方式是不同的,可以分为客户机程序和服务器程序。本实例介绍实现点对点通信,即一个服务器端监听一个客户端的请求的通信。

技术要点

实现点对点通信的技术要点如下:

• Socket通常称作“套接字”,用于描述IP地址和端口,是一个通信链的句柄。应用程序通常通过Socket向网络发出请求或者应答网络请求。Socket是在建立网络连接时使用。在连接成功时,应用程序两端都会产生一个Socket实例,操作这个实例,完成所需的会话。

• 对于一个网络连接来说,socket是平等的,并没有差别,不因为在服务器端或在客户端而产生不同级别。

• Socket服务器端需要在某个端口上开启服务器端类型的类:java.net.ServerSocket。通过accept()方法用于产生“阻塞”,直到接收到一个连接,并且返回一个客户端的Socket对象实例。“阻塞”是使程序运行暂时“停留”在这个地方,直到一个会话产生,然后程序继续;通常“阻塞”是由循环产生的。

• Socket客户端根据服务器端的IP地址和端口号创建一个Socket对象,连接服务器。

• 服务器端和客户端都持有一个Socket对象,服务器端的Socket从服务器端指向客户端,而客户端的Socket从客户端指向服务器端,就像在服务器端和客户端建立了两条单向的管道。

实现步骤

(1)创建两个类,分别为TextSocketServer.java和TextSocketClient.java。

(2)代码如下所示:

/**--------文件名:TextSocketServer.java-------------*/
package com.zf.s13;                                    //创建一个包
import java.io.*;                                      //引入类
import java.net.*;
class SocketServer {                                   //Socket服务器端
    private int port;                                  //端口
    public SocketServer(int port) {                    //带参数的构造方法进行初始化
          this.port = port;
          start();                                     //调用启动服务器端的方法
    }
    public String infoUpperCase(String line) {         //处理信息
          return line.toUpperCase();                   //将字符串大写
    }
    public void start() {                              //启动服务器端
          try {
                                                        //根据端口创建服务器端Socket对象
              ServerSocket serverSocket = new ServerSocket(port);
                                                        //显示连接信息
              System.out.println("服务器已启动,监听端口号为:" + port);
              System.out.println("正在等待客户端连接.........");
                                                        //挂起等待客户的请求
              Socket socketAccept = serverSocket.accept();
              BufferedReader in = new BufferedReader(new InputStreamReader(
                        socketAccept.getInputStream()));//获取读取客户端的数据流
              //获取写往客户端的数据输出流,true表示自动刷新
              PrintWriter out = new PrintWriter(socketAccept.getOutputStream(),true);
              out.println("服务器端连接成功.........");   //向客户发送连接信息
              out.println("输入exit断开与服务器的连接");
              boolean done = false;
              while (!done) {
                    String line = in.readLine();        //读取客户端每行的内容
                    if (line == null) {                 //没有写则不读取
                        done = true;
                    } else {
                                                        //显示客户端发送的内容
                        System.out.println("客户端传来的内容:" + line);
                                                        //信息处理
                        String message = infoUpperCase(line);
                                                        //向客户端发送信息
                        out.println("从服务器端口发送的内容:" + message);
                        if (line.trim().equals("exit")) //退出判断
                              done = true;
                    }
              }
              socketAccept.close();                     //关闭通信资源
          } catch (Exception e) {                       //捕获异常
              System.out.println("启动服务器端出现错误:"+e.getMessage());
          }
    }
}
public class TextSocketServer {                         //操作Socket服务器端的类
    public static void main(String[] args) {            //Java程序主入口处
        try {
              SocketServer server=new SocketServer(8080);//传入端口号实例化对象
        } catch (Exception e) {                         //捕获异常
              System.out.println("测试服务器端监听出错:"+e.getMessage());
        }
    }
}
/**--------文件名:TextSocketClient.java-------------*/
package com.zf.s13;                                    //创建一个包
import java.io.*;                                      //引入类
import java.net.*;
class Client {                                         //Socket客户端
    private String host;                               //IP地址(域名)
    private int port;                                  //端口号
    public Client(String host, int port) {             //带参数构造方法进行初始化
        this.host = host;
        this.port = port;
        connectSocket ();                              //调用连接方法
    }
    public void connectSocket () {                     //连接方法
        try {
              Socket socketConn;                       //声明Socket连接
                                                       //判断IP地址(域名)如果是本机localhost
              if (host.equals("localhost") || host.equals("127.0.0.1")) {
                                                        //创建本地Socket连接
                  socketConn = new Socket(InetAddress.getLocalHost(), port);
              } else {                                  //创建远程Socket连接
                  socketConn = new Socket(InetAddress.getByName(host), port);
              }
              BufferedReader stdin = new BufferedReader(new InputStreamReader(
                        System.in));                    //获得从键盘输入的流
              PrintWriter out = new PrintWriter(socketConn.getOutputStream(),
                        true);                          //获得服务器写内容的数据流
              BufferedReader in = new BufferedReader(new InputStreamReader(
                        socketConn.getInputStream()));  //获得接收服务器发送内容的缓冲流
                                                        //从服务器获得信息
              System.out.println("服务器信息:"+in.readLine());
              System.out.println("服务器信息:" + in.readLine());
              System.out.print("请输入>");               //用户输入
              boolean done = false;
              while (!done) {
                  String line = stdin.readLine();       //获得从键盘输入的每行字符
                  out.println(line);                    //发送到服务器端
                  if (line.equalsIgnoreCase("exit"))    //读到exit则结束循环
                        done = true;
                  String info = in.readLine();          //从服务器读取字符串
                  System.out.println("服务器信息:"+info);//显示从服务器发送来的数据
                  if (!done)                            //用户输入
                        System.out.print("请输入>");
              }
              socketConn.close();                       //关闭资源
          } catch (SecurityException e) {               //捕获安全性错误时引发的异常
              System.out.println("连接服务器出现安全问题!");
          } catch (IOException e) {                     //捕获IO流异常
              System.out.println("连接服务器出现I/O错误!");
          }
    }
}
public class TextSocketClient {                         //操作Socket客户端类
    public static void main(String[] args) {            //Java程序主入口处
          try {
              new Client("localhost", 8080);            //IP地址为本机,端口为80
          } catch (Exception e) {                       //捕获异常
              System.out.println("测试客户端连接出错:"+e.getMessage());
          }
    }
}

(3)服务器端TextSocketServer的输出如下所示:

服务器已启动,监听端口号为:8080
正在等待客户端连接.........
客户端传来的内容:这是第一句话!
客户端传来的内容:Hello,It is a nice day!
客户端传来的内容:exit

客户端TextSocketClient的输出如下所示:

服务器信息:服务器端连接成功.........
服务器信息:输入exit断开与服务器的连接
请输入>这是第一句话!
服务器信息:从服务器端口发送的内容:这是第一句话!
请输入>Hello,It is a nice day!
服务器信息:从服务器端口发送的内容:HELLO,IT IS A NICE DAY!
请输入>exit
服务器信息:从服务器端口发送的内容:EXIT

源程序解读

(1)SocketServer类构造方法传入端口参数和调用本类的start()方法来启动服务器端监听客户端的信息。

(2)infoUpperCase()方法将传入的英文参数转化成大写并返回。toUpperCase()方法是将字符串转化为大写,如果将字符串转化为小写,用方法toLowerCase()。

(3)start()方法创建服务器端的Socket对象,服务器端Socket对象的accept()方法创建客户端的Socket对象,挂起等待客户端的请求。根据getInputStream()方法获取读取的客户端的数据输入流创建缓冲读对象。根据getOutputStream()方法获取客户端的数据输出流创建写数据流。运用标识为真条件进行循环,根据缓冲读对象的readLine()方法读取客户端每行的内容,将读取的内容经过大写处理后输出,如果传来的数据是exit则设标识为假,退出循环。最后释放有关的通信资源。

(4)Client类的构造方法初始主机和端口并调用connectSocket()方法来调用相关主机连接和获得服务器端信息。

(5)connectSocket()方法判断传入的主机,如果是本机则创建本地Socket连接对象,否则创建远程Socket连接对象。根据封装从键盘输入的流创建缓冲读对象,根据getOutputStream()方法获得服务器端写内容的数据流创建写数据流。运用标识为条件循环读取键盘输入的每行字符,如果读取的数据为exit则退出循环。缓冲读对象的readLine()方法获得从服务器端读取的信息。

实例115 点对面通信(Socket基于TCP/IP协议)

多数情况下,网络通信经常需要多个客户机同一个服务器进行通信,如FTP服务器是同时接收多个客户访问的服务器。本实例介绍点对面通信,即一个服务器监听多个客户端的请求的通信。

技术要点

实现点对面通信的技术要点如下:

• 创建多客户连接的socket通信方式是在服务器端创建客户连接请求的监听线程,一旦客户端发起请求,则服务器端创建用于与此客户端通信的线程和Socket,服务器把与此客户的通信交给此线程进行处理。同时,继续在服务器指定端口进行监听,来响应其他客户的服务请求。

• 每一个客户端和服务器中的线程可以认为是单客户端通信模式下的客户端和服务器。一旦Socket和线程建立,服务器主程序会把与某个客户的通信完全交给线程去处理,并利用相应的Socket完成与客户的通信。

实现步骤

(1)创建三个类,分别为TextFaceServer.java、TextPointClient.java和TextSocketClient.java。

(2)代码如下所示:

/**--------文件名:TextFaceServer.java-------------*/
package com.zf.s13;                              //创建一个包
import java.io.IOException;                      //引入类
import java.net.ServerSocket;
import java.net.Socket;
import java.io.*;
class FaceServer extends ServerSocket {          //socket面服务器端
    private int port;                            //端口
    public FaceServer(int port) throws IOException {
          super(port);                           //继承其类的port
          this.port = port;
          System.out.println("服务器已启动,监听端口号为:" + port);
          System.out.println("正在等待客户连接.........");
          try {
              while (true) {                     //循环挂起等待客户的请求
                    Socket socketCon = accept();
                    new ServerThread(socketCon, port);   //建立服务线程
              }
          } catch (IOException e) {
              System.out.println("没有监听到客户端的信息......");
          } finally {
              close();                            //关闭通信资源
          }
      }
}
class ServerThread extends Thread {               //继承线程类实现服务线程
      private int port;                           //端口
      private Socket socketCon;
      private BufferedReader in;
      private PrintWriter out;
      public ServerThread(Socket s, int port) throws IOException {
          this.port = port;
          this.socketCon = s;
          in = new BufferedReader(new InputStreamReader(socketCon
                    .getInputStream(), "gb2312")); //读取客户端的数据流
          //获取写往客户端的数据输出流,true表示自动刷新
          out = new PrintWriter(socketCon.getOutputStream(), true);
          out.println("服务器端连接成功........."); //向客户发送连接信息
          out.println("输入exit断开与服务器的连接");
          start();                                //启动线程
      }
      public String infoUpperCase(String line) {  //处理信息
          return line.toUpperCase();              //将字符串大写
      }
      public void run() {
          try {
              boolean done = false;
              while (!done) {
                    String line = in.readLine();  //读取客户端每行的内容
                    if (line == null)
                        done = true;
                    else {                        //显示客户端发送的内容
                        System.out.println("客户端传来的内容:" + line);
                        String message = infoUpperCase(line);   //信息处理
                                                  //向客户端发送信息
                        out.println("从服务器端口发送的内容:" + message);
                        if (line.trim().equals("exit")) //退出判断
                              done = true;
                    }
              }
              System.out.println("客户端终止发送信息......");
              socketCon.close();                  //关闭通信资源
          } catch (Exception e) {                 //捕获异常
              System.out.println("启动服务器端出现错误:"+e.getMessage());
          }
      }
}
public class TextFaceServer {                     //操作Socket服务器端的类
      public static void main(String[] args) {    //Java程序主入口处
          try {
              new FaceServer(8080);               //传入端口实例化对象
          } catch (Exception e) {                 //捕获异常
              System.out.println("服务器端进行监听出现异常:" + e.getMessage());
          }
      }
}
/**--------文件名:TextPointClient.java-------------*/
package com.zf.s13;                               //创建一个包
import java.io.*;                                 //引入类
import java.net.*;
class PointClient {                               //Socket点客户端
      private String host;                        //IP地址(域名)
      private int port;                           //端口号
      public PointClient(String host, int port) { //带参数构造方法进行初始化
          this.host = host;
          this.port = port;
          connectSocket();                        //调用连接方法
      }
      public void connectSocket() {               //连接方法
          try {
              Socket socketConn;                  //声明Socket连接
                                                  //判断IP地址(域名)
              if (host.equals("localhost") || host.equals("127.0.0.1")) {
                                                  //创建本地连接
                    socketConn = new Socket(InetAddress.getLocalHost(), port);
              } else {                            //创建远程连接
                    socketConn = new Socket(InetAddress.getByName(host), port);
              }
              BufferedReader stdin = new BufferedReader(new InputStreamReader(
                        System.in));              //获得从键盘输入的流
              PrintWriter out = new PrintWriter(socketConn.getOutputStream(),
                        true);                    //获得服务器写内容的数据流
              BufferedReader in = new BufferedReader(new InputStreamReader(
                        socketConn.getInputStream()));//获得接收服务器发送内容的缓冲流
              System.out.println("服务器信息:"+in.readLine());//从服务器获得信息
              System.out.println("服务器信息:" + in.readLine());
              System.out.print("请输入>");         //用户输入
              boolean done = false;
              while (!done) {
                    String line = stdin.readLine();//获得从键盘输入的每行字符
                    out.println(line);             //发送到服务端
                    if (line.equalsIgnoreCase("exit"))//读到exit则结束循环
                        done = true;
                    String info = in.readLine();   //从服务器读取字符串
                    System.out.println("服务器信息:"+info);//显示从服务器发送来的数据
                    if (!done)                     //用户输入
                        System.out.print("请输入>");
              }
              socketConn.close();                  //关闭资源
          } catch (SecurityException e) {          //捕获安全性错误时引发的异常
              System.out.println("连接服务器出现安全问题!");
          } catch (IOException e) {                //捕获IO流异常
              System.out.println("连接服务器出现I/O错误!");
          }
    }
}
public class TextPointClient {                   //操作Socket客户端类
    public static void main(String[] args) {     //Java程序主入口处
          try {
              new Client("localhost", 8080);     //IP地址为本机,端口为8080
          } catch (Exception e) {                //捕获异常
              System.out.println("测试客户端连接出错:"+e.getMessage());
          }
    }
}
/**--------文件名:TextSocketClient.java-------------*/
package com.zf.s13;                              //创建一个包
import java.io.*;                                //引入类
import java.net.*;
class Client {                                   //Socket客户端
    private String host;                         //IP地址(域名)
    private int port;                            //端口号
    public Client(String host, int port) {       //带参数构造方法进行初始化
          this.host = host;
          this.port = port;
          connectSocket();                       //调用连接方法
    }
    public void connectSocket() {                //连接方法
          try {
              Socket socketConn;                 //声明Socket连接
                                                 //判断IP地址(域名)
              if (host.equals("localhost") || host.equals("127.0.0.1")) {
                                          //创建本地连接
                  socketConn = new Socket(InetAddress.getLocalHost(), port);
              } else {                           //创建远程连接
                  socketConn = new Socket(InetAddress.getByName(host), port);
              }
              BufferedReader stdin = new BufferedReader(new InputStreamReader(
                        System.in));             //获得从键盘输入的流
              PrintWriter out = new PrintWriter(socketConn.getOutputStream(),
                        true);                   //获得服务器写内容的数据流
              BufferedReader in = new BufferedReader(new InputStreamReader(
                        socketConn.getInputStream()));//获得接收服务器发送内容的缓冲流
              System.out.println("服务器信息:"+in.readLine());//从服务器获得信息
              System.out.println("服务器信息:" + in.readLine());
              System.out.print("请输入>");        //用户输入
              boolean done = false;
              while (!done) {
                  String line = stdin.readLine();//获得从键盘输入的每行字符
                  out.println(line);                       //发送到服务器端
                  if (line.equalsIgnoreCase("exit"))       //读到exit则结束循环
                        done = true;
                    String info = in.readLine();            //从服务器读取字符串
                    System.out.println("服务器信息:"+info);  //显示从服务器发送来的数据
                    if (!done)                              //用户输入
                        System.out.print("请输入>");
              }
              socketConn.close();                           //关闭资源
          } catch (SecurityException e) {                   //捕获安全性错误时引发的异常
              System.out.println("连接服务器出现安全问题!");
          } catch (IOException e) {                         //捕获IO流异常
              System.out.println("连接服务器出现I/O错误!");
          }
    }
}
public class TextSocketClient {                             //操作Socket客户端类
    public static void main(String[] args) {                //Java程序主入口处
          try {
              new Client("localhost", 8080);                //IP地址为本机,端口为8080
          } catch (Exception e) {                           //捕获异常
              System.out.println("测试客户端连接出错:"+e.getMessage());
          }
    }
}

(3)服务器端TextFaceServer类的输出如下所示:

服务器已启动,监听端口号为:8080
正在等待客户连接.........
客户端传来的内容:TextSocketClient类发送第一句话!
客户端传来的内容:TextPointClient类发送第二句话!
客户端传来的内容:TextSocketClient类: it is a nice day!
客户端传来的内容:TextPointClient类:hello,how are you !
客户端传来的内容:exit
客户端终止发送信息......
客户端传来的内容:exit
客户端终止发送信息......

客户端TextSocketClient类的输出如下所示:

服务器信息:服务器端连接成功.........
服务器信息:输入exit断开与服务器的连接
请输入>TextSocketClient类发送第一句话!
服务器信息:从服务器端口发送的内容:TextSocketClient类发送第一句话!
请输入>TextSocketClient类: it is a nice day!
服务器信息:从服务器端口发送的内容:TextSocketClient类: it is a nice day!
请输入>exit
服务器信息:从服务器端口发送的内容:exit

客户端TextPointClient类的输出如下所示:

服务器信息:服务器端连接成功.........
服务器信息:输入exit断开与服务器的连接
请输入>TextPointClient类发送第二句话!
服务器信息:从服务器端口发送的内容:TextPointClient类发送第二句话!
请输入>TextPointClient类:hello,how are you !
服务器信息:从服务器端口发送的内容:TextPointClient类:hello,how are you !
请输入>exit
服务器信息:从服务器端口发送的内容:exit

源程序解读

(1)FaceServer类继承服务器端Socket类,因此继承服务器端设置端口的方法。在构造方法中运用super(port)方法继承服务器端的方法,运用标识为真条件进行循环,根据基类的accept()方法创建Socket对象,循环挂起等待客户端的请求,并根据端口和Socket对象建立服务线程,监听来自客户端的信息。

(2)ServerThread类继承线程Thread类,继承Thread必须实现其run()方法。ServerThread类的构造方法中初始化端口和Socket通信对象,并运用通信对象的getInputStream()方法创建缓冲读对象,用来获取客户端的数据流。运用通信对象的getOutputStream()方法创建写往客户端的数据输出流,其中true参数是数据自动刷新从缓存写入到指定位置。调用start()方法启动线程。

(3)ServerThread类中的run()方法根据标识为真条件进行循环,按每行从获得的数据流中读取客户端的内容。当读取的内容为exit时设置标识为假,退出循环。

(4)TextPointClient类的构造方法初始主机和端口并调用connectSocket()方法来调用相关主机连接和获得服务器端信息。

(5)connectSocket()方法判断传入的主机,如果是本机则创建本地Socket连接对象,否则创建远程Socket连接对象。根据封装从键盘输入的流创建缓冲读对象,根据getOutputStream()方法获得服务器端写内容的数据流创建写数据流。运用标识为条件循环读取键盘输入的每行字符,如果读取的数据为exit则退出循环。缓冲读对象的readLine()方法获得从服务器端读取的信息。

实例116 多线程断点续传(基于HTTP)

所谓断点续传,是指从文件已经下载的地方开始继续下载文件,在客户端要给服务器端添加一条从哪里开始的请求。本实例介绍基于HTTP协议实现多线程从网络上下载文件,并支持文件的断点续传。

技术要点

实现多线程断点续传的技术要点如下:

• 断点续传需要服务器的支持,传统FTP服务器不支持REST指令,则FTP服务器不能支持断点续传。客户端要知道使用REST等一些指令来作断点续传。

• 客户端使用TYPE命令告知支持指令的FTP服务器使用BINARY模式传送文件;使用PASV命令告知FTP服务器使用被动打开模式来传送文件;使用REST指令来告知FTP服务器它需要从文件的某个点开始传,接着用STOR或RETR命令开始传文件。

实现步骤

(1)新建一个类名为TextThreadsAndPointAdd.java。

(2)代码如下所示:

package com.zf.s13;                         //创建一个包
import java.io.*;                           //引入类
import java.net.*;
class ControlFileFetch extends Thread {     //扩展线程类,负责文件的抓取,控制内部线程
    TranBean tranBean = null;               //文件信息Bean
    long[] startPosition;                   //开始位置
    long[] endposition;                     //结束位置
    FileFetch[] childThread;                //子线程对象
    long fileLength;                        //文件长度
    boolean isFirstGet = true;              //是否第一次取文件
    boolean isStopGet = false;              //停止标志
    File fileName;                          //文件下载的临时信息
    DataOutputStream output;                //输出到文件的输出流
    public ControlFileFetch(TranBean tranBean) throws IOException {
          this.tranBean = tranBean;
          fileName = new File(tranBean.getFileDir() + File.separator
                    + tranBean.getFileName() + ".info");//创建文件
          if (fileName.exists()) {          //判断文件是否存在
              isFirstGet = false;
              readInfo();                   //调用读取保存的下载文件信息
          } else {                          //文件不存在则设置开始与结束位置
              startPosition = new long[tranBean.getCount()];
              endposition = new long[tranBean.getCount()];
          }
    }
    public void run() {                     //实现线程类的方法
          try {
              if (isFirstGet) {             //第一次读取文件
                    fileLength = getFileSize();   //调用方法获得文件长度
                    if (fileLength == -1) {
                        System.err.println("文件长度未知!");
                    } else if (fileLength == -2) {
                        System.err.println("不能访问文件");
                    } else {
                                            //循环划分每个线程要下载的文件的开始位置
                        for (int i = 0; i < startPosition.length; i++) {
                              startPosition[i]=(long)(i*(fileLength/ startPosition.length));
                        }
                                            //循环划分每个线程要下载的文件的结束位置
                        for (int i = 0; i < endposition.length - 1; i++) {
                              endposition[i] = startPosition[i + 1];
                        }
                        endposition[endposition.length - 1] = fileLength;
                    }
              }
                                            //启动子线程
              childThread = new FileFetch[startPosition.length];
                                            //循环创建并启动子线程
              for (int i = 0; i < startPosition.length; i++) {
                    childThread[i] = new FileFetch(tranBean.getWebAddr()
                              , tranBean.getFileDir()
                              + File.separator + tranBean.getFileName(),
                              startPosition[i], endposition[i], i);
                    Log.log("线程:"+(i+1)+",开始位置 ="+startPosition[i]+",结束位置="
+ endposition[i]);
                    childThread[i].start();               //线程启动方法
                }
                boolean breakWhile = false;
                while (!isStopGet) {                      //如果没有停止一直循环下去
                    savePosition();                       //保存下载文件指针位置
                    Log.sleep(500);
                    breakWhile = true;
                    for (int i = 0; i < startPosition.length; i++) {//循环实现下载文件
                          if (!childThread[i].downLoadOver) {
                              breakWhile = false;
                              break;
                          }
                    }
                    if (breakWhile)
                          break;
                }
System.err.println("文件下载结束!"); } catch (Exception e) { //捕获异常 System.out.println("下载文件出错:"+e.getMessage()); } } public long getFileSize() { //获得文件长度 int fileLength = -1; try { //根据传入网址创建URL对象 URL url = new URL(tranBean.getWebAddr()); HttpURLConnection httpConnection = (HttpURLConnection) url .openConnection(); //创建打开连接对象 //设置描述发出HTTP请求的终端的信息 httpConnection.setRequestProperty("User-Agent", "NetFox"); //获得响应代码 int responseCode = httpConnection.getResponseCode(); if (responseCode >= 400) { //响应代码超过指定数字不能访问文件 errorCode(responseCode); return -2; } String head; for (int i = 1;; i++) { //循环获得文件头部信息 head = httpConnection.getHeaderFieldKey(i); //获得文件头部的信息 if (head != null) { if (head.equals("Content-Length")) { //根据头部信息获得文件长度 fileLength = Integer.parseInt(httpConnection .getHeaderField(head)); break; } } else break; } } catch(Exception e) { //捕获异常 System.out.println("获取文件长度出错:"+e.getMessage()); } Log.log("文件的长度:"+fileLength); return fileLength; } private void savePosition() { //保存下载信息(文件指针位置) try { output = new DataOutputStream(new FileOutputStream(fileName)); output.writeInt(startPosition.length); for (int i = 0; i < startPosition.length; i++) { output.writeLong(childThread[i].startPosition);//保存每个线程的开始位置 output.writeLong(childThread[i].endposition);//保存每个线程的结束位置 } output.close(); //释放资源 } catch (Exception e) { //捕获异常 System.out.println("保存下载信息出错:"+e.getMessage()); } } private void readInfo() { //读取文件指针位置 try { DataInputStream input = new DataInputStream(new FileInputStream( fileName)); //创建数据输入流 int count = input.readInt(); //读取分成的线程下载个数 startPosition = new long[count]; //设置开始线程 endposition = new long[count]; //设置结束线程 for (int i = 0; i < startPosition.length; i++) { startPosition[i] = input.readLong(); //读取每个线程的开始位置 endposition[i] = input.readLong(); //读取每个线程的结束位置 } input.close(); //释放资源 } catch (Exception e) { //捕获异常 System.out.println("读取文件指针位置出现错误:"+e.getMessage()); } } private void errorCode(int errorCode) { //显示错误代码 System.err.println("错误代码:" + errorCode); } public void stopDownLoad() { //停止文件下载 isStopGet = true; for (int i = 0; i < startPosition.length; i++) childThread[i].splitterStop(); } } class FileFetch extends Thread { //扩展线程类实现部分文件的抓取 String webAddr; //网址 long startPosition; //开始位置 long endposition; //结束位置 int threadID; //线程编号 boolean downLoadOver = false; //下载结束 boolean isStopGet = false; FileAccess fileAccessI = null; public FileFetch(String sURL, String sName, long nStart, long nEnd, int id) throws IOException { //带参数构造方法进行初始化 this.webAddr = sURL; this.startPosition = nStart; this.endposition = nEnd; threadID = id; fileAccessI = new FileAccess(sName, startPosition); } public void run() { //实现线程的方法 while (startPosition < endposition && !isStopGet) { try { URL url = new URL(webAddr); //根据网络资源创建URL对象 HttpURLConnection httpConnection = (HttpURLConnection) url .openConnection(); //创建打开连接的对象 //设置描述发出HTTP请求的终端的信息 httpConnection.setRequestProperty("User-Agent", "NetFox"); String sProperty = "bytes=" + startPosition + "-"; //设置描述发出HTTP请求的终端的信息 httpConnection.setRequestProperty("RANGE", sProperty); Log.log(sProperty); //创建输入流对象 InputStream input = httpConnection.getInputStream(); byte[] b = new byte[1024]; int nRead; //循环将文件下载到指定目录 while ((nRead = input.read(b, 0, 1024)) > 0 && startPosition < endposition && !isStopGet) { //调用方法将内容写入文件 startPosition += fileAccessI.write(b, 0, nRead); } Log.log("线程 " + (threadID+1) + " 结束..."); downLoadOver = true; } catch (Exception e) { //捕获异常 e.printStackTrace(); } } } //打印回应的头信息 public void logResponseHead(HttpURLConnection con) { for (int i = 1;; i++) { //循环打印回应的头信息 String header = con.getHeaderFieldKey(i); if (header != null) Log.log(header + ": " + con.getHeaderField(header)); else break; } } public void splitterStop() { isStopGet = true; } } class FileAccess implements Serializable { //存储文件的类 RandomAccessFile saveFile; long position; public FileAccess() throws IOException { //默认构造方法初始化 this("", 0); } //带参数构造方法进行初始化 public FileAccess(String sName, long position) throws IOException { //创建随机访问文件对象,以读/写方式 saveFile = new RandomAccessFile(sName, "rw"); this.position = position; saveFile.seek(position); //设置指针位置 } public synchronized int write(byte[] b, int start, int length) { int n = -1; try { saveFile.write(b, start, length); //将字节数据写入文件 n = length; } catch (IOException e) { //捕获IO流异常 e.printStackTrace(); } return n; } } class TranBean { //传输保存信息的类 private String webAddr; //下载地址 private String fileDir; //下载到指定目录 private String fileName; //下载后文件的新名字 private int count; //文件分几个线程下载,默认是3个 public TranBean() { //默认构造方法 this("", "", "", 3); } public TranBean(String webAddr, String fileDir, String fileName, int count) { //带参数的构造方法进行初始化 this.webAddr = webAddr; this.fileDir = fileDir; this.fileName = fileName; this.count = count; } public int getCount() { return count; } public void setCount(int count) { this.count = count; } public String getFileDir() { return fileDir; } public void setFileDir(String fileDir) { this.fileDir = fileDir; } public String getFileName() { return fileName; } public void setFileName(String fileName) { this.fileName = fileName; } public String getWebAddr() { return webAddr; } public void setWebAddr(String webAddr) { this.webAddr = webAddr; } } class Log { //线程运行信息显示的日志类 public Log() {} public static void sleep(int nSecond) { try { Thread.sleep(nSecond); } catch (Exception e) { System.out.println("线程沉睡..."); } } public static void log(String message) { //显示日志信息 System.err.println(message); } public static void log(int message) { //显示日志信息 System.err.println(message); } } public class TextThreadsAndPointAdd { //操作多线程支持断点续传的类 //构造方法进行初始化 publicTextThreadsAndPointAdd(StringwebAddr,StringfileDir,StringfileName,intcount) { try { TranBean bean = new TranBean(webAddr, fileDir, fileName, count); ControlFileFetch fileFetch = new ControlFileFetch(bean); fileFetch.start(); } catch (Exception e) { System.out.println("多线程下载文件出错:" + e.getMessage()); System.exit(1); } } public static void main(String[] args) { //Java程序主入口处 String webAddress = "http://localhost:8080/Demo/jdom-1.1.zip";//下载地址 String fileDir = "F:/temp"; //下载到指定的目录 String fileName = "sss.rar"; //下载后文件的名字包括后缀 int count = 5; //文件分几个线程下载,默认是3个 //传入参数实例化对象 new TextThreadsAndPointAdd(webAddress, fileDir, fileName, count); } }

(3)运行结果如下所示:

文件的长度:3541263
线程:1,开始位置 =0,结束位置=708252
bytes=0-
线程:2,开始位置 =708252,结束位置=1416504
线程:3,开始位置 =1416504,结束位置=2124756
bytes=1416504-
bytes=708252-
线程:4,开始位置 =2124756,结束位置=2833008
bytes=2124756-
线程:5,开始位置 =2833008,结束位置=3541263
bytes=2833008-
线程   1 结束...
线程   5 结束...
线程   3 结束...
线程   2 结束...
线程   4 结束...
文件下载结束!

源程序解读

(1)ControlFileFetch类继承Thread类负责抓取文件和控制内部线程,其构造方法中根据传入的传输对象获得文件的路径(包括后缀),判断路径的文件是否存在以及创建线程开始与结束位置数组。

(2)run()方法判断是否是第一次读取文件,如果是第一次读取则调用getFileSize()方法获得文件长度,根据返回的文件长度的值判断是否能访问该文件或该文件是否被破坏不能读取信息。利用循环划分每个线程要下载的文件的开始位置与结束位置,最后一个结束位置的值是文件的长度。创建FileFetch类数组循环启动每个子线程,记载每个线程的开始与结束位置。start()方法启动线程。根据标识为真条件循环调用保存下载文件指针位置的savePosition()方法,再利用循环根据标识为真实现下载文件。

(3)getFileSize()方法是获取下载文件的长度。根据传入的网址创建URL对象,再根据其openConnection()方法创建连接对象获得响应的代码。如果响应代码大于400则表明不能访问文件。连接对象中设置属性User-Agent是描述发出HTTP请求的终端的信息。连接对象的getHeaderFieldKey()方法获得文件的头部信息。根据头部信息的Content-Length再利用getHeaderField()方法可以获得文件的长度。

(4)savePosition()方法保存下载文件的指针位置信息。方法中封装文件输出流创建数据输出流对象,再利用循环保存每个线程的开始与结束位置。循环完毕后释放流资源。

(5)readInfo()方法读取文件的指针位置。方法中封装文件输入流创建数据输入流对象,其readInt()方法获取启动的线程个数,并根据此个数创建开始与结束位置的数组。循环利用readLong()方法读取每个线程的开始与结束位置。读取完毕后释放有关流资源。

(6)stopDownLoad()方法循环根据splitterStop()方法停止每个启动的线程来停止文件的下载。

(7)FileFetch类继承Thread类实现部分文件的抓取。其构造方法中初始化网址、开始位置与结束位置、线程编号以及创建文件访问对象。

(8)FileFetch类的run()方法根据标识为真以及判断开始与结束位置进行循环,根据网址创建URL对象,其openConnection()方法创建连接对象。连接对象的属性RANGE是描述发出HTTP请求的终端的信息。利用连接对象的getInputStream()方法创建输入流对象,循环读取流信息写入到指定的下载文件。每个线程读取文件结束后,改变downLoadOver标识的值,来表明线程读取文件结束。

(9)FileAccess类实现Serializable接口以启用其序列化功能。其构造方法根据传入参数以读/写方式创建随机访问文件对象,并设置对象的指针位置。write()方法由于声明为synchronized受保护则在同一时间只能有一个类访问该方法,当访问完毕后其他类或对象才能访问该方法。随机访问对象的write()方法将字节数据写入文件。

(10)TranBean类用于保存和传输数据,其声明下载地址、下载的指定目录、下载文件后的新名字以及启动几个线程下载文件。

(11)Log类是记录启动线程下载文件的相关信息,包括记录每个线程下载文件的开始与结束位置、每个线程的先后停止顺序以及下载过程中的出错信息。

(12)TextThreadsAndPointAdd类的构造方法根据传入的参数初始化传输功能的TranBean类,封装TranBean实例化ControlFileFetch线程类,再根据start()方法启动线程下载文件。

实例117 代理服务器的实现

代理服务器可以加速访问速度,可以访问无法直接访问的站点,部分解决网络IP地址紧缺的问题。本实例简单介绍如何实现代理服务器,解析用户请求的网页以及将网页发送给用户浏览器。

技术要点

实现代理服务器的技术要点如下:

• 代理服务器,即Proxy服务器,是介于浏览器和Web服务器之间的一台服务器,有了它之后,浏览器不是直接到We b服务器去取回网页而是向代理服务器发出请求,请求信号会先送到代理服务器,由代理服务器来取回浏览器所需要的信息并传给浏览器。大部分代理服务器都具有缓冲的功能,就好像一个大的Cache,有很大的存储空间,不断地将新取得的数据储存到本机的存储器上,如果浏览器所请求的数据在它本机的存储器上已经存在而且是最新的,那么它就不重新从We b服务器取数据,而直接将存储器上的数据传送给用户的浏览器,这样提高浏览速度和效率。

• 本实例使用代理服务器打开一个端口,接收浏览器发来的访问某个站点请求,从请求的字符串中解析出用户想访问哪个网页,然后通过URL对象建立输入流以读取相应的网页内容,最后按照We b服务器的工作方式将网页内容发送给用户浏览器。

实现步骤

(1)新建一个类名为TextHttpProxy.java。

(2)代码如下所示:

package com.zf.s13;                            //创建一个包
import java.lang.reflect.Constructor;          //引入类
import java.net.*;
import java.io.*;
public class TextHttpProxy extends Thread {    //操作实现代理服务器的类
    static public int RETRIES = 5;             //在放弃之前尝试连接远程主机的次数
    static public int PAUSE = 5;               //在两次连接尝试之间的暂停时间
    static public int TIMEOUT = 50;            //等待Socket输入的等待时间
    static public int BUFSIZ = 1024;           //输入的缓冲大小
    static public boolean logging = false;//是否要求代理服务器在日志中记录所有已传输的数据
    static public OutputStream log = null;//默认日志例程将向该OutputStream对象输出日志信息
    protected Socket socket;                   //传入数据用的Socket
    static private String parent = null;       //上级代理服务器
    static private int parentPort = -1;
    //用来把一个代理服务器链接到另一个代理服务器(需要指定另一个服务器的名称和端口)
    static public void setParentProxy(String name, int port) {
          parent = name;
          parentPort = port;
    }
    public TextHttpProxy(Socket s) {           //创建一个代理线程
          socket = s;
          start();                             //启动线程
    }
    public void writeLog(int c, boolean browser) throws IOException {//写日志
          log.write(c);
    }
    public void writeLog(byte[] bytes, int offset, int len, boolean browser)
              throws IOException {             //循环写日志
          for (int i = 0; i < len; i++)
              writeLog((int) bytes[offset + i], browser);
    }
                                               //默认情况下,日志信息输出到控制台或文件
    public String printLog(String url, String host, int port, Socket sock) {
          java.text.DateFormat cal = java.text.DateFormat.getDateTimeInstance();
          System.out.println(cal.format(new java.util.Date()) + "-" + url +" "
                    + sock.getInetAddress() + "\n");
          return host;
    }
    public void run() {                         //执行操作的线程
          String line;
          String host;
          int port = 80;
          Socket outbound = null;
          try {
              socket.setSoTimeout(TIMEOUT);//设置超时时间
              InputStream is = socket.getInputStream();//创建输入流
              OutputStream os = null;
              try {
                    line = "";                  //获取请求行的内容
                    host = "";
                    int state = 0;
                    boolean space;
                    while (true) {                                 //无限循环
                        int c = is.read();                         //读取输入流的信息
                        if (c == -1)                               //没有读取信息
                            break;
                        if (logging)
                            writeLog(c, true);                     //将信息写入日志
                        space = Character.isWhitespace((char) c);  //判断是否为空白字符
                        switch (state) {                           //判断状态
                        case 0:
                            if (space)
                                  continue;                         //跳过本次循环
                            state = 1;                              //更改状态
                        case 1:
                            if (space) {
                                  state = 2;
                                  continue;                         //跳过本次循环
                            }
                            line = line + (char) c;                 //添加读取的信息
                            break;
                        case 2:
                            if (space)
                                  continue;                         //跳过空白字符
                            state = 3;                              //更改状态
                        case 3:
                            if (space) {                            //如果是空白字符
                                  state = 4;                        //更改状态
                                  String host0 = host;              //取出网址
                                  int n;
                                  n = host.indexOf("//");           //获取网址(不包括协议)
                                  if (n != -1)                      //没有找到
                                      host = host.substring(n + 2);
                                  n = host.indexOf('/');
                                  if (n != -1)                      //没有找到
                                      host = host.substring(0, n);
                                  n = host.indexOf(":");            //分析可能存在的端口号
                                  if (n != -1) {                    //没有找到
                                      port = Integer.parseInt(host.substring(n + 1));
                                      host = host.substring(0, n);
                                  }
                                  host = printLog(host0, host, port, socket);//获得网站域名
                                  if (parent != null) {
                                      host = parent;
                                      port = parentPort;
                                  }
                                  int retry = RETRIES;
                                  while (retry-- != 0) {
                                      try {                         //创建连接对象
                                            outbound = new Socket(host, port);
                                            break;
                                      } catch (Exception e) {
                                            System.out.println("无法创建连接:"+e.getMessage());
                                      }
                                      Thread.sleep(PAUSE);         //设置线程等待
                                  }
                                  if (outbound == null)
                                      break;
                                  outbound.setSoTimeout(TIMEOUT);  //设置超时时间
                                  os = outbound.getOutputStream(); //获得输出流对象
                                  os.write(line.getBytes());       //将信息写入流
                                  os.write(' ');
                                  os.write(host0.getBytes());      //将信息写入流
                                  os.write(' ');
                                  writeInfo(is, outbound.getInputStream(), os, socket
                                          .getOutputStream()); //调用方法将信息写入日志
                                  break;
                            }
                            host = host + (char) c;
                            break;
                        }
                  }
              } catch (IOException e) {
              }
        } catch (Exception e) {
        } finally {
              try {
                  socket.close();                                  //释放资源
              } catch (Exception e1) {
              }
              try {
                  outbound.close();
              } catch (Exception e2) {
              }
        }
    }
    void writeInfo(InputStream is0, InputStream is1, OutputStream os0,
              OutputStream os1) throws IOException {         //读取流中信息写入日志
        try {
              int ir;
              byte bytes[] = new byte[BUFSIZ];               //创建字节数组,大小:1024
              while (true) {
                  try {
                        if ((ir = is0.read(bytes)) > 0) {    //判断读取输入流的信息
                            os0.write(bytes, 0, ir);         //读取数据写入输出流对象中
                            if (logging)
                                  writeLog(bytes, 0, ir, true);//写入日志
                        } else if (ir < 0)                    //读取完毕
                            break;
                  } catch (InterruptedIOException e) {        //捕获中断IO流异常
                  }
                  try {
                        if ((ir = is1.read(bytes)) > 0) {     //判断读取输入流的信息
                            os1.write(bytes, 0, ir);          //读取数据写入输出流对象中
                            if (logging)
                                  writeLog(bytes, 0, ir, false);//写入日志
                        } else if (ir < 0)                    //读取完毕
                              break;
                    } catch (InterruptedIOException e) {      //捕获中断IO流异常
                    }
              }
          } catch (Exception e0) {                            //捕获异常
          }
    }
    static public void proxyStart(int port, Class<TextHttpProxy> clobj) {
          ServerSocket serverSocket;
          try {
              serverSocket = new ServerSocket(port); //根据端口创建服务器端Socket对象
              while (true) {
                    Class[] objClass = new Class[1];         //创建类数组,大小为1
                    Object[] obj = new Object[1];            //创建对象数组,大小为1
                    objClass[0] = Socket.class;              //添加Socket类
                    try {                                    //创建代理服务器实例
                        Constructor cons = clobj.getDeclaredConstructor(objClass);
                        obj[0] = serverSocket.accept();      //挂起等待客户的请求
                        cons.newInstance(obj);         //创建HttpProxy或其派生类的实例
                    } catch (Exception e) {
                        Socket socket = (Socket) obj[0];     //对象强制转换
                        try {
                              socket.close();                //释放资源
                        } catch (Exception ec) {
                        }
                    }
              }
          } catch (IOException e) {
          }
    }
    static public void main(String args[]) {                //Java程序主入口处
          System.out.println("在端口8080启动代理服务器......");
          TextHttpProxy.log = System.out;                   //日志信息输出到控制台
          TextHttpProxy.logging = false;
          TextHttpProxy.proxyStart(8080,TextHttpProxy.class);//调用方法
    }
}

(3)在运行程序之前需要设置代理服务器的地址和端口:选择网页中的“工具”菜单下的“Internet选项”命令,单击“连接”面板中的“局域网设置”按钮,选中“为LAN使用代理服务器......”复选框,填入地址与端口。本实例的地址为127.0.0.1,端口为8080。填入完毕后,单击“确定”按钮。

运行结果如下所示:

在端口8080启动代理服务器......
2009-4-17 15:52:54 - http://www.baidu.com/s?wd= /127.0.0.1
2009-4-17 15:52:55 - http://www.baidu.com/img/baidu_logo.gif /127.0.0.1
2009-4-17 15:52:55 - http://www.baidu.com/js/bdsug.js?v=1.1.0.1 /127.0.0.1
2009-4-1715:53:00 - http://www.google.cn/search?hl=zh-CN&q=&btnG =
Google+%E6%90%9C%E7%B4%A2&meta=&aq=f&oq= /127.0.0.1
......

源程序解读

(1)TextHttpProxy类继承Thread类具有线程类的功能来实现代理服务器。其setParentProxy()方法初始化上级代理服务器以及上级代理服务器的端口,用来把一个代理服务器链接到另一个代理服务器。其构造方法创建一个代理线程并利用start()方法启动线程。

(2)writeLog(两个参数)方法根据输出流对象的write()方法将数据写到指定位置。writeLog(四个参数)利用循环调用writeLog(两个参数)方法将信息写到指定位置。

(3)printLog()方法创建日期对象,通过其format()方法格式化日期并将地址和主机信息输出到控制台。

(4)线程的run()方法设置Socket对象的超时时间,并根据其getInputStream()方法创建输入流对象。根据标识为真条件进行循环,读取输入流中的信息。如果读取的信息为-1,则表明没有读取的信息。再利用是否写日志的标识调用writeLog()方法将信息写入日志。Character类的isWhitespace()方法信息判断是否为空白字符。利用分支循环判断信息是否为空白字符以及当前状态取得网址,通过字符串的indexOf()方法获得指定字符在字符串中第一次出现的位置。调用printLog()方法获得主机地址。再循环创建Socket对象并设置对象的超时时间。根据Socket对象的getOutputStream()方法获得输出流对象。利用输出流对象的write()方法将读取的信息写入到流中,再调用writeInfo()方法将信息写入日志。

(5)writeInfo()方法是将传入的流信息写入到指定文件。方法中创建字节数组用来存储数据。根据标识为真条件进行循环,读取输入流中的信息写入到输出流对象中。根据是否写日志标识调用writeLog()方法将信息写入到日志中。

(6)proxyStart()方法根据传入的端口号创建服务器端的Socket对象。利用标识永远为真条件进行循环,创建类数组和对象数组,大小都为1。将Socket类添加到类数组中。根据传入类的getDeclaredConstructor()方法创建代理服务器实例。将服务器端Socket对象的accept()方法创建的Socket对象放入对象数组中,挂起等待客户端的请求。代理服务器对象的netInstance()方法创建传入类或其派生类的实例,用来代理服务器。如果其中出现异常,将对象数组的第一个对象强制转换成Socket对象并关闭其资源。

实例118 IP多点传送(基于UDP的C/S)

IP多点传送MulticastSocket类,IP多点传送是针对点对点的传送和广播传送两种方式而言的,它是指在一定的组内对其成员进行的广播,是一种有限的广播。组中的某个成员发出的信息,组中的其他成员都能收到。它是UDP Socket的一个分支。

技术要点

基于UDP实现IP多点传送的技术要点如下:

• 网组管理协议(Internet Group Management Protocol,IGMP)用于管理多点传送组中的成员。支持多点传送的路由可以使用IGMP决定本地的机器是否赞成加入某个组,一个多点传送路由可以决定是否转发一个多点传送报文。

• 影响多点传送报文的一个重要参数是time-to-live(TTL)。TTL用于描述发送者希望传送的信息能通过多少不同的网络。当报文被路由器转发,报文中的TTL将减1,当TTL为0时,报文将不再向前发送。

• 为了支持IP多点传送,某些范围的IP地址被单独留出专门用于这个目的,这些IP地址是D类地址,其地址的最高四比特的位模式为1110,即IP地址的范围在224.0.0.0和239.255.255.255之间。它们中的每一个IP地址都可以被引用作为一个多点传送组,任何以该IP地址编址的IP报文将被该组中的其他所有机器接收。也就是说,一个IP地址就相当于一个邮箱。另外,组中的成员是动态的并随时间而改变。

• 需要注意的是,IP地址只能作为信宿地址使用,绝对不能出现在任何信源地址域中,也不能出现在源路径或记录路径选项中;发送一个信息到一个组,发送主机可以不是组中的成员;如果先取的组已经被使用,与其他机器的通信将会混乱,一旦发生,可以退出应用,尝试其他地址。

• Java.net包中含有UDP通信所需要的工具,其中包括IP多点传送。

实现步骤

(1)创建两个类,分别为TextMuliticastServer.java和TextMuliticastClient.java。

(2)代码如下所示:

/**--------文件名:TextMuliticastServer.java-------------*/
package com.zf.s13;                                    //创建一个包
import java.io.*;                                      //引入类
import java.net.*;
import java.util.*;
                                            //继承线程类操作UDP实现数据报传送的服务器端
public class TextMuliticastServer extends Thread {
    protected DatagramSocket socket = null;            //声明数据报Socket对象
    protected BufferedReader in = null;                //用来读文件的一个Reader
    protected boolean moreQuotes = true;               //标志变量,是否继续操作
                                                                //无参数的构造函数调用有参构造方法
    public TextMuliticastServer() throws IOException {
          this("QuoteServerThread");                   //调用带参数的构造函数
    }
    public TextMuliticastServer(String name) throws IOException {
          super(name);                                 //调用父类的构造函数
          socket=new DatagramSocket(1111);             //在端口4445创建数据报套接字
          try {                                        //打开文件创建相应的缓冲读对象
              in = new BufferedReader(new FileReader("F:/poem.txt"));
          } catch (FileNotFoundException e) {          //捕获异常
              System.err.println("不能打开文件或文件不存在...");
          }
    }
    public void run(){                                 //线程主体
          while (moreQuotes) {                         //根据标识为真循环
              try {
                    byte[] buf=new byte[256];          //创建缓冲区
                                                                //由缓冲区创建数据报Packet对象
                    DatagramPacket packet = new DatagramPacket(buf, buf.length);
                    socket.receive(packet);            //接收数据报
                  String data = null;
                  if (in == null)   //初始化打开文件失败,则使用日期作要传送字符串
                      data = new Date().toString();
                  else                                 //调用方法从文件中读出字符串
                      data =getFileInfo();
                  buf = data.getBytes();               //把字符串转换成字节数组,以便传送
                                                       //获得从客户端packet的地址
                  InetAddress address = packet.getAddress();
                  int port = packet.getPort();         //获得端口号
                                                       //根据客户端信息创建数据报Packet对象
                  packet = new DatagramPacket(buf, buf.length, address, port);
                  socket.send(packet);               //发送数据报
              } catch (IOException e) {              //捕获异常
                  System.out.println("信息处理出错:"+e.getMessage());
                  moreQuotes = false;                //标识为false,以结束循环
              }
        }
        socket.close();                              //关闭数据报套接字
    }
    protected String getFileInfo () {                //读取文件内容
        String returnValue = null;                   //声明要返回的数据
        try {
              if ((returnValue=in.readLine())==null){//从文件中按每行读取数据
                  in.close();                        //如果读到了文件尾,关闭输入流
                  moreQuotes = false;                //标识为false,以结束循环
                  returnValue = "读取文件信息完毕...";
              }
        } catch (IOException e) {                    //捕获异常
              returnValue = "读取文件信息出错...";
        }finally{//内容总执行
              System.out.println("文件内容:"+returnValue);
        }
        return returnValue;                          //返回字符串
    }
    public static void main(String []args){          //Java程序主入口处
        try {
              new TextMuliticastServer().start();    //实例化对象并启动线程
        } catch (IOException e) {                    //捕获异常
              System.out.println("实例化对象启动线程出错:"+e.getMessage());
        }
    }
}
/**--------文件名:TextMuliticastClient.java-------------*/
package com.zf.s13;                                  //创建一个包
import java.net.*;                                   //引入类
public class TextMuliticastClient {                  //操作UDP实现数据报传送的客户端
    protected static boolean NoRead=false;           //标志变量,是否继续操作
    public static void main(String[] args){          //Java程序主入口处
        try {                                        //创建数据报Socket对象
              DatagramSocket socket = new DatagramSocket();
                                                      //根据域名创建主机地址对象
              InetAddress address = InetAddress.getByName("127.0.0.1");
              while(!NoRead){                         //标识为真循环
                  byte[] buf=new byte[256];           //创建缓冲区
                  DatagramPacket packet = new DatagramPacket(buf, buf.length, address,
                            1111);                    //由缓冲区创建数据报Packet对象
                  socket.send(packet);                //发送信息
                  packet = new DatagramPacket(buf, buf.length);
                                            //由缓冲区创建数据报Packet对象,用来接收数据报
                  socket.receive(packet);              //接收信息
                                                       //将接收到的信息转化成字符串
                  String received = new String(packet.getData());
                  if(received==null || received.equals(""))
                        NoRead=true;
                  System.out.println("从服务器端获得的数据报:" + received);
              }
              socket.close();                          //释放资源
          } catch (Exception e) {                      //捕获异常
              System.out.println("获取服务器端信息出错:"+e.getMessage());
          }
    }
}

(3)服务器端TextMuliticastServer类的输出如下所示:

文件内容:A contented mind is the greatest blessing a man can enjoy in this world.
文件内容:知足是人生在世最大的幸事。
文件内容:If you would know the value of money, go and try to borrow some.
文件内容:要想知道钱的价值,就想办法去借钱试试。
文件内容:That man is the richest whose pleasure are the cheapest.
文件内容:能处处寻求快乐的人才是最富有的人。
文件内容:读取文件信息完毕...

客户端TextMuliticastClient类的输出如下所示:

从服务器端获得的数据报:A contented mind is the greatest blessing a man can enjoy in this world.
从服务器端获得的数据报:知足是人生在世最大的幸事。
从服务器端获得的数据报:If you would know the value of money, go and try to borrow some.
从服务器端获得的数据报:要想知道钱的价值,就想办法去借钱试试。
从服务器端获得的数据报:That man is the richest whose pleasure are the cheapest.
从服务器端获得的数据报:能处处寻求快乐的人才是最富有的人。
从服务器端获得的数据报:读取文件信息完毕...

源程序解读

(1)TextMuliticastServer类继承Thread线程类,实现数据报传送的服务器端。其继承线程类需要实现run()方法。在该类中无参数的构造方法传参数给有参数的构造方法,有参数的构造方法在指定端口创建数据报Socket(套接字)对象,并打开指定文件创建相应的缓冲读对象用来读取数据报信息。

(2)TextMuliticastServer类的run()方法根据标识为真条件进行循环。创建缓冲区用来创建数据报Packet对象。数据报Socket对象的receive()方法用来接收数据报信息。判断缓冲读流是否读到信息,如果没有则使用日期作为要传送的字符串,如果有则调用getFileInfo()方法读取文件内容,将读取的内容转化为字节数组来便于传送。根据数据报Packet对象的getAddress()方法获得客户端的Packet地址和getPort()方法获得端口号,再利用客户端的信息以及读取的文件内容创建数据报packet对象,数据报Socket对象的send()方法将该数据报packet对象发送到客户端,用于客户端的读取。发送完毕后释放有关资源。

(3)getFileInfo()方法用于读取文件的内容。缓冲读流的readLine()方法用来读取文件每行的内容,每行的内容不为空则赋给要返回的数据变量,为空则关闭流对象并将标识置为假,以结束循环。

(4)TextMuliticastClient类是实现数据报传送的客户端。在其main()主方法中,创建数据报Socket对象,根据域名创建主机地址对象,利用标识为真条件进行循环。在循环中创建缓冲区用来创建数据报Packet对象。数据报Socket对象的send()方法用来发送数据。用缓冲区创建数据报Packet对象来接收服务器端传送的数据报。数据报Socket对象的receive()方法用来接收信息,将接收的信息根据getData()方法转化成字符串输出。