采用Java Socket實現基于TCP方式的客戶服務器聊天程序
本文講演示如何通過Java Socket建立C/S方式的聊天程序。實現的功能
主要包括如下幾個方面:
1. 用戶登錄,在線用戶列表刷新
2.客戶端與服務器的TCP連接,實現消息的發送與接受
3.Java Swing與多線程編程技巧
一個整體的Class關系圖如下:
程序實現的服務器端UI如下:
一個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設計如下:
一個自定義的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(); } }最終程序的運行結果截屏如下:
本文由用戶 jopen 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!