我用草圖畫了一下及基本流程為:

原理與思路:
使用TCP面向連接的套接字來建立服務端和客戶端兩個IP地址端點之間的會畫。
服務器端主要完成用戶信息的存儲、客戶端命令的響應與接收、信息的轉發等功能。
客戶端主要完成登陸、聊天信息的接收以及發送信息等功能。客戶端必須在服務器啟動后才能連接成功,用戶登陸以后聊天室的客戶端會顯示用戶已經登陸,可以與服務器進行即使聊天。
服務器端設計
服務器端是整個聊天室的核心部分,它涵蓋了客戶端的加入、客戶端請求如何處理等一系列功能,客戶發送的信息也是通過服務器端發送給其他用戶的。
系統的安全性
服務器的安全包括兩個部分,一是服務器本身軟件和硬件上的安全性,比如防止安全漏洞;而是客戶和服務器通訊協議的安全性設計,防止通過協議本身非法攻擊服務器。
系統並發服務能力
多線程並發處理可以使CPU效率達到最高,舉個例子來說,網絡的數據傳送速率遠遠低於CPU的處理能力,本地文件系統資源的讀寫速度也遠遠低於CPU的處理能力,在傳統的單線程環境中,在一個線程程序中如果出現阻塞則整個程序都可以能停止運行,而在一個多線程的程序中就不會出現這樣的問題。當一個線程阻塞時,別的線程依然可以大大地提高CPU的效率。
服務器端主要類的定義
SerFrame類是服務器端程序的入口,其中包括一個主窗體、若干個容器、按鈕和文本框。Init()方法是使整個圖形界面初始化。同時還有ServerStart() 方法和ServerStop()兩個方法,當運行ServerStart()方法時就會創建一個ServerSocket()對象並設定1001端口號,關閉聊天界面會自動斷開連接。
serConnect類集成了Thread線程類,並重寫了父類的main()方法。方法中的run()方法一直處於監聽狀態,直到返回值為false時,也就是服務器關閉時。控制輸入輸出流來顯示在線信息,並發送到用戶管理的設計。
服務器端用戶管理的設計
通過定義UesrInfoList和Node這兩個類來實現服務器管理用戶功能,當服務器接收到一個客戶端的請求,服務器就會初始化一個Node節點作為客戶端,Node類中包括輸入輸出、用戶名等必要的屬性,還定義了其它自身引用NodeNext,其作用是將客戶端設計成鏈接表,通過這個屬性讓他們互相連接,這樣設計的優點是不需要再定義Map或者List來存放客戶端結點,只要在Node中增加一個屬性就能夠實現,從而提高性能。UserInfoList中定義了對Node結點查找、增加、刪除等方法,無論是發送信息、上線、下線和私聊都是調用UserInfoList中的方法來確定對哪個客戶端進行操作的。Node對象是存放在內存中的,當ServerSocket關閉的時候會釋放資源,Node結點失效。
服務器端消息顯示設計
服務器端是所有消息中轉戰和系統消息發出站。在客戶端Node類定義兩個屬性,分別是ObjectOutputStream和ObjectIntputStream,這是java語言的輸出輸入流,應用所有所有信息的傳遞。首先ServerListenerThread會捕獲到客戶端的請求,然后引用ChatRoom類並調用ObjectOutStream方法發送消息。如果是群聊,則調用SendMessage中的SendMsgToAll發送到每一個客戶端,並調用UserInfoList來獲取所有客戶。客戶端獲得消息后調用服務器中定義的Node類中的OutputStream將接受的顯示到圖形界面中的文本輸出框。若果是私聊,UserInfoList會查找到是哪個用戶,再調用SendMessage方法將消息發送到指定客戶端。
客服端設計
客戶端中實現了客戶界面的顯示以及與服務器端的數據交換,包括接收和發送信息。它繼承了Thread線程類,因而多個客戶端可以並發執行。
客戶端設計准則
客戶端作為面對普通的群體,其作用是非常重要的,它的權限沒有服務器高。在客戶端的設計中只要加入客戶端啟動界面和發送、接收信息的線程以及一些對基本字符串的簡單功能,其他復雜的功能都由服務器來完成。
客戶端主要類的定義
ChatClient類中定義了客戶端的主要函數,login方法是用於用於登陸的 ,不需要事先注冊,用戶名不為空即可登陸。另外,還要logout下線以及發送信息的方法。客戶端的主界面的初始化寫在inti方法中,和服務器端界面類似,它由一個panel和若干個按鈕和textArea組成,這些Swing組件與服務器端的監聽器相連。
客戶端登陸設計
程序登陸需要驗證,若果用戶不是已經存在的用戶名,則基本的那個路成功。登陸功能中首先要實例化一個Socket,傳入本機的IP地址和連接服務器的端口號,然后congratulationsocket對象獲取輸入流,實例化用戶線程並啟用,若果服務器未啟動則拋出異常,並在主界面中顯示錯誤信息。
客戶端發端發送信息的設計
SendMessage方法實現發送信息,該方法首先獲得要發送的對象和內容,然后把提示信息、聊天方法、聊天對象、聊天內容和表情依次通過輸出流發送給服務器端,發送一條並及時清空一次緩存,服務器會根據接收到的信息作相應的處理。
客戶端顯示收到的消息的設計
ReiMessage方法實現接收消息,該方法中定義了一個while循環,只要用戶沒有斷開與服務器的連接或者下線,則消息一直處於接收狀態。當輸入流接收服務器發送的內容后,用了條件語句判斷是和何種類型的信息,並對其加上相對應的類型提示信息的處理。最后送交主界面進行顯示。
創建服務器主類:
package com.home.server;
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.ArrayList;
public class MyServer {
// 定義保存所有Socket的集合
public static ArrayList<Socket> socketList = new ArrayList<Socket>();
public static void main(String[] args) throws IOException {
ServerSocket ss = new ServerSocket(20000);
System.out.println("服務器創建成功!");
System.out.println("等待客戶端的連接。。。");
while (true) {
// 此行代碼會阻塞,等待用戶的連接
Socket socket = ss.accept();
System.out.println("有客戶端連接進來!");
socketList.add(socket);
// 每當客戶端連接后啟動一條ServerThread線程為該客戶端服務
new Thread(new ServerThread(socket)).start();
}
}
}
Server:
package com.home.server;
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.ArrayList;
public class MyServer {
// 定義保存所有Socket的集合
public static ArrayList<Socket> socketList = new ArrayList<Socket>();
public static void main(String[] args) throws IOException {
ServerSocket ss = new ServerSocket(20000);
System.out.println("服務器創建成功!");
System.out.println("等待客戶端的連接。。。");
while (true) {
// 此行代碼會阻塞,等待用戶的連接
Socket socket = ss.accept();
System.out.println("有客戶端連接進來!");
socketList.add(socket);
// 每當客戶端連接后啟動一條ServerThread線程為該客戶端服務
new Thread(new ServerThread(socket)).start();
}
}
}
服務器的線程:
package com.home.server;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.Socket;
public class ServerThread implements Runnable {
// 定義當前線程所處理的Socket
private Socket socket = null;
// 該線程所處理的Socket所對應的輸入流
BufferedReader br = null;
public ServerThread(Socket socket) throws IOException {
this.socket = socket;
// 初始化該Socket對應的輸入流
br = new BufferedReader(new InputStreamReader(socket.getInputStream(),
"utf-8"));
}
@Override
public void run() {
try {
String content = null;
// 采用循環不斷從Socket中讀取客戶端發送過來的數據
while ((content = readFromClient()) != null) {
// 遍歷socketList中的每個Socket,將讀到的內容向每個Socket發送一次
for (Socket s : MyServer.socketList) {
OutputStream os = s.getOutputStream();
os.write((content + "\n").getBytes("utf-8"));
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
定義讀取客戶端數據的方法:
private String readFromClient() {
try {
return br.readLine();
}
// 如果捕捉到異常,表明該Socket對應的客戶端已經關閉
catch (Exception e) {
// 刪除該Socket
MyServer.socketList.remove(socket);
e.printStackTrace();
}
return null;
}
}
客戶端主體類Activity:
package com.home.activity;
import java.io.OutputStream;
import java.net.Socket;
import android.app.Activity;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.EditText;
import com.home.R;
import com.home.util.ClientThread;
public class MultiThreadClient extends Activity {
private EditText input, show;
private Button sendBtn;
private OutputStream os;
private Handler handler;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
input = (EditText) findViewById(R.id.main_et_input);
show = (EditText) findViewById(R.id.main_et_show);
sendBtn = (Button) findViewById(R.id.main_btn_send);
handler = new Handler() {
@Override
public void handleMessage(Message msg) {
// 如果消息來自子線程
if (msg.what == 0x234) {
// 將讀取的內容追加顯示在文本框中
show.append("\n" + msg.obj.toString());
}
}
};
Socket socket;
try {
socket = new Socket("192.168.0.101", 20000);
// 客戶端啟動ClientThread線程不斷讀取來自服務器的數據
new Thread(new ClientThread(socket, handler)).start();
os = socket.getOutputStream();
} catch (Exception e) {
e.printStackTrace();
}
sendBtn.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
try {
// 將用戶在文本框內輸入的內容寫入網絡
os.write((input.getText().toString() + "\r\n").getBytes());
// 清空input文本框數據
input.setText("");
} catch (Exception e) {
e.printStackTrace();
}
}
});
}
}
