JAVA聊天软件教程(六)

期末考三天考四科,折磨死我了。。。。。

阔别已久,回来继续写教程。

再次声明,这个教程写得并不详细,我只对其中一些比较重要的问题进行分析。并且我的做法可能不是最好的。写这个东西只是为了复习J2SE的一些基础知识。实际上的聊天程序并不是这样实现的。

 

好了,回到正题。

之前我们讲到将各种信息封装为一个类。因此今天我们再次添加两个类,一个是退出时发送的信息类,一个是聊天信息类

//ExitMeg.java import java.io.Serializable; /* * 新增退出信息类 */ public class ExitMeg implements Serializable { private String userName; public ExitMeg(String userName) { //以退出用户的信息构造一个对象 this.userName = userName; } public String getUserName() { //方便获取名字 return userName; } }

//ChatMeg.java import java.io.Serializable; /* * 聊天信息类 */ public class ChatMeg implements Serializable { private String userName = null; //发送信息的用户 private String peerName = null; //接受信息的用户 private String message = null; //表示信息 public ChatMeg(String userName, String peerName, String message) { this.userName = userName; this.peerName = peerName; this.message = message; } public String getMessage() { //方便获取信息 return message; } public String getUserName() { return userName; } public String getPeerName() { return peerName; } }

接下来要在服务器进行接收:

//HandleUser.java import java.awt.Toolkit; import java.io.DataInputStream; import java.io.DataOutputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.net.Socket; import java.util.Vector; /* * 此线程用于监听处理每个客户请求 */ public class HandleUser implements Runnable { private ObjectInputStream ois = null; private ObjectOutputStream oos = null; private Object obj = null; private Socket s = null; String userName = null; //使用此HandleUser的用户名,方便服务器发送信息给客户时的查找工作 private static Vector allClients = new Vector(); //用来装注册用户的容器 StudyServer ss = null; //持有一个引用 public HandleUser(StudyServer ss,Socket s) { this.ss = ss; this.s = s; try { oos = new ObjectOutputStream(s.getOutputStream()); ois = new ObjectInputStream(s.getInputStream()); } catch (IOException e) { System.out.println("连接出错了!"); try { ois.close(); oos.close(); s.close(); } catch (IOException e1) { // TODO Auto-generated catch block e1.printStackTrace(); } } } public void run() { try { obj = ois.readObject(); if(obj.getClass().getName().equals("Reg_Customer")) { //如果是注册用户信息 register(obj); //将obj对象传给register函数处理 } else if(obj.getClass().getName().equals("Login_Customer")) { //如果是登录用户信息 login(obj); //将obj对象传给login信息处理 } else if(obj.getClass().getName().equals("OnlineMeg")) { //如果是message类型,则调用sendtoClient sendtoClient(obj); } else if(obj.getClass().getName().equals("ExitMeg")) { //如果是退出信息,将其从在线用户删除 removeUser(obj); } else if(obj.getClass().getName().equals("ChatMeg")) { //如果是聊天信息,调用chat函数 chat(obj); } } catch (IOException e) { e.printStackTrace(); } catch (ClassNotFoundException e) { e.printStackTrace(); } } public void register(Object obj) { Reg_Customer reg_customer = (Reg_Customer)obj; //将obj转型为注册用户对象 FileOutputStream fos = null; ObjectOutputStream os = null; try { File file = new File("Register.txt"); //此处应新建一个File,这样就可消除只能找到第一次注册的原因 fos = new FileOutputStream(file); //原本是写成FileOutputStream("Register.txt");错误! os = new ObjectOutputStream(fos); allClients.addElement(reg_customer); // System.out.println(allClients.size()); os.writeObject(allClients); oos.writeObject(new MsgType("success")); //以succes字符串构造信息对象并发送出去 ss.allusers.remove(this); //从在线用户里删除,因为其只是注册 } catch (FileNotFoundException e) { System.out.println("未找到文件!"); e.printStackTrace(); } catch (IOException e) { System.out.println("读取出错!"); } } public void login(Object obj) { Login_Customer login_customer = (Login_Customer)obj; FileInputStream fis = null; try { fis = new FileInputStream("Register.txt"); ois = new ObjectInputStream(fis); allClients = (Vector)ois.readObject(); } catch (FileNotFoundException e) { System.out.println("找不到Register.txt文件"); } catch (IOException e) { try { oos.writeObject(new MsgType("noexist")); //文件内无内容,发送用户不存在的消息 } catch (IOException e1) { e1.printStackTrace(); } } catch (ClassNotFoundException e) { System.out.println("未找到相应的类文件"); } int exist = 0; //判断用户名是否存在 for(int i=0;i<allClients.size();i++) { // System.out.println(allClients.size()); // 经测试发现,并没有新的对象加入去,导致容器内仅有第一次注册的用户 Reg_Customer reg_customer = (Reg_Customer)allClients.get(i); if(reg_customer.getName().equals(login_customer.getName())) { exist = 1; if(reg_customer.getPassword().equals(login_customer.getPassword())) { try { oos.writeObject(new MsgType("success")); //发送代表成功的消息类型 ss.online.addElement(new Online_Customer(login_customer.getName())); userName = login_customer.getName(); //将userName设置为登录进来用户的名称 ss.allusers.add(this); //将此HandleUser加入所有使用用户列表 } catch (IOException e) { e.printStackTrace(); } /*不再在这里发信息,而是用户界面发来消息时才发送 for(int i1=0;i1<ss.allusers.size();++i1) { //对于每一个连上的客户(存放于allusers中) HandleUser handle_user = ss.allusers.get(i1); //如果下线了而这里没有删除,在向其发送数据会报错! for(int i2=0;i2<ss.online.size();++i2) { System.out.println(ss.online.size()); System.out.println(i2); Online_Customer online_customer = (Online_Customer) ss.online.get(i2);//从在线用户容器中取出在线用户对象,逐一发送给客户端 try { handle_user.oos.writeObject((online_customer)); } catch (IOException e) { System.out.println("对方已下线,请从列表中删除!"); ss.allusers.remove(this); break; } } }*/ break; //注意,如果找到要退出循环 } else { try { oos.writeObject(new MsgType("wrongpass")); //发送代表密码错误的消息类型 ss.allusers.remove(this); //从在线用户里删除,因为其未登录上 } catch (IOException e) { e.printStackTrace(); } } } } if(exist==0) { try { oos.writeObject(new MsgType("noexist")); ss.allusers.remove(this); //从在线用户里删除,因为其未登录上 } catch (IOException e) { e.printStackTrace(); } } /*catch (FileNotFoundException e) { System.out.println("未找到文件"); e.printStackTrace(); } catch (IOException e) { //当文件内容为空时,发生“用户不存在”的消息 try { oos.writeObject(new MsgType("noexist")); } catch (IOException e1) { e1.printStackTrace(); } } catch (ClassNotFoundException e) { e.printStackTrace(); }*/ } public void sendtoClient(Object obj) { //如果收到客户发来message,则把在线用户列表和聊天信息发回给客户 OnlineMeg message = new OnlineMeg(); message.onLine = ss.online; //把在线用户传递给message try { oos.writeObject(message); } catch (IOException e) { e.printStackTrace(); } ss.allusers.remove(this); //从在线用户里删除,因为其已经记录 } public void removeUser(Object obj) { ExitMeg exitmeg = (ExitMeg) obj; for(int i=0;i<ss.online.size();++i) { Online_Customer online_customer = (Online_Customer) ss.online.get(i); //只能这样写,注意下面被注释掉的语法行不通 if(exitmeg.getUserName().equals(online_customer.getName())) ss.online.remove(i); } System.out.println(ss.online.size()); ss.allusers.remove(this); //从在线用户里删除,因为其已退出 /* * 这种方法不可以,原因未明 Online_Customer online_customer = new Online_Customer(exitmeg.getUserName()); System.out.println(online_customer.getName()); if(ss.online.removeElement(online_customer)) { //测试 System.out.println(ss.online.size()); //这里没有打印出来,说明删除元素失败 } */ try { this.ois.close(); //退出时关闭流 this.oos.close(); this.s.close(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } } public void sendChat(ChatMeg chatmeg) { try { oos.writeObject(chatmeg); } catch (IOException e) { System.out.println("IO有问题"); } } public void chat(Object obj) { ChatMeg chatmeg = (ChatMeg) obj; //将其转化为ChatMeg String peerName = chatmeg.getPeerName(); //取出接受者的用户名 for(int i=0;i<ss.allusers.size();++i) { System.out.println(ss.allusers.size()); HandleUser handle_user = ss.allusers.get(i); if(handle_user.userName.equals(peerName)) { System.out.println(chatmeg.getMessage()); System.out.println(handle_user.userName); handle_user.sendChat(chatmeg); } } } }

至于监听器的添加,在按下聊天对话框按钮时发送聊天信息,在按下关闭窗口时发送退出信息,这个大家就自己加上吧。

这样就算完成了程序退出处理和聊天信息的传送。

将聊天信息转发给接收方的方法,上面HandleUser中我已经实现了,大家仔细看看。方法就是取出接收方的用户名。并在在线用户列表中国搜索,如果在线的话就发送信息过去。

在线用户列表我之前没有讲,现在讲一下。我们定义一个容器来装载在线用户,每当有一个用户登录上就把这个用户加入该容器中,每当有一个用户退出,就把它从容器中删除,这样就OK了。

 

最后,我们发送信息给一个好友,好友要怎么接收呢?目前学到的知识还不能够实现像QQ那样闪烁,因此我们考虑使用一个小小的弹出对话框来提醒用户收到信息。

我们把这个对话框命名为Awoke,名为提醒的意思:

//Awoke.java import java.awt.Dimension; import java.awt.Font; import java.awt.HeadlessException; import java.awt.Toolkit; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.WindowAdapter; import java.awt.event.WindowEvent; import javax.swing.JButton; import javax.swing.JFrame; import javax.swing.JPanel; /* * 当用户收到聊天信息后弹出提醒窗口 */ public class Awoke extends JFrame{ private static Toolkit kit = Toolkit.getDefaultToolkit(); Dimension screenSize = kit.getScreenSize(); private int frameWidth = screenSize.width/5-40; private int frameHeight = 70; private int locatedX = screenSize.width-frameWidth-80; private int locatedY = screenSize.height-120; private JPanel panel = null; //用来装载其他组件的panel private JButton open = null; //“打开”按钮 private String peerName = null; //记录发送信息的用户名 private String userName = null; //记录当前用户的用户名 private String message = null; //记录聊天信息内容 private TalkFrame talkframe = null; public Awoke(TalkFrame talkframe) throws HeadlessException { //接受聊天信息对象 this.talkframe = talkframe; this.userName = talkframe.getUserName(); this.peerName = talkframe.getPeerName(); this.message = talkframe.getChatmes(); } public void launchFrame() { this.setBounds(locatedX,locatedY,frameWidth,frameHeight); //设置方位大小 this.setVisible(true); this.setTitle(peerName); //标题栏提醒发送信息的用户名 this.addWindowListener(new WindowAdapter() { public void windowClosing(WindowEvent e) { setVisible(false); System.exit(0); } }); panel = new JPanel(); this.add(panel); open = new JButton("查看"); open.setFont(new Font("微软雅黑",Font.PLAIN,12)); open.setBounds(20,60,30,20); open.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent arg0) { talkframe.launchFrame(); setVisible(false); } }); panel.add(open); } /* public static void main(String []args) { new Awoke("test").launchFrame(); }*/ }

在什么时候弹出这个提醒对话框呢?我们还记得之前我们使用StudyClient来接收服务器传回来的信息,这时候我们在StudyClient添加这一事件处理。当用户接收到ChatMeg类型的信息时,自动弹出提醒对话框。

但是这里有一个问题,如果用户已经在和好友聊天了,那么就不需要弹出提醒对话框,直接在聊天对话框上显示好友发送来的信息就可以了。

而如果之前没有打开和好友聊天的对话框,那么此时就会弹出提醒,这种情况我没有考虑好,希望大家多多提意见。

我采用的是如下的做法:检测当前有没打开聊天对话框,就是在打开聊天对话框时设立一个标志,当接收到聊天信息时检验该标志,如果标志被设立,则不需要弹出提醒,如果标志尚未被设立,则弹出提醒对话框。

但是这种方式对于和多个人同时聊天的时候是行不通的。因为此时你打开聊天对话框,和某一个好友聊天。如果另外一个好友向你发送信息,但是他检测到你已经打开了一个对话框了,因此它就不会弹出提醒对话框。这样显然是不正确的。

我还没有想出比较好的解决方法,先按我之前的思路写,以后想到了再改进:

//StudyClient.java //只看线程处理这块代码 public class SendAndRevThread implements Runnable { //此线程用于和服务器端保持联系。发送在线用户列表和聊天信息 public void run() { Socket s = null; ObjectOutputStream oos = null; ObjectInputStream ois = null; while(true) { try { s = new Socket("127.0.0.1",StudyServer.TCP_PORT); oos = new ObjectOutputStream(s.getOutputStream()); ois = new ObjectInputStream(s.getInputStream()); oos.writeObject(new MegCollection()); //发送信息到服务器,服务器收到后发来在线用户列表 MegCollection mes = (MegCollection)ois.readObject(); friendList.removeAll(); //先清空 for(int i=0;i<mes.onLine.size();++i){ //从message中读出在线用户列表,并加入自己的好友列表中 Online_Customer online_customer = (Online_Customer) mes.onLine.get(i); friendList.add(online_customer.getName()); } for(int i=0;i<mes.chat.size();++i) { //从message中读出聊天信息列表 ChatMeg chatmeg = (ChatMeg) mes.chat.get(i); if(userName.equals(chatmeg.getPeerName())) { String peerName = chatmeg.getUserName(); //记录发送信息的用户的名称 System.out.println(chatmeg.getUserName()+" say "+chatmeg.getMessage()+" to "+chatmeg.getPeerName()); if(!isVisible) { //判断其是否已经打开,如果未打开,启动提醒窗口 /* * 之前是直接调用Awoke再在awoke里启动talkframe。但是这样的话else语句中的talkframe就会造成空指针, * 因此需要new talkframe再把它传递给awoke */ talkframe = new TalkFrame(chatmeg.getPeerName(),chatmeg.getUserName(),chatmeg.getMessage()); //新建一个窗口 new Awoke(talkframe).launchFrame(); //启动提醒窗口,将聊天信息对象传递过去 isVisible = true; } else { talkframe.getMessage().setText(talkframe.getMessage().getText()+chatmeg.getMessage()); //先保存之前记录再更新,避免覆盖 } } } System.out.println(friendList.countItems()); } catch (IOException e) { JLabel closed = new JLabel("服务器已关闭,请退出"); JOptionPane.showMessageDialog(null,closed,"警告",JOptionPane.ERROR_MESSAGE); //当服务器关闭时弹出信息框并退出 setVisible(false); System.exit(0); } catch (ClassNotFoundException e) { // TODO Auto-generated catch block e.printStackTrace(); } try { oos.close(); ois.close(); s.close(); Thread.sleep(500); //使用sleep,方可刷新列表 } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (InterruptedException e) { e.printStackTrace(); } } } }

 

这样,我们的无比简陋的聊天软件就算完成了。不得不说这个教程写的真的很不好,再次希望大家谅解。下次写教程,我会每完成一个功能就边写教程,这样思路清晰点,教程也会详细点。

 

总结我们这个程序,要写东西要明白下面几点:

1.边写边想思路,不要想着我都考虑全面了才动手。在编写过程中遇到问题,自己想办法解决,这样才能进步

2.找bug不要浮躁,好好利用好debug,用熟它,一步一步摸清你程序的运行轨迹,这样就可以发现你到底错在哪。有时候找一个bug花个两三天是很正常的事,你必须一天到晚想哪里可能出了错。虽然烦,但是解决了一个bug后你会很有成就感。

3.对于不懂的问题,上网搜资料后一定要做好记录。不要看过之后就忘了,慢慢积累,这将是你宝贵的财富!

 

 

 

你可能感兴趣的:(java,String,服务器,null,聊天,login)