Java---基於TCP協議的相互即時通信小程序

這是幾年前,新浪的一個面試題~要求是3天以內實現~
經過TCP 協議,創建一個服務器端。java

經過配置服務器端的IP和端口:
客戶端之間就能夠相互通信~git

上線了所有在線用戶會收到你上線的通知。
下線了所有的在線用戶會收到你下線的通知!
能夠私聊,能夠羣聊。github

這是第一個版本~之後有空能夠再增長功能~好比傳文件啊~等等~面試

設計思想以下:

在服務器端 用一個HashMap< userName,socket> 維護全部用戶相關的信息,從而可以保證和全部的用戶進行通信。服務器

客戶端的動做:
(1)鏈接(登陸):發送userName 服務器的對應動做:1)界面顯示,2)通知其餘用戶關於你登陸的信息, 3)把其餘在線用戶的userName通知當前用戶 4)開啓一個線程專門爲當前線程服務markdown

(2)退出(註銷):網絡

(3)發送消息app

※※發送通信內容以後,對方如何知道是幹什麼,經過消息協議來實現:socket

客戶端向服務器發的消息格式設計:
命令關鍵字@#接收方@#消息內容@#發送方
1)鏈接:userName —-握手的線程serverSocket專門接收該消息,其它的由服務器新開的與客戶進行通信的socket來接收
2)退出:exit@#所有@#null@#userName
3)發送: on @# JList.getSelectedValue() @# tfdMsg.getText() @# tfdUserName.getText()ide

服務器向客戶端發的消息格式設計:
命令關鍵字@#發送方@#消息內容
登陸:
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("\r\n用戶[ " + 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("\r\n用戶[ " + 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, "鏈接服務器失敗!\r\n用戶名有誤,請從新輸入!");
                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("\r\n" + 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();
    }
}

相關文章
相關標籤/搜索