posts - 51, comments - 17, trackbacks - 0, articles - 9
  BlogJava :: 首页 :: 新随笔 :: 联系 :: 聚合  :: 管理

Historical Collection Classes(JDK1.1 之前)
提供的容器有Arrays,Vector,Stack,Hashtable,Properties,BitSet。其中定义出一种走访群集内各元素的标准方式,称为Enumeration(列举器)接口,用法如下:
Vector v=new Vector();
for (Enumeration enum =v.elements(); enum.hasMoreElements();) {
Object o 
= enum.nextElement();
processObject(o);
}

而在JDK1.2版本中引入了Iterator接口,新版本的集合对象(HashSet,HashMap,WeakHeahMap,ArrayList,TreeSet,TreeMap, LinkedList)是通过Iterator接口访问集合元素的。
例如:
List list=new ArrayList();
for(Iterator it=list.iterator();it.hasNext();)
{
    System.out.println(it.next());
}

这样,如果将老版本的程序运行在新的Java编译器上就会出错。因为List接口中已经没有elements(),而只有iterator()了。那么如何可以使老版本的程序运行在新的Java编译器上呢?如果不加修改,是肯定不行的,但是修改要遵循“开-闭”原则。
这时候我想到了Java设计模式中的适配器模式。


package net.blogjava.lzqdiy;

import java.util.ArrayList;
import java.util.Enumeration;
import java.util.Iterator;
import java.util.List;

public class NewEnumeration implements Enumeration
{

    Iterator it;
    
public NewEnumeration(Iterator it)
    
{
        
this.it=it;
        
// TODO Auto-generated constructor stub
    }


    
public boolean hasMoreElements()
    
{
        
// TODO Auto-generated method stub
        return it.hasNext();
    }


    
public Object nextElement()
    
{
        
// TODO Auto-generated method stub
        return it.next();
    }

    
public static void main(String[] args)
    
{
        List list
=new ArrayList();
        list.add(
"a");
        list.add(
"b");
        list.add(
"C");
        
for(Enumeration e=new NewEnumeration(list.iterator());e.hasMoreElements();)
        
{
            System.out.println(e.nextElement());
        }

    }

}

NewEnumeration是一个适配器类,通过它实现了从Iterator接口到Enumeration接口的适配,这样我们就可以使用老版本的代码来使用新的集合对象了。

posted @ 2007-04-23 10:35 chenweicai 阅读(161) | 评论 (0)编辑 收藏

一、反射的概念 :
反射的概念是由Smith在1982年首次提出的,主要是指程序可以访问、检测和修改它本身状态或行为的一种能力。这一概念的提出很快引发了计算机科学领域关于应用反射性的研究。它首先被程序语言的设计领域所采用,并在Lisp和面向对象方面取得了成绩。其中LEAD/LEAD++ 、OpenC++ 、MetaXa和OpenJava等就是基于反射机制的语言。最近,反射机制也被应用到了视窗系统、操作系统和文件系统中。

反射本身并不是一个新概念,它可能会使我们联想到光学中的反射概念,尽管计算机科学赋予了反射概念新的含义,但是,从现象上来说,它们确实有某些相通之处,这些有助于我们的理解。在计算机科学领域,反射是指一类应用,它们能够自描述自控制。也就是说,这类应用通过采用某种机制来实现对自己行为的描述(self-representation)和监测(examination),并能根据自身行为的状态和结果,调整或修改应用所描述行为的状态和相关的语义。可以看出,同一般的反射概念相比,计算机科学领域的反射不单单指反射本身,还包括对反射结果所采取的措施。所有采用反射机制的系统(即反射系统)都希望使系统的实现更开放。可以说,实现了反射机制的系统都具有开放性,但具有开放性的系统并不一定采用了反射机制,开放性是反射系统的必要条件。一般来说,反射系统除了满足开放性条件外还必须满足原因连接(Causally-connected)。所谓原因连接是指对反射系统自描述的改变能够立即反映到系统底层的实际状态和行为上的情况,反之亦然。开放性和原因连接是反射系统的两大基本要素.

Java中,反射是一种强大的工具。它使您能够创建灵活的代码,这些代码可以在运行时装配,无需在组件之间进行源代表链接反射允许我们在编写与执行时,使我们的程序代码能够接入装载到JVM中的类的内部信息,而不是源代码中选定的类协作的代码。这使反射成为构建灵活的应用的主要工具。但需注意的是:如果使用不当,反射的成本很高。

二、Java中的类反射:
Reflection 是 Java 程序开发语言的特征之一,它允许运行中的 Java 程序对自身进行检查,或者说“自审”,并能直接操作程序的内部属性。Java 的这一能力在实际应用中也许用得不是很多,但是在其它的程序设计语言中根本就不存在这一特性。例如,Pascal、C 或者 C++ 中就没有办法在程序中获得函数定义相关的信息。

1.检测类:

1.1 reflection的工作机制

考虑下面这个简单的例子,让我们看看 reflection 是如何工作的。

import java.lang.reflect.*;
public class DumpMethods {
    public static void main(String args[]) {
        try {
            Class c = Class.forName(args[0]);
            Method m[] = c.getDeclaredMethods();
            for (int i = 0; i < m.length; i++)
                System.out.println(m[i].toString());
        } catch (Throwable e) {
            System.err.println(e);
        }
    }
}

按如下语句执行:

java DumpMethods java.util.Stack

它的结果输出为:

public java.lang.Object java.util.Stack.push(java.lang.Object)

public synchronized java.lang.Object java.util.Stack.pop()

public synchronized java.lang.Object java.util.Stack.peek()

public boolean java.util.Stack.empty()

public synchronized int java.util.Stack.search(java.lang.Object)

这样就列出了java.util.Stack 类的各方法名以及它们的限制符和返回类型。

这个程序使用 Class.forName 载入指定的类,然后调用 getDeclaredMethods 来获取这个类中定义了的方法列表。java.lang.reflect.Methods 是用来描述某个类中单个方法的一个类。

1.2 Java类反射中的主要方法

对于以下三类组件中的任何一类来说 -- 构造函数、字段和方法 -- java.lang.Class 提供四种独立的反射调用,以不同的方式来获得信息。调用都遵循一种标准格式。以下是用于查找构造函数的一组反射调用:

       Constructor getConstructor(Class[] params) -- 获得使用特殊的参数类型的公共构造函数,

       Constructor[] getConstructors() -- 获得类的所有公共构造函数

       Constructor getDeclaredConstructor(Class[] params) -- 获得使用特定参数类型的构造函数(与接入级别无关)

       Constructor[] getDeclaredConstructors() -- 获得类的所有构造函数(与接入级别无关)

获得字段信息的Class 反射调用不同于那些用于接入构造函数的调用,在参数类型数组中使用了字段名:

l         Field getField(String name) -- 获得命名的公共字段

l         Field[] getFields() -- 获得类的所有公共字段

l         Field getDeclaredField(String name) -- 获得类声明的命名的字段

l         Field[] getDeclaredFields() -- 获得类声明的所有字段

用于获得方法信息函数:

l         Method getMethod(String name, Class[] params) -- 使用特定的参数类型,获得命名的公共方法

l         Method[] getMethods() -- 获得类的所有公共方法

l         Method getDeclaredMethod(String name, Class[] params) -- 使用特写的参数类型,获得类声明的命名的方法

l         Method[] getDeclaredMethods() -- 获得类声明的所有方法

 

1.3开始使用 Reflection:

用于 reflection 的类,如 Method,可以在 java.lang.relfect 包中找到。使用这些类的时候必须要遵循三个步骤:第一步是获得你想操作的类的 java.lang.Class 对象。在运行中的 Java 程序中,用 java.lang.Class 类来描述类和接口等。

下面就是获得一个 Class 对象的方法之一:

Class c = Class.forName("java.lang.String");

这条语句得到一个 String 类的类对象。还有另一种方法,如下面的语句:

Class c = int.class;

或者

Class c = Integer.TYPE;

它们可获得基本类型的类信息。其中后一种方法中访问的是基本类型的封装类 (如 Integer) 中预先定义好的 TYPE 字段。

第二步是调用诸如 getDeclaredMethods 的方法,以取得该类中定义的所有方法的列表。

一旦取得这个信息,就可以进行第三步了——使用 reflection API 来操作这些信息,如下面这段代码:

Class c = Class.forName("java.lang.String");

Method m[] = c.getDeclaredMethods();

System.out.println(m[0].toString());

它将以文本方式打印出 String 中定义的第一个方法的原型。

2.处理对象:

如果要作一个开发工具像debugger之类的,你必须能发现filed values,以下是三个步骤:

a.创建一个Class对象
b.通过getField 创建一个Field对象
c.调用Field.getXXX(Object)方法(XXX是Int,Float等,如果是对象就省略;Object是指实例).

例如:
import java.lang.reflect.*;
import java.awt.*;

class SampleGet {

   public static void main(String[] args) {
      Rectangle r = new Rectangle(100, 325);
      printHeight(r);

   }

   static void printHeight(Rectangle r) {
      Field heightField;
      Integer heightValue;
      Class c = r.getClass();
      try {
        heightField = c.getField("height");
        heightValue = (Integer) heightField.get(r);
        System.out.println("Height: " + heightValue.toString());
      } catch (NoSuchFieldException e) {
          System.out.println(e);
      } catch (SecurityException e) {
          System.out.println(e);
      } catch (IllegalAcces***ception e) {
          System.out.println(e);
      }
   }
}

三、安全性和反射:
在处理反射时安全性是一个较复杂的问题。反射经常由框架型代码使用,由于这一点,我们可能希望框架能够全面接入代码,无需考虑常规的接入限制。但是,在其它情况下,不受控制的接入会带来严重的安全性风险,例如当代码在不值得信任的代码共享的环境中运行时。

由于这些互相矛盾的需求,Java编程语言定义一种多级别方法来处理反射的安全性。基本模式是对反射实施与应用于源代码接入相同的限制:

n         从任意位置到类公共组件的接入

n         类自身外部无任何到私有组件的接入

n         受保护和打包(缺省接入)组件的有限接入

不过至少有些时候,围绕这些限制还有一种简单的方法。我们可以在我们所写的类中,扩展一个普通的基本类java.lang.reflect.AccessibleObject 类。这个类定义了一种setAccessible方法,使我们能够启动或关闭对这些类中其中一个类的实例的接入检测。唯一的问题在于如果使用了安全性管理器,它将检测正在关闭接入检测的代码是否许可了这样做。如果未许可,安全性管理器抛出一个例外。

下面是一段程序,在TwoString 类的一个实例上使用反射来显示安全性正在运行:

public class ReflectSecurity {

    public static void main(String[] args) {

        try {

            TwoString ts = new TwoString("a", "b");

            Field field = clas.getDeclaredField("m_s1");

//          field.setAccessible(true);

            System.out.println("Retrieved value is " +

                field.get(inst));

        } catch (Exception ex) {

            ex.printStackTrace(System.out);

        }

    }

}

如果我们编译这一程序时,不使用任何特定参数直接从命令行运行,它将在field .get(inst)调用中抛出一个IllegalAcces***ception异常。如果我们不注释field.setAccessible(true)代码行,那么重新编译并重新运行该代码,它将编译成功。最后,如果我们在命令行添加了JVM参数-Djava.security.manager以实现安全性管理器,它仍然将不能通过编译,除非我们定义了ReflectSecurity类的许可权限。

四、反射性能:
反射是一种强大的工具,但也存在一些不足。一个主要的缺点是对性能有影响。使用反射基本上是一种解释操作,我们可以告诉JVM,我们希望做什么并且它满足我们的要求。这类操作总是慢于只直接执行相同的操作。

下面的程序是字段接入性能测试的一个例子,包括基本的测试方法。每种方法测试字段接入的一种形式 -- accessSame 与同一对象的成员字段协作,accessOther 使用可直接接入的另一对象的字段,accessReflection 使用可通过反射接入的另一对象的字段。在每种情况下,方法执行相同的计算 -- 循环中简单的加/乘顺序。

程序如下:

public int accessSame(int loops) {

    m_value = 0;

    for (int index = 0; index < loops; index++) {

        m_value = (m_value + ADDITIVE_VALUE) *

            MULTIPLIER_VALUE;

    }

    return m_value;

}

 

public int accessReference(int loops) {

    TimingClass timing = new TimingClass();

    for (int index = 0; index < loops; index++) {

        timing.m_value = (timing.m_value + ADDITIVE_VALUE) *

            MULTIPLIER_VALUE;

    }

    return timing.m_value;

}

 

public int accessReflection(int loops) throw* **ception {

    TimingClass timing = new TimingClass();

    try {

        Field field = TimingClass.class.

            getDeclaredField("m_value");

        for (int index = 0; index < loops; index++) {

            int value = (field.getInt(timing) +

                ADDITIVE_VALUE) * MULTIPLIER_VALUE;

            field.setInt(timing, value);

        }

        return timing.m_value;

    } catch (Exception ex) {

        System.out.println("Error using reflection");

        throw ex;

    }

}

在上面的例子中,测试程序重复调用每种方法,使用一个大循环数,从而平均多次调用的时间衡量结果。平均值中不包括每种方法第一次调用的时间,因此初始化时间不是结果中的一个因素。下面的图清楚的向我们展示了每种方法字段接入的时间:

图 1:字段接入时间 :


我们可以看出:在前两副图中(Sun JVM),使用反射的执行时间超过使用直接接入的1000倍以上。通过比较,IBM JVM可能稍好一些,但反射方法仍旧需要比其它方法长700倍以上的时间。任何JVM上其它两种方法之间时间方面无任何显著差异,但IBM JVM几乎比Sun JVM快一倍。最有可能的是这种差异反映了Sun Hot Spot JVM的专业优化,它在简单基准方面表现得很糟糕。反射性能是Sun开发1.4 JVM时关注的一个方面,它在反射方法调用结果中显示。在这类操作的性能方面,Sun 1.4.1 JVM显示了比1.3.1版本很大的改进。

如果为为创建使用反射的对象编写了类似的计时测试程序,我们会发现这种情况下的差异不象字段和方法调用情况下那么显著。使用newInstance()调用创建一个简单的java.lang.Object实例耗用的时间大约是在Sun 1.3.1 JVM上使用new Object()的12倍,是在IBM 1.4.0 JVM的四倍,只是Sun 1.4.1 JVM上的两部。使用Array.newInstance(type, size)创建一个数组耗用的时间是任何测试的JVM上使用new type[size]的两倍,随着数组大小的增加,差异逐步缩小。

结束语:
Java语言反射提供一种动态链接程序组件的多功能方法。它允许程序创建和控制任何类的对象(根据安全性限制),无需提前硬编码目标类。这些特性使得反射特别适用于创建以非常普通的方式与对象协作的库。例如,反射经常在持续存储对象为数据库、XML或其它外部格式的框架中使用。Java reflection 非常有用,它使类和数据结构能按名称动态检索相关信息,并允许在运行着的程序中操作这些信息。Java 的这一特性非常强大,并且是其它一些常用语言,如 C、C++、Fortran 或者 Pascal 等都不具备的。

但反射有两个缺点。第一个是性能问题。用于字段和方法接入时反射要远慢于直接代码。性能问题的程度取决于程序中是如何使用反射的。如果它作为程序运行中相对很少涉及的部分,缓慢的性能将不会是一个问题。即使测试中最坏情况下的计时图显示的反射操作只耗用几微秒。仅反射在性能关键的应用的核心逻辑中使用时性能问题才变得至关重要。

许多应用中更严重的一个缺点是使用反射会模糊程序内部实际要发生的事情。程序人员希望在源代码中看到程序的逻辑,反射等绕过了源代码的技术会带来维护问题。反射代码比相应的直接代码更复杂,正如性能比较的代码实例中看到的一样。解决这些问题的最佳方案是保守地使用反射——仅在它可以真正增加灵活性的地方——记录其在目标类中的使用。

posted @ 2007-04-23 10:27 chenweicai 阅读(155) | 评论 (0)编辑 收藏

public class ServerSocket
extends Object

此类实现服务器套接字服务器套接字等待请求通过网络传入。它基于该请求执行某些操作,然后可能向请求者返回结果。

服务器套接字的实际工作由 SocketImpl 类的实例执行。应用程序可以更改创建套接字实现的套接字工厂来配置它自身,从而创建适合本地防火墙的套接字。



public class Socket
extends Object

此类实现客户端套接字(也可以就叫“套接字”)。套接字是两台机器之间的通信端点

套接字的实际工作由 SocketImpl 类的实例执行。应用程序通过更改创建套接字实现的套接字工厂可以配置它自身,以创建适合本地防火墙的套接字。


/**
 * 为了验证我们的服务器程序能否正常工作,还必须有一个客户端程序与之通信。
 * 我们也没必要每次都要编写客户端程序来测试。其实, Windows提供的telnet
 * 程序,就是一个TCP客户端程序,我们只要使用telnet程序对我们的服务器程序
 * 进行测试,我们只要在运行telnet程序时指定所要连接的服务器程序的IP地址
 * 和端口号,telnet程序就会按照指定的参数去与服务器程序进行连接。建立连接
 * 后,在telnet程序窗口中键入的内容会发送到服务器,从服务器端接收到的数据
 * 会显示在窗口中。
 * 先运行TcpServer程序,然后在命令行中输入 telnet 10.214.16.80 8001
 * 就会执行
 */

// TCP 服务端程序
public class TcpServer {

 public static void main(String[] args){
  
  try{
   //建立一个在8001端口上等待连接的ServerSocket对象
   ServerSocket ss = new ServerSocket(8001);
   
   //侦听并接受到此套接字的连接。此方法在进行连接之前一直阻塞,返回客户端套接字
   Socket s = ss.accept();
   
   InputStream ips = s.getInputStream();//返回此套接字的输入流
   OutputStream ops = s.getOutputStream();//返回此套接字的输出流
   
   //将字节从指定的字节数组写入此输出流
   ops.write("chenweicai...".getBytes());
   
//   byte[] buf = new byte[1024];
//   // 从输入流中读取一定数量的字节并将其存储在缓冲区数组buf中
//   int len = ips.read(buf);
//   System.out.println(new String(buf, 0, len));
   
   //由于telent只要输入就发送,而不管有没有回车,所以只有第一个字符被发送。
   //java提供了一个BufferedReader类,可以帮助我们按行处理输入流。
   BufferedReader br = new BufferedReader (new InputStreamReader(ips));
   System.out.println("The message comes form client: " + br.readLine());//输出客户端输入的内容
   
   br.close();//关闭包装类,会自动关闭包装类中所包装的底层类,所以不用调用ips.close().
//   ips.close();
   ops.close();
   s.close();
   ss.close();
  }catch (Exception e){
   e.printStackTrace();
  }
 }
}

给个具体的例子

/**
 * 实现服务器和客户端之间的对话功能
 */
public class Servicer implements Runnable {

 Socket s;
 
 public Servicer (Socket s){
  this.s = s;
 }
 
 public void run(){
  
  try {
   InputStream ips = s.getInputStream();
   OutputStream ops = s.getOutputStream();
   BufferedReader br = new BufferedReader(new InputStreamReader(ips));
   DataOutputStream dos = new DataOutputStream(ops);
   
   while(true){
    String strWord = br.readLine();
    System.out.println("From Client StrWord: " + strWord + strWord.length());
    if(strWord.equalsIgnoreCase("quit"))
     break;
    
    String strEcho = (new StringBuffer(strWord).reverse()).toString();
    dos.writeBytes(strWord + "---->" + strEcho + System.getProperty("line.separator"));
   }
   br.close();
   dos.close();
   s.close();
  } catch (IOException e) {
   e.printStackTrace();
  }
  
 }
}


import java.net.ServerSocket;
import java.net.Socket;

public class TcpServer {

 public static void main(String[] args){
  
  try{
   ServerSocket ss = new ServerSocket(8001);
   
   while(true){
    Socket s = ss.accept();
    new Thread(new Servicer(s)).start();
   }
  }catch(Exception e){
   
  }
 }
}



public class TcpClient {

 public static void main(String[] args){
  
  try{
   if(args.length < 2){
    System.out.println("Usage: java TcpClient ServerIP ServerPort");
    return;
   }
   
   Socket s = new Socket(InetAddress.getByName(args[0]), Integer.parseInt(args[1]));
   InputStream ips = s.getInputStream();
   OutputStream ops = s.getOutputStream();
   
   BufferedReader brKey = new BufferedReader(new InputStreamReader(System.in));
   DataOutputStream dos = new DataOutputStream(ops);
   BufferedReader brNet = new BufferedReader(new InputStreamReader(ips));
   
   while(true){
    String strWord = brKey.readLine();
    dos.writeBytes(strWord + System.getProperty("ling.separator"));
    if(strWord.equalsIgnoreCase("quit"))
     break;
    else
     System.out.println(brNet.readLine());
   }
   dos.close();
   brNet.close();
   brKey.close();
   s.close();
  }catch (Exception e){
   
  }
 }
}

posted @ 2007-04-20 22:54 chenweicai 阅读(365) | 评论 (0)编辑 收藏

public class DatagramSocket
extends Object

此类表示用来发送和接收数据报包的套接字

数据报套接字是包投递服务的发送或接收点。每个在数据报套接字上发送或接收的包都是单独编址和路由的。从一台机器发送到另一台机器的多个包可能选择不同的路由,也可能按不同的顺序到达。

在 DatagramSocket 上总是启用 UDP 广播发送。为了接收广播包,应该将 DatagramSocket 绑定到通配符地址。在某些实现中,将 DatagramSocket 绑定到一个更加具体的地址时广播包也可以被接收。

示例:DatagramSocket s = new DatagramSocket(null); s.bind(new InetSocketAddress(8888)); 这等价于:DatagramSocket s = new DatagramSocket(8888); 两个例子都能创建能够在 UDP 8888 端口上接收广播的 DatagramSocket。



public final class DatagramPacket
extends Object

此类表示数据报包

数据报包用来实现无连接包投递服务。每条报文仅根据该包中包含的信息从一台机器路由到另一台机器。从一台机器发送到另一台机器的多个包可能选择不同的路由,也可能按不同的顺序到达。不对包投递做出保证。



给个具体实现的例子

//UDP数据的发送类似发送寻呼一样的道理。就像寻呼机必须先处于开机接收状态才能接收寻呼一样的道理
//我们先要运行UDP接收程序,再运行UDP发送程序。UDP数据包的接收是过期作废的。
public class UdpSend {

 public static void main(String[] args) throws SocketException, UnknownHostException{
  DatagramSocket ds = new DatagramSocket();//创建用来发送数据报包的套接字
  String str = "Hello World,陈伟才";
  DatagramPacket dp = new DatagramPacket(str.getBytes(), str.getBytes().length,
    InetAddress.getByName("10.214.16.80"), 3000);
  //构造数据报包,用来将长度为 length 的包发送到指定主机上的指定端口号
  
  try {
   ds.send(dp);
  } catch (IOException e) {
   e.printStackTrace();
  }
  ds.close();
 }
}

public class UdpRecv {

 public static void main(String[] args) throws Exception{
  DatagramSocket ds = new DatagramSocket(3000);//创建接收数据报套接字并将其绑定到本地主机上的指定端口
  byte[] buf = new byte[1024];
  DatagramPacket dp = new DatagramPacket(buf, 1024);
  ds.receive(dp);
  String strRecv = new String (dp.getData(), 0, dp.getLength()) + " from "
  + dp.getAddress().getHostAddress() + ":" + dp.getPort();
  System.out.println(strRecv);
  ds.close();
  
 }
}

再给个例子Chat

public class Chat {

 Frame f = new Frame("我的聊天室");
 
 /*
  * tfIp 是用于输入IP地址的文本框,在发送数据时, 要取出其中的IP地址,
  * 所以将其定义成员变量,以便发送消息的程序代码能访问。
  */
 TextField tfIP = new TextField(15);
 
 /**
  * lst时用于显示接收消息的列表框, 在接收到数据时,要向其中增加新的记录项
  * 所以将其定义成员变量,以便发送消息的程序代码能访问
  */
 List lst = new List(6);
 
 DatagramSocket ds;
 
 public Chat(){
  try{
   ds = new DatagramSocket(3000);
  }catch(Exception e){
   e.printStackTrace();
  }
  
  new Thread (new Runnable(){
   
   public void run(){
    
    byte[] buf = new byte[1024];
    DatagramPacket dp = new DatagramPacket(buf, 1024);
    while (true){
     try{
      ds.receive(dp);
      lst.add(new String(buf, 0 , dp.getLength()) +
        " : form " + dp.getAddress().getHostAddress(), 0);
     }catch(Exception ee){
      ee.printStackTrace();
     }
    }
   }
  }).start();
 }
 
 public void init(){
  f.setSize(300, 300);
  f.add(lst);
  
  Panel p = new Panel();
  p.setLayout(new BorderLayout());
  p.add("West", tfIP);
  TextField tfData = new TextField (20);
  p.add("East", tfData);
  f.add("South", p);
  
  f.setVisible(true);
  f.setResizable(false);//不能改变窗口大小
  
  //增加关闭窗口的事件处理代码
  f.addWindowListener(new WindowAdapter(){
   public void windowClosing(WindowEvent e){
    ds.close();//程序退出时,关闭Socket,释放相关的资源
    f.setVisible(false);
    f.dispose();
    System.exit(0);
   }
  });
  
  //增加在消息文本框中按下回车键的事件处理代码
  tfData.addActionListener(new ActionListener(){
   public void actionPerformed (ActionEvent e){
    //要在这里增加网络消息发送相关程序代码
    //下面的语句用于数据发送后,清空文本框中原来的内容
    
    // 取出文本框的消息字符串,并将其转换成字节数组
    byte[] buf;
    buf = e.getActionCommand().getBytes();
    try {
     DatagramPacket dp = new DatagramPacket(buf, buf.length,
       InetAddress.getByName(tfIP.getText()), 3000);
     try {
      ds.send(dp);
     } catch (IOException e1) {
      e1.printStackTrace();
     }
    } catch (UnknownHostException e1) {
     e1.printStackTrace();
    }
    ((TextField)e.getSource()).setText("");
   }
  });
 }
 
 public static void main(String[] args){
  Chat chat = new Chat();
  chat.init();
 }
}

posted @ 2007-04-20 22:50 chenweicai 阅读(897) | 评论 (0)编辑 收藏

课前思考
  1. 什么是TCP/ IP协议?
  2. TCP/IP有哪两种传输协议,各有什么特点?
  3. 什么是URL
  4. URLIP地址有什么样的关系?
  5. 什么叫套接字(Socket)?
  6. 套接字(Socket)和TCP/IP协议的关系?
  7. URL和套接字(Socket)的关系?
8.1 网络编程基本概念,TCP/IP协议简介

8.1.1 网络基础知识

网络编程的目的就是指直接或间接地通过网络协议与其他计算机进行通讯。网络编程中有两个主要的问题,一个是如何准确的定位网络上一台或多台主机,另一个就是找到主机后如何可靠高效的进行数据传输。在TCP/IP协议中IP层主要负责网络主机的定位,数据传输的路由,由IP地址可以唯一地确定Internet上的一台主机。而TCP层则提供面向应用的可靠的或非可靠的数据传输机制,这是网络编程的主要对象,一般不需要关心IP层是如何处理数据的。
  目前较为流行的网络编程模型是客户机/服务器(C/S)结构。即通信双方一方作为服务器等待客户提出请求并予以响应。客户则在需要服务时向服务器提出申请。服务器一般作为守护进程始终运行,监听网络端口,一旦有客户请求,就会启动一个服务进程来响应该客户,同时自己继续监听服务端口,使后来的客户也能及时得到服务。

8.1.3两类传输协议:TCP;UDP
  尽管TCP/IP协议的名称中只有TCP这个协议名,但是在TCP/IP的传输层同时存在TCPUDP两个协议。

TCPTranfer Control Protocol的简称,是一种面向连接的保证可靠传输的协议。通过TCP协议传输,得到的是一个顺序的无差错的数据流。发送方和接收方的成对的两个socket之间必须建立连接,以便在TCP协议的基础上进行通信,当一个socket(通常都是server socket)等待建立连接时,另一个socket可以要求进行连接,一旦这两个socket连接起来,它们就可以进行双向数据传输,双方都可以进行发送或接收操作。
  UDPUser Datagram Protocol的简称,是一种无连接的协议,每个数据报都是一个独立的信息,包括完整的源地址或目的地址,它在网络上以任何可能的路径传往目的地,因此能否到达目的地,到达目的地的时间以及内容的正确性都是不能被保证的。
  下面我们对这两种协议做简单比较:
  使用UDP时,每个数据报中都给出了完整的地址信息,因此无需要建立发送方和接收方的连接。对于TCP协议,由于它是一个面向连接的协议,在socket之间进行数据传输之前必然要建立连接,所以在TCP中多了一个连接建立的时间。
 
 使用UDP传输数据时是有大小限制的,每个被传输的数据报必须限定在64KB之内。而TCP没有这方面的限制,一旦连接建立起来,双方的socket就可以按统一的格式传输大量的数据。UDP是一个不可靠的协议,发送方所发送的数据报并不一定以相同的次序到达接收方。而TCP是一个可靠的协议,它确保接收方完全正确地获取发送方所发送的全部数据。
  总之,TCP在网络通信上有极强的生命力,例如远程连接(Telnet)和文件传输(FTP)都需要不定长度的数据被可靠地传输。相比之下UDP操作简单,而且仅需要较少的监护,因此通常用于局域网高可靠性的分散系统中client/server应用程序。
  读者可能要问,既然有了保证可靠传输的TCP协议,为什么还要非可靠传输的UDP协议呢?主要的原因有两个。一是可靠的传输是要付出代价的,对数据内容正确性的检验必然占用计算机的处理时间和网络的带宽,因此TCP传输的效率不如UDP高。二是在许多应用中并不需要保证严格的传输可靠性,比如视频会议系统,并不要求音频视频数据绝对的正确,只要保证连贯性就可以了,这种情况下显然使用UDP会更合理一些。

8.2 基于URL的高层次Java网络编程

8.2.1一致资源定位器URL

URL(Uniform Resource Locator)是一致资源定位器的简称,它表示Internet上某一资源的地址。通过URL我们可以访问Internet上的各种网络资源,比如最常见的WWWFTP站点。浏览器通过解析给定的URL可以在网络上查找相应的文件或其他资源。

8.2.2 URL的组成

protocol://resourceName
  协议名(protocol)指明获取资源所使用的传输协议,如httpftpgopherfile等,资源名(resourceName)则应该是资源的完整地址,包括主机名、端口号、文件名或文件内部的一个引用。例如:
  http://www.sun.com/ 协议名://主机名
  http://home.netscape.com/home/welcome.html 协议名://机器名+文件名
  http://www.gamelan.com:80/Gamelan/network.html#BOTTOM 协议名://机器名+端口号+文件名+内部引用.

8.2.3 创建一个URL

为了表示URL, java.net中实现了类URL。我们可以通过下面的构造方法来初始化一个URL对象:
  (1) public URL (String spec);
     通过一个表示URL地址的字符串可以构造一个URL对象。
     URL urlBase=new URL("http://www. 263.net/")
  (2) public URL(URL context, String spec);
     通过基URL和相对URL构造一个URL对象。
     URL net263=new URL ("http://www.263.net/");
     URL index263=new URL(net263, "index.html")
  (3) public URL(String protocol, String host, String file);
     new URL("http", "www.gamelan.com", "/pages/Gamelan.net. html");
  (4) public URL(String protocol, String host, int port, String file);
     URL gamelan=new URL("http", "www.gamelan.com", 80, "Pages/Gamelan.network.html");

  注意:URL的构造方法都声明抛弃非运行时例外(MalformedURLException),因此生成URL对象时,我们必须要对这一例外进行处理,通常是用try-catch语句进行捕获。格式如下:

try{
     URL myURL= new URL(…)
  }catch (MalformedURLException e){
    }

8.2.4 解析一个URL

一个URL对象生成后,其属性是不能被改变的,但是我们可以通过类URL所提供的方法来获取这些属性:
   public String getProtocol() 获取该URL的协议名。
   public String getHost() 获取该URL的主机名。
   public int getPort() 获取该URL的端口号,如果没有设置端口,返回-1。
   public String getFile() 获取该URL的文件名。
   public String getRef() 获取该URL在文件中的相对位置。
   public String getQuery() 获取该URL的查询信息。
   public String getPath() 获取该URL的路径
   public String getAuthority() 获取该URL的权限信息
   public String getUserInfo() 获得使用者的信息
    public String getRef() 获得该URL的锚

8.2.5 从URL读取WWW网络资源

当我们得到一个URL对象后,就可以通过它读取指定的WWW资源。这时我们将使用URL的方法openStream(),其定义为:
         InputStream openStream();
  
  方法openSteam()与指定的URL建立连接并返回InputStream类的对象以从这一连接中读取数据。
  public class URLReader {
  public static void main(String[] args) throws Exception {
                      //声明抛出所有例外
    URL tirc = new URL("http://www.tirc1.cs.tsinghua.edu.cn/");
                      //构建一URL对象
    BufferedReader in = new BufferedReader(new InputStreamReader(tirc.openStream()));
    //使用openStream得到一输入流并由此构造一个BufferedReader对象
    String inputLine;
    while ((inputLine = in.readLine()) != null)
                 //从输入流不断的读数据,直到读完为止
       System.out.println(inputLine); //把读入的数据打印到屏幕上
    in.close(); //关闭输入流
  }
  }

8.2.6 通过URLConnetction连接WWW

通过URL的方法openStream(),我们只能从网络上读取数据,如果我们同时还想输出数据,例如向服务器端的CGI程序发送一些数据,我们必须先与URL建立连接,然后才能对其进行读写,这时就要用到类URLConnection了。CGI是公共网关接口(Common Gateway Interface)的简称,它是用户浏览器和服务器端的应用程序进行连接的接口,有关CGI程序设计,请读者参考有关书籍。
  URLConnection也在包java.net中定义,它表示Java程序和URL在网络上的通信连接。当与一个URL建立连接时,首先要在一个URL对象上通过方法openConnection()生成对应的URLConnection对象。例如下面的程序段首先生成一个指向地址http://edu.chinaren.com/index.shtml的对象,然后用openConnection()打开该URL对象上的一个连接,返回一个URLConnection对象。如果连接过程失败,将产生IOException.
  Try{
    URL netchinaren = new URL ("http://edu.chinaren.com/index.shtml");
    URLConnectonn tc = netchinaren.openConnection();
  }catch(MalformedURLException e){ //创建URL()对象失败
  
  }catch (IOException e){ //openConnection()失败
  
  }
  类URLConnection提供了很多方法来设置或获取连接参数,程序设计时最常使用的是getInputStream()getOurputStream(),其定义为:
     InputSteram getInputSteram();
     OutputSteram getOutputStream();
  通过返回的输入/输出流我们可以与远程对象进行通信。看下面的例子:
  URL url =new URL ("http://www.javasoft.com/cgi-bin/backwards");
  //创建一URL对象
  URLConnectin con=url.openConnection();
  //URL对象获取URLConnection对象
  DataInputStream dis=new DataInputStream (con.getInputSteam());
  //URLConnection获取输入流,并构造DataInputStream对象
  PrintStream ps=new PrintSteam(con.getOutupSteam());
  //URLConnection获取输出流,并构造PrintStream对象
  String line=dis.readLine(); //从服务器读入一行
  ps.println("client…"); //向服务器写出字符串 "client…"
    其中backwards为服务器端的CGI程序。实际上,类URL的方法openSteam()是通过URLConnection来实现的。它等价于
    openConnection().getInputStream();
  基于URL的网络编程在底层其实还是基于下面要讲的Socket接口的。WWWFTP等标准化的网络服务都是基于TCP协议的,所以本质上讲URL编程也是基于TCP的一种应用.

8.3 基于Socket的低层次Java网络编程

8.3.1 Socket通讯

网络上的两个程序通过一个双向的通讯连接实现数据的交换,这个双向链路的一端称为一个SocketSocket通常用来实现客户方和服务方的连接。SocketTCP/IP协议的一个十分流行的编程界面,一个Socket由一个IP地址和一个端口号唯一确定。
  在传统的UNIX环境下可以操作TCP/IP协议的接口不止Socket一个,Socket所支持的协议种类也不光TCP/IP一种,因此两者之间是没有必然联系的。在Java环境下,Socket编程主要是指基于TCP/IP协议的网络编程。

8.3.2 Socket通讯的一般过程

使用Socket进行Client/Server程序设计的一般连接过程是这样的:ServerListen(监听)某个端口是否有连接请求,Client端向Server端发出Connect(连接)请求,Server端向Client端发回Accept(接受)消息。一个连接就建立起来了。Server端和Client端都可以通过SendWrite等方法与对方通信。

对于一个功能齐全的Socket,都要包含以下基本结构,其工作过程包含以下四个基本的步骤:
  (1 创建Socket
  (2 打开连接到Socket的输入/出流;
  (3 按照一定的协议对Socket进行读/写操作;
  (4 关闭Socket.

8.3.3 创建Socket

java在包java.net中提供了两个类SocketServerSocket,分别用来表示双向连接的客户端和服务端。这是两个封装得非常好的类,使用很方便。其构造方法如下:
  Socket(InetAddress address, int port);
  Socket(InetAddress address, int port, boolean stream);
  Socket(String host, int prot);
  Socket(String host, int prot, boolean stream);
  Socket(SocketImpl impl)
  Socket(String host, int port, InetAddress localAddr, int localPort)
  Socket(InetAddress address, int port, InetAddress localAddr, int localPort)
  ServerSocket(int port);
  ServerSocket(int port, int backlog);
  ServerSocket(int port, int backlog, InetAddress bindAddr)
  其中addresshostport分别是双向连接中另一方的IP地址、主机名和端口号,stream指明socket是流socket还是数据报socketlocalPort表示本地主机的端口号,localAddrbindAddr是本地机器的地址(ServerSocket的主机地址),implsocket的父类,既可以用来创建serverSocket又可以用来创建Socketcount则表示服务端所能支持的最大连接数。例如:
  Socket client = new Socket("127.0.01.", 80);
  ServerSocket server = new ServerSocket(80);
  注意,在选择端口时,必须小心。每一个端口提供一种特定的服务,只有给出正确的端口,才能获得相应的服务。0~1023的端口号为系统所保留,例如http服务的端口号为80,telnet服务的端口号为21,ftp服务的端口号为23, 所以我们在选择端口号时,最好选择一个大于1023的数以防止发生冲突。
  在创建socket时如果发生错误,将产生IOException,在程序中必须对之作出处理。所以在创建SocketServerSocket是必须捕获或抛出例外。

8.3.8 简单的Client/Server程序设计

下面我们给出一个用Socket实现的客户和服务器交互的典型的C/S结构的演示程序,读者通过仔细阅读该程序,会对前面所讨论的各个概念有更深刻的认识。程序的意义请参考注释。

1. 客户端程序
  import java.io.*;
  import java.net.*;
  public class TalkClient {
    public static void main(String args[]) {
      try{
        Socket socket=new Socket("127.0.0.1",4700);
        
//向本机的4700端口发出客户请求
        BufferedReader sin=new BufferedReader(new InputStreamReader(System.in));
        
//由系统标准输入设备构造BufferedReader对象
        PrintWriter os=new PrintWriter(socket.getOutputStream());
        
//由Socket对象得到输出流,并构造PrintWriter对象
        BufferedReader is=new BufferedReader(new InputStreamReader(socket.getInputStream()));
        
//由Socket对象得到输入流,并构造相应的BufferedReader对象
        String readline;
        readline=sin.readLine();
//从系统标准输入读入一字符串
        while(!readline.equals("bye")){
        
//若从标准输入读入的字符串为 "bye"则停止循环
          os.println(readline);
          
//将从系统标准输入读入的字符串输出到Server
          os.flush();
          
//刷新输出流,使Server马上收到该字符串
          System.out.println("Client:"+readline);

          //在系统标准输出上打印读入的字符串

          System.out.println("Server:"+is.readLine());
          
//从Server读入一字符串,并打印到标准输出上
          readline=sin.readLine();
//从系统标准输入读入一字符串
        }
//继续循环
        os.close();
//关闭Socket输出流
        is.close();
//关闭Socket输入流
        socket.close();
//关闭Socket
      }catch(Exception e) {
        System.out.println("Error"+e);
//出错,则打印出错信息
      }
  }
}

 2. 服务器端程序
  import java.io.*;
  import java.net.*;
  import java.applet.Applet;
  public class TalkServer{
    public static void main(String args[]) {
      try{
        ServerSocket server=null;
        try{
          server=new ServerSocket(4700);
        
//创建一个ServerSocket在端口4700监听客户请求
        }catch(Exception e) {
          System.out.println("can not listen to:"+e);
        
//出错,打印出错信息
        }

        Socket socket=null;
        try{
          socket=server.accept();
          
//使用accept()阻塞等待客户请求,有客户
          //请求到来则产生一个Socket对象,并继续执行

        }catch(Exception e) {
          System.out.println("Error."+e);
          
//出错,打印出错信息
        }
        String line;
        BufferedReader is=new BufferedReader(new InputStreamReader(socket.getInputStream()));
         
//由Socket对象得到输入流,并构造相应的BufferedReader对象
        PrintWriter os=newPrintWriter(socket.getOutputStream());
         
//由Socket对象得到输出流,并构造PrintWriter对象
        BufferedReader sin=new BufferedReader(new InputStreamReader(System.in));
         
//由系统标准输入设备构造BufferedReader对象

        System.out.println("Client:"+is.readLine());
        
//在标准输出上打印从客户端读入的字符串
        line=sin.readLine();
        
//从标准输入读入一字符串
        while(!line.equals("bye")){
        
//如果该字符串为 "bye",则停止循环
          os.println(line);
          
//向客户端输出该字符串
          os.flush();
          
//刷新输出流,使Client马上收到该字符串
          System.out.println("Server:"+line);
          
//在系统标准输出上打印读入的字符串
          System.out.println("Client:"+is.readLine());
          
//从Client读入一字符串,并打印到标准输出上
          line=sin.readLine();
          
//从系统标准输入读入一字符串
        }  
//继续循环
        os.close();
//关闭Socket输出流
        is.close();
//关闭Socket输入流
        socket.close();
//关闭Socket
        server.close();
//关闭ServerSocket
      }catch(Exception e){
        System.out.println("Error:"+e);
        
//出错,打印出错信息
      }
    }
  }

8.3.9 支持多客户的client/server程序设计

前面提供的Client/Server程序只能实现Server和一个客户的对话。在实际应用中,往往是在服务器上运行一个永久的程序,它可以接收来自其他多个客户端的请求,提供相应的服务。为了实现在服务器方给多个客户提供服务的功能,需要对上面的程序进行改造,利用多线程实现多客户机制。服务器总是在指定的端口上监听是否有客户请求,一旦监听到客户请求,服务器就会启动一个专门的服务线程来响应该客户的请求,而服务器本身在启动完线程之后马上又进入监听状态,等待下一个客户的到来。

ServerSocket serverSocket=null;
    boolean listening=true;
    try{
      serverSocket=new ServerSocket(4700);
      //创建一个ServerSocket在端口4700监听客户请求
    }catch(IOException e) {  }
    while(listening){ //永远循环监听
      new ServerThread(serverSocket.accept(),clientnum).start();
      //监听到客户请求,根据得到的Socket对象和
       客户计数创建服务线程,并启动之
      clientnum++; //增加客户计数
    }
    serverSocket.close(); //关闭ServerSocket

设计ServerThread

 public class ServerThread extends Thread{
   Socket socket=null; //保存与本线程相关的Socket对象
   int clientnum; //保存本进程的客户计数
   public ServerThread(Socket socket,int num) { //构造函数
    this.socket=socket; //初始化socket变量
    clientnum=num+1; //初始化clientnum变量
   }
   public void run() { //线程主体
    try{//在这里实现数据的接受和发送}

8.3.10 据报Datagram通讯

前面在介绍TCP/IP协议的时候,我们已经提到,在TCP/IP协议的传输层除了TCP协议之外还有一个UDP协议,相比而言UDP的应用不如TCP广泛,几个标准的应用层协议HTTPFTPSMTP…使用的都是TCP协议。但是,随着计算机网络的发展,UDP协议正越来越来显示出其威力,尤其是在需要很强的实时交互性的场合,如网络游戏,视频会议等,UDP更是显示出极强的威力,下面我们就介绍一下Java环境下如何实现UDP网络传输。

8.3.11 什么是Datagram

所谓数据报(Datagram)就跟日常生活中的邮件系统一样,是不能保证可靠的寄到的,而面向链接的TCP就好比电话,双方能肯定对方接受到了信息。在本章前面,我们已经对UDPTCP进行了比较,在这里再稍作小节:
  TCP,可靠,传输大小无限制,但是需要连接建立时间,差错控制开销大。
  UDP,不可靠,差错控制开销较小,传输大小限制在64K以下,不需要建立连接。

8.3.12 Datagram通讯的表示方法:DatagramSocketDatagramPacket

java.net中提供了两个类DatagramSocketDatagramPacket用来支持数据报通信,DatagramSocket用于在程序之间建立传送数据报的通信连接, DatagramPacket则用来表示一个数据报。先来看一下DatagramSocket的构造方法:
   DatagramSocket();
   DatagramSocketint prot;
   DatagramSocket(int port, InetAddress laddr)
    其中,port指明socket所使用的端口号,如果未指明端口号,则把socket连接到本地主机上一个可用的端口。laddr指明一个可用的本地地址。给出端口号时要保证不发生端口冲突,否则会生成SocketException类例外。注意:上述的两个构造方法都声明抛弃非运行时例外SocketException,程序中必须进行处理,或者捕获、或者声明抛弃。
  
用数据报方式编写client/server程序时,无论在客户方还是服务方,首先都要建立一个DatagramSocket对象,用来接收或发送数据报,然后使用DatagramPacket类对象作为传输数据的载体。下面看一下DatagramPacket的构造方法
   DatagramPacketbyte buf[],int length);
   DatagramPacket(byte buf[], int length, InetAddress addr, int port);
   DatagramPacket(byte[] buf, int offset, int length)
   DatagramPacket(byte[] buf, int offset, int length, InetAddress address, int port)

  其中,buf中存放数据报数据,length为数据报中数据的长度,addrport旨明目的地址,offset指明了数据报的位移量。
  在接收数据前,应该采用上面的第一种方法生成一个DatagramPacket对象,给出接收数据的缓冲区及其长度。然后调用DatagramSocket 的方法receive()等待数据报的到来,receive()将一直等待,直到收到一个数据报为止。
  DatagramPacket packet=new DatagramPacket(buf, 256);
  Socket.receive (packet);
  发送数据前,也要先生成一个新的DatagramPacket对象,这时要使用上面的第二种构造方法,在给出存放发送数据的缓冲区的同时,还要给出完整的目的地址,包括IP地址和端口号。发送数据是通过DatagramSocket的方法send()实现的,send()根据数据报的目的地址来寻径,以传递数据报。
  DatagramPacket packet=new DatagramPacket(buf, length, address, port);
  Socket.send(packet)
   
在构造数据报时,要给出InetAddress类参数。类InetAddress在包java.net中定义,用来表示一个Internet地址,我们可以通过它提供的类方法getByName()从一个表示主机名的字符串获取该主机的IP地址,然后再获取相应的地址信息。

8.3.14 用数据报进行广播通讯

DatagramSocket只允许数据报发送一个目的地址,java.net包中提供了一个类MulticastSocket,允许数据报以广播方式发送到该端口的所有客户。MulticastSocket用在客户端,监听服务器广播来的数据。

1. 客户方程序:MulticastClient.java
  import java.io.*;
  import java.net.*;
  import java.util.*;
  public class MulticastClient {
    public static void main(String args[]) throws IOException
    {
     MulticastSocket socket=new MulticastSocket(4446);
     
//创建4446端口的广播套接字
     InetAddress address=InetAddress.getByName("230.0.0.1");
     
//得到230.0.0.1的地址信息
     socket.joinGroup(address);
     
//使用joinGroup()将广播套接字绑定到地址上
     DatagramPacket packet;

     for(int i=0;i<5;i++) {
       byte[] buf=new byte[256];
       
//创建缓冲区

       packet=new DatagramPacket(buf,buf.length);
       
//创建接收数据报
       socket.receive(packet);
//接收
       String received=new String(packet.getData());
       
//由接收到的数据报得到字节数组,
       
//并由此构造一个String对象
       System.out.println("Quote of theMoment:"+received);
       
//打印得到的字符串
     }
//循环5次
     socket.leaveGroup(address);
     
//把广播套接字从地址上解除绑定
     socket.close();
//关闭广播套接字
   }
 }

 2. 服务器方程序:MulticastServer.java
  public class MulticastServer{
    public static void main(String args[]) throws java.io.IOException
    {
      new MulticastServerThread().start();
      
//启动一个服务器线程
    }
  }

 3. 程序MulticastServerThread.java
  import java.io.*;
  import java.net.*;
  import java.util.*;
  public class MulticastServerThread extends QuoteServerThread
  
//从QuoteServerThread继承得到新的服务器线程类MulticastServerThread
  {
    Private long FIVE_SECOND=5000;
//定义常量,5秒钟
    public MulticastServerThread(String name) throws IOException
    {
      super("MulticastServerThread");
      
//调用父类,也就是QuoteServerThread的构造函数
    }

    public void run() //重写父类的线程主体
    {
     while(moreQuotes) {
     
//根据标志变量判断是否继续循环
      try{
        byte[] buf=new byte[256];
        
//创建缓冲区
        String dString=null;
        if(in==null) dString=new Date().toString();
        
//如果初始化的时候打开文件失败了,
        //则使用日期作为要传送的字符串

        else dString=getNextQuote();
        
//否则调用成员函数从文件中读出字符串
        buf=dString.getByte();
        
//把String转换成字节数组,以便传送send it
        InetAddress group=InetAddress.getByName("230.0.0.1");
        
//得到230.0.0.1的地址信息
        DatagramPacket packet=new DatagramPacket(buf,buf.length,group,4446);
        
//根据缓冲区,广播地址,和端口号创建DatagramPacket对象
        socket.send(packet);
//发送该Packet
        try{
          sleep((long)(Math.random()*FIVE_SECONDS));
          
//随机等待一段时间,0~5秒之间
        }catch(InterruptedException e) { }
//异常处理
      }catch(IOException e){
//异常处理
        e.printStackTrace( );
//打印错误栈

        moreQuotes=false; //置结束循环标志
      }
    }
    socket.close( );
//关闭广播套接口
   }
 }

【本讲小结】

本讲主要讲解了Java环境下的网络编程。因为TCP/IP协议是Java网络编程的基础知识,本讲开篇重点介绍了TCP/IP协议中的一些概念,TCP/IP协议本身是一个十分庞大的系统,用几个小节是不可能讲清楚的。所以我们只是联系实际,讲解了一些最基本的概念,帮助学生理解后面的相关内容。重点有一下几个概念:主机名,IP,端口,服务类型,TCPUDP
  后续的内容分为两大块,一块是以URL为主线,讲解如何通过URL类和URLConnection类访问WWW网络资源,由于使用URL十分方便直观,尽管功能不是很强,还是值得推荐的一种网络编程方法,尤其是对于初学者特别容易接受。本质上讲,URL网络编程在传输层使用的还是TCP协议。
  另一块是以Socket接口和C/S网络编程模型为主线,依次讲解了如何用Java实现基于TCPC/S结构,主要用到的类有SocketServerSocket。以及如何用Java实现基于UDPC/S结构,还讨论了一种特殊的传输方式,广播方式,这种方式是UDP所特有的,主要用到的类有DatagramSocket , DatagramPacket, MulticastSocket。这一块在Java网络编程中相对而言是最难的(尽管Java在网络编程这方面已经做的够"傻瓜"了,但是网络编程在其他环境下的却是一件极为头痛的事情,再"傻瓜"还是有一定的难度),也是功能最为强大的一部分,读者应该好好研究,领悟其中的思想。
  最后要强调的是要学好Java网络编程,Java语言,最重要的还是在于多多练习!

posted @ 2007-04-20 17:01 chenweicai 阅读(330) | 评论 (0)编辑 收藏

java中的事件机制

Java做的图形界面软件通过事件响应机制实现用户与程序的交互,原理大概是这样:

 

首先,在java控件对象(比如文本框)上添加一个监控对象,方法是one.addXXXListenner(two),这就相当于你要对某人进行监听,先要在他身上绑定一个窃听器一样,这里“one”就是你要监听的那个家伙,two就是你自己造的一个窃听器。

 

第二步就是要考虑怎样造这个窃听器了,我们首先要搞清楚它要实现的功能:它不仅能够监听one的一举一动,还能够把监听到的事件告诉系统,并让系统对这个事件做出相应的处理。Java中是通过接口实现这样的功能的。这些接口请参见jdk中java.awt.event包,里面那几个XXXListener就是(不是很多,常用的更少)。在这些接口中定义了一些方法,这些方法就是相应的事件处理方式,它们只由系统调用并且都有一个事件类的对象作为参数,这个参数正是联系发生事件主体one和操作系统的纽带。当然接口是虚的,不能产生对象的;所以想必你也猜到,上面的“窃听器”two的类型肯定是某个实现了XXXListener接口的类。

好了,现在在回顾一下这个过程:

(1)用户通过鼠标、键盘等在one对象上做动作(比如点击鼠标),

(2)这个动作被two监听到并产生事件对象e(即XXXEvent的对象),

(3)two通过事件e对象向系统打报告,

(4)系统调用two中实现的方法对事件进行处理,并向方法传送事件e。

 

 

如果你清楚了这个过程,再来细看一下这个XXXListener接口。我们必须用类来实现接口的方法(这是java基础知识吧◎),再用这个类产生two这样的对象。类实现接口必须实现接口中的每个方法,而实际上我们需要的也许只是该接口中的某一个方法(比如处理鼠标右键点击的那个),那么每个方法还得去实现一个空的,真是浪费。Sun为了让程序员省点事,在JDK中已经为我们把这些接口实现好了,这些类就是我们所谓的“适配器”(XXXAdapter),我们只需要继承(extends)这些类并重写里面我们需要的方法就OK了,所以,其实适配器就是类,只不过这些类中只有一些空的方法,别无所有。

到此,你大概明白程序员在事件处理过程中该做哪些事了吧:

(1)制造“窃听器”,即:实现事件监听接口,重写相关方法,

(2)安装窃听器,即:为监控对象添加监听器。

 

下面,我们通过程序再来仔细看看事件响应的实现方式(待续):

(以下内容参考陈刚《eclipse从入门到精通》)

1)、 匿名内部类写法

例程:

text.addMouseListener(new MouseAdapter() {

    public void mouseDoubleClick(MouseEvent e) {//鼠标双击事件的方法

         //打开一个信息框

        MessageDialog.openInformation (null,"","Hello World");

    }

});

new MouseAdapter()就是一个匿名内部类。其实就是在实现类的同时用new构造一个该类的对象,并把它作为addMouseListener的参数,它的效果和下面的一样,只不过代码比较集中。

 

(2)、命名内部类写法

public class HelloWorld {

    public static void main(String[] args) {

           ……

        Text text = new Text(shell, SWT.BORDER);

//加入鼠标事件监听器,并用下面代码所定义的内部类生成一个对象

        text.addMouseListener(new MyMouseDoubleClick());

        ……

    }

 

//定义一个名为MyMouseDoubleClick的内部类

    private static final class MyMouseDoubleClick extends MouseAdapter {

        public void mouseDoubleClick(MouseEvent e) {

            MessageDialog.openInformation(null, "", "Hello World");

        }

    }

}

 

(3)、外部类写法

这种写法和命名内部类有些相似,只不过是将MyMouseDoubleClick类从HelloWorld.java中拿出去,单独写成一个类文件。实现代码如下

//文件1: HelloWorld.java

public class HelloWorld {

    public static void main(String[] args) {

              ……

        Text text = new Text(shell, SWT.BORDER);

//加入鼠标事件监听器,并用下面代码所定义的内部类生成一个对象

        text.addMouseListener(new MyMouseDoubleClick());

        ……

    }

}

 

//文件2:MyMouseDoubleClick.java

public class MyMouseDoubleClick extends MouseAdapter {

    public void mouseDoubleClick(MouseEvent e) {

        MessageDialog.openInformation(null, "", "Hello World");

    }

}

(4)、实现监听接口的写法

将HelloWorld类实现MouseListener接口,这样类本身就成了一个监听器,使得加入监听器的代码可以更简洁。这种方式适合加入监听器的组件较多,且要求监听器的事件处理代码可以被组件共用。这种方式还有一个要注意的地方:事件方法和其他方法混合在了一起,容易引起误读,所以应该在事件方法前加入详细的注释说明。

实现MouseListener接口要写的事件方法多一些,当然没用的事件方法可以空实现。如果继承MouseListener接口的适配器MouseAdapter,则只写需要的方法就行了。另外要注意:只有接口才能有多继承的特性,所以如果HelloWorld已经是某个类的子类,就只能用实现接口的方式,而不能继承接口的适配器了。

给出示例代码如下:

public class HelloWorld extends MouseAdapter{//或implements MouseListener

    public static void main(String[] args) {

            ……

        Text text1 = new Text(shell, SWT.BORDER);

        Text text2 = new Text(shell, SWT.BORDER);

        text1.addMouseListener(this);

        text2.addMouseListener(this);

        ……

    }

 

    public void mouseDoubleClick(MouseEvent e) {

        MessageDialog.openInformation(null, "", "Hello World");

    }

}

posted @ 2007-04-13 09:37 chenweicai 阅读(349) | 评论 (0)编辑 收藏

java中的事件机制的参与者有3种角色:

1.event object:就是事件产生时具体的“事件”,用于listener的相应的方法之中,作为参数,一般存在与listerner的方法之中

2.event source:具体的接受事件的实体,比如说,你点击一个button,那么button就是event source,这样你必须使button对某些事件进行相应,你就需要注册特定的listener,比如说MouseEvent之中的MouseClicked方法,这是他就必须有了add方法

3.event listener:具体的对监听的事件类,当有其对应的event object产生的时候,它就调用相应的方法,进行处理。在windows程序设计里边这种相应使用callback机制来实现的

先看看jdk提供的event包:
public interface EventListener:所有事件侦听器接口必须扩展的标记接口。
public class EventObject extends Object implements Serializable

所有事件状态对象都将从其派生的根类。 所有 Event 在构造时都引用了对象 "source",在逻辑上认为该对象是最初发生有关 Event 的对象。

        在Java2处理事件时,没有采用dispatchEvent()-postEvent()-handleEvent()方式,采用了监听器类,每个事件类都有相关联的监听器接口。事件从事件源到监听者的传递是通过对目标监听者对象的Java方法调用进行的。

  对每个明确的事件的发生,都相应地定义一个明确的Java方法。这些方法都集中定义在事件监听者(EventListener)接口中,这个接口要继承 java.util.EventListener。 实现了事件监听者接口中一些或全部方法的类就是事件监听者。

  伴随着事件的发生,相应的状态通常都封装在事件状态对象中,该对象必须继承自java.util.EventObject。事件状态对象作为单参传递给应响应该事件的监听者方法中。发出某种特定事件的事件源的标识是:遵从规定的设计格式为事件监听者定义注册方法,并接受对指定事件监听者接口实例的引用。

开始之前首先问个问题:您熟悉java.util.EventObject 和java.util.EventListener两个类以及他们已有的子类吗?

如果你已经能够熟练使用jdk为我们提供的事件监听器,并且很熟悉MouseEvent, KeyEvent, WindowEvent等等这些jdk为我们准备好的事件,那么想必你对java的事件机制已经有所理解。但是也许你还是觉得虽然用起来没什么问题,但是原理还是有些糊涂,那么下面我们再进一步自己实现这些事件和监听器,我们把这个取名为自定义事件。

其实自定义事件在java中很有用处,我们有的时候想让自己的程序产生一个事件,但有不希望(或者不可能)用鼠标,键盘之类的输入设备进行操作,比如你写一个应用程序,在这个程序中一旦收到邮件就对邮件进行相关处理,对于“收到邮件”这个事件,jdk中就没有定义。对于这样的事件,以及对于这样的事件的监听器,我们只能自己动手完成了。

那么下面就以实例开始我们这个“创新”的过程:首先,我们要明确jdk中需要的资源:类EventObject作为父类用来生成我们自己的事件类,接口EventListener用来实现我们自己的监听器;剩下的事情就是如何注册这些事件以及测试他们了。

(1)       通过DemoEvent.java文件创建DemoEvent类,这个类继承EventObject。这个类的构造函数的参数传递了产生这个事件的事件源(比如各种控件),方法getSource用来获得这个事件源的引用。

DemoEvent.java

package demo.listener;

 

import java.util.EventObject;

 

public class DemoEvent extends EventObject

{

        Object obj;

        public DemoEvent(Object source)

        {

               super(source);

               obj = source;

        }

        public Object getSource()

        {

               return obj;

        }

        public void say()

        {

               System.out.println("This is say method...");

        }

}

 

(2)       定义新的事件监听接口,该接口继承自EventListener;该接口包含对DemeEvent事件的处理程序:

DemoListener.java

package demo.listener;

 

import java.util.EventListener;

 

public interface DemoListener extends EventListener

{

       public void demoEvent(DemoEvent dm);

}

 

通过上面的接口我们再定义事件监听类,这些类具体实现了监听功能和事件处理功能。回想一下上文中那四种实现方式,我们这里不正是使用了其中的第三种——外部类写法的方式吗?

Listener1.java

package demo.listener;

 

public class Listener1 implements DemoListener

{

       public void demoEvent(DemoEvent de)

       {

              System.out.println("Inside listener1...");

       }

}



Listener2.java

package demo.listener;

 

public class Listener2 implements DemoListener

{

       public void demoEvent(DemoEvent de)

       {

              System.out.println("Inside listener2...");

       }

}


Listener3.java

package demo.listener;

 

public class Listener3 implements DemoListener

{

       public void demoEvent(DemoEvent de)

       {

              System.out.println("Inside listener3...");

       }

}

 

(3)       通过DemeSource..ava文件创造一个事件源类,它用一个java.utile.Vector对象来存储所有的事件监听器对象,存储方式是通过addListener(..)这样的方法。notifyDemeEvent(..)是触发事件的方法,用来通知系统:事件发生了,你调用相应的处理函数(回调函数)吧。

DemoSource.java

 

package demo.listener;

import java.util.*;

 

public class DemoSource

{

       private Vector repository = new Vector();

       DemoListener dl;

       public DemoSource()

       {

 

       }

       public void addDemoListener(DemoListener dl)

       {

              repository.addElement(dl);

       }

       public void notifyDemoEvent()

       {

              Enumeration enum = repository.elements();

              while(enum.hasMoreElements())

              {

                    dl = (DemoListener)enum.nextElement();

                    dl.demoEvent(new DemoEvent(this));

              }

       }

}

 

 

             

(4)       好了,最后写一个测试程序测试一下我们自定义的事件吧,这段程序应该不难理解吧:)

TestDemo.java

 

package demo.listener;

 

public class TestDemo

{

       DemoSource ds;

 

       public TestDemo()

       {

              try{

                    ds = new DemoSource();

                    Listener1 l1 = new Listener1();

                    Listener2 l2 = new Listener2();

                    Listener3 l3 = new Listener3();

 

                    ds.addDemoListener(l1);

                    ds.addDemoListener(l2);

                    ds.addDemoListener(l3);

                    ds.addDemoListener(new DemoListener(){
                               public void demoEvent(DemoEvent event){
                                         System.out.println("Method come from 匿名类...");
                               }
                       });

                    ds.notifyDemoEvent();

 

              }catch(Exception ex)

              {ex.printStackTrace();}

       }

 

       public static void main(String args[])

       {

              new TestDemo();

       }

}


posted @ 2007-04-13 09:22 chenweicai 阅读(10328) | 评论 (14)编辑 收藏

1.
import java.net.InetAddress;
import java.net.UnknownHostException;

public class NetWork {

 //static fields and static block
 
    //InetAddress of the local host
 private static InetAddress local = null;
 
 //integer version of the local host
 private static int packedLocal = 0;
 
 static {
  try{
   local = InetAddress.getLocalHost();
  }catch(UnknownHostException e){
   e.printStackTrace();
  }

  System.out.println("localhost : " + local);
  packedLocal = translate(local);
 }
 
 public static InetAddress getLocalHost(){
  return local;
 }
 
 public static String getLocalHostName(){
  return local.getHostName();
 }
 
 public static String getLocalMachineName(){
  String hostname = local.getHostName();
  int machEndIndex = hostname.indexOf('.');
  if(machEndIndex == -1)
   return hostname;
  else
   return hostname.substring(0, machEndIndex);
 }
 
 public static boolean isLocal(InetAddress address){
  return local.equals(address);
 }
 
 public static boolean isLocal(int packedAddress){
  return packedAddress == packedLocal;
 }
 
 /**
  * return an integer representation of the specified
  * InetAddress. This is used for efficiency
  */
 public static int translate(InetAddress address){
  byte[] ad = address.getAddress();//原始IP地址
  if(ad.length != 4)
   throw new IllegalArgumentException("only accept 32-byte IP address");
  int packedAddress = ((ad[0] << 24) & 0xFF000000) |
       ((ad[1] << 16) & 0xFF0000) |
       ((ad[2] <<  8) & 0xFF00) |
       (ad[3] & 0xFF);
  return packedAddress;
 }
 
 /**
  * return an InetAddress representation of the specified integer.
  * This is used for performance improvements in serivalization.
  */
 public static InetAddress translate (int packed)
  throws UnknownHostException{
  int[] ad = new int[4];
     ad[0] = (packed  >>> 24);
     ad[1] = ((packed >>> 16) & 0x000000FF);
     ad[2] = ((packed >>>  8) & 0x000000FF);
     ad[3] = (packed          & 0x000000FF);
     StringBuffer buf = new StringBuffer();
     buf.append(ad[0]);
     buf.append(".");
     buf.append(ad[1]);
     buf.append(".");
     buf.append(ad[2]);
     buf.append(".");
     buf.append(ad[3]);
    
     return InetAddress.getByName(buf.toString());
 }
 
 public static String translateToString(int packed){
  int[] ad = new int[4];
  ad[0] = (packed >>> 24);
  ad[1] = ((packed >>> 16) & 0x000000FF);
  ad[2] = ((packed >>> 8) & 0x000000FF);
  ad[3] = (packed & 0x000000FF);
  
  StringBuffer buf = new StringBuffer();
  buf.append(ad[0]);
  buf.append(".");
  buf.append(ad[1]);
  buf.append(".");
  buf.append(ad[2]);
  buf.append(".");
  buf.append(ad[3]);
  
  return buf.toString();
 }
 
 public static void main(String[] args){
  InetAddress localhost = getLocalHost();
  System.out.println("Local Host Name is : " + getLocalHostName());
  System.out.println("Local Host Machine Name is : " + getLocalMachineName());
  if(isLocal(localhost))
   System.out.println(localhost.getHostName() + " is Local Host.");
  int intforaddress = translate(localhost);
  System.out.println("localhost integer representation is " + intforaddress);
  System.out.println("localhost string representation is " + translateToString(intforaddress));
  System.out.println(translateToString(intforaddress).toString());
  
 }
}



2. public interfece Externalizable extendx Serializable
Externalizable 实例类的惟一特性是可以被写入序列化流中,该类负责保存和恢复实例内容。 若某个要完全控制某一对象及其超类型的流格式和内容,则它要实现 Externalizable 接口的 writeExternal 和 readExternal 方法。这些方法必须显式与超类型进行协调以保存其状态。这些方法将代替自定义的 writeObject 和 readObject 方法实现。
Serialization 对象将使用 Serializable 和 Externalizable 接口。对象持久性机制也可以使用它们。要存储的每个对象都需要检测是否支持 Externalizable 接口。如果对象支持 Externalizable,则调用 writeExternal 方法。如果对象不支持 Externalizable 但实现了 Serializable,则使用 ObjectOutputStream 保存该对象。
在重构 Externalizable 对象时,先使用无参数的公共构造方法创建一个实例,然后调用 readExternal 方法。通过从 ObjectInputStream 中读取 Serializable 对象可以恢复这些对象。
Externalizable 实例可以通过 Serializable 接口中记录的 writeReplace 和 readResolve 方法来指派一个替代对象。

writeExternal

void writeExternal(ObjectOutput out)
throws IOException
该对象可实现 writeExternal 方法来保存其内容,它可以通过调用 DataOutput 的方法来保存其基本值,或调用 ObjectOutput 的 writeObject 方法来保存对象、字符串和数组。

 

参数:
out - 要写入对象的流
抛出:
IOException - 包含可能发生的所有 I/O 异常

readExternal

void readExternal(ObjectInput in)
throws IOException,
ClassNotFoundException
对象实现 readExternal 方法来恢复其内容,它通过调用 DataInput 的方法来恢复其基础类型,调用 readObject 来恢复对象、字符串和数组。readExternal 方法必须按照与 writeExternal 方法写入值时使用的相同顺序和类型来读取这些值。

 

参数:
in - 为了恢复对象而从中读取数据的流
抛出:
IOException - 如果发生 I/O 错误
ClassNotFoundException - 如果无法找到需要恢复的某个对象的类。

import java.io.Externalizable;
import java.net.InetAddress;

public interface EndPoint extends Externalizable {

 /**
  *
  * @return Returns the IP address contained in this endpoint.
  */
 public InetAddress getAddress();
 
 /**
  * returns the IP address contained in this endpoint.
  * enclosed in an integer value.
  */
 public int getIntAddress();
 
 /**
  * Returns the port number contained in this endpoint.
  */
 public int getPort();
 
 
 /**
  * Return true if this endpoint is the local endpoint.
  */
 public boolean isLocal();
 
 /**
  * Returns true if this endpoint is a multicast endpoint.
  */
 public boolean isMulticastEndPoint();
}



import internet.network.NetWork;

import java.io.Externalizable;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectInputStream;
import java.io.ObjectOutput;
import java.io.ObjectOutputStream;
import java.net.InetAddress;
import java.net.UnknownHostException;

public class EndPointImpl implements EndPoint, Comparable, Externalizable{

 //Constants
 
 /* Size of this object (in bytes) when marshalled. */
 public static final int SIZE = 6;
 
 //Fields
 protected InetAddress address;
 
 private int packedAddress;
 
 private int port;
 
 protected boolean local;
 
 //Constructors
 
 /**
  * Default constructor for externalization
  */
 public EndPointImpl(){
  
 }
 
 public EndPointImpl(InetAddress address, int port){
  this.address = address;
  this.port = port;
  packedAddress = NetWork.translate(address);
  local = NetWork.isLocal(packedAddress);
 }
 
 public EndPointImpl(int packedAddress, int port){
  this.packedAddress = packedAddress;
  this.port = port;
  this.local = NetWork.isLocal(packedAddress);
  //The InetAddress version of the address is computed
  //only when needed, to avoid uselesss DNS lookups.
  address = null;
 }
 
 public InetAddress getAddress() {
  if(address == null)
   try{
    address = NetWork.translate(packedAddress);
   }catch(UnknownHostException e){
    System.out.println(e.getMessage());
   }
   return address;
 }

 public int getIntAddress() {
  return this.packedAddress;
 }

 public int getPort() {
  return this.port;
 }

 public boolean isLocal() {
  return this.local;
 }

 public boolean isMulticastEndPoint() {
  return address.isMulticastAddress();
 }
 
 
 ////////////////////////////////////////////////////////////
 // Methods from Externalizable
 ////////////////////////////////////////////////////////////

 /**
  * Restores the content of this object form the marshaled data contained
  * in the specified input stream.
  *
  * @param in the stream to be read.
  */
 public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
  
  packedAddress = in.readInt();
  port = (int) in.readUnsignedShort();
  local = NetWork.isLocal(packedAddress);
  address = null;
 }

 /**
  * Marshals the content of this object to the specified output stream.
  *
  * @param out the stream to be written
  */
 public void writeExternal(ObjectOutput out) throws IOException {
  
  out.writeInt(packedAddress);
  out.writeShort(port);
 }

 
 ////////////////////////////////////////////////////////////
 // Methods from Object and Comparable
 ////////////////////////////////////////////////////////////
 /**
  *  Compares this object with the specified object for order.
  */
 public int compareTo(Object obj) {
  if(this == obj){
   return 0;
  }
  else{
   EndPointImpl endpoint = (EndPointImpl) obj;
   if(packedAddress < endpoint.packedAddress){
    return -1;
   }
   else if(packedAddress == endpoint.packedAddress){
    if(port < endpoint.port)
     return -1;
    else if(port == endpoint.port)
     return 0;
    else
     return 1;
   }
   else{
    return 1;
   }
  }
 }
 
 public int hashcode(){
  return packedAddress ^ port;
 }

 /**
  * Compares two EndPointImpl objects for content equality.
  */
 public boolean equals (Object obj){
  if(!(obj instanceof EndPointImpl))
   return false;
  EndPointImpl endpoint = (EndPointImpl) obj;
  return (endpoint.packedAddress == packedAddress && endpoint.port == port);
 }
 
 /**
  * Returns a string representation of this object.
  */
 public String toString(){
  StringBuffer buf = new StringBuffer();
  buf.append("[");
  buf.append(NetWork.translateToString(packedAddress));
  buf.append(":");
  buf.append(port);
  buf.append("]");
  
  return buf.toString();
 }
 
 
 public static void main(String[] args) throws FileNotFoundException, IOException{
  InetAddress localhost = NetWork.getLocalHost();
  EndPointImpl endpoint = new EndPointImpl(localhost, 100);
  System.out.println("This EndPoint is : " + endpoint.toString());
  EndPointImpl endpoint2 = new EndPointImpl();
  ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("endpoint.txt"));
  try {
   endpoint.writeExternal(oos);
  } catch (IOException e) {
   e.printStackTrace();
  }
  oos.close();
  
  ObjectInputStream ios = new ObjectInputStream(new FileInputStream("endpoint.txt"));
  try {
   endpoint2.readExternal(ios);
  } catch (IOException e) {
   e.printStackTrace();
  } catch (ClassNotFoundException e) {
   e.printStackTrace();
  }
  ios.close();
  System.out.println("This EndPoint is : " + endpoint2.toString());
 }
}

posted @ 2007-04-11 21:41 chenweicai 阅读(161) | 评论 (0)编辑 收藏

类Deflater 和 类Inflater:此类使用流行的 ZLIB 压缩程序库为通用压缩提供支持。ZLIB 压缩程序库最初是作为 PNG 图形标准的一部分开发的,不受专利的保护。

  //encoding a string into bytes
  String inputString = "chenweicai";
  byte[] input = inputString.getBytes("UTF-8");
  System.out.println("before compressed, the bytes length is :" + input.length);
  
  //compress the bytes
  byte[] output = new byte[100];
  Deflater compresser = new Deflater();
  compresser.setInput(input);
  compresser.finish();
  int compressedDataLength = compresser.deflate(output);
  System.out.println("compressed bytes length is :" + compressedDataLength);
  
  //decompress the bytes
  Inflater decompresser = new Inflater();
  decompresser.setInput(output, 0, compressedDataLength);
  byte[] result = new byte[100];
  int resultLength = decompresser.inflate(result);
  decompresser.end();
  System.out.println("after decompressed, the bytes length is :" + resultLength);
  
  //decode the bytes into a string
  String outputString = new String(result, 0, resultLength, "UTF-8");
  System.out.println("after compress and decompress the string is :" + outputString);


2.


import java.io.Serializable;

public class Employee implements Serializable {

 private String name;
 
 private double salary;

 public Employee(String name, double salary) {
  super();
  // TODO Auto-generated constructor stub
  this.name = name;
  this.salary = salary;
 }
 
 public void raiseSalary(double byPercent){
  double temp = salary * byPercent / 100;
  salary += temp;
 }

 public String toString() {
  // TODO Auto-generated method stub
  return getClass().getName() +
   "[ Name = " + name + ", salary = " + salary +"]";
 }
 
}





public class Test2 {

 protected byte[] deflate(Object object) throws IOException{
  
  ByteArrayOutputStream baos = new ByteArrayOutputStream();
  Deflater def = new Deflater (Deflater.BEST_COMPRESSION);
  DeflaterOutputStream dos = new DeflaterOutputStream(baos, def);
  
  ObjectOutputStream out = new ObjectOutputStream(dos);
  out.writeObject(object);
  out.close();
  dos.finish();
  dos.close();
  
  return baos.toByteArray();
 }
 
 protected Object inflate(byte[] compressedContent) throws IOException{
  if(compressedContent == null)
   return null;
  
  try{
   ObjectInputStream in = new ObjectInputStream(
     new InflaterInputStream(new ByteArrayInputStream(compressedContent)));
   Object object = in.readObject();
   in.close();
   return object;
  }catch(Exception e){
   throw new IOException(e.toString());
  }
 }
 
 public static void main(String[] args) throws IOException{
  Employee employee = new Employee("LiLei", 1000);
  Test2 test2 = new Test2();
  byte[] bytes = new byte[1000];
  
  bytes = test2.deflate(employee);
  System.out.println(bytes);
  
  System.out.println(test2.inflate(bytes));
  
 }
}

posted @ 2007-04-10 21:29 chenweicai 阅读(590) | 评论 (0)编辑 收藏

  JDK为程序员提供了大量的类库,而为了保持类库的可重用性,可扩展性和灵活性,其中使用到了大量的设计模式,本文将介绍JDK的I/O包中使用到的Decorator模式,并运用此模式,实现一个新的输出流类。

  Decorator模式简介

  Decorator模式又名包装器(Wrapper),它的主要用途在于给一个对象动态的添加一些额外的职责。与生成子类相比,它更具有灵活性。
有时候,我们需要为一个对象而不是整个类添加一些新的功能,比如,给一个文本区添加一个滚动条的功能。我们可以使用继承机制来实现这一功能,但是这种方法不够灵活,我们无法控制文本区加滚动条的方式和时机。而且当文本区需要添加更多的功能时,比如边框等,需要创建新的类,而当需要组合使用这些功能时无疑将会引起类的爆炸。

  我们可以使用一种更为灵活的方法,就是把文本区嵌入到滚动条中。而这个滚动条的类就相当于对文本区的一个装饰。这个装饰(滚动条)必须与被装饰的组件(文本区)继承自同一个接口,这样,用户就不必关心装饰的实现,因为这对他们来说是透明的。装饰会将用户的请求转发给相应的组件(即调用相关的方法),并可能在转发的前后做一些额外的动作(如添加滚动条)。通过这种方法,我们可以根据组合对文本区嵌套不同的装饰,从而添加任意多的功能。这种动态的对对象添加功能的方法不会引起类的爆炸,也具有了更多的灵活性。

  以上的方法就是Decorator模式,它通过给对象添加装饰来动态的添加新的功能。如下是Decorator模式的UML图:



  Component为组件和装饰的公共父类,它定义了子类必须实现的方法。

  ConcreteComponent是一个具体的组件类,可以通过给它添加装饰来增加新的功能。

  Decorator是所有装饰的公共父类,它定义了所有装饰必须实现的方法,同时,它还保存了一个对于Component的引用,以便将用户的请求转发给Component,并可能在转发请求前后执行一些附加的动作。

  ConcreteDecoratorA和ConcreteDecoratorB是具体的装饰,可以使用它们来装饰具体的Component。

  Java IO包中的Decorator模式

  JDK提供的java.io包中使用了Decorator模式来实现对各种输入输出流的封装。以下将以java.io.OutputStream及其子类为例,讨论一下Decorator模式在IO中的使用。

  首先来看一段用来创建IO流的代码:

以下是代码片段:
try {
 OutputStream out = new DataOutputStream(new FileOutputStream("test.txt"));
} catch (FileNotFoundException e) {
 e.printStackTrace();
}

  这段代码对于使用过JAVA输入输出流的人来说再熟悉不过了,我们使用DataOutputStream封装了一个FileOutputStream。这是一个典型的Decorator模式的使用,FileOutputStream相当于Component,DataOutputStream就是一个Decorator。将代码改成如下,将会更容易理解:

以下是代码片段:
try {
 OutputStream out = new FileOutputStream("test.txt");
 out = new DataOutputStream(out);
} catch(FileNotFoundException e) {
 e.printStatckTrace();
}

  由于FileOutputStream和DataOutputStream有公共的父类OutputStream,因此对对象的装饰对于用户来说几乎是透明的。下面就来看看OutputStream及其子类是如何构成Decorator模式的:



  OutputStream是一个抽象类,它是所有输出流的公共父类,其源代码如下:

以下是代码片段:
public abstract class OutputStream implements Closeable, Flushable {
 public abstract void write(int b) throws IOException;
 ...
}

  它定义了write(int b)的抽象方法。这相当于Decorator模式中的Component类。

  ByteArrayOutputStream,FileOutputStream 和 PipedOutputStream 三个类都直接从OutputStream继承,以ByteArrayOutputStream为例:

以下是代码片段:
public class ByteArrayOutputStream extends OutputStream {
 protected byte buf[];
 protected int count;
 public ByteArrayOutputStream() {
  this(32);
 }
 public ByteArrayOutputStream(int size) {
  if (size 〈 0) {
   throw new IllegalArgumentException("Negative initial size: " + size);
  }
  buf = new byte[size];
 }
 public synchronized void write(int b) {
  int newcount = count + 1;
  if (newcount 〉 buf.length) {
   byte newbuf[] = new byte[Math.max(buf.length 〈〈 1, newcount)];
   System.arraycopy(buf, 0, newbuf, 0, count);
   buf = newbuf;
  }
  buf[count] = (byte)b;
  count = newcount;
 }
 ...
}

  它实现了OutputStream中的write(int b)方法,因此我们可以用来创建输出流的对象,并完成特定格式的输出。它相当于Decorator模式中的ConcreteComponent类。

  接着来看一下FilterOutputStream,代码如下:

以下是代码片段:
public class FilterOutputStream extends OutputStream {
 protected OutputStream out;
 public FilterOutputStream(OutputStream out) {
  this.out = out;
 }
 public void write(int b) throws IOException {
  out.write(b);
 }
 ...
}

  同样,它也是从OutputStream继承。但是,它的构造函数很特别,需要传递一个OutputStream的引用给它,并且它将保存对此对象的引用。而如果没有具体的OutputStream对象存在,我们将无法创建FilterOutputStream。由于out既可以是指向FilterOutputStream类型的引用,也可以是指向ByteArrayOutputStream等具体输出流类的引用,因此使用多层嵌套的方式,我们可以为ByteArrayOutputStream添加多种装饰。这个FilterOutputStream类相当于Decorator模式中的Decorator类,它的write(int b)方法只是简单的调用了传入的流的write(int b)方法,而没有做更多的处理,因此它本质上没有对流进行装饰,所以继承它的子类必须覆盖此方法,以达到装饰的目的。

  BufferedOutputStream 和 DataOutputStream是FilterOutputStream的两个子类,它们相当于Decorator模式中的ConcreteDecorator,并对传入的输出流做了不同的装饰。以BufferedOutputStream类为例:

以下是代码片段:
public class BufferedOutputStream extends FilterOutputStream {
 ...
 private void flushBuffer() throws IOException {
  if (count 〉 0) {
   out.write(buf, 0, count);
   count = 0;
  }
 }
 public synchronized void write(int b) throws IOException {
  if (count 〉= buf.length) {
   flushBuffer();
  }
  buf[count++] = (byte)b;
 }
 ...
}

  这个类提供了一个缓存机制,等到缓存的容量达到一定的字节数时才写入输出流。首先它继承了FilterOutputStream,并且覆盖了父类的write(int b)方法,在调用输出流写出数据前都会检查缓存是否已满,如果未满,则不写。这样就实现了对输出流对象动态的添加新功能的目的。

  下面,将使用Decorator模式,为IO写一个新的输出流。  JDK为程序员提供了大量的类库,而为了保持类库的可重用性,可扩展性和灵活性,其中使用到了大量的设计模式,本文将介绍JDK的I/O包中使用到的Decorator模式,并运用此模式,实现一个新的输出流类。

  Decorator模式简介

  Decorator模式又名包装器(Wrapper),它的主要用途在于给一个对象动态的添加一些额外的职责。与生成子类相比,它更具有灵活性。
有时候,我们需要为一个对象而不是整个类添加一些新的功能,比如,给一个文本区添加一个滚动条的功能。我们可以使用继承机制来实现这一功能,但是这种方法不够灵活,我们无法控制文本区加滚动条的方式和时机。而且当文本区需要添加更多的功能时,比如边框等,需要创建新的类,而当需要组合使用这些功能时无疑将会引起类的爆炸。

  我们可以使用一种更为灵活的方法,就是把文本区嵌入到滚动条中。而这个滚动条的类就相当于对文本区的一个装饰。这个装饰(滚动条)必须与被装饰的组件(文本区)继承自同一个接口,这样,用户就不必关心装饰的实现,因为这对他们来说是透明的。装饰会将用户的请求转发给相应的组件(即调用相关的方法),并可能在转发的前后做一些额外的动作(如添加滚动条)。通过这种方法,我们可以根据组合对文本区嵌套不同的装饰,从而添加任意多的功能。这种动态的对对象添加功能的方法不会引起类的爆炸,也具有了更多的灵活性。

  以上的方法就是Decorator模式,它通过给对象添加装饰来动态的添加新的功能。如下是Decorator模式的UML图:



  Component为组件和装饰的公共父类,它定义了子类必须实现的方法。

  ConcreteComponent是一个具体的组件类,可以通过给它添加装饰来增加新的功能。

  Decorator是所有装饰的公共父类,它定义了所有装饰必须实现的方法,同时,它还保存了一个对于Component的引用,以便将用户的请求转发给Component,并可能在转发请求前后执行一些附加的动作。

  ConcreteDecoratorA和ConcreteDecoratorB是具体的装饰,可以使用它们来装饰具体的Component。

  Java IO包中的Decorator模式

  JDK提供的java.io包中使用了Decorator模式来实现对各种输入输出流的封装。以下将以java.io.OutputStream及其子类为例,讨论一下Decorator模式在IO中的使用。

  首先来看一段用来创建IO流的代码:

以下是代码片段:
try {
 OutputStream out = new DataOutputStream(new FileOutputStream("test.txt"));
} catch (FileNotFoundException e) {
 e.printStackTrace();
}

  这段代码对于使用过JAVA输入输出流的人来说再熟悉不过了,我们使用DataOutputStream封装了一个FileOutputStream。这是一个典型的Decorator模式的使用,FileOutputStream相当于Component,DataOutputStream就是一个Decorator。将代码改成如下,将会更容易理解:

以下是代码片段:
try {
 OutputStream out = new FileOutputStream("test.txt");
 out = new DataOutputStream(out);
} catch(FileNotFoundException e) {
 e.printStatckTrace();
}

  由于FileOutputStream和DataOutputStream有公共的父类OutputStream,因此对对象的装饰对于用户来说几乎是透明的。下面就来看看OutputStream及其子类是如何构成Decorator模式的:



  OutputStream是一个抽象类,它是所有输出流的公共父类,其源代码如下:

以下是代码片段:
public abstract class OutputStream implements Closeable, Flushable {
 public abstract void write(int b) throws IOException;
 ...
}

  它定义了write(int b)的抽象方法。这相当于Decorator模式中的Component类。

  ByteArrayOutputStream,FileOutputStream 和 PipedOutputStream 三个类都直接从OutputStream继承,以ByteArrayOutputStream为例:

以下是代码片段:
public class ByteArrayOutputStream extends OutputStream {
 protected byte buf[];
 protected int count;
 public ByteArrayOutputStream() {
  this(32);
 }
 public ByteArrayOutputStream(int size) {
  if (size 〈 0) {
   throw new IllegalArgumentException("Negative initial size: " + size);
  }
  buf = new byte[size];
 }
 public synchronized void write(int b) {
  int newcount = count + 1;
  if (newcount 〉 buf.length) {
   byte newbuf[] = new byte[Math.max(buf.length 〈〈 1, newcount)];
   System.arraycopy(buf, 0, newbuf, 0, count);
   buf = newbuf;
  }
  buf[count] = (byte)b;
  count = newcount;
 }
 ...
}

  它实现了OutputStream中的write(int b)方法,因此我们可以用来创建输出流的对象,并完成特定格式的输出。它相当于Decorator模式中的ConcreteComponent类。

  接着来看一下FilterOutputStream,代码如下:

以下是代码片段:
public class FilterOutputStream extends OutputStream {
 protected OutputStream out;
 public FilterOutputStream(OutputStream out) {
  this.out = out;
 }
 public void write(int b) throws IOException {
  out.write(b);
 }
 ...
}

  同样,它也是从OutputStream继承。但是,它的构造函数很特别,需要传递一个OutputStream的引用给它,并且它将保存对此对象的引用。而如果没有具体的OutputStream对象存在,我们将无法创建FilterOutputStream。由于out既可以是指向FilterOutputStream类型的引用,也可以是指向ByteArrayOutputStream等具体输出流类的引用,因此使用多层嵌套的方式,我们可以为ByteArrayOutputStream添加多种装饰。这个FilterOutputStream类相当于Decorator模式中的Decorator类,它的write(int b)方法只是简单的调用了传入的流的write(int b)方法,而没有做更多的处理,因此它本质上没有对流进行装饰,所以继承它的子类必须覆盖此方法,以达到装饰的目的。

  BufferedOutputStream 和 DataOutputStream是FilterOutputStream的两个子类,它们相当于Decorator模式中的ConcreteDecorator,并对传入的输出流做了不同的装饰。以BufferedOutputStream类为例:

以下是代码片段:
public class BufferedOutputStream extends FilterOutputStream {
 ...
 private void flushBuffer() throws IOException {
  if (count 〉 0) {
   out.write(buf, 0, count);
   count = 0;
  }
 }
 public synchronized void write(int b) throws IOException {
  if (count 〉= buf.length) {
   flushBuffer();
  }
  buf[count++] = (byte)b;
 }
 ...
}

  这个类提供了一个缓存机制,等到缓存的容量达到一定的字节数时才写入输出流。首先它继承了FilterOutputStream,并且覆盖了父类的write(int b)方法,在调用输出流写出数据前都会检查缓存是否已满,如果未满,则不写。这样就实现了对输出流对象动态的添加新功能的目的。

  下面,将使用Decorator模式,为IO写一个新的输出流。


自己写一个新的输出流

  了解了OutputStream及其子类的结构原理后,我们可以写一个新的输出流,来添加新的功能。这部分中将给出一个新的输出流的例子,它将过滤待输出语句中的空格符号。比如需要输出"java io OutputStream",则过滤后的输出为"javaioOutputStream"。以下为SkipSpaceOutputStream类的代码:

以下是代码片段:
import java.io.FilterOutputStream;
import java.io.IOException;
import java.io.OutputStream;
/**
* A new output stream, which will check the space character
* and won’t write it to the output stream.
* @author Magic
*
*/
public class SkipSpaceOutputStream extends FilterOutputStream {
 public SkipSpaceOutputStream(OutputStream out) {
  super(out);
 }
 /**
 * Rewrite the method in the parent class, and
 * skip the space character.
 */
 public void write(int b) throws IOException{
  if(b!=’ ’){
   super.write(b);
  }
 }
}

  它从FilterOutputStream继承,并且重写了它的write(int b)方法。在write(int b)方法中首先对输入字符进行了检查,如果不是空格,则输出。

  以下是一个测试程序:

以下是代码片段:
import java.io.BufferedInputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
/**
* Test the SkipSpaceOutputStream.
* @author Magic
*
*/
public class Test {
 public static void main(String[] args){
  byte[] buffer = new byte[1024];

  /**
  * Create input stream from the standard input.
  */
  InputStream in = new BufferedInputStream(new DataInputStream(System.in));

  /**
  * write to the standard output.
  */
  OutputStream out = new SkipSpaceOutputStream(new DataOutputStream(System.out));

  try {
   System.out.println("Please input your words: ");
   int n = in.read(buffer,0,buffer.length);
   for(int i=0;i〈n;i++){
    out.write(buffer[i]);
   }
  } catch (IOException e) {
   e.printStackTrace();
  }
 }
}

  执行以上测试程序,将要求用户在console窗口中输入信息,程序将过滤掉信息中的空格,并将最后的结果输出到console窗口。比如:

以下是引用片段:
Please input your words:
a b c d e f
abcdef

  总 结

  在java.io包中,不仅OutputStream用到了Decorator设计模式,InputStream,Reader,Writer等都用到了此模式。而作为一个灵活的,可扩展的类库,JDK中使用了大量的设计模式,比如在Swing包中的MVC模式,RMI中的Proxy模式等等。对于JDK中模式的研究不仅能加深对于模式的理解,而且还有利于更透彻的了解类库的结构和组成。

posted @ 2007-04-10 09:44 chenweicai 阅读(114) | 评论 (0)编辑 收藏

仅列出标题
共6页: 上一页 1 2 3 4 5 6 下一页