Java实现局域网C/S群聊:服务端与客户端完整代码

清欢
清欢
发布时间:2026-10-13| 5人浏览过
原创

本文将演示如何使用 Java 的 Socket 和网络 IO 流构建一个基础的局域网多人群聊应用。该系统基于 TCP 协议,采用客户端/服务器(C/S)架构,实现了用户上线通知、消息广播、断线检测及简单的黑名单屏蔽功能。

实现目标与技术准备

本实例旨在解决如何在本地网络环境中让多个终端进行文字聊天。核心技术点包括:

  • TCP 连接管理:利用 ServerSocket 监听端口,Socket 处理具体通信链路。
  • 并发处理:服务端需为每个连接的客户端分配独立线程,保证多人同时聊天不阻塞。
  • 数据同步:使用 synchronized 关键字确保共享的客户端列表在增删时的线程安全。
  • I/O 流:结合 Scanner 读取控制台输入,PrintStream 发送数据,BufferedReader 异步接收数据。

系统架构说明

系统由以下五个核心类组成:

  1. Server:服务端入口,负责监听端口并维护在线客户端集合。
  2. Client:客户端入口,启动发送与接收两个子线程。
  3. ClientSend:负责将用户控制台输入的文本发送至服务端。
  4. ClientReceive:负责从服务端循环读取消息并打印到控制台。
  5. HandleSocket:服务端线程类,负责处理单个客户端的生命周期(上线、发消息、下线)及广播逻辑。

第一步:编写服务端 Server

服务端需要创建一个永久运行的循环,等待客户端连接。每当有新连接加入,将其加入同步列表,并启动一个 HandleSocket 线程来处理该客户端的具体业务。

package com.dragon.java.tcpchat;

import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.ArrayList;
import java.util.List;

/**
 * 服务器端主程序
 */
public class Server {
  public static void main(String[] args) throws IOException,
      InterruptedException {
    // 使用 ArrayList 存储所有已连接的客户端 Socket
    List<Socket> list = new ArrayList<>();

    // 创建服务器端的套接字,监听 10000 端口
    ServerSocket server = new ServerSocket(10000);

    System.out.println("服务器已启动,等待客户端连接...");

    while (true) {
      // 阻塞等待客户端连接
      Socket socket = server.accept();
      
      // 将新连接的 socket 加入列表,操作需同步
      synchronized (list) {
        list.add(socket);
      }
      
      // 启动新线程处理该客户端的逻辑
      new HandleSocket(socket, list).start();
    }
  }
}

第二步:编写客户端 Client

客户端连接到指定 IP 和端口后,不能关闭输出流和 Socket,否则无法进行双向通信。因此需要启动两个后台线程,分别负责发送和接收消息。

package com.dragon.java.tcpchat;

import java.io.IOException;
import java.net.Socket;
import java.net.UnknownHostException;

/**
 * 客户端主程序
 */
public class Client {
  public static void main(String args[]) throws UnknownHostException,
      IOException {
    // 连接到服务器,请修改 IP 为实际服务端地址
    Socket client = new Socket("192.168.1.188", 10000);

    // 启动发送线程
    new ClientSend(client).start();
    // 启动接收线程
    new ClientReceive(client).start();
    
    // 注意:聊天过程中保持长连接,不执行 shutdownOutput 或 close
  }
}

第三步:实现客户端发送逻辑 ClientSend

该类继承自 Thread。它在构造函数中获取 Socket 引用,在 run 方法中包装 System.in 为 Scanner,逐行读取用户输入并写入 Socket 的输出流。

package com.dragon.java.tcpchat;

import java.io.IOException;
import java.io.PrintStream;
import java.net.Socket;
import java.util.Scanner;

/**
 * 客户端发送信息的线程
 */
public class ClientSend extends Thread {
  private Scanner scanner;
  private Socket socket;

  public ClientSend(Socket socket) {
    this.socket = socket;
  }

  @Override
  public void run() {
    scanner = new Scanner(System.in);

    try {
      // 获取输出流,用于发送数据
      PrintStream ps = new PrintStream(socket.getOutputStream());
      String line = "";
      
      // 持续读取控制台输入
      while ((line = scanner.nextLine()) != null) {
        ps.println(line); // 发送一行消息
      }
    } catch (IOException e) {
      e.printStackTrace();
    }
  }
}

第四步:实现客户端接收逻辑 ClientReceive

此线程包裹 Socket 的输入流。由于是长期运行,它会在循环中调用 readLine() 阻塞等待服务端发来的消息。一旦有数据到达,即刻打印到屏幕。

package com.dragon.java.tcpchat;

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.net.Socket;

/**
 * 客户端接收信息的线程
 */
public class ClientReceive extends Thread {
  private Socket socket;

  public ClientReceive(Socket socket) {
    this.socket = socket;
  }

  @Override
  public void run() {
    try {
      // 获取输入流,编码建议使用 UTF-8 或 GBK
      BufferedReader br = new BufferedReader(new InputStreamReader(
          socket.getInputStream()));
          
      String line = "";
      // 持续读取服务端推送的消息
      while ((line = br.readLine()) != null) {
        System.out.println(line);
      }
    } catch (Exception e) {
      e.printStackTrace();
    }
  }
}

第五步:实现服务端核心处理类 HandleSocket

这是最关键的类。每个接入的用户都会生成一个 HandleSocket 实例。其主要职责包括: 1. 记录并提示用户上线。 2. 实现简单的 IP 黑名单过滤。 3. 循环读取该用户的消息,格式化后发送给其他所有人(广播)。 4. 捕获异常处理断连情况,并通知下线。

package com.dragon.java.tcpchat;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintStream;
import java.net.InetAddress;
import java.net.Socket;
import java.util.List;

/**
 * 处理单个客户端连接的线程
 */
public class HandleSocket extends Thread {

  private Socket socket;
  private List<Socket> list;

  public HandleSocket(Socket socket, List<Socket> list) {
    this.socket = socket;
    this.list = list;
  }

  /**
   * 线程主体:处理上线、收发消息、下线流程
   */
  @Override
  public void run() {
    // 获取客户端 IP
    InetAddress address = socket.getInetAddress();
    String ip = address.getHostAddress();
    System.out.println(ip + " 上线了!");
    
    // 示例:黑名单逻辑,此处仅作为演示,IP硬编码
    if (ip.equals("192.168.1.117")) {
      synchronized (list) {
        sendToAll(ip + " 由于违规操作,已被拉入黑名单!");
        list.remove(socket);
      }
      return; // 直接结束线程
    }

    try {
      // 读取该客户端发来的数据,设置字符集防止乱码
      BufferedReader br = new BufferedReader(new InputStreamReader(
          socket.getInputStream(), "gbk"));

      String line = "";
      // 循环读取该用户的消息
      while ((line = br.readLine()) != null) {
        String msg = ip + ": " + line;
        System.out.println(msg); // 服务器控制台显示
        
        // 将该消息广播给其他所有客户端
        sendToAll(msg);
      }

    } catch (IOException e) {
      // 出现IO异常通常意味着连接断开
      System.out.println(ip + " 掉线了!");
      synchronized (list) {
        list.remove(socket);
      }
    }
  }

  /**
   * 将消息发送给除了自己以外的所有在线客户端
   */
  private void sendToAll(String msg) {
    synchronized (list) {
      for (Socket s : list) {
        if (s != socket) {
          try {
            PrintStream ps = new PrintStream(s.getOutputStream());
            ps.println(msg); // 发送消息
          } catch (IOException e) {
            e.printStackTrace();
          }
        }
      }
    }
  }
}

排错与注意事项

  • 启动顺序:务必先运行 Server.java,再运行 Client.java。如果服务端未启动,客户端连接时会抛出 ConnectException。
  • IP 地址配置:示例代码中使用了固定 IP 192.168.1.188。请根据运行服务端电脑的局域网 IP 进行修改,或使用 localhost 进行单机测试。
  • 字符编码:HandleSocket 中使用了 "gbk" 编码。如果是 Windows 系统默认中文环境通常兼容,若发现乱码可尝试改为 "UTF-8" 并保持客户端与服务端一致。
  • 死锁风险:代码中大量使用了 synchronized(list)。在实际生产环境中,建议改用 Collections.synchronizedList 或 CopyOnWriteArrayList 以减少手动锁定的复杂度。
  • 资源释放:示例为了简化逻辑,在循环内部并未显式关闭流。实际应用中应在客户端关闭或断连时妥善处理流资源。

总结

通过以上步骤,我们实现了一个具备基础广播功能的局域网聊天室。其核心在于利用 Java 的多线程机制隔离不同用户的 I/O 操作,并通过同步集合保证消息分发的原子性。以上就是 Java 实现小型局域网 C/S 群聊功能的详细内容,更多关于 Java 网络编程的资料请关注本站其它相关文章!

免责声明:本文内容仅供参考,不代表本站观点。转载或引用请注明出处。

Python 相关产品和应用

全部下载
Selenium

Selenium

4.51.0 · Windows

用于浏览器自动化和 Web 测试的 Python 工具。

scikit-learn

scikit-learn

1.9.1 · Windows

常用 Python 机器学习算法与模型评估工具库。

Black

Black

26.10.1 · Windows

强调一致性的 Python 自动代码格式化工具。

Ruff

Ruff

0.17.1 · macOS

由 Rust 编写的高速 Python 代码检查和格式化工具。

pytest

pytest

9.1.1 · Windows

简洁而强大的 Python 自动化测试框架。

FastAPI

FastAPI

0.146.0 · Windows

高性能 Python API 框架,支持类型提示和自动接口文档。

Flask

Flask

3.1.3 · Windows

轻量灵活的 Python Web 应用框架。

Django

Django

6.1.2 · Windows

成熟的 Python Web 框架,适合构建内容站点和业务系统。

Spyder

Spyder

6.1.7 · Windows

面向科学计算与数据分析的 Python 集成开发环境。

SciPy

SciPy

1.18.1 · Windows

面向科学、工程和技术计算的 Python 算法库。