采用Java Socket實現基于TCP方式的客戶服務器聊天程序

jopen 12年前發布 | 49K 次閱讀 Java開發 java socket

本文講演示如何通過Java Socket建立C/S方式的聊天程序。實現的功能

主要包括如下幾個方面:

1. 用戶登錄,在線用戶列表刷新

2.客戶端與服務器的TCP連接,實現消息的發送與接受

3.Java Swing與多線程編程技巧

一個整體的Class關系圖如下:

采用Java Socket實現基于TCP方式的客戶服務器聊天程序

程序實現的服務器端UI如下:

采用Java Socket實現基于TCP方式的客戶服務器聊天程序

一個JList組件用來顯示在線的所有用戶,一個JTextArea組件用來顯示所有消息

記錄。所有消息必須通過服務器端轉發。點擊【start】按鈕啟動服務器端監聽

默認監聽端口為9999。

啟動服務器端的Action中的代碼如下:

 Thread startThread = new Thread(new Runnable() {
                public void run() {
                    startServer(9999);
                }
            });
    startThread.start();
    startBtn.setEnabled(false);
    shutDownBtn.setEnabled(true);
startServer()的代碼如下:

private void startServer(int port) {
    try {
        serverSocket = new ServerSocket(port);
        System.out.println("Server started at port :" + port);
        while(true) {
            Socket client = serverSocket.accept(); // blocked & waiting for income socket
            System.out.println("Just connected to " + client.getRemoteSocketAddress());
            DataInputStream bufferedReader = new DataInputStream(client.getInputStream());
            byte[] cbuff = new byte[256];
            int size = bufferedReader.read(cbuff);
            char[] charBuff = convertByteToChar(cbuff, size);
            String userName = String.valueOf(charBuff);
            ChatServerClientThread clentThread = new ChatServerClientThread(userName, client, this);
            clientList.add(clentThread);
            userNameList.add(userName);
            clentThread.start();
            updateUserList();
        }

    } catch (IOException e) {
        e.printStackTrace();
    }
}
簡單協議規則:

1.      任何消息發送完以后系統自動加上結束標志EOF

2.      接受到用戶消息以后通過解析EOF來完成消息傳遞

3.      自動發送更新用戶列表到所有客戶端當有新客戶登入時

為什么我要實現上述簡單協議,其實任何網絡通信都是基于協議實現

只有基于協議實現才可控制,可檢查。協議是網絡通信的最重要一環。

客戶端UI設計如下:

采用Java Socket實現基于TCP方式的客戶服務器聊天程序

一個自定義的JPanel實現背景漸進顏色填充。

- Message組件用來接受用戶輸入的聊天信息

- Friend List 會自動刷新用戶列表,當有新用戶登錄時候

- History Record用來顯示聊天記錄

- 【Connect】點擊連接到Server端,前提是必須填寫設置中全部,默認

    的機器IP為127.0.0.1端口為9999

- 【send】按鈕點擊會發送用戶輸入的消息到指定的其它客戶端。如果

    沒有選擇用戶,則發送到服務器端。

-  一次發送消息的大小不得大于200個字節。

完整的客戶端代碼如下:

package com.gloomyfish.socket.tutorial.chat;

import java.awt.BorderLayout;
import java.awt.FlowLayout;
import java.awt.GridLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.net.SocketAddress;

import javax.swing.BorderFactory;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JList;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JPasswordField;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
import javax.swing.JTextField;

import com.gloomyfish.custom.swing.ui.CurvedGradientPanel;

public class ChatClient extends JFrame implements ActionListener {
    public final static String CONNECT_CMD = "Connect";
    public final static String DISCONNECT_CMD = "Disconnect";
    public final static String SEND_CMD = "Send";
    public final static String END_FLAG = "EOF";

    /**
     * 
     */
    private static final long serialVersionUID = 5837742337463099673L;
    private String winTitle;
    private JLabel userLabel;
    private JLabel passwordLabel;
    private JLabel ipLabel;
    private JLabel portLabel;

    // text field
    private JTextField userField;
    private JPasswordField passwordField;
    private JTextField ipField;
    private JTextField portField;

    private JList friendList;
    private JTextArea historyRecordArea;
    private JTextArea chatContentArea;

    // buttons
    private JButton connectBtn;
    private JButton disConnectBtn;
    private JButton sendBtn;
    private JCheckBox send2AllBtn;

    // socket
    private Socket mSocket;
    private SocketAddress address;
    private ChatClientThread m_client;

    public ChatClient() {
        super("Chat Client");
        initComponents();
        setupListener();
    }

    private void initComponents() {
        JPanel settingsPanel = new CurvedGradientPanel();
        JPanel chatPanel = new CurvedGradientPanel();
        GridLayout gy = new GridLayout(1,2,10,2);
        getContentPane().setLayout(gy);
        getContentPane().add(settingsPanel);
        getContentPane().add(chatPanel);

        // set up settings info
        settingsPanel.setLayout(new BorderLayout());
        settingsPanel.setOpaque(false);
        JPanel gridPanel = new JPanel(new GridLayout(4, 2));
        gridPanel.setBorder(BorderFactory.createTitledBorder("Server Settings & User Info"));
        gridPanel.setOpaque(false);
        userLabel = new JLabel("User Name:");
        passwordLabel = new JLabel("User Password:");
        ipLabel = new JLabel("Server IP Address:");
        portLabel = new JLabel("Server Port");
        userLabel.setOpaque(false);
        passwordLabel.setOpaque(false);
        ipLabel.setOpaque(false);
        portLabel.setOpaque(false);
        userField = new JTextField();
        passwordField = new JPasswordField();
        ipField = new JTextField();
        portField = new JTextField();
        connectBtn = new JButton(CONNECT_CMD);
        disConnectBtn = new JButton(DISCONNECT_CMD);
        JPanel btnPanel = new JPanel();
        btnPanel.setOpaque(false);
        btnPanel.setLayout(new FlowLayout());
        btnPanel.add(connectBtn);
        btnPanel.add(disConnectBtn);

        gridPanel.add(userLabel);
        gridPanel.add(userField);
        gridPanel.add(passwordLabel);
        gridPanel.add(passwordField);
        gridPanel.add(ipLabel);
        gridPanel.add(ipField);
        gridPanel.add(portLabel);
        gridPanel.add(portField);
        friendList = new JList();
        JScrollPane friendPanel = new JScrollPane(friendList);
        friendPanel.setOpaque(false);
        friendPanel.setBorder(BorderFactory.createTitledBorder("Friend List:"));
        settingsPanel.add(btnPanel, BorderLayout.SOUTH);
        settingsPanel.add(gridPanel, BorderLayout.NORTH);
        settingsPanel.add(friendPanel,BorderLayout.CENTER);

        chatPanel.setLayout(new GridLayout(3,1));
        chatPanel.setOpaque(false);
        historyRecordArea = new JTextArea();
        JScrollPane histroyPanel = new JScrollPane(historyRecordArea);
        histroyPanel.setBorder(BorderFactory.createTitledBorder("Chat History Record:"));
        histroyPanel.setOpaque(false);
        chatContentArea = new JTextArea();
        JScrollPane messagePanel = new JScrollPane(chatContentArea);
        messagePanel.setBorder(BorderFactory.createTitledBorder("Message:"));
        messagePanel.setOpaque(false);
        // chatPanel.add(friendPanel);
        chatPanel.add(histroyPanel);
        chatPanel.add(messagePanel);
        sendBtn = new JButton(SEND_CMD);
        send2AllBtn = new JCheckBox("Send to All online Users");
        send2AllBtn.setOpaque(false);
        JPanel sendbtnPanel = new JPanel();
        sendbtnPanel.setOpaque(false);
        sendbtnPanel.setLayout(new FlowLayout());
        sendbtnPanel.add(sendBtn);
        sendbtnPanel.add(send2AllBtn);
        chatPanel.add(sendbtnPanel);
    }

    private void setupListener() {
        connectBtn.addActionListener(this);
        disConnectBtn.addActionListener(this);
        sendBtn.addActionListener(this);
        disConnectBtn.setEnabled(false);
    }

    /**
     * <p></p>
     * 
     * @param content - byte array
     * @param bsize - the size of bytes
     */
    public synchronized void handleMessage(char[] content, int bsize) {
        // char[] inputMessage = convertByteToChar(content, bsize);
        String receivedContent = String.valueOf(content);
        int endFlag = receivedContent.indexOf(END_FLAG);
        receivedContent = receivedContent.substring(0, endFlag);
        System.out.println("Client " + userField.getText() + " Message:" + receivedContent);
        if(receivedContent.contains("#")) {
            String[] onlineUserList = receivedContent.split("#");
            friendList.setListData(onlineUserList);
        } else {
            // just append to chat history record...
            appendHistoryRecord(receivedContent + "\r\n");
        }
    }

    public synchronized void appendHistoryRecord(String record) {
        historyRecordArea.append(record);
    }

    private String getSelectedUser() {
        int index = friendList.getSelectedIndex();
        if(index >= 0) {
        String user = (String)friendList.getSelectedValue();
        return user;
        } else {
            return "Server";
        }
    }

//  private char[] convertByteToChar(byte[] cbuff, int size) {
//      char[] charBuff = new char[size];
//      for(int i=0; i<size; i++) {
//          charBuff[i] = (char)cbuff[i];
//      }
//      return charBuff;
//  }

    public void setTitle(String title) {
        winTitle = title;
        super.setTitle(winTitle);
    }

    public String getTitle() {
        return super.getTitle();
    }

    public static void main(String[] args) {
        ChatClient client = new ChatClient();
        client.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        client.pack();
        client.setVisible(true);
    }

    @Override
    public void actionPerformed(ActionEvent e) {
        if(SEND_CMD.equals(e.getActionCommand())) {
            String chatContent = chatContentArea.getText();
            if(checkNull(chatContent)) {
                JOptionPane.showMessageDialog(this, "Please enter the message at least 6 characters!");
                return;
            } else if(chatContent.getBytes().length > 200) {
                JOptionPane.showMessageDialog(this, "The length of the message must be less than 200 characters!");
                return;
            }
            try {
                m_client.dispatchMessage(getSelectedUser() + "#" + chatContent);
                m_client.dispatchMessage(END_FLAG);
                appendHistoryRecord("me :" + chatContent + "\r\n");
                chatContentArea.setText(""); // try to clear user enter......
            } catch (IOException e1) {
                e1.printStackTrace();
            }
        } else if(DISCONNECT_CMD.equals(e.getActionCommand())) {
            enableSettingsUI(true);
        } else if(CONNECT_CMD.equals(e.getActionCommand())) {
            String serverHostName = ipField.getText();
            String portStr = portField.getText();
            String userName = userField.getText();
            char[] password = passwordField.getPassword();
            System.out.println("Password = " + password.length);
            if(checkNull(serverHostName) || checkNull(portStr) || checkNull(userName)) {
                JOptionPane.showMessageDialog(this, "Please enter user name, server host name, server port!");
                return;
            }
            setTitle("Chat Client-" + userName);
            address = new InetSocketAddress(serverHostName, Integer.parseInt(portStr));
            mSocket = new Socket();
            try {
                mSocket.connect(address);
                m_client = new ChatClientThread(this, mSocket);
                m_client.dispatchMessage(userName); // send user name
                // m_client.dispatchMessage(END_FLAG); // send end flag
                m_client.start();
                enableSettingsUI(false);
            } catch (IOException ioe) {
                ioe.printStackTrace();
            }

        }
    }

    private void enableSettingsUI(boolean enable) {
        ipField.setEditable(enable);
        portField.setEnabled(enable);
        userField.setEditable(enable);
        passwordField.setEnabled(enable);
        connectBtn.setEnabled(enable);
        disConnectBtn.setEnabled(!enable);
    }

    private boolean checkNull(String inputString) {
        if(inputString ==  null || inputString.length() == 0) {
            return true;
        } else {
            return false;
        }
    }

}
客戶端SOCKET通信線程的代碼如下:

package com.gloomyfish.socket.tutorial.chat;

import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.net.Socket;

public class ChatClientThread extends Thread {
    private ChatClient _mClient;
    private Socket _mSocket;
    private DataOutputStream dos;

    public ChatClientThread(ChatClient cclient, Socket socket) {
        this._mClient = cclient;
        this._mSocket = socket;
    }

    public void run() {
        try {
            DataInputStream bufferedReader = new DataInputStream(_mSocket.getInputStream());
            byte[] cbuff = new byte[256];
            char[] tbuff = new char[256];
            int size = 0;
            int byteCount = 0;
            int length = 0;
            while(true) {
                if((size = bufferedReader.read(cbuff))> 0) {
                    char[] temp = convertByteToChar(cbuff, size);
                    length = temp.length;
                    if((length + byteCount) > 256) {
                        length = 256 - byteCount;
                    }
                    System.arraycopy(temp, 0, tbuff, byteCount, length);
                    byteCount += size;
                    if(String.valueOf(tbuff).indexOf(ChatClient.END_FLAG) > 0) {
                        _mClient.handleMessage(tbuff, byteCount);
                        byteCount = 0;
                        clearTempBuffer(tbuff);
                    }
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    private void clearTempBuffer(char[] tbuff) {
        for(int i=0; i<tbuff.length; i++) {
            tbuff[i] = ' ';
        }
    }

    private char[] convertByteToChar(byte[] cbuff, int size) {
        char[] charBuff = new char[size];
        for(int i=0; i<size; i++) {
            charBuff[i] = (char)cbuff[i];
        }
        return charBuff;
    }

    public synchronized void dispatchMessage(String textMsg) throws IOException {
        if(dos == null) {
            dos = new DataOutputStream(_mSocket.getOutputStream());
        }
        byte[] contentBytes = textMsg.getBytes();
        dos.write(contentBytes, 0, contentBytes.length);
    }
}
服務器端的消息轉發代碼如下:

 public synchronized void dispatchMessage(String[] keyValue, String userName) throws IOException {
        chatArea.append(userName + " to " + keyValue[0] + " : " + keyValue[1] + "\r\n");
        for(ChatServerClientThread client : clientList) {
            if(client.getUserName().equals(keyValue[0])) {
                client.dispatchMessage(userName + " says: " + keyValue[1]);
                client.dispatchMessage(END_FLAG);
                break;
            }
        }
    }
服務器端的客戶端線程run方法的代碼如下:

public void run() {
    System.out.println("start user = " + userName);
    try {
        DataInputStream bufferedReader = new DataInputStream(userSocket.getInputStream());
        byte[] cbuff = new byte[256];
        char[] tbuff = new char[256];
        int size = 0;
        int byteCount = 0;
        int length = 0;
        while(true) {
            if((size = bufferedReader.read(cbuff))> 0) {
                char[] temp = convertByteToChar(cbuff, size);
                length = temp.length;
                if((length + byteCount) > 256) {
                    length = 256 - byteCount;
                }
                System.arraycopy(temp, 0, tbuff, byteCount, length);
                byteCount += size;
                if(String.valueOf(tbuff).indexOf(ChatServer.END_FLAG) > 0) {
                    String receivedContent = String.valueOf(tbuff);
                    int endFlag = receivedContent.indexOf(ChatServer.END_FLAG);
                    receivedContent = receivedContent.substring(0, endFlag);
                    String[] keyValue = receivedContent.split("#");
                    if(keyValue.length > 1) {
                        server.dispatchMessage(keyValue, userName);
                    }
                    byteCount = 0;
                    clearTempBuffer(tbuff);
                }
            }
        }
    } catch (IOException e) {
        e.printStackTrace();
    }
}
最終程序的運行結果截屏如下:

采用Java Socket實現基于TCP方式的客戶服務器聊天程序

采用Java Socket實現基于TCP方式的客戶服務器聊天程序

 

 本文由用戶 jopen 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!