关于java socket多用户聊天--图形界面版&带高层架构思想(经典面试题)

这个程序主要是吃透socket的tcp传输协议,但这里还有一个比较烧脑的就是几个线程之间的关系一定要搞清楚,因为我们是用了专门的线程去维护,以保证多用户的通信。

TCP知识点:即Socket类和ServerSocket类。ServerSocket:accept();

Socket包含输入、输出流:getImputStream()、 getOutputStream();

我们要注意的是要给服务器发送结束标志否则服务器在数据接收完毕再调用read或者readline时会出异常(server.shutdownOutput())。

另外就是要有要有如下架构思想:

在服务器端 用一个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])

通过上面的思想架构再来写代码就显得简单很多。另外要注意的地方除了我在代码的备注中提到的就是内部类用外部的成员变量这个变量要final是比较麻烦的,所以我们再写一个函数专门来做这个动作比较方便。

代码如下:

客户端:

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.net.UnknownHostException;
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;
    private JTextArea allMsg;
    private JList list;
    private DefaultListModel lm;
    private JTextField tfdMsg;
    private JButton btnCon;
    private JButton btnExit;
    public ClientForm() {
        //造一个主面板
        setBounds(400, 400, 500, 400);
        //菜单
        addMenu();

        //上部面板
        JPanel topP = new JPanel();
        getContentPane().add(topP,BorderLayout.NORTH);
        topP.add(new JLabel("用户标识:"));
        tfdUserName = new JTextField(10);
        topP.add(tfdUserName);
        btnCon = new JButton("连接");
        btnCon.setActionCommand("c");
        btnCon.addActionListener(this);
        topP.add(btnCon);
        btnExit = new JButton("注销");
        btnExit.setActionCommand("exit");
        btnExit.addActionListener(this);
        topP.add(btnExit);

        //中间面板
        JPanel centerP = new JPanel();
        centerP.setLayout(new BorderLayout());
        getContentPane().add(centerP,BorderLayout.CENTER);
        //中间面板的消息文本框
        allMsg= new JTextArea();
        allMsg.setEditable(false);
        centerP.add(new JScrollPane(allMsg),BorderLayout.CENTER);
        //中间面板的用户列表
        lm = new DefaultListModel();
        list= new JList(lm);
        lm.addElement("全部");
        list.setSelectedIndex(0);
        list.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
        list.setVisibleRowCount(2);
        JScrollPane jc = new JScrollPane(list);
        jc.setBorder(new TitledBorder("在线"));
        jc.setPreferredSize(new Dimension(100,centerP.getHeight()));
        centerP.add(jc,BorderLayout.EAST);

        //下部面板
        JPanel sendP = new JPanel();
        getContentPane().add(sendP,BorderLayout.SOUTH);
        sendP.add(new JLabel("消息:"));
        tfdMsg = new JTextField(20);
        sendP.add(tfdMsg);
        JButton btnSend = new JButton("发送");
        btnSend.setActionCommand("send");
        btnSend.addActionListener(this);
        sendP.add(btnSend);

        addWindowListener(new WindowAdapter() {
            @Override
            public void windowClosing(WindowEvent e) {
                if(clientSocket==null){
                    System.exit(0);
                }
                sendExitMsg();
                System.exit(0);
            }
        });

        setVisible(true);
    }
    private void addMenu() {
        JMenuBar menubar = new JMenuBar();
        setJMenuBar(menubar);
        JMenu menu = new JMenu("选项");
        menubar.add(menu);
        JMenuItem setMenuItem= new JMenuItem("设置");
        JMenuItem helpMenuItem= new JMenuItem("帮助");
        menu.add(setMenuItem);
        menu.addSeparator();
        menu.add(helpMenuItem);
        setMenuItem.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                final JDialog dlg = new JDialog();
                dlg.setLayout(new FlowLayout());
                dlg.add(new JLabel("服务器:"));
                final JTextField tfdIp = new JTextField(10);
                tfdIp.setText(HOST);
                dlg.add(tfdIp);
                dlg.add(new JLabel(":"));
                final JTextField tfdPort = new JTextField(5);
                tfdPort.setText(""+PORT);
                dlg.add(tfdPort);
                dlg.setBounds(ClientForm.this.getX(), ClientForm.this.getY(), 400, 100);
                JButton setBtn = new JButton("设置");
                dlg.add(setBtn);
                setBtn.addActionListener(new ActionListener() {
                    @Override
                    public void actionPerformed(ActionEvent e) {
                        String ip = tfdIp.getText();
                        String port = tfdPort.getText();
                        HOST = ip ;
                        PORT = Integer.parseInt(port);
                        dlg.dispose();
                    }
                });
                dlg.setVisible(true);



            }
        });

        helpMenuItem.addActionListener(new ActionListener() {

            @Override
            public void actionPerformed(ActionEvent e) {
                JDialog dlg = new JDialog();
                dlg.setLayout(new FlowLayout());
                dlg.setBounds(ClientForm.this.getX(), ClientForm.this.getY(), 300, 100);
                dlg.add(new JLabel("版权所有@军街.2016.05.15,qq:469547258"));
                dlg.setVisible(true);
            }
        });


    }
    public static void main(String[] args) {
        JFrame.setDefaultLookAndFeelDecorated(true);
        new ClientForm();
    }
    @Override
    public void actionPerformed(ActionEvent e) {
        if(e.getActionCommand().equals("c")){
            System.out.println("连接服务器...");
            tfdUserName.setEditable(false);
            btnCon.setEnabled(false);
            connecting();
            btnExit.setEnabled(true);
            btnCon.setEnabled(false);

        }else if(e.getActionCommand().equals("send")){
            try {
                PrintWriter pw = new PrintWriter(clientSocket.getOutputStream(),true);
                //我对谁说了什么消息
                if(tfdMsg.getText()==null || tfdMsg.getText().trim().length()==0){
                    JOptionPane.showMessageDialog(this, "请输出信息");
                    return ;
                }
                String msg = "on@#"+list.getSelectedValue()+"@#"+tfdMsg.getText()+"@#"+tfdUserName.getText();
                pw.println(msg);
                //添加到自己的面板 作为记录
                allMsg.append("\r\n[我]说:"+tfdMsg.getText());
                tfdMsg.setText("");

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

        }else if(e.getActionCommand().equals("exit")){
            sendExitMsg();
        }


    }
    private void sendExitMsg() {
        try {
            PrintWriter pw = new PrintWriter(clientSocket.getOutputStream(),true);
            //向服务器发送退出消息
            String msg = "exit@#"+"全部"+"@#@#"+tfdUserName.getText();
            pw.println(msg);
            btnCon.setEnabled(true);
            btnExit.setEnabled(false);
            tfdUserName.setEditable(true);
            lm.removeAllElements();//点了退出要把lm全部移除  不然再点连接会导致之前列表的还在
        } catch (IOException e1) {
            e1.printStackTrace();
        }
    }
    private  String HOST = "192.168.1.103";
    private  int PORT = 9090;
    private Socket clientSocket;
    private PrintWriter pw ;



    private void connecting() {
        try {
            clientSocket = new Socket(HOST,PORT);
            String userName = tfdUserName.getText();
            pw= new PrintWriter(clientSocket.getOutputStream(),true);
            pw.println(userName); 
            this.setTitle("用户["+userName+"]在线...");
            new ClientThread().start();//专门负责通讯的线程
        } catch (UnknownHostException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    class ClientThread extends Thread{
        @Override
        public void run() {
            try {
                //接受服务器发过来的信息
                Scanner sc = new Scanner(clientSocket.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"+str);
                    }else if("cmdAdd".equals(msgs[0])){//列表添加消息
                        lm.addElement(msgs[2]);
                    }else if("cmdRed".equals(msgs[0])){//列表删除消息
                        lm.removeElement(msgs[2]);
                    }
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

    }

}

服务器端:

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.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
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.JScrollPane;
import javax.swing.JTextArea;
import javax.swing.KeyStroke;
import javax.swing.border.TitledBorder;

import com.sun.org.apache.bcel.internal.generic.ACONST_NULL;

public class ServerForm extends JFrame{
    private static final int PORT=9090;
    private JTextArea area;
    private DefaultListModel lm ;
    private Map<String, Socket> usersMap = new HashMap<String, Socket>();

    public ServerForm() {
        //主面板
        super("这是服务器");
        final int hight = (int) Toolkit.getDefaultToolkit().getScreenSize().getHeight();
        final int width  = (int) Toolkit.getDefaultToolkit().getScreenSize().getWidth();
        this.setBounds(width/4,hight/4, width/3,hight/2);
        //菜单
        JMenuBar bar = new JMenuBar();
        setJMenuBar(bar);
        JMenu menu = new JMenu("控制(c)");
        bar.add(menu);
        menu.setMnemonic('c');//设置助记符
        final JMenuItem itemRun = new JMenuItem("启动");
        itemRun.setActionCommand("run");

        menu.add(itemRun);

        menu.addSeparator();
        itemRun.setAccelerator(KeyStroke.getKeyStroke('R',KeyEvent.CTRL_MASK));//设置快捷键
        JMenuItem itemExit = new JMenuItem("退出");
        itemExit.setAccelerator(KeyStroke.getKeyStroke('E',KeyEvent.CTRL_MASK));//设置快捷键
        itemExit.setActionCommand("exit");
        menu.add(itemExit);

        //消息面板
        area = new JTextArea();
        area.setEditable(false);
        getContentPane().add(area,BorderLayout.CENTER);
        //用户列表
        lm= new DefaultListModel();
        JList list = new JList(lm);
        JScrollPane jc = new JScrollPane(list);
        jc.setBorder(new TitledBorder("在线"));
        jc.setPreferredSize(new Dimension(100, this.getHeight()));
        getContentPane().add(jc,BorderLayout.EAST);

        //监听
        ActionListener al = new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                if(e.getActionCommand().equals("run")){
                    startServer();
                    itemRun.setEnabled(false);
                }
                if(e.getActionCommand().equals("exit")){
                    System.exit(0);
                }
            }
        };
        itemRun.addActionListener(al);
        itemExit.addActionListener(al);

        addWindowListener(new WindowAdapter() {

            @Override
            public void windowClosing(WindowEvent e) {
                System.exit(0);
            }

        });

        setVisible(true);
    }
    private void startServer() {
        try {
            ServerSocket server = new ServerSocket(PORT);
            area.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 clientSocket = server.accept();
                    Scanner sc = new Scanner(clientSocket.getInputStream());
                    if(sc.hasNextLine()){
                        String userName = sc.nextLine();
                        area.append("\r\n用户:["+userName+"]登录,"+clientSocket);
                        lm.addElement(userName);
                        new ClientThread(clientSocket).start();
                        msgAll(userName);
                        msgSelf(clientSocket);

                        usersMap.put(userName, clientSocket);//将此时登录的用户加入池
                    }

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

    }

    private void receiveAndSendMsg(Socket clientSocketReceive) {
        try {
            Scanner sc = new Scanner(clientSocketReceive.getInputStream());
            while(sc.hasNextLine()){
                String str = sc.nextLine();
                String msgs[]=str.split("@#");
                if("on".equals(msgs[0])){
                    if("全部".equals(msgs[1])){
                        String msg = "msg@#"+msgs[3]+"@#"+msgs[2];

                        Iterator<Socket> it= usersMap.values().iterator();
                        while(it.hasNext()){
                            Socket clientSocketSend = it.next();
                            PrintWriter pw = new PrintWriter(clientSocketSend.getOutputStream(), true);
                            pw.println(msg);
                            pw.flush();
                        }
                    }else{
                        Socket clientSocketSend = usersMap.get(msgs[1]);
                        String msg = "msg@#"+msgs[3]+"@#"+msgs[2];
                        PrintWriter pw = new PrintWriter(clientSocketSend.getOutputStream(), true);
                        pw.println(msg);
                        pw.flush();
                    }
                }else if("exit".equals(msgs[0])){
                    usersMap.remove(msgs[3]);
                    Socket removeSocket = usersMap.get(msgs[3]);
                    area.append("\r\n用户:["+msgs[3]+"]退出登录");
                    lm.removeElement(msgs[3]);

                    Iterator<Socket> it= usersMap.values().iterator();
                    String msg = "msg@#server@#用户["+msgs[3]+"]退出登录";
                    String msg2 ="cmdRed@#server@#"+msgs[3];
                    while(it.hasNext()){
                        Socket clientSocketSend = it.next();
                        PrintWriter pw = new PrintWriter(clientSocketSend.getOutputStream(), true);
                        pw.println(msg);
                        pw.flush();
                        pw.println(msg2);
                        pw.flush();
                    }
                }
            }

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

    }
    private void msgSelf(Socket clientSocket) {
        try {
            Iterator<String> it = usersMap.keySet().iterator();
            PrintWriter pw = new PrintWriter(clientSocket.getOutputStream(), true);
            while(it.hasNext()){
                String userName = it.next();
                String msg = "cmdAdd@#server@#"+userName;
                pw.println(msg);
                pw.flush();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    //通知其他在线的用户,此时登录的用户登陆了
    private void msgAll(String userName) {
        Iterator<Socket> it = usersMap.values().iterator();
        while(it.hasNext()){
            try {
                PrintWriter pw = new PrintWriter(it.next().getOutputStream(), true);
                pw.println("msg@#server@#用户["+userName+"]登陆了.");
                pw.flush();
                pw.println("cmdAdd@#server@#"+userName);
                pw.flush();
            } catch (IOException e) {
                e.printStackTrace();
            }

        }
    }

    class ClientThread extends Thread{
        private Socket clientSocket;
        public ClientThread(Socket clientSocket) {
            this.clientSocket = clientSocket;
        }
        @Override
        public void run() {
            receiveAndSendMsg(clientSocket);
        }


    }
    public static void main(String[] args) {
        JFrame.setDefaultLookAndFeelDecorated(true);
        new ServerForm();

    }

}

提示:运行的时候要先启动服务器端,在去开客户端(可多开)。此外,客户端可以在选项中设置服务器的地址和端口号。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值