基於 UDP 協議的聊天室服務端,採用多線程架構實現並發消息處理。本文詳細解析服務端核心模塊:初始化、監聽線程、消息分析路由、用戶管理、廣播/單播轉發,附完整可運行代碼。
-help/-run/-stop/-quitHashtable)public class Server implements Runnable {
private DatagramSocket server;
private DatagramPacket receivePacket;
private InetAddress serverAddress;
private int port;
private boolean running = false; // 監聽線程控制
private boolean testPass = true; // 初始化測試標誌
private Hashtable userRegistry; // 暱稱->地址
// 核心方法...
}
public void initServer(String ip, int port) {
fixServerLink(ip, port); // 解析 IP/端口,支持 "0" 為默認值
try {
server = new DatagramSocket(this.port, serverAddress);
System.out.println("!-The Server Initialization Success!");
running = true;
} catch (SocketException e) {
testPass = false;
errorTip("!-The Server Initialization Fail!");
}
}
private void fixServerLink(String ip, int port) {
this.port = (port == 0) ? 9999 : port;
try {
this.serverAddress = "0".equalsIgnoreCase(ip) ?
InetAddress.getLocalHost() : InetAddress.getByName(ip);
} catch (UnknownHostException e) {
errorTip("!-Invalid IP address!");
}
}
public void run() { listenLink(); } // Runnable 入口
public void listenLink() {
byte[] buf = new byte[1024];
receivePacket = new DatagramPacket(buf, buf.length);
System.out.println("!-Server starts listening...");
while (running) {
try {
server.receive(receivePacket); // 阻塞等待數據包
String msg = new String(buf, 0, receivePacket.getLength());
analyseMsg(msg); // 分發處理
} catch (IOException e) {
errorTip("!-Receive failed!");
}
}
}
private void analyseMsg(String msg) {
SocketAddress clientAddr = receivePacket.getSocketAddress();
String trimmed = msg.trim();
if ("-test".equalsIgnoreCase(trimmed)) { // 連接測試
sendSingle(clientAddr, "-test: !-From Server: Succeed in Testing.");
}
else if ("-quit".equalsIgnoreCase(trimmed)) { // 用戶退出
String name = getUserName(clientAddr);
System.out.println(name + " quit!");
broadcast("* " + name + " 退出聊天室");
userRegistry.remove(name);
}
else if ("-getList".equals(trimmed)) { // 獲取在線列表
StringBuilder list = new StringBuilder("-getList,");
for (String name : Collections.list(userRegistry.keys())) {
list.append(name).append(",");
}
sendSingle(clientAddr, list.toString());
}
else if (trimmed.startsWith("-to ")) { // 私聊
String content = trimmed.substring(4); // "-to ".length()
String targetName = content.substring(0, content.indexOf(' '));
String sender = getUserName(clientAddr);
String privateMsg = sender + " say to you: " + content.substring(targetName.length()+1);
if (!userRegistry.containsKey(targetName)) {
sendSingle(clientAddr, "!-User not found.");
} else {
sendSingle(userRegistry.get(targetName), privateMsg);
}
}
else if (trimmed.startsWith("-nick ")) { // 用戶登錄
String nick = trimmed.substring(6);
if (userRegistry.containsKey(nick)) {
sendSingle(clientAddr, "-nick: !-Nickname exists.");
} else {
userRegistry.put(nick, clientAddr);
broadcast("Welcome " + nick + " to Chat!");
System.out.println(nick + " joined.");
}
}
else { // 廣播消息
String sender = getUserName(clientAddr);
broadcast(sender + ": " + msg);
}
}
private String getUserName(SocketAddress addr) {
for (String name : Collections.list(userRegistry.keys())) {
if (addr.equals(userRegistry.get(name))) return name;
}
return "Unknown";
}
public void broadcast(String msg) {
byte[] buf = msg.getBytes();
for (SocketAddress addr : Collections.list(userRegistry.elements())) {
try {
server.send(new DatagramPacket(buf, buf.length, addr));
} catch (IOException e) { errorTip("Broadcast failed!"); }
}
}
public void sendSingle(SocketAddress addr, String msg) {
try {
server.send(new DatagramPacket(msg.getBytes(), msg.length(), addr));
} catch (IOException e) { errorTip("Single send failed!"); }
}
public static void main(String[] args) {
Server server = new Server();
Scanner input = new Scanner(System.in);
System.out.println("!-Input server command:");
while (true) {
String cmd = input.next();
if ("-quit".equalsIgnoreCase(cmd)) break;
if ("-help".equalsIgnoreCase(cmd)) { server.helpList(); continue; }
if ("-run".equalsIgnoreCase(cmd)) {
boolean retry = true;
while (retry) {
System.out.print("Server IP (0=default): "); String ip = input.next();
System.out.print("Server Port (0=default): ");
int port = 0;
try { port = input.nextInt(); }
catch (Exception e) { System.out.println("Invalid port!"); continue; }
if (server.testServer(ip, port)) {
new Thread(server).start(); // 啟動監聽線程
retry = false;
} else System.out.println("Server start failed!");
}
}
if ("-stop".equalsIgnoreCase(cmd)) server.cutServer();
}
input.close();
server.cutServer();
}
| 挑戰 | 應對策略 |
|---|---|
| 無連接、不可靠 | 應用層心跳(-test)、序列號重傳(未實現,可擴展) |
| 數據包大小限制 | 緩衝區 1024 字節,大消息需分片 |
| 客戶端端口變化 | 使用 SocketAddress (IP+Port) 唯一標識用戶 |