你好,欢迎来到电脑编程技巧与维护杂志社! 杂志社简介广告服务读者反馈编程社区  
合订本订阅
 
 
您的位置:杂志经典 / 编程语言
基于Java的Web聊天室系统(上)
 

 :本文讨论了用Java开发Web聊天室系统的优点,并给出了一个Java聊天室系统的实例。作者解决了其中的中文传输、显示及输入的问题。该聊天室系统具有高效性、健壮性和灵活性,达到了预期的设计目标。

关键字: WEB聊天室  JAVA  UTF-8  异常  哈希表 广播

 

 

一、概述

聊天室(Chatroom)是Web站点提供的常用服务之一,它给网络用户带来了在线实时交流的机会,而且使用起来不需要安装专门的聊天软件,只需要浏览器即可。Web聊天室系统由于其方便、灵活和易于使用的特点而广受欢迎。目前开发Web聊天室系统的方法主要有以下几种:CGIJavaActiveXASP等等。相比较起来,Java语言具有其优越之处[1]:一是跨平台和可移植性好。Java ApplicationJava Applet程序几乎能在所有平台上编译、执行,而象含ActiveX的页面主要针对x86win32系统,且在Netscape浏览器中运行时需额外安装plug-in。二是使用Java语言编写的聊天室能够做到真正的实时聊天。常见的CGIASP等方法一般是通过无连接的HTTP协议来传输数据,需要靠HTML页面的自动定时刷新来模拟聊天过程。而Java Applet能够与服务器端建立永久的TCP/IP连接,用户的发言能够被马上传输和广播,而且也不需要传输额外的HTML内容。三是Java语言本身的功能非常适合于编写网络应用程序,如鲁棒性(Robust),完善的Net类库和多线程支持等等。本文基于Java语言来开发一个完整的Web聊天室系统。

二、结构与目标

基于JavaWeb聊天室系统包括聊天服务器和客户端两部分。聊天服务器是一个Java Application,与Web服务器程序运行在同一机器上。客户端部分即是一个含Java AppletHTML页面,它由Web服务器传送给客户端浏览器,交由浏览器的Java虚拟机(VM)解释执行。该Applet初始化后与聊天服务器进行连接,聊天服务器对于每个连接请求产生一个连接线程(Connection Thread),来维护和管理与该客户端的会话。客户端的发言被传送到服务器端后由其向其他客户进行广播(Broadcast),达到相互聊天的目的。Java聊天室系统结构如图1所示:

1  Java聊天室的结构

 

为了让这个聊天室系统能够真正实用,必须达到以下要求:

⑴完善的支持中文。由于Java编译器版本及运行环境的差异等原因,在Java语言的中文处理中常出现乱码等现象,表现在中文显示与网络传输不正常、中文不能输入等方面。在这个聊天室系统中我们将彻底解决中文的兼容性问题。

⑵具有健壮性。即聊天室系统能够处理各种异常,能够识别和控制客户端的各种行为,能够返回清理不正常退出后所分配的系统资源,能够踢出超时连接用户以减轻服务器负载等。虽然Java语言本身能够自动收集处理无用的对象[2],但我们仍然需要作一定的清理工作。

⑶广泛的适应性。因为我们不能要求聊天用户必须使用某种浏览器或操作系统,因此所编写的Java程序,尤其是Applet,必须能在各种平台的各个版本的浏览器上都能正常运行。考虑到网络用户的使用情况,我们定的标准是能适应以下版本的浏览器:Netscape 3.xNetscape Communicator 4.xInternet Explorer 3.x4.x5.x中英文版。

三、聊天服务器

在聊天服务器中,我们使用哈希表(Hashtable)来存储所有的连接线程。主线程为ChatServer,对于每个新的客户连接请求产生一个Connection线程。同时我们还运行了一个检查线程CheckActiveTimer,它相当于一个定时器,每隔一定时间就扫描所有的客户连接线程(即扫描Hashtable),检查每个客户连接是否超时(例如很长时间没有发言或者死机),并给出警告或直接踢出(Kick)用户。

在网络传输过程中,我们使用字节输入输出流DataInputStreamDataOutputStreamwriteUTFreadUTF方法进行传送接收,这两个方法以UTF-8编码方式来对Unicode字符串进行编码和解码,这样我们就能正确的进行中英文的传送。虽然在JDK 1.1以上版本编译器中,我们可以使用基于字符的流,如BufferdReaderPrintWriter等类进行网络传输,但是经过试验,在某些旧的Java VM上(如IE4所带的VM)仍然出现一些汉字传输后变成?字符的现象,所以我们不采用这种方式。

聊天服务器的主要程序段代码及其解释如下:

⑴主线程ChatServer的定义及初始化

public class ChatServer extends Thread {  //主线程ChatServer

    public Hashtable chatusers; //存储与所有聊天用户的连接线程

    protected ServerSocket listen_socket; //监听Socket

    protected CheckActiveTimer check; //定时检查用户活动情况的线程

    public final static int PORT=6543; //默认端口号

public final static long WARNTIMEOUT=180000l; //超过3分钟用户无反应则警告

public final static long KICKTIMEOUT=240000l; //超过4分钟用户无反应则被踢出

    //类初始化过程

    public ChatServer() {

        chatusers=new Hashtable(); //分配哈希表

        check=new CheckActiveTimer(this);  //启动检查线程

        try { //创建监听Socket

            listen_socket=new ServerSocket(PORT);  }

        catch(IOException e)    {   fail("创建监听Socket失败"); }

        this.start();  //启动线程的执行

    }

⑵主线程的执行过程run()

    public void run(){

        try{    while(true){

                Socket client_socket=listen_socket.accept();

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

                Connection c=new Connection(client_socket,this);

            }}

        catch(IOException e)

        {   error("与新登录用户连接失败");  }

    }

⑶检查连接用户活动情况的方法checkit()

实际上,ChatServercheckit()方法是在CheckActiveTimer线程里被定时调用的。

    public void checkit()  {

        Vector kill=new Vector();  //Vector存储要被踢出的超时用户

        long now=(new Date()).getTime();  //取现在的时间,单位:毫秒

        Enumeration e=chatusers.keys();

        // 遍历连接线程,检查每个用户的上次发言时间

        while(e.hasMoreElements()){

            String key=(String)e.nextElement();

            Connection c=(Connection)chatusers.get(key);

            long inactive=now-c.lasttime;

            if(inactive>KICKTIMEOUT)    { //连接超时大于踢出时间

                kill.addElement(c);

            }else if(inactive>WARNTIMEOUT) //连接超时大于警告时间

            {

            try{  //向该用户发出警告消息

            c.out.writeUTF("注意:您将在"+((KICKTIMEOUT-inactive)/1000)+"秒后被踢出聊天室");

            c.out.flush();

            }

            catch(IOException e1){ error("警告用户"+c.logname+"失败"); }}}

        // 检查完毕后,踢出超时用户

        for(int i=0;i<kill.size();i++){

            Connection c=(Connection)kill.elementAt(i);

            log("准备踢出用户"+c.logname);

            try{    c.client_socket.close();    }

            catch(IOException e2)   {

                error("无法踢出用户:"+c.logname);  }}}

⑷检查用户是否超时活动的线程CheckActive

class CheckActiveTimer extends Thread{

    

    public void run()   {

        while(true) {

            if(server!=null) server.checkit();

            try{    sleep(PERIOD);  }  //线程暂时中止PERIOD时间

            catch(InterruptedException e){ System.err.println("错误:检查线程被中止"); }

        }}}

⑸处理同客户端连接的线程Connection

class Connection extends Thread{

    public Socket client_socket;

    public long lasttime; //上次活动时间

    protected DataInputStream in;    // 读缓冲流

    protected DataOutputStream out;  // 写缓冲流

    public String hashkey; // 标志此Connection的字符串

    public String username; // 聊天用户代号

    public String logname;

    protected Hashtable chatusers; // 引用ChatServerchatusers

    public Connection(Socket client_socket,ChatServer server) { // 初始化

        this.client_socket=client_socket;

        chatusers=server.chatusers;

        lasttime=(new Date()).getTime();

        username="未知";

        try{   

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

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

        }catch(IOException e)   {

            System.err.println("错误:获取客户端Socket流时发生错误");

            try{ client_socket.close();}

            catch(IOException e2)   {

                System.err.println("错误:无法关闭客户端Socket");

            }

            this.stop(); //停止线程

            return;

        }

        hashkey=client_socket.getInetAddress()+":"+client_socket.getPort();

        logname="["+hashkey+"/"+username+"]";

        chatusers.put(hashkey,this); // 加入用户列表

        this.start();

    }

    public void run()   {

        String line;

        boolean command;

        try{    for(;;){

                command=false;

                line=in.readUTF();  //读入一行

                if(line==null) break;

                lasttime=(new Date()).getTime();

                if(line.startsWith("$username")) { //用户登录名字

                    username=line.substring(9);

                    logname="["+hashkey+"/"+username+"]";

                    broadcast(username+"进入聊天室,目前聊天室用户人数为"+chatusers.size());

                    command=true;

                }

                if(line.startsWith("list ")) {  // 列用户命令

                    Enumeration e=chatusers.keys();

                    String msg="目前在聊天室的用户有:\n";

                    while(e.hasMoreElements())  {

                        String key=(String)e.nextElement();

                        Connection c=(Connection)chatusers.get(key);

                        msg=msg+c.username+"\n";

                    }

                    out.writeUTF(msg); out.flush();

                    command=true;

                }

                if(!command)  broadcast(username+":"+line); //进行广播

            }

        }

        catch(IOException e){

            System.err.println("错误:"+logname+"通信时发生错误");

        }

        finally {  // 与客户端通信失败后,资源清理工作

            try{

                client_socket.close();  //关闭Socket

                System.out.println("关闭与"+logname+"的连接");

            }

            catch(IOException e){

                System.err.println("错误:不能关闭与"+logname+"的连接");

            }

            finally{  //最终删除线程

                client_socket=null;

                chatusers.remove(hashkey);  //从哈希表中删除

                try{

                    System.out.println(logname+"离开了聊天室.");

                    broadcast(username+"离开了聊天室.");

                }catch(IOException e){

                    System.err.println("错误:广播错误");

                }

                this.stop();

            }

        }

    }
  推荐精品文章

·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