首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 相關知識

Java UDP 聊天室服務端實現:多線程架構與消息路由詳解

     編輯:Java 相關知識

基於 UDP 協議的聊天室服務端,採用多線程架構實現並發消息處理。本文詳細解析服務端核心模塊:初始化、監聽線程、消息分析路由、用戶管理、廣播/單播轉發,附完整可運行代碼。

功能特性

  • 控制台命令交互:-help/-run/-stop/-quit
  • 獨立監聽線程處理客戶端數據包
  • 指令解析:連接測試、用戶登錄/退出、在線列表、私聊/廣播
  • 用戶信息內存存儲(Hashtable)

核心類結構

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; // 暱稱->地址
    // 核心方法...
}

關鍵模塊實現

1. 服務初始化與地址綁定

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!");
    }
}

2. 監聽線程:核心事件循環


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!");
        }
    }
}

3. 消息分析器:指令路由中樞


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);
    }
}

4. 用戶地址反查:Hashtable 只支持 Key→Value


private String getUserName(SocketAddress addr) {
    for (String name : Collections.list(userRegistry.keys())) {
        if (addr.equals(userRegistry.get(name))) return name;
    }
    return "Unknown";
}

5. 消息發送:廣播與單播


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!"); }
}

6. 主控制台交互循環


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();
}

UDP 協議特性應對

擴展建議

  • 引入 ConcurrentHashMap 替換 Hashtable 提升並發性能
  • 添加用戶心跳檢測,清理僵屍連接
  • 實現消息確認機制(ACK/重傳)保證可靠性
  • 支持群組聊天、文件傳輸、加密通訊
  • 持久化聊天記錄至數據庫
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved
挑戰應對策略
無連接、不可靠應用層心跳(-test)、序列號重傳(未實現,可擴展)
數據包大小限制緩衝區 1024 字節,大消息需分片
客戶端端口變化使用 SocketAddress (IP+Port) 唯一標識用戶