准备工作:
1.在服务端和客户端项目中都建立中间传递对象,implements序列化
1 public class ContextDemo implements Serializable { 2 3 // 0:上线 1:发送信息 2:下线 3:抖窗 4:发送文件 4 private int type; 5 private HashSet<String> clients; 6 private String info; 7 private String name; 8 private String time; 9 private String last;10 private ArrayList<Byte> list2;11 12 public String getLast() {13 return last;14 }15 16 public void setLast(String last) {17 this.last = last;18 }19 public ArrayList<Byte> getList2() {20 return list2;21 }22 23 public void setList2(ArrayList<Byte> list2) {24 this.list2 = list2;25 }26 27 public String getName() {28 return name;29 }30 31 public void setName(String name) {32 this.name = name;33 }34 35 public int getType() {36 return type;37 }38 39 public void setType(int type) {40 this.type = type;41 }42 43 public HashSet<String> getClients() {44 return clients;45 }46 47 public void setClients(HashSet<String> clients) {48 this.clients = clients;49 }50 51 public String getInfo() {52 return info;53 }54 55 public void setInfo(String info) {56 this.info = info;57 }58 59 public String getTime() {60 return time;61 }62 63 public void setTime(String time) {64 this.time = time;65 }66 67 }
2.服务端实现监听
服务端静态创建一个可以监听客户端连接的serversocket和可以存储在线用户名与socket的Hashmap,服务端开启监听,并在监听到任意客户端的连接时开启一个新的服务端监听线程(将该用户的socket通过构造函数传递给这个线程)
1 public static ServerSocket ss; 2 public static HashMap<String, Socket> online; 3 public static Socket socket; 4 5 static { 6 try { 7 ss = new ServerSocket(8080); 8 System.out.println("服务器已开启"); 9 online = new HashMap<String, Socket>();10 } catch (IOException e) {11 e.printStackTrace();12 }13 }14 15 public void action() {16 try {17 while (true) {18 Socket s = ss.accept();19 System.out.println("服务器正在监听");20 new Server_Thread(s).start();21 }22 } catch (IOException e) {23 // TODO Auto-generated catch block24 e.printStackTrace();25 }26 }27 28 public static void main(String[] args) {29 30 new Chat_Server().action();31 32 }
线程中定义
(1)发送给全部在线用户的方法,主要是通过创建Hashmap的iterator来实现遍历,获取到每一个在线用户的socket,并用这个用户的socket来建立outputstream进行消息的传递
1 // 定义发送给全部用户的方法 2 public void sendtoall(ContextDemo servercontext) { 3 Collection<Socket> clients = online.values(); 4 Iterator<Socket> iterator = clients.iterator(); 5 ObjectOutputStream oo; 6 while (iterator.hasNext()) { 7 8 Socket socket = iterator.next(); 9 try {10 oo = new ObjectOutputStream(socket.getOutputStream());11 oo.writeObject(servercontext);12 oo.flush();13 } catch (IOException e) {14 // TODO Auto-generated catch block15 e.printStackTrace();16 }17 }18 }
(2)发送给指定用户的方法,方法实现与发送给全部用户的方法基本一致,只是需要进行判断,如果选中的用户中包含遍历出来的在线用户,再取socket进行发送
1 // 定义发送给指定用户的方法 2 public void sendtothis(ContextDemo servercontext) { 3 HashSet<String> clients = clientcontext.getClients(); 4 Set<String> oo = online.keySet(); 5 Iterator<String> it = oo.iterator(); 6 //如果用的同一个输出流对象,可能会因前一个未写完而发生错误 7 ObjectOutputStream ooo; 8 while (it.hasNext()) { 9 String name = it.next();10 if (clients.contains(name)) {11 Socket s = online.get(name);12 try {13 ooo = new ObjectOutputStream(s.getOutputStream());14 ooo.writeObject(servercontext);15 ooo.flush();16 } catch (IOException e) {17 // TODO Auto-generated catch block18 e.printStackTrace();19 }20 }21 }22 }
注意:此时应该在其中单独定义一个objectoutputstream,因为写出的时候有时间差!!!此bug耗费10小时寻找!!!!(关掉一个客户端后,另外一个报IOexcption)
线程开始不停地监听从客户端发过来的信息(中间传递对象),并判断type,通过switch语句执行不同的操作
1 @Override2 public void run() {3 try {4 while (true) {5 in = new ObjectInputStream(socket.getInputStream());6 clientcontext = (ContextDemo) in.readObject();7 8 // 分析type的种类,来用以判断执行哪种操作9 switch (clientcontext.getType()) {
一、注册
界面:
获取到用户输入的用户名密码,进行一系列的判断,与配置文件中的数值进行比对,如果无误则保存当前用户输入的用户名、密码进配置文件,并成功登陆到聊天室界面(通过构造函数传递socket和username),否则则报错
1 try { 2 Properties userPro = new Properties(); 3 userPro.load(new FileInputStream("Users.properties")); 4 5 String username = usertext.getText(); 6 String passwordFirst = new String(password1.getPassword()); 7 String passwordLast = new String(password2.getPassword()); 8 9 if (username.length() != 0) {10 11 if (userPro.containsKey(username)) {12 JOptionPane.showMessageDialog(getContentPane(), "用户名已经存在!");13 } else {14 15 if(passwordFirst.equals(passwordLast)){16 if(passwordFirst.length()!=0){17 userPro.setProperty(username, passwordFirst);18 userPro.store(new FileOutputStream("Users.properties"), "用户名——密码");19 20 //进入聊天界面21 Socket socket = new Socket("localhost", 8080);22 ChatRoom chatRoom = new ChatRoom(username,socket);23 chatRoom.setVisible(true);24 setVisible(false);25 }else {26 JOptionPane.showMessageDialog(getContentPane(), "密码不能为空");27 }28 }else {29 JOptionPane.showMessageDialog(getContentPane(), "两次输入密码不一致");30 }31 }32 } else {33 JOptionPane.showMessageDialog(getContentPane(), "用户名不能为空");34 }35 }36 catch (FileNotFoundException e) {37 // TODO Auto-generated catch block38 e.printStackTrace();39 } catch (IOException e) {40 // TODO Auto-generated catch block41 e.printStackTrace();42 }
二、登录
界面:
用户输入用户名和密码,此时通过与项目自带的properties配置文件中的内容进行比对,如果有记录,则可以进入到聊天室(通过设置了每个testfield和登录按钮的keypressed事件,可通过检测按下回车按钮进入),若没有,则要求进入注册界面。
注意:如可以进入到聊天室界面,需要将此时新建的socket和用户名通过聊天室界面的构造函数传给聊天室。
1 try { 2 Properties properties = new Properties(); 3 properties.load(new FileInputStream("Users.properties")); 4 5 String usernameString = textusername.getText(); 6 String pasString = new String(password1.getPassword()); 7 8 if(properties.containsKey(usernameString)){ 9 if(properties.getProperty(usernameString).equals(pasString)){10 Socket socket = new Socket("localhost", 8080);11 ChatRoom chatRoom = new ChatRoom(usernameString,socket);12 chatRoom.setVisible(true);13 setVisible(false);14 }else {15 JOptionPane.showMessageDialog(getContentPane(), "密码输入错误");16 }17 }else {18 JOptionPane.showMessageDialog(getContentPane(), "该用户名不存在");19 }
三、聊天室主界面
界面:
1.本用户上线报告服务器+服务器将该用户上线信息发送给全部用户
(1)客户端的构造函数中,将本用户的socket,用户名,当前系统时间,中间传递对象的类型(上线类型为0),封装进中间传递对象并通过socket的outputstream发送给服务端,并开启客户端线程进行监控服务端传过来的信息
1 // 上线则汇报给服务器 2 try { 3 4 out = new ObjectOutputStream(socket.getOutputStream()); 5 // 创建传输对象 6 ContextDemo clientcontext = new ContextDemo(); 7 clientcontext.setType(0); 8 clientcontext.setName(username); 9 SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");10 clientcontext.setTime(sdf.format(new Date()));11 // 发送给服务端12 out.writeObject(clientcontext);13 out.flush();14 // 开启客户端线程15 new Client_Thread().start();16 17 } catch (IOException e) {18 // TODO Auto-generated catch block19 e.printStackTrace();20 }
客户端线程开始监听
1 @Override2 public void run() {3 4 try {5 while (true) {6 System.out.println("客户端线程启动");7 in = new ObjectInputStream(socket.getInputStream());8 final ContextDemo servercontext = (ContextDemo) in.readObject();9 switch (servercontext.getType()) {
(2)服务端得到该用户的中间传递对象,判断出执行上线操作,执行相应的switch语句,将用户名与socket存入hashmap,并创建当前用户的上线信息,对象类型type,以及在线用户名的集合封装进对象发送给所有的在线用户
1 // 上线 2 case 0: 3 // 添加在线用户进online 4 { 5 6 online.put(clientcontext.getName(), socket); 7 8 ContextDemo serverContext = new ContextDemo(); 9 // 将用户上线信息发送给各客户端10 serverContext.setType(0);11 serverContext.setInfo(clientcontext.getTime() + " " + clientcontext.getName() + " 上线啦!");12 // 将在线用户用户名全部发给客户端13 HashSet<String> set = new HashSet<String>();14 set.addAll(online.keySet());15 serverContext.setClients(set);16 sendtoall(serverContext);17 break;18 }
(3)客户端监听到信息,判断出为上下线更新操作,先清空在线列表集合,在将所要取得的各种信息从对象中取得,并获取到在线人数集合中数目以实现在线人数显示,并将上线消息显示在testArea中
1 case 0: 2 // 重新装填容器 3 { 4 // 清空容器 5 online.clear(); 6 HashSet<String> set = servercontext.getClients(); 7 Iterator<String> iterator = set.iterator(); 8 while (iterator.hasNext()) { 9 String name = iterator.next();10 if (username.equals(name)) {11 online.add(name + "(本机)");12 13 } else {14 online.add(name);15 }16 }17 18 // 显示在list中19 listmodel = new UUListModel(online);20 count = listmodel.getSize();21 list.setBorder(new TitledBorder(UIManager.getBorder("TitledBorder.border"), "在线用户:"+count+"人",22 TitledBorder.LEADING, TitledBorder.TOP, null, new Color(255, 0, 0)));23 list.setModel(listmodel);24 docs.insertString(docs.getLength(), servercontext.getInfo() + "\n", attrset);25 break;26 }
2.发送消息
(1)客户端获取到list中选中的用户(若不选则为群发),将该选中用户、自己的用户名、时间、要发送的信息等内容封装进对象,设置type为1(发送信息识别码),将对象传递给服务器,在自己的聊天界面添加聊天的内容,随后清空list表的选中状态以及发送框的内容(发送按钮与回车键均可发送消息)
1 // 发送按钮 2 JButton btnNewButton = new JButton("\u53D1\u9001"); 3 btnNewButton.addActionListener(new ActionListener() { 4 public void actionPerformed(ActionEvent arg0) { 5 6 List selected = list.getSelectedValuesList(); 7 String info = textArea_1.getText(); 8 9 if (selected.size() < 1) {10 // 在客户端中,容器online中本机的名字后面多加了字符,所以在客户端不能与hashmap中的key匹配,所以本机收不到本机传来的信息11 selected = online;12 }13 14 if (info.equals("")) {15 JOptionPane.showMessageDialog(getContentPane(), "不能发送空信息");16 return;17 }18 19 ContextDemo clientcontext = new ContextDemo();20 clientcontext.setInfo(info);21 SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");22 String time = sdf.format(new Date());23 clientcontext.setTime(time);24 clientcontext.setType(1);// 发信息25 clientcontext.setName(username);26 HashSet<String> people = new HashSet<String>();27 people.addAll(selected);28 29 clientcontext.setClients(people);30 31 // 清空发送页面32 textArea_1.setText("");33 // 发送界面获得焦点34 textArea_1.requestFocus();35 // 列表消除选中状态36 list.clearSelection();37 38 try {39 out = new ObjectOutputStream(ChatRoom.socket.getOutputStream());40 out.writeObject(clientcontext);41 out.flush();42 } catch (IOException e) {43 // TODO Auto-generated catch block44 e.printStackTrace();45 }46 47 try {48 docs.insertString(docs.getLength(), time + " " + "我对"+selected+"说:\n" + info + "\n", attrset);49 } catch (BadLocationException e) {50 // TODO Auto-generated catch block51 e.printStackTrace();52 }53 }54 });
(2)服务器判断出为发送消息后,将得到的信息、时间等消息进行封装,再通过发送给指定用户的方法将封装好的对象传递给指定的用户
1 // 发信息 2 case 1: { 3 ContextDemo servercontext = new ContextDemo(); 4 servercontext.setType(1); 5 servercontext.setName(clientcontext.getName()); 6 servercontext.setInfo(clientcontext.getInfo()); 7 servercontext.setTime(clientcontext.getTime()); 8 9 sendtothis(servercontext);10 break;11 }
(3)指定的客户端收到对象后判断出了是要进行发送消息的操作,解封对象,取得消息等信息,在消息窗口显示
1 // 发信息2 case 1: {3 String info = servercontext.getInfo();4 String time = servercontext.getTime();5 docs.insertString(docs.getLength(),6 time + " " + servercontext.getName() + "对我说:\n" + info + "\n", attrset);7 break;8 }
3.下线
(1)聊天室页面添加窗口关闭的监听事件,在其后弹出对话框确定是否退出,若退出则设置type为2(下线识别码)、时间等信息封装进对象发送给服务器
1 // 下线操作 2 this.addWindowListener(new WindowAdapter() { 3 @Override 4 public void windowClosing(WindowEvent e) { 5 6 int result = JOptionPane.showConfirmDialog(getContentPane(),"您确定要离开聊天室"); 7 if (result == 0) { 8 ContextDemo clientcontext = new ContextDemo(); 9 clientcontext.setType(2);10 SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");11 clientcontext.setTime(sdf.format(new Date()));12 clientcontext.setName(username);13 14 try {15 out = new ObjectOutputStream(socket.getOutputStream());16 out.writeObject(clientcontext);17 out.flush();18 } catch (IOException e1) {19 // TODO Auto-generated catch block20 e1.printStackTrace();21 }22 }23 }24 });
(2)服务器判断为是下线操作后,首先建立服务端对象,设置type为2,用当前用户传递过来的socket传递此对象给即将下线的客户端批准其下线,随后用下线用户的用户名在Hashmap中删除此用户,随后新建另一服务端对象,封装下线的信息、时间、当前在线用户名等内容发送给其余所有在线的客户端,让他们执行上下线更新操作,并在case的最后用return退出whlie(true)循环,在finally中关闭掉输入输出流对象与socket对象,意在此客户端与服务端的线程关闭
1 // 下线 2 case 2: { 3 // 通知请求下线客户端,批准下线 4 ContextDemo servercontext1 = new ContextDemo(); 5 servercontext1.setType(2); 6 out = new ObjectOutputStream(socket.getOutputStream()); 7 out.writeObject(servercontext1); 8 out.flush();//需要时间 9 10 // 刷新在线hashmap11 online.remove(clientcontext.getName());12 13 // 通知其他在线用户,该用户已下线14 ContextDemo servercontext2 = new ContextDemo();15 servercontext2.setType(0);16 servercontext2.setInfo(clientcontext.getTime() + " " + clientcontext.getName() + "用户已下线 ");17 HashSet<String> set = new HashSet<String>();18 set.addAll(online.keySet());19 servercontext2.setClients(set);20 sendtoall(servercontext2);21 // 退出当前循环,不再监听该客户端传来的信息22 return;23 }
(3)
1)即将下线的客户端接收到对象后,判断出是执行下线操作,用return退出while(true),并在finally中关闭掉socket与输入输出流对象,并用System.exit(0)退出虚拟机断线
1 // 下线2 case 2:{3 return;4 }
2)其余在线客户端接收到对象,判断是要进行上下线操作,所以执行与上线更新时一样的操作
1 // 上下线刷新操作 2 case 0: 3 // 重新装填容器 4 { 5 // 清空容器 6 online.clear(); 7 HashSet<String> set = servercontext.getClients(); 8 Iterator<String> iterator = set.iterator(); 9 while (iterator.hasNext()) {10 String name = iterator.next();11 if (username.equals(name)) {12 online.add(name + "(本机)");13 14 } else {15 online.add(name);16 }17 }18 19 // 显示在list中20 listmodel = new UUListModel(online);21 count = listmodel.getSize();22 list.setBorder(new TitledBorder(UIManager.getBorder("TitledBorder.border"), "在线用户:"+count+"人",23 TitledBorder.LEADING, TitledBorder.TOP, null, new Color(255, 0, 0)));24 list.setModel(listmodel);25 docs.insertString(docs.getLength(), servercontext.getInfo() + "\n", attrset);26 break;27 }
4.抖窗
(1)客户端将列表中得到的用户名封装进对象,设置type为3,再将时间等信息封装进对象,将对象发送给服务端
1 btnShakeFrame.addActionListener(new ActionListener() { 2 public void actionPerformed(ActionEvent e) { 3 4 List selected = list.getSelectedValuesList(); 5 6 if (selected.size() < 1) { 7 // 在客户端中,容器online中本机的名字后面多加了字符,所以在客户端不能与hashmap中的key匹配,所以本机收不到本机传来的信息 8 selected = online; 9 }10 11 ContextDemo clientcontext = new ContextDemo();12 clientcontext.setType(3);13 SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");14 String time = sdf.format(new Date());15 clientcontext.setTime(time);16 clientcontext.setName(username);17 HashSet<String> set = new HashSet<String>();18 set.addAll(selected);19 clientcontext.setClients(set);20 21 // 列表消除选中状态22 list.clearSelection();23 24 try {25 out = new ObjectOutputStream(socket.getOutputStream());26 out.writeObject(clientcontext);27 out.flush();28 } catch (IOException e1) {29 // TODO Auto-generated catch block30 e1.printStackTrace();31 }32 33 try {34 docs.insertString(docs.getLength(), time + "您发送了一个屏幕抖动\n", attrset);35 } catch (BadLocationException e1) {36 // TODO Auto-generated catch block37 e1.printStackTrace();38 }39 }40 });
(2)服务端判断出是要进行抖窗操作后,封装有用的信息进对象,并通过发送给指定用户方法进行发送
1 // 抖窗2 case 3: {3 ContextDemo servercontext = new ContextDemo();4 servercontext.setType(3);5 servercontext.setInfo(clientcontext.getTime() + " " + clientcontext.getName() + "对你抖了一下屏 \n");6 7 sendtothis(servercontext);8 break;9 }
(3)指定的客户端判断要进行抖屏,先解封有用的信息进消息显示窗口,然后再调用封装好的抖屏方法进行抖屏操作
1 // 抖窗2 case 3: {3 String info = servercontext.getInfo();4 docs.insertString(docs.getLength(), info, attrset);5 shakeFrame();6 break;7 }
1 public void shakeFrame() { 2 int x = ChatRoom.this.getX(); 3 int y = ChatRoom.this.getY(); 4 for (int i = 0; i < 20; i++) { 5 if ((i & 1) == 0) { 6 x += 8; 7 y += 8; 8 } else { 9 x -= 8;10 y -= 8;11 }12 ChatRoom.this.setLocation(x, y);13 try {14 Thread.sleep(50);15 } catch (InterruptedException e1) {16 e1.printStackTrace();17 }18 }19 }
5.发送文件
(1)客户端先获取到想要发送文件给哪个用户的用户名,随后弹出文件选择框,构建文件的抽象路径,并得到后缀名,其后通过文件的输入流将所得到的文件一个字节一个字节的读入到Arraylist<byte>集合中,将后缀名保存到一个字符串中,将这些与其他有用的信息一起封装进对象,将对象发送给服务器
1 btnSendFile.addActionListener(new ActionListener() { 2 public void actionPerformed(ActionEvent e) { 3 4 List selected = list.getSelectedValuesList(); 5 if (selected.size() < 1) { 6 JOptionPane.showMessageDialog(getContentPane(), "必须选择一名用户"); 7 return; 8 } 9 if (selected.toString().contains(username+"(本机)")) { 10 JOptionPane.showMessageDialog(getContentPane(), "不能向自己发送文件"); 11 return; 12 } 13 14 JFileChooser chooser = new JFileChooser(); 15 chooser.setDialogTitle("选择文件框"); 16 chooser.showDialog(getContentPane(), "选择"); 17 18 //判断是否拿到文件 19 if(chooser.getSelectedFile()!=null){ 20 path = chooser.getSelectedFile().getPath(); 21 file = new File(path); 22 //获取文件的后缀名 23 int i =file.getName().indexOf("."); 24 last = file.getName().substring(i); 25 26 //判断文件是否为空 27 if(file.length()==0){ 28 JOptionPane.showMessageDialog(getContentPane(), "文件为空,不能传送"); 29 return; 30 } 31 } 32 33 ContextDemo clientcontext = new ContextDemo(); 34 //发送文件(基础工作) 35 clientcontext.setType(4); 36 //保存后缀名 37 clientcontext.setLast(last); 38 clientcontext.setName(username); 39 HashSet<String> set = new HashSet<String>(); 40 set.addAll(selected); 41 clientcontext.setClients(set); 42 SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); 43 String time = sdf.format(new Date()); 44 clientcontext.setTime(time); 45 46 //发送文件(文件部分操作) 47 list2 = new ArrayList<Byte>(); 48 try { 49 input = new FileInputStream(file); 50 51 int result = -1; 52 53 while((result = input.read()) != -1) 54 { 55 list2.add((byte)result); 56 } 57 58 59 60 } catch (FileNotFoundException e2) { 61 // TODO Auto-generated catch block 62 e2.printStackTrace(); 63 } catch (IOException e1) { 64 // TODO Auto-generated catch block 65 e1.printStackTrace(); 66 }finally { 67 68 try { 69 if(input != null) 70 { 71 input.close(); 72 } 73 } catch (IOException e1) { 74 e1.printStackTrace(); 75 } 76 } 77 78 clientcontext.setList2(list2); 79 80 81 // 列表消除选中状态 82 list.clearSelection(); 83 84 //发送给服务端 85 try { 86 out = new ObjectOutputStream(socket.getOutputStream()); 87 out.writeObject(clientcontext); 88 out.flush(); 89 } catch (IOException e1) { 90 // TODO Auto-generated catch block 91 e1.printStackTrace(); 92 } 93 94 try { 95 docs.insertString(docs.getLength(), time + "您发送了一个文件\n", attrset); 96 } catch (BadLocationException e1) { 97 // TODO Auto-generated catch block 98 e1.printStackTrace(); 99 }100 }101 });102
(2)服务端判断出是要进行文件的发送操作,将所有有用的信息封装进对象发送给指定的用户
1 // 发送文件 2 case 4: { 3 ContextDemo servercontext = new ContextDemo(); 4 servercontext.setList2(clientcontext.getList2()); 5 servercontext.setLast(clientcontext.getLast()); 6 servercontext.setInfo(clientcontext.getTime() + " " + clientcontext.getName() + "对你发送了一个文件\n"); 7 servercontext.setType(4); 8 9 sendtothis(servercontext);10 11 break;12 }
(3)指定客户端收到对象后,先将字节集合解封出来,用iterator将字节写入一个字节数组,再通过缓冲字节流读入字节,通过解封出来的后缀名构造好新的文件后,通过缓冲字节流写入文件,并接收到需要的信息显示在消息窗口中
1 //接收到文件 2 case 4: 3 { 4 ArrayList<Byte> list3 = new ArrayList<Byte>(); 5 list3 = servercontext.getList2(); 6 Iterator<Byte> it = list3.iterator(); 7 byte[] dedao = new byte[10000000]; 8 int i = 0; 9 while(it.hasNext()){10 dedao[i] = it.next();11 i++;12 }13 14 15 ii = new ByteArrayInputStream(dedao);16 inputStream = new BufferedInputStream(ii,10000);17 //获取后缀名18 String thLast = servercontext.getLast();19 output = new FileOutputStream("sourse/dedao"+thLast);20 outputStream = new BufferedOutputStream(output,10000);21 22 byte[] b = new byte[10000];//暂存读取的字节内容23 int result = -1;24 while((result = inputStream.read(b)) != -1)25 {26 outputStream.write(b, 0, result);27 }28 output.flush();29 30 31 32 String info = servercontext.getInfo();33 docs.insertString(docs.getLength(), info, attrset);34 break;35 }
原标题:Java项目——聊天器
关键词:JAVA