设计选题 网络聊天室
姓 名 马继鸣
学 号 0909090630
完成时间 2012-1-12
一、需求分析
聊天工具大多数由客户端程序和服务器程序外加服务器端,本程序采用客户机/服务器架构模式。通过Java提供的Socket类来连接客户机和服务器并使客户机和服务器之间相互通信,由于聊天是多点对多点的而Java提供的多线程功能。用多线程可完成多点对多点的聊天。
主要有两个应用程序,分别为服务器程序和客户端程序。服务器应用程序主要用于消息转发,以及向所有用户发送系统消息等;客户端应用程序主要用于客户聊天记录的显示和信息输入。采用Client/Server(C/S)体系结构,即客户机/服务器体系结构。聊天服务器专门用于监控用户状态和转发消息,客户端负责接收消息的用户序列和消息文本发送到服务器。该聊天系统实现群聊,用户登陆,退出聊天系统等功能。
二. 设计
1.设计思想:
课程设计中利用JAVA实现基于C/S模式的聊天程序。聊天室共分为服务器端和客户端两部分,服务器端程序主要负责侦听客户发来的消息,客户端登录到服务器端才可以实现正常的聊天功能。
2.功能设计
(1)服务器端的主要功能如下。
在特定端口上进行侦听,等待客户端连接。用户可以配置服务端的侦听端口,默认端口为10000.
当停止服务时,断开所有用户连接。
(2)客户端的主要功能如下。
连接到已经开启聊天服务的服务端。
当服务器端开启的话,用户可以随时登录与注销。
用户可以向所有人发送消息。
3.详细设计
聊天室服务器端设计
聊天室服务器中主要的功能文件的功能如下:
MyButLis.java
主要用于添加事件侦听和动作处理,具体是服务器按钮中的“终止”“发送”的处理。
public class MyButLis implements ActionListener
private MyServe server;
private JTextField filed1;
public void actionPerformed(ActionEvent e)
MyChatSet.java
服务器的线程队列,运行的方法,新增一个用户,则增加一个线程,并可以返回应答消息,给其余用户发送上下线信息,发送聊天信息(群聊私聊虽然已写,但是不知为何没有实现),发送用户列表。关键在于除了线程之外,还定义了传输数据时的协议内容,如何分辨不同的信息,如聊天信息,登陆信息,上下线信息,并把信息完整准确的传递。
public static ArrayList public static void addThread(MyserThread th) //增加一个服务器线程对象 public static void sendReMess(String type,String state,String s,MyserThread myth)//返回应答信息 public static void SendOSMess(String type,String user)//其他用户上下线信息 public static void SendChatMess(String type,String sender,String reciver,String Mess)//聊天信息 public static void SendList//用户列表 MyCheckTool.java 保存用户的登陆信息,在其中建立一个码表,并且可以判断登陆信息是否合法。 //建立码表,用以保存用户信息 private static Map public static boolean CheckUser(MyUserInfo user)// 验证用户名 MyFrame.java 建立服务器的主界面,关联侦听器。并启动线程。 MyFrame my=new MyFrame(); my.ShowUI();// 显示UI界面 public void ShowUI() server.SetUP(); MyserThread.java 服务器线程类,定义输入输出流,发送读取信息,获取信息中相应标签的信息,用以判断用户名,密码,并可以中断连接。 this.socket=socket; output=socket.getOutputStream(); input=socket.getInputStream(); public void SendMess(String s)//发送信息 public String ReadMess() //读取信息 public boolean ReadFirst()//将第一条信息读出来 public String GetValue(String type ,String Mess) public void Pocess()//用以判断用户名、密码 public void run()//线程开始 public MyUserInfo getUser()//获得该线程中的用户信息 MyServe.java 建立服务器,并启动该线程。 server=new ServerSocket(inport); Socket sc=server.accept();//获得socket对象 MyserThread th=new MyserThread(sc); th.start(); //启动线程 客户端中主要的功能文件概述如下: MyButLis.java 主要用于添加事件侦听和动作处理,具体是在客户端中的登陆注册发送等的处理。 public MyButLis(MySocketChat socket,JTextArea area1,JTextArea area2,MyLoginUI loginUI,JComboBox box) public void actionPerformed(ActionEvent e) //设置动作 MyChatFrame.java 客户端的图形界面。关联侦听器。并启动线程。 socket.setArea(area1); socket.start() MyLoginUI.java 登陆界面 MySocketChat.java 构建连接,截取字符串,发送登陆,注册,聊天信息,读取信息并判断设置启动线程方法。 public MySocketChat(JTextField field1,JTextField field2,JTextArea area)//重写构造函数 public String GetValue(String type ,String Mess) //截取字符串 public void SendMess(String type)// 发送登陆或者注册信息 public void SendChat(String getter,String chat)// 发送聊天消息 public String ReadMess ()//读入的信息 public void CheckMess(String Mess) //判断是什么消息 public void run()//设置启动方法 三. 调试及测试 1、调试过程中遇到的主要问题及解决方法 在对于群聊和私聊的处理中,只实现了群聊,私聊虽有写,但是还没有实现,由于时间仓促,只能在寒假的其余时间努力完成。 还有就是如何在服务器与客户端的信息交互过程中,让他们能分辨是哪种信息并准确的读出来。如果不能准确的分析,则数据的交换只能完成一种粗略的互换,而不能提取聊天内容等用户希望得到的信息。为此,我专门看了很多相关的资料,在很多方法里面选取了一种自己容易理解的方法,我就定义了几种方式,分别辨认登陆注册聊天列表等信息: " //第一个与最后一个msg是标示一整句话的标示符 type标示数据类型,两个 " //其中sender,reciever,content分别指发送者、接受者、内容。 一些语法问题,在随着写程序的过程中,也慢慢纠正。在服务器组建中,由于java对于网络连接强有力的支持和丰富的代码,让我没有遇到太难的问题。 2、对设计和编码的回顾讨论和分析 在获取系统时间这一块上,还是做得比较好。 Calendar ca=Calendar.getInstance(); Date da=new Date();// 注意是在java.util.Date SimpleDateFormat format=new SimpleDateFormat("HH:mm:ss");//设置时间格式 da=ca.getTime(); time=format.format(da); 处理保存的注册信息时,可能做得不是很好,将hashmap写入文件,这样不能长期保存注册信息,只能在服务器不关闭的状态下,保存注册信息。这个也会继续改进 3、程序运行的时空效率分析 本程序应用了线程对端口进行侦听。线程一直后台开启,长期占用系统资源,但又能及时的实现端口监听和消息的收发。 4、测试数据集 服务器端程序主界面: 可以记录用户名,账号,IP地址,并可以发送系统消息 客户登陆注册界面 注册成功提示框 客户端运行界面 用户端测试界面 中间的(全部)按钮, 点击后可以显示当前在线的好友列表,并选择聊天对象 四、经验和体会。 这是我第一次做java项目,只是这个学期选修了面向程序设计,每周一节课,很浅显的学习了java语言,在综合性如此强的项目设计中,感觉力不从心。 刚开始的一天里遇到了各种困难,不管是从原理上还是细节语法上,都感觉很难,索性前两天没有着手做, 只是在看别人的项目制作过程去学习思路,或者是在看java中的io stream,swing,awt,net的基础原理和内容,然后的才慢慢一步一步的开始做,因为慢,所以坚持写,一共五天的时间,有两天没有睡觉。还好有些设计基础,在最后终于可以完成一些基本的目标。通过这次程序设计,进一步巩固、加深了我们所学专业课程的基本理论知识,理论联系实际,进一步培养了综合分析问题,解决问题的能力,进一步加强了JAVA的实际应用能力。在网络知识中,也许这个知识点是可以铭记终身的。在如此痛苦又快乐的第一次的后面,我想,要我继续写别的程序的话, 我可以更有信息一些,一些流程已经了然于胸。不管怎么样,只有自己动手做了,才能是自己牢牢掌握的知识。所以我也会坚持去做些动手的内容,去锻炼自己。 关键段落的代码:服务器端 Myserve.java package com.javake.Server; import java.io.IOException; import java.net.ServerSocket; import java.net.Socket; public class MyServe { private int inport; private ServerSocket server; //重写构造函数 public MyServe(int inport) { this.inport=inport; } //建立服务器 public void SetUP() { try { server=new ServerSocket(inport); while(true) { //获得socket对象 Socket sc=server.accept(); MyserThread th=new MyserThread(sc); //启动线程 th.start(); } } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } } public ServerSocket getServer() { return server; } } MyserThread.java package com.javake.Server; import java.io.*; import java.net.Socket; public class MyserThread extends Thread { private Socket socket; private MyUserInfo useIn; //用户对象 private InputStream input; private OutputStream output; public MyserThread(Socket socket) //建立构造函数 { this.socket=socket; //传入管道 try { output=socket.getOutputStream(); input=socket.getInputStream(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } } public void SendMess(String s) { byte[] b=s.getBytes(); try { output.write(b); output.flush(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } } public String ReadMess() { StringBuffer buff=new StringBuffer(); String s=""; boolean end=false; int i=0; try { //循环 while(!end) { i = input.read(); char c=(char)i; buff.append(c); s=buff.toString().trim(); if(s.endsWith("")) { s=new String(s.getBytes("ISO-8859-1"),"GBK").trim(); break; } } } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } return s; } //将第一条信息出来 public boolean ReadFirst() { String Mess=ReadMess(); String flag=GetValue("type", Mess); //截取标记 if(flag.equals("reg")) //判断是否为注册信息 { String name=GetValue("name", Mess); //获得注册的用户 String pwd=GetValue("pwd", Mess); MyUserInfo user=new MyUserInfo(); user.setName(name); user.setPassWord(pwd); if( MyCheckTool.SetUser(user)) //验证注册是否成功 { this.useIn=user; MyChatSet.sendReMess("regResp", "1", "欢迎来到进入聊天室", this); return true; } else { MyChatSet.sendReMess("regResp", "0用户已存在,注册失败" , this); return false; } } if(flag.equals("login")) //判断是否为登陆用户 { String name=GetValue("name", Mess); //获得注册的用户 String pwd=GetValue("pwd", Mess); String IP=GetValue("IP", Mess); MyUserInfo user=new MyUserInfo(); user.setName(name); user.setPassWord(pwd); user.setIPaddress(IP); if( MyCheckTool.CheckUser(user)) //验证登陆是否成功 { this.useIn=user; MyChatSet.sendReMess("loginResp", "1", "欢迎来到进入聊天室", this); MyChatSet.SendOSMess("onLine", this.useIn.getName());//发送用户上线消息 MyChatSet.addThread(this); //将线程加入队列 MyChatSet.SendList();//发送用户队列 return true; } else { MyChatSet.sendReMess("loginResp", "0用户名或账号错误" , this); } } return false; } //否则返回登陆失败 public String GetValue(String type ,String Mess) { int i=Mess.indexOf("<"+type+">")+type.length()+2; int j=Mess.indexOf(""+type+">"); String s= Mess.substring(i,j); //获得截取的信息 return s; } public void Pocess()//用以判断用户名、密码 { if(ReadFirst()) //登陆或注册成功 { String str=ReadMess(); String tp=GetValue("type", str); while(tp!="offLine") if(tp.equals("chat")){//获得类型标签 { String sender=GetValue("sender", str); //获得内容 String getter=GetValue("reciver", str); String content=GetValue("content", str); MyChatSet.SendChatMess("chat", sender, getter, content); //发送消息} else if(tp.equals("offline")) //判断是否为下线消息 { String user=GetValue("user", str); MyChatSet.SendOSMess("offline", user); } else if(tp.equals("login")) //注册后可能发来登陆消息 { String name=GetValue("name", str); //获得注册的用户 String pwd=GetValue("pwd", str); String IP=GetValue("IP", str); MyUserInfo user=new MyUserInfo(); user.setName(name); user.setPassWord(pwd); user.setIPaddress(IP); if( MyCheckTool.CheckUser(user)) //验证登陆是否成功 { this.useIn=user; MyChatSet.sendReMess("loginResp", "1", "欢迎来到进入聊天室", this); MyChatSet.addThread(this); System.out.println("二次注册或登录成功"); MyChatSet.SendList(); MyChatSet.SendOSMess("onLine", this.useIn.getName());//发送用户上线消息 } else //将线程加入队列//发送用户队列 { MyChatSet.sendReMess("loginResp", "0用户名或账号错误" , this); } } str=ReadMess(); tp=GetValue("type", str); } } try { //关闭socket socket.close(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } } public void run()//线程开始 { Pocess(); } //获得该线程中的用户信息 public MyUserInfo getUser() { return useIn; } } MyButLis.java package com.javake.Server; import java.awt.event.*; import java.io.IOException; import javax.swing.JTextField; public class MyButLis implements ActionListener{ private MyServe server; private JTextField filed1; public MyButLis(MyServe server,JTextField filed1) { this.server=server; this.filed1=filed1; } @Override public void actionPerformed(ActionEvent e) { String s=e.getActionCommand(); System.out.println("服务器在运行"); if(s.equals("终止")) { System.out.println("在进行中"); try { server.getServer().close(); } catch (IOException e1) { // TODO Auto-generated catch block e1.printStackTrace(); } } else if(s.equals("发送")) { MyUserInfo user=new MyUserInfo(); user.setName("服务器"); String Mess=filed1.getText(); MyChatSet.SendChatMess("chat", user.getName(), "", Mess); filed1.setText(""); } } } MyChatSet.java package com.javake.Server; import java.util.ArrayList; import javax.swing.SwingUtilities; import com.javake.Server.MyFrame; public class MyChatSet { public static ArrayList public static void addThread(MyserThread th) //增加一个服务器线程对象 { servers.add(th); SwingUtilities.updateComponentTreeUI(MyFrame.table); System.out.println("有"+servers.size()+"在线上"); } public static void sendReMess(String type,String state,String s,MyserThread myth) { myth.SendMess(" } public static void SendOSMess(String type,String user) { for(int i=0;i MyserThread myth=servers.get(i); //获得线程类 myth.SendMess(" } } public static void SendChatMess(String type,String sender,String reciver,String Mess) { if(reciver.equals(""))//如果获得的接受者为“”就是群发 { for(int i=0;i MyserThread myth=servers.get(i); //获得线程类 myth.SendMess(" } } else //发送给某个对象 { for(int i=0;i //获得线程类 MyserThread myth=servers.get(i); if(myth.getUser().getName().equals(reciver)) { myth.SendMess(" } } } } public static void SendList() { String list=" for(int i=0;i MyserThread ser=servers.get(i); String name=ser.getUser().getName(); list=list+name+ } list=list+" for(int i=0;i MyserThread ser=servers.get(i); ser.SendMess(list); //给每个在线用户发送列表 } } } 客户端: MySocketChat.java package com.javake.MySocket; import java.io.*; …….. { private Socket socket; private String IP="localhost"; private InputStream input; private OutputStream output; private JTextField field1; private JTextField field2; private String user; private JTextArea area; private boolean end=false; private MyChatFrame my; public MySocketChat(JTextField field1,JTextField field2,JTextArea area) { this.field1=field1; this.field2=field2; this.area=area; //建立socket try { socket=new Socket(IP,10000); input=socket.getInputStream(); output=socket.getOutputStream(); } catch (UnknownHostException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } } public String GetValue(String type ,String Mess) //截取字符串 { int i=Mess.indexOf("<"+type+">")+type.length()+2; int j=Mess.indexOf(""+type+">"); String s= Mess.substring(i,j); //获得截取的信息 return s; } public void SendMess(String type) { user=field1.getText();//账号 String pwd=field2.getText();//密码 String Mess=""; if(type.equals("reg")) { Mess=" }else { Mess=" } byte[] b=Mess.getBytes();//获得字符 try { output.write(b); //输出 output.flush(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace();} } public void SendChat(String getter,String chat) { String str=" byte[] b=str.getBytes(); try { output.write(b); output.flush(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace();} } public String ReadMess () throws IOException { StringBuffer buff=new StringBuffer(); String s=""; int i=0; boolean flag=false; while(!flag) //循环 { i = input.read(); char c=(char)i; buff.append(c); s=buff.toString().trim(); if(s.endsWith("")) { s=new String(s.getBytes("ISO-8859-1"),"GBK").trim(); break; } } return s; } public void CheckMess(String Mess) //判断是什么消息 { String time; Calendar ca=Calendar.getInstance(); SimpleDateFormat format=new SimpleDateFormat("HH:mm:ss"); java.util.Date da=new java.util.Date(); String type=this.GetValue("type", Mess); da=ca.getTime(); time=format.format(da); if(type.equals("chat")) {//当读入的类型是chat String sender=this.GetValue("sender", Mess); String content=this.GetValue("content", Mess); area.append(time+"\\r\\n"+sender+"说: "+content+"\\r\\n"); } else if(type.equals("offLine")) //上下线信息 { String name=this.GetValue("user", Mess); String show=name+" 下线了"; area.append(time+"\\r\\n"+show+"\\r\\n"); end=true; } else if(type.equals("onLine")) { String user=GetValue("user", Mess); area.append(time+"\\r\\n"+user+"上线了\\r\\n"); } else if(type.equals("budyList")) { System.out.println("------->用户列表已收到"); String users=this.GetValue("users", Mess); StringTokenizer toke=new StringTokenizer(users, JComboBox box=MyChatFrame.box; box.removeAllItems(); while(toke.hasMoreElements()) { String Item=toke.nextToken(); box.addItem(Item); } System.out.println("------->box列表"+box.getItemCount()); } } public Socket getSocket() { return socket; } public void setSocket(Socket socket) { this.socket = socket; } public void run()//设置启动方法 { try{ String Mess=this.ReadMess(); while(!end) { this.CheckMess(Mess); Mess=this.ReadMess(); } } catch(Exception e) { e.printStackTrace(); } } public JTextArea getArea() { return area; } public void setArea(JTextArea area) { this.area = area; } public MyChatFrame getMy() { return my; } public void setMy(MyChatFrame my) { this.my = my; }}下载本文