你好,欢迎来到电脑编程技巧与维护杂志社! 杂志社简介广告服务读者反馈编程社区  
合订本订阅
 
 
您的位置:杂志经典 / 编程语言
WEB聊天室的四种实现方案及其比较
 

一、引言

WEB聊天室(Chatroom)ICP提供的常用服务之一,它给网络用户提供了在线实时交流的机会。使用WEB聊天室不需要安装象IRCmIRCMSChat等专门的软件,而只需要浏览器即可。也就是说,聊天过程实际上是通过HTTP协议进行的。WEB聊天室的实现方案有多种,目前常见的包括:基于CGI、基于Java、基于ActiveX和基于ASP的实现方案等。各种方案都有其独特之处,需要根据场合选用。下面我们就来讨论这四种方案的具体实现过程并对它们进行比较。

 

二、基于CGI的聊天室

CGI(Common Gateway Interface,通用网关接口)往往被认为是一种效率较低的WEB动态页面实现方式。这是因为对于客户端(即浏览器端)的每个CGI请求,服务器端都会产生一个新的进程。其实对于UNIX平台的服务器来说,采用CGI方式并不会有效率问题,因为UNIX是基于进程的操作系统,当参加聊天的用户数很多而导致产生大量的CGI进程时,UNIX有足够能力来进行进程的管理调度,这时执行性能主要取决于机器的硬件配置。而对于NT平台的服务器来说,就不一样了。因为NT是针对线程优化的操作系统,即它管理的最小单位是线程而不是进程,大量的CGI进程会增加很多无谓的开销,从而影响系统的执行效率。

编写CGI程序可以采用多种语言和工具,如C/C++PerlPHP3等,在Windows平台上还可以用VBDelphi等编写WinCGI程序(此时CGI程序与Web服务器之间不是通过stdin/stdout进行数据交互,而是通过临时文件)。为了统一调试的方便,本文中所有聊天室程序都以NT ServerIIS 4.0服务器作为平台。下面我们以Perl语言来编写一个简单的基于CGI方式的WEB聊天室程序,如果这个程序要在UnixApache上运行,需要作少量的改动。

一般来说,基于CGI方式的聊天室程序的设计思想是这样的:将每个聊天用户的发言(Message)按后进先出的顺序存入到数据库或文件中,并在用户端进行定时刷新来获取最新的Message数据。通过在HTML文件的<HEAD>部分插入相应的<META>元素,即可实现定时刷新,例如:

<META HTTP-EQUIV="Refresh" CONTENT="4">:表示在4秒后重新调入本页面

具体实现过程如下:

(1)首先要为IIS4安装Perl解释器。笔者选用的是ActivePerl(版本号:Build 517),安装时让其自动对IIS4进行配置,使得IIS4能够调用Perl.exe去解释以.pl为后缀的CGI程序。如果Web服务器是IIS3PWS,需要修改注册表来达到此目的(详见ActivePerl的联机文档)

(2)IIS4的管理界面MMC里建立虚拟目录,例如Chat,对应物理目录C:\Chat,将此虚拟目录属性设定为“读取”和“执行”。

(3)C:\Chat下建立主框架HTML文件Default.htm,将画面分为上下两个frame,上面的显示所有用户发言,下面的用于当前用户输入发言。同时建立message.htm作为存储用户发言的模板,login.htm用于用户输入名字进入聊天室。

default.htm

<FRAMESET ROWS="*,45">

<FRAME SRC=message.htm>

<FRAME SRC=login.htm>

</FRAMESET>

 

message.htm

<html><head><meta http-equiv="Refresh" content="4"></head><body>

<br>欢迎光临CGI聊天室!

</body></html>

 

login.htm

<html><body>

<form action="chat.pl" method="post">输入你的名字:

<input type=text name=username>

<input type=submit value="进入聊天室">

<input type=hidden name=message value="我来到了聊天室!">

</form></body></html>

(4)建立主程序chat.pl文件:

chat.pl

print "Content-type: text/html\n\n";

&get_form_data; #获得用户输入

open(MESSAGE,"c:/chat/message.htm"); #获取message.htm的内容

@lines=<MESSAGE>;

close(MESSAGE);

$now_string = localtime;

@thetime = split(/ +/,$now_string); #获取当前时间,存放在$thetime[3]

print "<html><body>\n";

#显示发言输入Form

print "<form action=chat.pl method=\"post\">\n";

print "<input name=username type=hidden value=\"$formdata{'username'}\">\n";

print "<input type=text name=message size=40>\n";

print "<input type=submit value=\"发言\"></form>\n";

if($formdata{'message'} ne "")

{

    #新的发言存放于$newmessage

    $newmessage = "<br>$formdata{'username'}:$formdata{'message'} (时间:$thetime[3])\n";

    #重写message.htm

    open (NEW, ">c:/chat/message.htm");

    print NEW "<html><head><meta http-equiv=\"Refresh\" content=\"4\"></head><body>\n";

    print NEW $newmessage;

    #最近的发言最多保存10

    $limit_lines=10;

    if( $#lines < 10 )

    { $limit_lines=$#lines; }

    for ($i = 1; $i < $limit_lines; $i++)

    { print NEW "$lines[$i]"; }

    print NEW '</body></html>';

    close(NEW);

}

print "</body></html>\n";

exit 0;

#获取Form数据的子过程

sub get_form_data {

    $buffer = "";

    read(STDIN, $buffer, $ENV{'CONTENT_LENGTH'});

    @pairs=split(/&/,$buffer);

    foreach $pair (@pairs)

    {

        @a = split(/=/,$pair);

        $name=$a[0];

        $value=$a[1];

        $value =~ s/%([a-fA-F0-9][a-fA-F0-9])/pack("C", hex($1))/eg;

        $value =~ s/~!/ ~!/g;

        $value =~ s/\+/ /g;

        #下面两句过滤掉发言中的HTML Tag

        $value =~ s/\</\&lt\;/g; 

        $value =~ s/\>/\&gt\;/g; 

        $value =~ s/\r//g;

        push (@data,$name);

        push (@data, $value);

    }

    %formdata=@data;

    %formdata;

}

(4)在浏览器的URL地址栏中输入 http://localhost/chat/ 即可进入聊天室。

 

    运行时的画面如图1所示:


在图中最新的发言最先显示,读者可改变程序以支持更多功能。在实际使用中为了避免多用户并发写message.htm,最好用数据库存储发言数据。可见,基于CGI方式的聊天室并非真正实时的,新的发言需要等到下一次自动刷新时才能看到。要做到真正的实时聊天,必需建立客户和服务器端的永久性网络连接,下面所述的两种方案:使用Java AppletActiveX就是这种思想。

三、基于Java的聊天室

基于Java的聊天室其过程是这样的:在返回给客户端的HTML页面里插入Java Applet,该AppletWeb服务器上运行的专用聊天服务器程序进行连接,当客户端用户输入新发言时,发言被传送到聊天服务器并由其向其他的聊天用户进行广播。Java聊天室的最大优点是在网络带宽比较理想的前提下(如局域网内),能真正作到实时性。

众所周知,Java是一种跨平台的语言,也就是说,用Java编写的程序具有很高的可移植性,可事实上并非如此,Java的中文兼容性问题一直是众多Java程序员所头疼的事情,这主要表现在中文文本的读写、中文的输入输出、中文的网络传输等方面,汉字的GB内码往往被当作Unicode码进行处理而导致乱码。

如果聊天室程序要支持中文,就要涉及汉字的显示、传输与输入。所幸的是,随着Java编译器版本的提高,Java的国际化和本地化问题也逐步得到解决。在JDK 1.1以上版本中,通过使用BufferedReaderBufferedWriterJava类进行流输入输出,可以解决Java Applet的汉字传输和显示问题。但经过试验,经过JDK1.1.6编译完的Applet,在NT服务器所装的IE4中文版浏览器下(版本4.72.3110.8;SP1),仍然出现一些汉字不能正常显示而用?代替的情况,问题经过传输后变成?”。针对这种情况,我们在聊天室程序中仍然使用DataInputStreamDataOutputStream进行网络流传输,而利用它们的readUTF()writeUTF()函数对传输内容进行统一的UTF编码,即:

//…传送端

String s=”中文问题

Output.writeUTF(s);    // Unicode码到UTF

//…接收端

String s=Input.readUTF();  // UTF码到Unicode

使用这种方法实现了中英文的完全正确传输,而且另一个好处是聊天室程序也能被Visual J++ 1.1或其他只兼容到JDK1.0的编译器所编译。汉字传输问题是解决了,但在大部分版本(3,4,5)IE浏览器中,Java AppletTextField仍然不能输入汉字,而Netscape浏览器和JDK所带的AppletViewer却可以。这可能是因为IE使用的是Microsoft自己的JVM(Java Virtual Machine)的缘故。可见,Java的跨平台特性仍然依赖于编译器和解释器的兼容性。

虽然在IEAppletTextField顽固不化的不接受中文,我们可以使用一个小技巧来解决这个问题,那就是不使用JavaTextField类,而是依靠HTMLFormText元素进行输入,然后把输入结果传递给Applet。在HTML页面里加入JavaScript程序,与该页面的Applet进行通讯,即

<script language="javascript">

function SendIt()

{

   document.Applet1.Procedure();  //调用Applet1Procedure过程

}

</script>

<applet code=”appletcode.class” name=”Applet1”>…

 

Java聊天室的具体实现过程如下:

(1)为测试兼容性,我们安装了两种Java编译器,Visual J++ 1.1JDK。笔者安装的JDK版本是1.1.6 for Win,安装到D:\JDK目录,装完后打开NT的控制面板-“系统”-“环境,添加D:\JDK\BINPATH环境变量,并添加一个CLASSPATH环境变量,值为“.;D:\JDK\LIB\CLASSES.ZIP”

(2)因为Java的字符都是Unicode码,为统一网络传输起见,聊天服务器程序我们亦采用Java编写。它是一个Java Application,其源程序如下:

Server.java

import java.io.*;

import java.net.*;

import java.util.*;

public class Server extends Thread

{

    protected ServerSocket listen_socket;

    public Vector clients=new Vector(); //clients用于存储各个连接的用户

    // 出错时打印信息并退出

    public static void fail(Exception e,String msg)

    {

        System.err.println(msg+":"+e);

        System.exit(1);

    }

    //创建ServerSocket并监听客户端连接

    public Server()

    {

        try

        {

            // 监听端口为6543

            listen_socket=new ServerSocket(6543);

        }

        catch(IOException e){ fail(e,"创建ServerSocket失败!");}

        System.out.println("聊天服务器启动: 监听端口号为6543... ");

        this.start();

    }

    public void run()

    {

        try

        {

            while(true)

            {

                Socket client_socket=listen_socket.accept();

                //创建与客户端连接线程

                Connection c=new Connection(client_socket,this);

                clients.addElement(c);  //存储

                System.out.println("新用户连接:

"+client_socket.getInetAddress());

            }

        }

        catch(IOException e) {fail(e,"监听时出现错误!");}

    }

    //主函数

    public static void main (String[] args) throws IOException

    {

        new Server();   //创建Server主线程

    }

}

 

// 处理同客户端连接的线程

class Connection extends Thread

{

    public Socket client;

    protected DataInputStream in;    // 从客户端读的缓冲

    protected DataOutputStream out;       // 向服务器端输出的缓冲

    protected Server serv;

    // 初始化缓冲流并启动线程

    public Connection(Socket client_socket,Server sv)

    {

        client=client_socket;

        serv=sv; //保存Server线程

        try

        {  

            in=new DataInputStream(client.getInputStream());

        }

        catch(IOException e)

        {

            try{ client.close();}

            catch(IOException e2)

            {

                System.err.println("获取Socket流时发生错误:"+e2);

                return;

            }

        }

        this.start();

    }

        public void run()

    {

        String line;

        StringBuffer revline;

        int len,nums,i;

        try{

            for(;;){

            line=in.readUTF();  //读入一行 UTFCode String—>UniCode String

            System.out.println(line);

            if(line==null) break;

            //进行广播

            nums=serv.clients.size();      

            for(i=0;i<nums;i++)

            {

                //取得每个Connection

                Connection c=(Connection)serv.clients.elementAt(i);

                out=new DataOutputStream(c.client.getOutputStream());

                out.writeUTF(line); //广播,UTF编码进行传输

            }}

        }

        catch(IOException e)

        {

              System.err.println("与客户端通信时发生错误:"+e);

              try{client.close();}

              catch(IOException e2){ System.err.println("不能关闭客户端连接:"+e2);}

        }

    }

}  

    (3)编写客户端Applet,其源代码如下:

AppletClient.java

import java.applet.*;

import java.awt.*;

import java.io.*;

import java.net.*;

import java.util.*;

public class AppletClient extends Applet

{  

    Socket s;

    DataInputStream in; // 读缓冲

    DataOutputStream out; // 写缓冲

    TextArea outputarea; // 显示发言的区域

    StreamListener listener; //监听线程用于显示服务器发回的消息

    String username; //聊天用户名

    public void init()

    {

        try{

            username=getParameter("username"); //获取用户名参数

            //由于Applet的安全限制,只能同下载服务器进行连接

            s=new Socket(this.getCodeBase().getHost(),6543);

            in=new DataInputStream(s.getInputStream());

            out=new DataOutputStream(s.getOutputStream());

            outputarea=new TextArea();

            outputarea.setEditable(false); //仅用于显示

            this.setLayout(new BorderLayout());

            this.add("Center",outputarea);

            listener=new StreamListener(in,outputarea);

            outputarea.appendText("连接到服务器

"+s.getInetAddress().getHostName()

                +":"+s.getPort()+"\n");

            out.writeUTF(username+" 进入聊天室.");

            }

        catch(IOException e){ this.showStatus(e.toString()); }

    }

    public void SendToServer(String s)

    //此过程供网页内的JavaScript程序调用,s发送到服务器

    {

        try{

        Date nowTime=new Date();

        out.writeUTF(username+":"+s+"("+nowTime.toLocaleString()+")");

        }

        catch(IOException e){ this.showStatus(e.toString()); }

    }

    public void Clear()

    //此过程供网页内的JavaScript程序调用,清除显示区

    {

        outputarea.setText("");

    }

}

 

// 客户端监听线程

class StreamListener extends Thread

{

    TextArea output;

    DataInputStream in;

    public StreamListener(DataInputStream in,TextArea output)

    {

        this.in=in;

        this.output=output;

        this.start();

    }

    public void run()

    {

        String line;

        try

        {

            for(;;){

                line=in.readUTF();

                if(line==null) break;

                //将服务器发回信息添加到textarea显示区

                output.appendText(line+"\n");

            }

        }

        catch(IOException e){ output.appendText(e.toString()+"\n");}

        finally{ output.appendText("连接被服务器关闭.\n");}

    }

}

(4)由于该Applet使用username参数来确定聊天用户名,故在此我们使用ASP动态传递此参数。登录及聊天的HTML代码均由default.asp控制。

Default.asp

<HTML>

<HEAD>

<TITLE>聊天室</TITLE>

<script language="javascript">

function SendIt() //发送发言到Applet

{

document.AppletClient.SendToServer(document.CHATFORM.textField.value);

document.CHATFORM.textField.value="";

}

function ClearDisplay()

{

document.AppletClient.Clear();

}

</script>

</HEAD>

<BODY>

<%

If trim(request("username"))="" then

'显示用户登录Form

%>

<form name="LOGINFORM" action="default.asp">

输入你的代号:<input type="text" name="username" size=20>

<input type="submit" value="进入聊天室">

</form>

<%

else  '否则显示聊天Form

%>

<APPLET CODE="AppletClient.class" name=AppletClient WIDTH=500 HEIGHT=300>

<PARAM name="username" value="<%=request("username")%>">

</APPLET>

<form name="CHATFORM" onSubmit="SendIt();return false;">

<input type="text" name="textField" size=40">

<input type="button" value="发送" onClick="SendIt()">

<input type="button" value="清除" onClick="ClearDisplay()">

</form>

<% end if%>

</BODY>

</HTML>

(5) 编译Server.javaAppletClient.java,如果使用VJ++,只需打开.java文件进行Build即可。如果用JDK,使用JAVAC 进行编译。最后生成文件Server.classAppletClient.class。将这两个.class文件连同default.asp文件放入C:\JAVACHAT,并在MMC中为C:\JAVACHAT建立虚拟目录JAVACHAT

(6)启动Server,对于VJ++,命令是: jview Server

对于JDK,命令是 java Server.class

    (7)在浏览器的URL地址栏中输入 http://localhost/javachat/ 即可进入聊天室。


运行结果如下图所示:

  推荐精品文章

·2024年9月目录 
·2024年8月目录 
·2024年7月目录 
·2024年6月目录 
·2024年5月目录 
·2024年4月目录 
·2024年3月目录 
·2024年2月目录 
·2024年1月目录
·2023年12月目录
·2023年11月目录
·2023年10月目录
·2023年9月目录 
·2023年8月目录 

  联系方式
TEL:010-82561037
Fax: 010-82561614
QQ: 100164630
Mail:gaojian@comprg.com.cn

  友情链接
 
Copyright 2001-2010, www.comprg.com.cn, All Rights Reserved
京ICP备14022230号-1,电话/传真:010-82561037 82561614 ,Mail:gaojian@comprg.com.cn
地址:北京市海淀区远大路20号宝蓝大厦E座704,邮编:100089