你的位置:首页 > Java教程

[Java教程]Java项目——聊天器


准备工作:

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           }