1. 前言
關於Socket和ServerSocket的知識可以看:Java Socket 通信
2. 功能需求
2.1 服務器端
- 基於線程池服務器端可以同時與5個客戶端保持通信
- 監聽客戶端連接,並創建獨立線程保持與客戶端的通信
- 監聽客戶端消息,並轉發給聊天室裏的其他在線用戶
- 通過HashMap存儲客戶端的信息
2.2 客戶端
- 連接服務器,並創建獨立線程保持與服務器的通信(主要用於處理用戶輸入)
- 發送消息給服務器,接收服務器發送的消息
3. 程序架構圖
4. 程序源代碼
4.1 服務器端
ChatServer類:
package bio.server;
import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class ChatServer {
private int DEFAULT_PORT = 8888;//默認端口
private final String QUIT = "quit";//退出指令
private ExecutorService executorService;
private ServerSocket serverSocket;
private Map<Integer, Writer> connectedClients;//存儲客戶端信息
public ChatServer(){
executorService = Executors.newFixedThreadPool(5);//創建線程池
connectedClients = new HashMap<>();
}
/**
* 客戶端連接,添加一個客戶端
* synchronized 保證線程的安全性,防止多個線程同時添加客戶端
* @param socket 客戶端的socket
* @throws IOException
*/
public synchronized void addClient(Socket socket) throws IOException {
if(socket != null){
int port = socket.getPort();
BufferedWriter bufferedWriter = new BufferedWriter(
new OutputStreamWriter(socket.getOutputStream())
);
connectedClients.put(port, bufferedWriter);
System.out.println("客戶端["+port+"]已連接到服務器");
}
}
/**
* 客戶端斷開連接,從列表中刪除客戶端
* synchronized 保證線程安全性, 防止多個線程同時刪除客戶端
* @param socket 斷開連接的客戶端socket
* @throws IOException
*/
public synchronized void removeClient(Socket socket) throws IOException {
if(socket != null){
int port = socket.getPort();
if(connectedClients.containsKey(port)){
connectedClients.get(port).close();
}
connectedClients.remove(port);
System.out.println("客戶端["+port+"]已斷開連接");
}
}
/**
* 向服務器中其他的客戶端轉發消息,除了消息本身的擁有者
* synchronized 保證線程安全性,防止多個線程同時去轉發消息,訪問connectedClients
* @param socket 消息所屬的客戶端的socket
* @param fwdMsg 消息的具體內容
* @throws IOException
*/
public synchronized void forwardMessage(Socket socket, String fwdMsg) throws IOException {
for(Integer id : connectedClients.keySet()){
if(!id.equals(socket.getPort())){
Writer writer = connectedClients.get(id);
writer.write(fwdMsg);
writer.flush();
}
}
}
/**
* 如果用戶發送quit則準備退出
* @param msg 用戶返送的消息
* @return
*/
public boolean readyToQuit(String msg){
return QUIT.equals(msg);
}
/**
* synchronized 保護serverSocket的狀態
* 關閉serverSocket
*/
public synchronized void close(){
if(serverSocket != null){
try {
serverSocket.close();
System.out.println("關閉serverSocket");
} catch (IOException e) {
e.printStackTrace();
}
}
}
/**
* 開啓服務器,監聽端口,並且啓動ChatHandler線程
*/
public void start(){
try {
// 綁定監聽端口
serverSocket = new ServerSocket(DEFAULT_PORT);
System.out.println("啓動服務器,監聽端口:" + DEFAULT_PORT + "...");
while(true){
//等待客戶端連接
Socket socket = serverSocket.accept();
//創建額外的ChatHandler線程
executorService.execute(new ChatHandler(this, socket));
}
} catch (IOException e) {
e.printStackTrace();
} finally {
close();
}
}
public static void main(String[] args) {
ChatServer chatServer = new ChatServer();
chatServer.start();
}
}
ChatHandler類:
package bio.server;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.Socket;
public class ChatHandler implements Runnable{
private ChatServer server; //用來操作服務器端的connectedClients
private Socket socket;
public ChatHandler(ChatServer server, Socket socket){
this.server = server;
this.socket = socket;
}
/**
* 用於對客戶端進行服務
*/
@Override
public void run() {
try {
//存儲新上線用戶
server.addClient(socket);
//讀取用戶發送的消息
BufferedReader bufferedReader = new BufferedReader(
new InputStreamReader(socket.getInputStream())
);
String msg = null;
while((msg = bufferedReader.readLine()) != null){
String fwdMsg = "客戶端["+socket.getPort() +"]:" + msg + "\n";//readLine()函數讀取需要加換行符
System.out.print(fwdMsg);
//將收到的消息轉發給聊天室裏在線的其他用戶
server.forwardMessage(socket, fwdMsg);
//檢查用戶是否準備退出
if(server.readyToQuit(msg)){
break;
}
}
} catch (IOException e) {
e.printStackTrace();
}finally {
try {
server.removeClient(socket);
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
4.2 客戶端
ChatClient類:
package bio.client;
import java.io.*;
import java.net.Socket;
public class ChatClient {
private final String QUIT = "quit";
private final String DEFAULT_SERVER_HOST = "127.0.0.1";
private final int DEFAULT_SERVER_PORT = 8888;
private Socket socket;
private BufferedReader bufferedReader = null;
private BufferedWriter bufferedWriter = null;
/**
* 發送消息給服務器
* @param msg 用戶發送的消息
* @throws IOException
*/
public void send(String msg) throws IOException {
if(!socket.isOutputShutdown()){//確定socket的輸出流未關閉
bufferedWriter.write(msg + "\n");
bufferedWriter.flush();
}
}
/**
* 從服務器接收消息
* @return
* @throws IOException
*/
public String receive() throws IOException {
String msg = null;
if(!socket.isInputShutdown()){//確定socket的輸入流未關閉
msg = bufferedReader.readLine();
}
return msg;
}
/**
* 檢查用戶是否準備退出
* @param msg 用戶發送的消息
* @return
*/
public boolean readyToQuit(String msg){
return QUIT.equals(msg);
}
/**
* 關閉socket
*/
public void close(){
if(bufferedWriter != null){
try {//關閉bufferedWriter的同時也關閉socket
System.out.println("關閉socket");
bufferedWriter.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
/**
* 創建實例、IO流、處理用戶輸入以及讀取服務器轉發的消息
*/
public void start(){
try {
//創建socket實例
socket = new Socket(DEFAULT_SERVER_HOST, DEFAULT_SERVER_PORT);
//創建IO流
bufferedReader = new BufferedReader(
new InputStreamReader(socket.getInputStream())
);
bufferedWriter = new BufferedWriter(
new OutputStreamWriter(socket.getOutputStream())
);
//創建額外的線程處理用戶的輸入
new Thread(new UserInputHandler(this)).start();
//讀取服務器轉發的消息
String msg = null;
while((msg = receive()) != null){
System.out.println(msg);
}
} catch (IOException e) {
e.printStackTrace();
}finally {
close();
}
}
public static void main(String[] args) {
ChatClient chatClient = new ChatClient();
chatClient.start();
}
}
UserInputHandler類:
package bio.client;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
public class UserInputHandler implements Runnable{
private ChatClient chatclient;
public UserInputHandler(ChatClient chatClient){
this.chatclient = chatClient;
}
public UserInputHandler(nio.test.client.ChatClient chatClient) {
}
/**
*
*/
@Override
public void run() {
try {
//等待用戶輸入的消息
BufferedReader consoleReader = new BufferedReader(
new InputStreamReader(System.in)
);
while(true){
String input = consoleReader.readLine();
//向服務器發送消息
chatclient.send(input);
//檢查用戶是否準備退出
if(chatclient.readyToQuit(input)){
break;
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
}