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