模拟QQ聊天
1、一个服务器可以与多个用户同时通讯
2、用户可以通过服务器与用户之间通讯
3、用户可以选择和所有人发消息,也可以选择和某个用户单独发消息
4、服务器要显示当前所有在线人员
5、用户要显示当前在线的人员
6、当有新用户登录时或在线用户退出时,服务器要向所有其他在线用户发送提示信息,并且服务器也要显示相应的提示信息
7、不能有相同的用户名同时登陆
8、不能发送空消息
9、客户端可以设置连接的服务器IP和端口
在服务器端 用一个HashMap<userName,socket> 维护所有用户相关的信息,从而能够保证和所有的用户进行通讯。
客户端的动作:
(1)连接(登录):发送userName 服务器的对应动作:1)界面显示,2)通知其他用户关于你登录的信息, 3)把其他在线用户的userName通知当前用户 4)开启一个线程专门为当前线程服务
(2)退出(注销):
(3)发送消息
※※发送通讯内容之后,对方如何知道是干什么,通过消息协议来实现:
客户端向服务器发的消息格式设计:
命令关键字@#接收方@#消息内容@#发送方
1)连接:userName ----握手的线程serverSocket专门接收该消息,其它的由服务器新开的与客户进行通讯的socket来接收
2)退出:exit@#全部@#null@#userName
3)发送: on @# JList.getSelectedValue() @# tfdMsg.getText() @# tfdUserName.getText()
服务器向客户端发的消息格式设计:
命令关键字@#发送方@#消息内容
登录:
1) msg @#server @# 用户[userName]登录了 (给客户端显示用的)
2) cmdAdd@#server @# userName (给客户端维护在线用户列表用的)
退出:
1) msg @#server @# 用户[userName]退出了 (给客户端显示用的)
2) cmdRed@#server @# userName (给客户端维护在线用户列表用的)
发送:
msg @#消息发送者( msgs[3] ) @# 消息内容 (msgs[2])
三、代码
ClientForm.java
package cn.hncu; import java.awt.BorderLayout; import java.awt.Dimension; import java.awt.FlowLayout; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.WindowAdapter; import java.awt.event.WindowEvent; import java.io.IOException; import java.io.PrintWriter; import java.net.Socket; import java.util.Scanner; import javax.swing.DefaultListModel; import javax.swing.JButton; import javax.swing.JDialog; import javax.swing.JFrame; import javax.swing.JLabel; import javax.swing.JList; import javax.swing.JMenu; import javax.swing.JMenuBar; import javax.swing.JMenuItem; import javax.swing.JOptionPane; import javax.swing.JPanel; import javax.swing.JScrollPane; import javax.swing.JTextArea; import javax.swing.JTextField; import javax.swing.ListSelectionModel; import javax.swing.border.TitledBorder; public class ClientForm extends JFrame implements ActionListener{ private JTextField tfdUserName=null; private JTextArea allMsg=null; private DefaultListModel dlm=null; private JList list=null; private JTextField tfdMsg=null; private static PrintWriter pw; private static String HOST="192.168.1.106"; private static int PORT=9090; private static Socket clientSocket=null; public ClientForm(){ //menu addMenu(); //panel above JPanel p1=new JPanel(); p1.add(new JLabel("userLogo")); tfdUserName=new JTextField(10); p1.add(tfdUserName); JButton connect=new JButton("connect"); connect.setActionCommand("connect"); connect.addActionListener(this); JButton exit=new JButton("exit"); exit.setActionCommand("exit"); exit.addActionListener(this); p1.add(connect); p1.add(exit); this.getContentPane().add(p1,BorderLayout.NORTH); //middle panel JPanel p2=new JPanel(new BorderLayout()); this.getContentPane().add(p2,BorderLayout.CENTER); //online list widget dlm=new DefaultListModel(); dlm.addElement("ALL"); list=new JList(dlm); list.setSelectedIndex(0); list.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); list.setVisibleRowCount(2); JScrollPane js=new JScrollPane(list); js.setBorder(new TitledBorder("Online")); js.setPreferredSize(new Dimension(70, p2.getHeight())); p2.add(js,BorderLayout.EAST); //chatting message area allMsg=new JTextArea(); allMsg.setEditable(false); p2.add(new JScrollPane(allMsg),BorderLayout.CENTER); //message sending panel JPanel p3=new JPanel(); JLabel messageLabel=new JLabel("message"); p3.add(messageLabel); tfdMsg=new JTextField(20); p3.add(tfdMsg); JButton sendBtn=new JButton("send"); sendBtn.setActionCommand("send"); sendBtn.addActionListener(this); p3.add(sendBtn); this.getContentPane().add(p3,BorderLayout.SOUTH); this.addWindowListener(new WindowAdapter() { @Override public void windowClosing(WindowEvent e) { sendExitMsg(); } }); this.setBounds(300, 300, 400, 300); this.setVisible(true); } private void addMenu() { JMenuBar menubar=new JMenuBar(); this.setJMenuBar(menubar); JMenu menu=new JMenu("Selection"); menubar.add(menu); JMenuItem itemSet=new JMenuItem("Set"); JMenuItem itemHelp=new JMenuItem("Help"); menu.add(itemSet); menu.add(itemHelp); itemSet.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { final JDialog dlg=new JDialog(ClientForm.this); dlg.setLayout(new FlowLayout()); dlg.setBounds(ClientForm.this.getX()+40, ClientForm.this.getY()+40, 300, 100); dlg.add(new JLabel("server")); final JTextField tfdId=new JTextField(HOST); final JTextField tfdPort=new JTextField(""+PORT); dlg.add(tfdId); dlg.add(new JLabel(":")); dlg.add(tfdPort); JButton btnSet=new JButton("Set"); dlg.add(btnSet); btnSet.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { HOST=tfdId.getText(); PORT=Integer.parseInt(tfdPort.getText()); dlg.dispose(); } }); dlg.setVisible(true); } }); itemHelp.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { JDialog dlg=new JDialog(ClientForm.this); dlg.setLayout(new FlowLayout()); dlg.setBounds(ClientForm.this.getX()+40, ClientForm.this.getY()+40, 300, 100); dlg.add(new JLabel("[email protected],QQ:666666")); dlg.setVisible(true); } }); } @Override public void actionPerformed(ActionEvent e) { if (e.getActionCommand().equalsIgnoreCase("connect")){ System.out.println("connecting server"); String userName=tfdUserName.getText().trim(); byte byteName[]=userName.getBytes(); if (byteName.length==0){ JOptionPane.showMessageDialog(this, "please input right name format"); return; } else { try { connecting(userName); } catch (Exception e1) { JOptionPane.showMessageDialog(this, "Failed in connecting,please check your internet or ip address and port"); return; } ((JButton)e.getSource()).setEnabled(false); tfdUserName.setEditable(false); } } if (e.getActionCommand().equalsIgnoreCase("send")){ if (tfdMsg.getText()==null){ JOptionPane.showMessageDialog(this, "Please input dialog message"); return; } String str="on@#"+list.getSelectedValue()+"@#"+tfdMsg.getText()+"@#"+tfdUserName.getText(); try { pw.println(str); pw.flush(); } catch (Exception e1) { JOptionPane.showMessageDialog(this, "Please connect the server first."); return; } tfdMsg.setText(""); } if (e.getActionCommand().equalsIgnoreCase("exit")){ sendExitMsg(); } } private void sendExitMsg() { if (clientSocket==null){ System.exit(0); } String str="exit@#all@#null@#"+tfdUserName.getText(); pw.println(str); pw.flush(); System.exit(0); } private void connecting(String userName) throws Exception{ clientSocket=new Socket(HOST,PORT); if (userName!=""){ pw=new PrintWriter(clientSocket.getOutputStream(),true); pw.println(userName); this.setTitle("User ["+userName+"] is online..."); new ClientThread().start(); } } class ClientThread extends Thread{ @Override public void run() { try { Scanner input=new Scanner(clientSocket.getInputStream()); while (input.hasNextLine()){ String str=input.nextLine(); String msgs[]=str.split("@#"); if ("msg".equals(msgs[0])){ if ("server".equals(msgs[1])){ str="[notify]:"+msgs[2]; } else{ str="["+msgs[1]+"] says:"+msgs[2]; } allMsg.append("\r\n"+str); } else if ("cmdAdd".equals(msgs[0])){ dlm.addElement(msgs[2]); } else if ("cmdRed".equalsIgnoreCase(msgs[0])){ dlm.removeElement(msgs[2]); } } } catch (IOException e) { e.printStackTrace(); } } } public static void main(String[] args) { JFrame.setDefaultLookAndFeelDecorated(true); new ClientForm(); } }ServerForm.java
package cn.hncu; import java.awt.BorderLayout; import java.awt.Dimension; import java.awt.Toolkit; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.KeyEvent; import java.io.IOException; import java.io.PrintWriter; import java.net.ServerSocket; import java.net.Socket; import java.util.HashMap; import java.util.Iterator; import java.util.Map; import java.util.Scanner; import javax.swing.DefaultListModel; import javax.swing.JFrame; import javax.swing.JList; import javax.swing.JMenu; import javax.swing.JMenuBar; import javax.swing.JMenuItem; import javax.swing.JOptionPane; import javax.swing.JScrollPane; import javax.swing.JTextArea; import javax.swing.KeyStroke; import javax.swing.border.TitledBorder; public class ServerForm extends JFrame { private static final int PORT=9090; private JTextArea area; private DefaultListModel dlm; private Map<String, Socket> map=new HashMap<String, Socket>(); public ServerForm() { this.setDefaultCloseOperation(EXIT_ON_CLOSE); final int winWidth=(int) Toolkit.getDefaultToolkit().getScreenSize().getWidth(); final int winHeight=(int) Toolkit.getDefaultToolkit().getScreenSize().getHeight(); //message window area=new JTextArea(); area.setEditable(false); this.getContentPane().add(new JScrollPane(area),BorderLayout.CENTER); //online list widget dlm=new DefaultListModel(); JList list=new JList(dlm); JScrollPane js=new JScrollPane(list); js.setBorder(new TitledBorder("Online")); js.setPreferredSize(new Dimension(100, this.getHeight())); this.getContentPane().add(js,BorderLayout.EAST); //menu JMenuBar bar=new JMenuBar(); this.setJMenuBar(bar); JMenu menu=new JMenu("Control(C)"); bar.add(menu); menu.setMnemonic('C'); final JMenuItem run=new JMenuItem("run"); run.setActionCommand("run"); run.setAccelerator(KeyStroke.getKeyStroke('R',KeyEvent.CTRL_MASK)); menu.add(run); menu.addSeparator(); final JMenuItem exit=new JMenuItem("exit"); exit.setActionCommand("exit"); exit.setAccelerator(KeyStroke.getKeyStroke('E',KeyEvent.CTRL_MASK)); menu.add(exit); ActionListener al=new ActionListener() { @Override public void actionPerformed(ActionEvent e) { if (e.getActionCommand().equalsIgnoreCase("run")){ startServer(); run.setEnabled(false); } else { System.exit(0); } } }; run.addActionListener(al); exit.addActionListener(al); this.setBounds(winWidth/4, winHeight/4, winWidth/2, winHeight/2); this.setVisible(true); } private void startServer() { try { ServerSocket server=new ServerSocket(PORT); area.append("start service:"+server); new ServerThread(server).start(); } catch (IOException e) { e.printStackTrace(); } } class ServerThread extends Thread{ private ServerSocket server=null; ServerThread(ServerSocket server){ this.server=server; } @Override public void run() { try { while (true){ Socket clientSocket=server.accept(); Scanner input=new Scanner(clientSocket.getInputStream()); if (input.hasNextLine()){ String userName=input.nextLine(); area.append("\r\n user:["+userName+"] login,"+clientSocket); dlm.addElement(userName); new ClientThread(clientSocket).start(); msgAll(userName); msgSelf(clientSocket); map.put(userName, clientSocket); } } } catch (IOException e) { e.printStackTrace(); } } } class ClientThread extends Thread{ private Socket clientSocket=null; public ClientThread(Socket clientSocket) { this.clientSocket = clientSocket; } @Override public void run() { try { Scanner input=new Scanner(clientSocket.getInputStream()); while (input.hasNextLine()){ String str=input.nextLine(); String msgs[]=str.split("@#"); if (msgs.length!=4){ JOptionPane.showMessageDialog(null, "wrong message format"); } if ("on".equalsIgnoreCase(msgs[0])){ sendMsgToSb(msgs); } if ("exit".equalsIgnoreCase(msgs[0])){ map.remove(msgs[3]); dlm.removeElement(msgs[3]); sendExitMsgToAll(msgs); area.append("\r\n user["+msgs[3]+"] leaves."); } } } catch (IOException e) { e.printStackTrace(); } } } private void sendExitMsgToAll(String msgs[]) throws IOException { Iterator<String> it=map.keySet().iterator(); while (it.hasNext()){ String userName=it.next(); Socket s=map.get(userName); PrintWriter pw=new PrintWriter(s.getOutputStream(), true); String msg="msg@#server@#user ["+msgs[3]+"] leaves"; pw.println(msg); msg="cmdRed@#server@#"+msgs[3]; pw.println(msg); pw.flush(); } } private void sendMsgToSb(String[] msgs) throws IOException { if ("all".equalsIgnoreCase(msgs[1])){ Iterator<String> userNames=map.keySet().iterator(); while (userNames.hasNext()){ String userName=userNames.next(); Socket s=map.get(userName); String msg="msg@#"+msgs[3]+"@#"+msgs[2]; PrintWriter pw=new PrintWriter(s.getOutputStream(), true); pw.println(msg); pw.flush(); } } else { String userName=msgs[1]; Socket s=map.get(userName); String msg="msg@#"+msgs[3]+"@#"+msgs[2]; PrintWriter pw=new PrintWriter(s.getOutputStream(), true); pw.println(msg); pw.flush(); } } private void msgAll(String userName) { Iterator<Socket> it = map.values().iterator(); while(it.hasNext()){ Socket s = it.next(); try { PrintWriter pw = new PrintWriter(s.getOutputStream(), true); String msg = "msg@#server@#用户["+userName+"]登录了."; pw.println(msg); pw.flush(); msg = "cmdAdd@#server@#"+userName; pw.println(msg); pw.flush(); } catch (IOException e) { e.printStackTrace(); } } } private void msgSelf(Socket clientSocket) { try { PrintWriter pw=new PrintWriter(clientSocket.getOutputStream(),true); Iterator<String> it=map.keySet().iterator(); while (it.hasNext()){ String msg="cmdAdd@#server@#"+it.next(); pw.println(msg); pw.flush(); } } catch (IOException e) { e.printStackTrace(); } } public static void main(String[] args) { JFrame.setDefaultLookAndFeelDecorated(true); new ServerForm(); } }
本程序修复好的漏洞:
没有输入用户名应不可登录
登陆后应不可再次点击登录按钮
没有登录应不可发送消息且发送的消息不可为空
IP地址和端口错误也不可登录