zoukankan      html  css  js  c++  java
  • Java---基于TCP协议的相互即时通讯小程序

    这是几年前,新浪的一个面试题~要求是3天之内实现~
    通过TCP 协议,建立一个服务器端。

    通过配置服务器端的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])

    服务器端源代码:

    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.JPanel;
    import javax.swing.JScrollPane;
    import javax.swing.JTextArea;
    import javax.swing.KeyStroke;
    import javax.swing.border.Border;
    import javax.swing.border.TitledBorder;
    
    /**
     * 服务器
     * 
     * @author 陈浩翔
     * 
     * @version 1.0 2016-5-13
     */
    public class ServerForm extends JFrame {
        private JList<String> list;
        private JTextArea area;
        private DefaultListModel<String> lm;
    
        public ServerForm() {
            JPanel p = new JPanel(new BorderLayout());
            // 最右边的用户在线列表
            lm = new DefaultListModel<String>();
            list = new JList<String>(lm);
            JScrollPane js = new JScrollPane(list);
            Border border = new TitledBorder("在线");
            js.setBorder(border);
            Dimension d = new Dimension(100, p.getHeight());
            js.setPreferredSize(d);// 设置位置
            p.add(js, BorderLayout.EAST);
    
            // 通知文本区域
            area = new JTextArea();
            //area.setEnabled(false);//不能选中和修改
            area.setEditable(false);
            p.add(new JScrollPane(area), BorderLayout.CENTER);
            this.getContentPane().add(p);
    
            // 添加菜单项
            JMenuBar bar = new JMenuBar();// 菜单条
            this.setJMenuBar(bar);
            JMenu jm = new JMenu("控制(C)");
            jm.setMnemonic('C');// 设置助记符---Alt+'C',显示出来,但不运行
            bar.add(jm);
            final JMenuItem jmi1 = new JMenuItem("开启");
            jmi1.setAccelerator(KeyStroke.getKeyStroke('R', KeyEvent.CTRL_MASK));// 设置快捷键Ctrl+'R'
            jmi1.setActionCommand("run");
            jm.add(jmi1);
    
            JMenuItem jmi2 = new JMenuItem("退出");
            jmi2.setAccelerator(KeyStroke.getKeyStroke('E', KeyEvent.CTRL_MASK));// 设置快捷键Ctrl+'R'
            jmi2.setActionCommand("exit");
            jm.add(jmi2);
    
    
            // 监听
            ActionListener a1 = new ActionListener() {
    
                @Override
                public void actionPerformed(ActionEvent e) {
                    if (e.getActionCommand().equals("run")) {
                        startServer();
                        jmi1.setEnabled(false);// 内部方法~访问的只能是final对象
                    } else {
                        System.exit(0);
                    }
                }
            };
    
            jmi1.addActionListener(a1);
    
            Toolkit tk = Toolkit.getDefaultToolkit();
            int width = (int) tk.getScreenSize().getWidth();
            int height = (int) tk.getScreenSize().getHeight();
            this.setBounds(width / 4, height / 4, width / 2, height / 2);
            this.setDefaultCloseOperation(EXIT_ON_CLOSE);// 关闭按钮器作用
    
            setVisible(true);
        }
    
        private static final int PORT = 9090;
    
        protected void startServer() {
            try {
                ServerSocket server = new ServerSocket(PORT);
                area.append("启动服务:" + server);
                new ServerThread(server).start();
            } catch (IOException e) {
                e.printStackTrace();
            }
    
        }
    
        // 用来保存所有在线用户的名字和Socket----池
        private Map<String, Socket> usersMap = new HashMap<String, Socket>();
    
        class ServerThread extends Thread {
            private ServerSocket server;
    
            public ServerThread(ServerSocket server) {
                this.server = server;
            }
    
            @Override
            public void run() {
                try {// 和客户端握手
                    while (true) {
                        Socket socketClient = server.accept();
                        Scanner sc = new Scanner(socketClient.getInputStream());
                        if (sc.hasNext()) {
                            String userName = sc.nextLine();
                            area.append("
    用户[ " + userName + " ]登录 " + socketClient);// 在客户端通知
                            lm.addElement(userName);// 添加到用户在线列表
    
                            new ClientThread(socketClient).start();// 专门为这个客户端服务
    
                            usersMap.put(userName, socketClient);// 把当前登录的用户加到“在线用户”池中
    
                            msgAll(userName);// 把“当前用户登录的消息即用户名”通知给所有其他已经在线的人
                            msgSelf(socketClient);// 通知当前登录的用户,有关其他在线人的信息
    
                        }
    
                    }
                } catch (IOException e) {
                    e.printStackTrace();
                }
    
            }
        }
    
        class ClientThread extends Thread {
            private Socket socketClient;
    
            public ClientThread(Socket socketClient) {
                this.socketClient = socketClient;
            }
    
            @Override
            public void run() {
                System.out.println("一个与客户端通讯的线程启动并开始通讯...");
                try {
                    Scanner sc = new Scanner(socketClient.getInputStream());
                    while (sc.hasNext()) {
                        String msg = sc.nextLine();
                        System.out.println(msg);
                        String msgs[] = msg.split("@#@#");
                        //防黑
                        if(msgs.length!=4){
                            System.out.println("防黑处理...");
                            continue;
                        }
    
                        if("on".equals(msgs[0])){
                            sendMsgToSb(msgs);
                        }
    
                        if("exit".equals(msgs[0])){
                            //服务器显示
                            area.append("
    用户[ " + msgs[3] + " ]已退出!" + usersMap.get(msgs[3]));
    
                            //从在线用户池中把该用户删除
                            usersMap.remove(msgs[3]);
    
                            //服务器的在线列表中把该用户删除
                            lm.removeElement(msgs[3]);
    
                            //通知其他用户,该用户已经退出
                            sendExitMsgToAll(msgs);
                        }
    
                    }
    
                } catch (IOException e) {
                    e.printStackTrace();
                }
    
            }
    
        }
    
    
        //通知其他用户。该用户已经退出
        private void sendExitMsgToAll(String[] msgs) throws IOException {
            Iterator<String> userNames = usersMap.keySet().iterator();
    
            while(userNames.hasNext()){
                String userName = userNames.next();
                Socket s = usersMap.get(userName);
                PrintWriter pw = new PrintWriter(s.getOutputStream(),true);
                String str = "msg@#@#server@#@#用户[ "+msgs[3]+" ]已退出!";
                pw.println(str);
                pw.flush();
    
                str = "cmdRed@#@#server@#@#"+msgs[3];
                pw.println(str);
                pw.flush();
            }
    
        }
    
    
        //服务器把客户端的聊天消息转发给相应的其他客户端
        public void sendMsgToSb(String[] msgs) throws IOException {
    
            if("全部".equals(msgs[1])){
                Iterator<String> userNames = usersMap.keySet().iterator();
                //遍历每一个在线用户,把聊天消息发给他
                while(userNames.hasNext()){
                    String userName = userNames.next();
                    Socket s = usersMap.get(userName);
                    PrintWriter pw = new PrintWriter(s.getOutputStream(),true);
                    String str = "msg@#@#"+msgs[3]+"@#@#"+msgs[2];
                    pw.println(str);
                    pw.flush();
                }
            }else{
                Socket s = usersMap.get(msgs[1]);
                PrintWriter pw = new PrintWriter(s.getOutputStream(),true);
                String str = "msg@#@#"+msgs[3]+"对你@#@#"+msgs[2];
                pw.println(str);
                pw.flush();
            }
        }
    
    
    
        /**
         * 把“当前用户登录的消息即用户名”通知给所有其他已经在线的人
         * 
         * @param userName
         */
        // 技术思路:从池中依次把每个socket(代表每个在线用户)取出,向它发送userName
        public void msgAll(String userName) {
            Iterator<Socket> it = usersMap.values().iterator();
            while (it.hasNext()) {
                Socket s = it.next();
                try {
                    PrintWriter pw = new PrintWriter(s.getOutputStream(), true);// 加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();
                }
            }
        }
    
    
        /**
         * 通知当前登录的用户,有关其他在线人的信息
         * 
         * @param socketClient
         */
        // 把原先已经在线的那些用户的名字发给该登录用户,让他给自己界面中的lm添加相应的用户名
        public void msgSelf(Socket socketClient) {
            try {
                PrintWriter pw = new PrintWriter(socketClient.getOutputStream(),true);
                Iterator<String> it = usersMap.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();
        }
    }

    客户端源代码:

    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.ImageIcon;
    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.Border;
    import javax.swing.border.TitledBorder;
    
    /**
     * 客户端
     * 
     * @author 陈浩翔
     * @version 1.0 2016-5-13
     */
    public class ClientForm extends JFrame implements ActionListener {
        private JTextField tfdUserName;
        private JList<String> list;
        private DefaultListModel<String> lm;
        private JTextArea allMsg;
        private JTextField tfdMsg;
        private JButton btnCon;
        private JButton btnExit;
        private JButton btnSend;
    
        // private static String HOST="192.168.31.168";
        private static String HOST = "127.0.0.1";// 自己机子,服务器的ip地址
        private static int PORT = 9090;// 服务器的端口号
        private Socket clientSocket;
        private PrintWriter pw;
    
        public ClientForm() {
    
            super("即时通讯工具1.0");
            // 菜单条
            addJMenu();
    
            // 上面的面板
            JPanel p = new JPanel();
            JLabel jlb1 = new JLabel("用户标识:");
            tfdUserName = new JTextField(10);
            // tfdUserName.setEnabled(false);//不能选中和修改
            // dtfdUserName.setEditable(false);//不能修改
    
            // 链接按钮
            ImageIcon icon = new ImageIcon("a.png");
            btnCon = new JButton("", icon);
            btnCon.setActionCommand("c");
            btnCon.addActionListener(this);
    
            // 退出按钮
            icon = new ImageIcon("b.jpg");
            btnExit = new JButton("", icon);
            btnExit.setActionCommand("exit");
    
            btnExit.addActionListener(this);
            btnExit.setEnabled(false);
            p.add(jlb1);
            p.add(tfdUserName);
            p.add(btnCon);
            p.add(btnExit);
            getContentPane().add(p, BorderLayout.NORTH);
    
            // 中间的面板
            JPanel cenP = new JPanel(new BorderLayout());
            this.getContentPane().add(cenP, BorderLayout.CENTER);
    
            // 在线列表
            lm = new DefaultListModel<String>();
            list = new JList<String>(lm);
            lm.addElement("全部");
            list.setSelectedIndex(0);// 设置默认显示
            list.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);// 只能选中一行
            list.setVisibleRowCount(2);
            JScrollPane js = new JScrollPane(list);
            Border border = new TitledBorder("在线");
            js.setBorder(border);
            Dimension preferredSize = new Dimension(70, cenP.getHeight());
            js.setPreferredSize(preferredSize);
            cenP.add(js, BorderLayout.EAST);
    
            // 聊天消息框
            allMsg = new JTextArea();
            allMsg.setEditable(false);
            cenP.add(new JScrollPane(allMsg), BorderLayout.CENTER);
    
            // 消息发送面板
            JPanel p3 = new JPanel();
            JLabel jlb2 = new JLabel("消息:");
            p3.add(jlb2);
            tfdMsg = new JTextField(20);
            p3.add(tfdMsg);
            btnSend = new JButton("发送");
            btnSend.setEnabled(false);
            btnSend.setActionCommand("send");
            btnSend.addActionListener(this);
            p3.add(btnSend);
            this.getContentPane().add(p3, BorderLayout.SOUTH);
    
            // *************************************************
            // 右上角的X-关闭按钮-添加事件处理
            addWindowListener(new WindowAdapter() {
                // 适配器
                @Override
                public void windowClosing(WindowEvent e) {
                    if (pw == null) {
                        System.exit(0);
                    }
                    String msg = "exit@#@#全部@#@#null@#@#" + tfdUserName.getText();
                    pw.println(msg);
                    pw.flush();
                    System.exit(0);
                }
            });
    
            setBounds(300, 300, 400, 300);
            setVisible(true);
        }
    
        private void addJMenu() {
            JMenuBar menuBar = new JMenuBar();
            this.setJMenuBar(menuBar);
    
            JMenu menu = new JMenu("选项");
            menuBar.add(menu);
    
            JMenuItem menuItemSet = new JMenuItem("设置");
            JMenuItem menuItemHelp = new JMenuItem("帮助");
            menu.add(menuItemSet);
            menu.add(menuItemHelp);
    
            menuItemSet.addActionListener(new ActionListener() {
    
                @Override
                public void actionPerformed(ActionEvent e) {
                    final JDialog dlg = new JDialog(ClientForm.this);// 弹出一个界面
                    // 不能直接用this
    
                    dlg.setBounds(ClientForm.this.getX()+20, ClientForm.this.getY()+30,
                            350, 150);
                    dlg.setLayout(new FlowLayout());
                    dlg.add(new JLabel("服务器IP和端口:"));
    
                    final JTextField tfdHost = new JTextField(10);
                    tfdHost.setText(ClientForm.HOST);
                    dlg.add(tfdHost);
    
                    dlg.add(new JLabel(":"));
    
                    final JTextField tfdPort = new JTextField(5);
                    tfdPort.setText(""+ClientForm.PORT);
                    dlg.add(tfdPort);
    
                    JButton btnSet = new JButton("设置");
                    dlg.add(btnSet);
                    btnSet.addActionListener(new ActionListener() {
    
                        @Override
                        public void actionPerformed(ActionEvent e) {
                            String ip = tfdHost.getText();//解析并判断ip是否合法
                            String strs[] = ip.split("\.");
                            if(strs==null||strs.length!=4){
                                JOptionPane.showMessageDialog(ClientForm.this, "IP类型有误!");
                                return ;
                            }
                            try {
                                for(int i=0;i<4;i++){
                                    int num = Integer.parseInt(strs[i]);
                                    if(num>255||num<0){
                                        JOptionPane.showMessageDialog(ClientForm.this, "IP类型有误!");
                                        return ;
                                    }
                                }
                            } catch (NumberFormatException e2) {
                                JOptionPane.showMessageDialog(ClientForm.this, "IP类型有误!");
                                return ;
                            }
    
                            ClientForm.HOST=tfdHost.getText();//先解析并判断ip是否合法
    
                            try {
                                int port = Integer.parseInt( tfdPort.getText() );
                                if(port<0||port>65535){
                                    JOptionPane.showMessageDialog(ClientForm.this, "端口范围有误!");
                                    return ;
                                }
                            } catch (NumberFormatException e1) {
                                JOptionPane.showMessageDialog(ClientForm.this, "端口类型有误!");
                                return ;
                            }
                            ClientForm.PORT=Integer.parseInt( tfdPort.getText() );
    
                            dlg.dispose();//关闭这个界面
                        }
                    });
                    dlg.setVisible(true);//显示出来
                }
            });
    
            menuItemHelp.addActionListener(new ActionListener() {
    
                @Override
                public void actionPerformed(ActionEvent e) {
                    JDialog dlg = new JDialog(ClientForm.this);
    
                    dlg.setBounds(ClientForm.this.getX()+30,ClientForm.this.getY()+30, 400, 100);
                    dlg.setLayout(new FlowLayout());
                    dlg.add(new JLabel("版本所有@陈浩翔.2016.5.16 我的主页:http://chenhaoxiang.github.io"));
                    dlg.setVisible(true);
                }
            });
        }
    
        @Override
        public void actionPerformed(ActionEvent e) {
            if (e.getActionCommand().equals("c")) {
                if (tfdUserName.getText() == null
                        || tfdUserName.getText().trim().length() == 0
                        || "@#@#".equals(tfdUserName.getText())
                        || "@#".equals(tfdUserName.getText())) {
                    JOptionPane.showMessageDialog(this, "用户名输入有误,请重新输入!");
                    return;
                }
    
                connecting();// 连接服务器的动作
                if (pw == null) {
                    JOptionPane.showMessageDialog(this, "服务器未开启或网络未连接,无法连接!");
                    return;
                }
    
                ((JButton) (e.getSource())).setEnabled(false);
                // 获得btnCon按钮--获得源
                // 相当于btnCon.setEnabled(false);
                btnExit.setEnabled(true);
                btnSend.setEnabled(true);
                tfdUserName.setEditable(false);
            } else if (e.getActionCommand().equals("send")) {
                if (tfdMsg.getText() == null
                        || tfdMsg.getText().trim().length() == 0) {
                    return;
                }
                String msg = "on@#@#" + list.getSelectedValue() + "@#@#"
                        + tfdMsg.getText() + "@#@#" + tfdUserName.getText();
                pw.println(msg);
                pw.flush();
    
                // 将发送消息的文本设为空
                tfdMsg.setText("");
            } else if (e.getActionCommand().equals("exit")) {
                //先把自己在线的菜单清空
                lm.removeAllElements();
    
                sendExitMsg();
                btnCon.setEnabled(true);
                btnExit.setEnabled(false);
                tfdUserName.setEditable(true);
            }
    
        }
    
        // 向服务器发送退出消息
        private void sendExitMsg() {
            String msg = "exit@#@#全部@#@#null@#@#" + tfdUserName.getText();
            System.out.println("退出:" + msg);
            pw.println(msg);
            pw.flush();
        }
    
        private void connecting() {
            try {
                // 先根据用户名防范
                String userName = tfdUserName.getText();
                if (userName == null || userName.trim().length() == 0) {
                    JOptionPane.showMessageDialog(this, "连接服务器失败!
    用户名有误,请重新输入!");
                    return;
                }
    
                clientSocket = new Socket(HOST, PORT);// 跟服务器握手
                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("@#@#");
                        System.out.println(tfdUserName.getText() + ": " + str);
                        if ("msg".equals(msgs[0])) {
                            if ("server".equals(msgs[1])) {// 服务器发送的官方消息
                                str = "[ 通知 ]:" + msgs[2];
                            } else {// 服务器转发的聊天消息
                                str = "[ " + msgs[1] + " ]说: " + msgs[2];
                            }
                            allMsg.append("
    " + str);
                        }
                        if ("cmdAdd".equals(msgs[0])) {
                            boolean eq = false;
                            for (int i = 0; i < lm.getSize(); i++) {
                                if (lm.getElementAt(i).equals(msgs[2])) {
                                    eq = true;
                                }
                            }
                            if (!eq) {
                                lm.addElement(msgs[2]);// 用户上线--添加
                            }
                        }
                        if ("cmdRed".equals(msgs[0])) {
                            lm.removeElement(msgs[2]);// 用户离线了--移除
                        }
    
                    }
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    
        public static void main(String[] args) {
            JFrame.setDefaultLookAndFeelDecorated(true);// 设置装饰
            new ClientForm();
        }
    }

  • 相关阅读:
    读后感四
    读后感五
    公文流转系统
    统计文件中单词的频率,给出前N的单词
    读入一个文件输出每个字母的频率
    小工到专家
    从小工到专家读后感
    动手动脑 类与对象
    海芋
    棕竹
  • 原文地址:https://www.cnblogs.com/webmen/p/5739184.html
Copyright © 2011-2022 走看看