欢迎使用CSDN-markdown编辑器

java多线程聊天咨询程序

本聊天程序涉及三个工程,实现的功能是:implement a Online Help program for businesses such as banks which have company agents answer the customer questions.

1、client端,

  一个登入界面一个聊天界面

2、agent端

一个登入界面,进入之后将当前给该agent发消息的用户列表产生出来,并在当前登录的agent之间创建一个聊天室。agent可以给用户回答问题。
1) 用户列表采用Jlist生成,同时对每个列项进行监听 ,如果有新的用户发登录过来的话就将用户列表刷新。
2)开启一个timer实时将服务器端收到的用户列表进行刷新,注意timer本身是extends线程:

public void startTimer(){
           MessageTran messageTran = new MessageTran();
           messageTran.setFrom(adviser);
           messageTran.setMessageType(MessageType.REFRESH);
           Timer timer = new Timer();  
            timer.schedule(new TimerTask() {  
                public void run() {  
                    try {
                      synchronized (out){   
                          out.writeObject(messageTran);
                      }
                      if(list != null){
                         // System.out.println("list");
                          onlineCustomers.setListData(getList(list));
                          onlineCustomers.repaint();
                      }
                    } catch (IOException e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                    }  
                }  
            }, 1000,2000);
    }  

3) 创建一个刷新线程实现对服务器发送过来的refresh信息和聊天室消息进行接收,这个线程是以内部类的格式创建在ChatClient中的,以此来访问ChatClient的Jlist和JTextArea。

 class RefreshResult implements Runnable{
        MessageTran obj = null;
        @Override
        public void run() {
            try {       
                while (true) {          
                    synchronized(in){
                        obj = (MessageTran) in.getObject();
                        Thread.yield();             
                    }
                    switch(obj.getMessageType()){               

                    case REFRESH:
                        list = (Vector<User>)obj.getObjectTran();
                        break;
                    case MESSAGEALL: 
                        msgBd.append(obj.getFrom().getUserName()+":" +((String)obj.getObjectTran())+"\n"); 
                        break;
                    default:
                        break;
                    }

                }
            } catch (Exception e) {
            }

        }

    }

4)同时创建另外一个线程实现服务器接收登录结果和client发送

class CAgent implements Runnable {
    JTextArea msgBd = null;
    private Instream in = null;
    ObjectOutputStream out = null;
    MessageTran obj = null;
    Vector<User> listV = null;
    private Lock lock = new ReentrantLock();  
    public CAgent(Instream in,  ObjectOutputStream objOutput){
        this.in = in;
        this.out = objOutput;
        listV = new Vector<User>();
    }
    public CAgent(JTextArea taMsg, Instream in) {
        msgBd = taMsg;
        this.in = in;

    }

    public void run() { 
        try {       
            while (true) {          
                //synchronized(in){
                    obj = (MessageTran) in.getObject();
                //}
                switch(obj.getMessageType()){               
                case LOGIN: 
                    User user = (User)obj.getFrom();    
                    listV = (Vector<User>)obj.getObjectTran();
                    ChatClient chatClient = new ChatClient(user, listV, in ,out);   
                    this.wait();
                    break;

                case MESSAGE: 
                    msgBd.append(obj.getFrom().getUserName()+":" +((String)obj.getObjectTran())+"\n"); 
                    break;
                default:
                    break;
                }

            }
        } catch (Exception e) {
        }
    }

}

3、服务器端

1)通过线程池来管理每一个连入的线程,每当ServerSocket Accept一个连入的SOCKET的后就创建一个线程,用来管理其输入和输出。

private final class SocketTask implements Runnable {
        private Socket socket;
        private InputThread in;
        private OutputThread out;
        private OutputThreadMap map;

        public SocketTask(Socket socket) {
            this.socket = socket;
            map = OutputThreadMap.getInstance();

        }

        @Override
        public void run() {
            out = new OutputThread(socket, map);//
            // 先实例化写消息线程,(把对应用户的写线程存入map缓存器中)
            in = new InputThread(socket, out, map, adviserVector, customerVector);// 再实例化读消息线程
            out.setStart(true);
            in.setStart(true);
            in.start();
            out.start();
        }
    }

2)、通过HashMap将用户id 和 输出线程行程一个键值对保存起来,方便通过id进行查找输出线程。通过单例模式创建OutputThreadMap.本来想利用string来做键值对的,但想了想还是算了

    public class OutputThreadMap {
    private HashMap<Integer, OutputThread> map;
    private static OutputThreadMap instance;

    // 私有构造器,防止被外面实例化改对像
    private OutputThreadMap() {
        map = new HashMap<Integer, OutputThread>();
    }

    // 单例模式像外面提供该对象
    public synchronized static OutputThreadMap getInstance() {
        if (instance == null) {
            instance = new OutputThreadMap();
        }
        return instance;
    }

    // 添加写线程的方法
    public synchronized void add(Integer id, OutputThread out) {
        map.put(id, out);
    }

    // 移除写线程的方法
    public synchronized void remove(Integer id) {
        map.remove(id);
    }

    // 取出写线程的方法,群聊的话,可以遍历取出对应写线程
    public synchronized OutputThread getById(Integer id) {
        return map.get(id);
    }
}

4、遇到的问题
1).java.io.StreamCorruptedException: invalid type code: AC
在同一个线程里面ObjectIputStream
原因:这是因为一个线程中定义了多个objectOutputStream
2)NullPointerException
原因:引用对象没有实例化
3)发送对象没有实现
解决:

public class MessageTran implements Serializable 

4)多线程资源互斥问题:
在agent端,对于socket.getInputStream()的IO流发生访问冲突,刚开始采用的方法是在线程中直接对IO流使用同步控制块

synchronized(in){
        obj = (MessageTran) in.getObject();
}

但是由于ChatClient中的Refresh线程也需要获取到in的输入,同时SingleChat开启的CAgent线程也要获取in的输入,所以虽然实现了对in 的互斥访问,但是其结果是各个线程无法及时获取到in 的锁而出现了用户消息的丢失。
所以最后修改的方法是使用java的LOCK,来实现对锁的释放。

public class Instream {
    Socket socket;
    ObjectInputStream ois;
    private Lock lock = new ReentrantLock();  
    Instream(Socket socket){
        this.socket = socket;
        try {
            ois = new ObjectInputStream(socket.getInputStream());
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }
    public Object getObject() throws ClassNotFoundException, IOException{
        Object object = null;
        lock.lock();
        try{
            object = ois.readObject();
        }finally{
            lock.unlock();
        }
        return object;
    }

}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值