sina面试题--用图形用户界面模拟聊天室

要求:自定义一个服务器,接收浏览器发来的信息。显示浏览器发送了什么信息,并向浏览器发送简单的网页信息。


设计思想


在服务器端 用一个HashMap 维护所有用户相关的信息,从而能够保证和所有的用户进行通讯。

客户端的动作:
(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])


客户端:
sina面试题--用图形用户界面模拟聊天室_第1张图片
服务器:
sina面试题--用图形用户界面模拟聊天室_第2张图片

这是客户端代码:

package cn.hncu.chat;

import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.Font;
import java.awt.List;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.awt.image.TileObserver;
import java.io.IOException;
import java.io.PrintWriter;
import java.net.Socket;
import java.net.UnknownHostException;
import java.util.Date;
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.JScrollBar;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
import javax.swing.JTextField;
import javax.swing.ListSelectionModel;
import javax.swing.border.Border;
import javax.swing.border.TitledBorder;

public class ClientForm extends JFrame implements ActionListener{
    private JTextField tfdUserName;
    private JTextArea allMsg ;
    private JList list;
    private JTextField tfdMsg;
    private DefaultListModel lm;
    private PrintWriter pw;
    private static String HOST="127.0.0.1";//必须是静态的,因为内部的内部类要用,要改值,又不能是final
    private static int PORT=9090;
    private Socket socketClient;
    public ClientForm() {
        setBounds(300, 200, 400, 300);
        setDefaultCloseOperation(EXIT_ON_CLOSE);
        //上边布局
        JPanel panelUp=new JPanel();
        panelUp.add(new JLabel("用户标识 : "));
        tfdUserName=new JTextField(10);
        tfdUserName.setFont(new Font("a", Font.BOLD, 16));
        panelUp.add(tfdUserName);
        JButton btnCon=new JButton("连接");
        btnCon.setActionCommand("con");
        panelUp.add(btnCon);
        JButton btnExit=new JButton("断开");
        btnExit.setActionCommand("exit");
        panelUp.add(btnExit);
        getContentPane().add(panelUp,BorderLayout.NORTH);

        //中部布局
        JPanel panelMid=new JPanel(new BorderLayout());
        getContentPane().add(panelMid);
        allMsg =new JTextArea();
        allMsg.setEditable(false);
        lm=new DefaultListModel();//分层设计,有利于维护
        list=new JList(lm);
        lm.addElement("全部");
        list.setSelectedIndex(0);
//      list.setVisibleRowCount(2);
        list.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);//此方法是在选择模型上直接设置选择模式的覆盖方法。
        panelMid.add(new JScrollPane(allMsg) , BorderLayout.CENTER);
        JScrollPane jp=new JScrollPane(list);//jp.setViewportView(list);//如果构造函数为空用着个添加
        getContentPane().add(jp,BorderLayout.EAST);
        jp.setBorder(new TitledBorder("在线"));//具有指定边框和标题
        jp.setPreferredSize(new Dimension(70, panelMid.getWidth()));//注意添加的顺序
        //底层信息面板
        JPanel panelMsg=new JPanel();
        getContentPane().add(panelMsg,BorderLayout.SOUTH);
        panelMsg.add(new JLabel("信息:"));
        tfdMsg=new JTextField(20);
        tfdMsg.setFont(new Font("a", Font.BOLD, 13));
        panelMsg.add(tfdMsg);
        JButton btnMsg=new JButton("发送");
        panelMsg.add(btnMsg);
        btnMsg.setActionCommand("send");
        btnCon.addActionListener(this);
        btnExit.addActionListener(this);
        btnMsg.addActionListener(this);
        addWindowListener(new WindowAdapter() {

            @Override
            public void windowClosing(WindowEvent e) {
                sendExitMsg();
            }

        } );//用windowAdatpter继承WindowListener

        addMenu();
        this.setVisible(true);
    }
    private void addMenu() {
        JMenuBar menubar=new JMenuBar();
        this.setJMenuBar(menubar);
        JMenu menu=new JMenu("选项");
        menubar.add(menu);
        JMenuItem menuItemSet=new JMenuItem("设置");
        menu.add(menuItemSet);
        menuItemSet.addActionListener(new ActionListener() {

            @Override
            public void actionPerformed(ActionEvent arg0) {
                final JDialog dlg=new JDialog();//用这个框架,可以添加东西
                dlg.setLayout(new FlowLayout());
                dlg.setBounds(ClientForm.this.getX(), ClientForm.this.getY(), 380, 100);
                dlg.add(new JLabel("服务器地址:"));
                final JTextField tfdHost=new JTextField(HOST,15);
                dlg.add(tfdHost);
                final JTextField tfdPort=new JTextField(String.valueOf(PORT),5);
                dlg.add(tfdPort);
                JButton btnSet=new JButton("设置");
                dlg.add(btnSet);
                btnSet.addActionListener(new ActionListener() {
                    @Override
                    public void actionPerformed(ActionEvent arg0) {
                        //按理,应该先解析并判断ip和端口号是否合法,此处省略了
                        ClientForm.HOST=tfdHost.getText();
                        ClientForm.PORT=Integer.parseInt(tfdPort.getText());
                        dlg.dispose();
                    }
                });
                dlg.setVisible(true);
            }

        });
        JMenuItem menuItemHelp=new JMenuItem("帮助");
        menu.add(menuItemHelp);
        menuItemHelp.addActionListener(new ActionListener() {

            @Override
            public void actionPerformed(ActionEvent arg0) {
                JDialog dlg = new JDialog(ClientForm.this);
                dlg.setBounds(ClientForm.this.getX()+30, ClientForm.this.getY()+30, 340, 100);
                dlg.setLayout(new FlowLayout());
                dlg.add(new JLabel("版本所有@城院.2016-5-15,QQ:747205398"));
                dlg.setVisible(true);
            }
        });
    }
    public static void main(String[] args) {
        ClientForm.setDefaultLookAndFeelDecorated(true);//设置为装饰
        new ClientForm();
    }

    @Override
    public void actionPerformed(ActionEvent e) {
        if(e.getActionCommand().equalsIgnoreCase("con")){
            try {
                connection(e);
                tfdUserName.setEditable(false);
                ((JButton)e.getSource()).setEnabled(false);
            } catch (UnknownHostException e1) {
                JOptionPane.showMessageDialog(null, "ip地址和端口号错误,请重新输入:");
                return;
            } catch (IOException e1) {
                JOptionPane.showMessageDialog(null, "ip地址和端口号错误,请重新输入:");
                return;
            }
        }else if(e.getActionCommand().equalsIgnoreCase("exit")){
            sendExitMsg();
        }else if(e.getActionCommand().equalsIgnoreCase("send")){
            sendMsg();
            tfdMsg.setText("");
        }

    }
    // 向服务器发送退出消息
    private void sendExitMsg() {
        if(socketClient==null){
            System.exit(0);
        }
        String msgs="exit@#全部@#null@#"+tfdUserName.getText();//exit@#全部@#null@#userName
        String str[]=msgs.split("@#");
        if(str.length!=4){
            JOptionPane.showMessageDialog(null, "信息不应该包含\"@#\"");//防黑处理
            return;
        }
        pw.println(msgs);
        System.exit(0);
    }
    private void sendMsg() {
        if(!(tfdMsg.getText().length()>0)){
            JOptionPane.showMessageDialog(this, "请输入聊天消息");
            return;
        }
        String msgs="on@#"+list.getSelectedValue()+"@#"+tfdMsg.getText()+"@#"+tfdUserName.getText();
        String str[]=msgs.split("@#");
        if(str.length!=4){
            JOptionPane.showMessageDialog(null, "信息不应该包含\"@#\"");//防黑处理
            return;
        }
        pw.println(msgs);
        pw.flush();//刷新
    }
    public void connection(ActionEvent e) throws UnknownHostException, IOException {
            String userName = tfdUserName.getText();//最好验证
            if(userName==null||userName.length()<=0){
                JOptionPane.showMessageDialog(null, "用户名不能为空");
                return;
            }
            socketClient=new Socket(HOST, PORT);
            pw=new PrintWriter(socketClient.getOutputStream(),true);
            pw.println(userName);//向服务器报上自己的名字
            pw.flush();
            this.setTitle("用户["+userName+"]在线...");
            new ClientThread().start();
    }
    //用来读取服务器发送来的消息
    class ClientThread extends Thread{
        @Override
        public void run() {
            try {
                Scanner sc = new Scanner(socketClient.getInputStream());
                while(sc.hasNextLine()){
                    String str=sc.nextLine();
                    String msgs[]=str.split("@#");
                    if("msg".equals(msgs[0])){
                        if("server".equals(msgs[1])){
                            str="[通知]:"+msgs[2];
                        }else{//服务器进行转发
                            str=msgs[1]+"  说  :"+msgs[2];
                        }
                        allMsg.append("\r\n"+"("+new Date(System.currentTimeMillis())+")   "+str);
                    }else if("cmdAdd".equals(msgs[0])){
                        if("server".equals(msgs[1])){
                            lm.addElement((msgs[2]));
                            setVisible(true);
                        }
                    }else if("cmdRed".equals(msgs[0])){
                        if("server".equals(msgs[1])){
                            lm.removeElement(msgs[2]);

                        }
                    }
                }
            } catch (IOException e) {
                e.printStackTrace();
            }

        }
    }
}

这是服务器代码:

package cn.hncu.chat;

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.net.SocketImpl;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
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.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
import javax.swing.JTextField;
import javax.swing.KeyStroke;
import javax.swing.ListSelectionModel;
import javax.swing.border.TitledBorder;

public class ServerForm extends JFrame {
    private JTextArea tfdArea;
    private DefaultListModel lm;
    private HashMap usersMap=new HashMap();
    public ServerForm(){
        setDefaultCloseOperation(EXIT_ON_CLOSE);
        final int winWeight=(int) Toolkit.getDefaultToolkit().getScreenSize().getWidth();
        final int winHeight=(int) Toolkit.getDefaultToolkit().getScreenSize().getHeight();
        setBounds(winWeight/4, winHeight/4, winWeight/2, winHeight/2);
        //消息窗口
        tfdArea=new JTextArea();
        tfdArea.setEditable(false);
        getContentPane().add(new JScrollPane(tfdArea),BorderLayout.CENTER);
        //在线列表控件

        lm=new DefaultListModel();//分层设计,有利于维护
        JList list=new JList(lm);
        list=new JList(lm);
        lm.addElement("全部");
        JScrollPane jp=new JScrollPane(list);
        getContentPane().add(jp,BorderLayout.EAST);
        jp.setBorder(new TitledBorder("在线"));//具有指定边框和标题
        jp.setPreferredSize(new Dimension(100, this.getWidth()));//注意添加的顺序

        //设置菜单栏
        JMenuBar bar=new JMenuBar();
        this.setJMenuBar(bar);
        JMenu menu=new JMenu("控制(C)");
        menu.setMnemonic('C');//设置菜单或按钮的助记符(ALT+C激活
        bar.add(menu);
        final JMenuItem menuOpen=new JMenuItem("开启");//内部访问
        menuOpen.setAccelerator(KeyStroke.getKeyStroke('O',KeyEvent.CTRL_MASK));//设置快捷键:Ctrl+O
        menuOpen.setActionCommand("run");
        menu.add(menuOpen);
        menu.addSeparator();
        JMenuItem menuExit=new JMenuItem("退出");
        menuExit.setAccelerator(KeyStroke.getKeyStroke('E',KeyEvent.CTRL_MASK));//设置快捷键:Ctrl+E
        menuExit.setActionCommand("exit");
        menu.add(menuExit);
        //监听
        ActionListener al=new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                if(e.getActionCommand().equals("run")){
                    startServer();
                    menuOpen.setEnabled(false);
                }else{
                    System.exit(0);
                }
            }
        };
        menuOpen.addActionListener(al);
        menuExit.addActionListener(al);
        setVisible(true);
    }
    public static void main(String[] args) {
        ServerForm.setDefaultLookAndFeelDecorated(true);//设置为装饰
        new ServerForm();
    }
    private final  int PORT=9090;
    private void startServer() {
        try {
            ServerSocket server=new ServerSocket(PORT);
            tfdArea.append("启动服务:"+server);
            new ServerThread(server).start();

        } catch (IOException e) {
            e.printStackTrace();
        }

    }
    class ServerThread extends Thread{
        private ServerSocket server;
        public ServerThread(ServerSocket server) {
            this.server = server;
        }
        @Override
        public void run() {
            //握手
            while(true){
                try {
                    Socket socketClient=server.accept();
                    Scanner sc=new Scanner(socketClient.getInputStream());
                    if(sc.hasNextLine()){//注意用if判断第一行
                        String userName = sc.nextLine();//用户名
                        tfdArea.append("\r\n用户:["+userName+"]"+socketClient+"  ("+new Date(System.currentTimeMillis())+")");
                        lm.addElement(userName);
                        new ClientThread(socketClient).start();
                        msgAll(userName);//把“当前用户登录的消息即用户名”通知给所有其他已经在线的人
                        msgSelf(socketClient);//通知当前登录的用户,有关其他在线人的信息
                        usersMap.put(userName, socketClient);
                        //把当前登录的用户加到“在线用户”池中
                    }
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }

    }
    class ClientThread extends Thread{
        private Socket socketClient;
        public ClientThread(Socket socketClient) {
            this.socketClient = socketClient;
        }

        @Override
        public void run() {
            try {
                Scanner sc=new Scanner(socketClient.getInputStream());
                while(sc.hasNextLine()){
                    String str=sc.nextLine();
                    String msgs[]=str.split("@#");
                    if("on".equals(msgs[0])){
                        sendMsgToSb(msgs);
                    }else if("exit".equals(msgs[0])){
                        //从 在线用户池 中把该用户删除
                        usersMap.remove(msgs[3]);
                        //从服务器的在线用户显示列表中移除该用户的姓名
                        lm.removeElement(msgs[3]);
                        //通知其他人有关该用户退出的消息
                        sendExitMsgToAll(msgs);
                        //在服务器窗口显示该用户退出的消息
                        tfdArea.append("\r\n用户:["+msgs[3]+"]  ("+new Date(System.currentTimeMillis())+")"+"退出了...");

                    }
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
    //服务器把客户端的退出的消息转发给相应的其他客户端
    private void sendExitMsgToAll(String[] msgs) throws IOException {
        Iterator usersName=usersMap.keySet().iterator();
        while(usersName.hasNext()){
            Socket s=usersMap.get(usersName.next());
            PrintWriter pw=new PrintWriter(s.getOutputStream(), true);
            String str="msg@#server@#用户["+msgs[3]+"]退出了...";
            pw.println(str);
            str="cmdRed@#server@#"+msgs[3];
            pw.println(str);
        }
    }
    //服务器把客户端的聊天消息转发给相应的其他客户端
    private void sendMsgToSb(String[] msgs) throws IOException {
        if("全部".equals(msgs[1])){
            Iterator usersName=usersMap.keySet().iterator();
            while(usersName.hasNext()){
                Socket s=usersMap.get(usersName.next());
                PrintWriter pw=new PrintWriter(s.getOutputStream(), true);
                String str="msg@#"+msgs[3]+"@#"+msgs[2];// msg   @#消息发送者( msgs[3] ) @# 消息内容 (msgs[2])
                pw.println(str);
            }
        }else{
            Socket s=usersMap.get(msgs[1]);
            PrintWriter pw=new PrintWriter(s.getOutputStream(), true);
            String str="msg@#"+msgs[3]+"@#"+msgs[2];// msg   @#消息发送者( msgs[3] ) @# 消息内容 (msgs[2])
            pw.println(str);
        }

    }
    //通知当前登录的用户,有关其他在线人的信息---把原先已经在线的那些用户的名字发给该登录用户,让他给自己界面中的lm添加相应的用户名
    private void msgSelf(Socket socketClient) {
        try {
            PrintWriter pw=new PrintWriter(socketClient.getOutputStream(), true);
            Iterator it=usersMap.keySet().iterator();
            while(it.hasNext()){
                String userName=it.next();
                String msg = "cmdAdd@#server@#"+userName;
                pw.println(msg);
                pw.flush();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }

    }
    //把“当前用户登录的消息即用户名”通知给所有其他已经在线的人
    //技术思路:从池中依次把每个socket(代表每个在线用户)取出,向它发送userName
    private void msgAll(String userName) {
        Iterator it=usersMap.values().iterator();
        while(it.hasNext()){
            Socket socketClient=it.next();
            try {
                PrintWriter pw=new PrintWriter(socketClient.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();
            }
        }
    }
}

你可能感兴趣的:(网络编程)