
简介这份资源是面向计算机专业学生与Java初学者的一套银行排号系统完整项目资料基于Java Socket网络通信与Java GUI图形界面开发后端采用Oracle数据库适合作为课程设计、毕业设计或Java综合练习的参考方案。压缩包整体约292.61MB内含项目全套源码与配套文档源码均经过测试校正可百分百成功运行方便读者直接部署与二次开发。项目围绕银行排队取号、叫号、窗口调度等核心业务展开涵盖Socket多客户端通信、GUI界面交互与数据库持久化等关键知识点能帮助读者理解C/S架构下网络编程与界面设计的整合思路。目前已有475人浏览学习说明该方案在同类课程设计中具有一定参考价值。对于需要完成相关项目设计、希望获得可运行代码与文档支撑的读者这份资料能提供从源码结构到实现逻辑的完整参照便于快速上手并对照学习。1. 银行排号系统这套 Java Socket GUI 源码到底能解决什么很多人做课程设计时卡在同一个地方业务逻辑想得明白但一落到「多台机器怎么同时连一个服务端、窗口怎么实时刷新队列」就写不动了。这套基于 Java Socket Java GUI 的银行排号系统解决的正是这个场景——它用原生 Socket 做 C/S 通信用 Swing/AWT 搭客户端界面后端接 Oracle 存业务数据把「取号、叫号、队列管理、窗口状态同步」这条链路完整跑通。适合正在做 Java 课程设计、想找一个能跑起来的 C/S 架构参考的人也适合想补 Socket 网络编程实战的初学者。它不是什么高并发中间件而是一份结构清晰、能本地复现的完整工程源码加文档一起给省掉你自己拼通信协议和界面布局的时间。2. 拆开看架构Socket 通信层和 GUI 层是怎么分工的拿到一个 C/S 项目先别急着跑先看清楚它把职责切在哪。这套系统的核心就两条线一条是客户端和服务端之间的 Socket 长连接负责把「取号请求」「叫号指令」「队列变更」这些消息传过去另一条是每个客户端自己的 GUI 线程负责把收到的数据渲染成界面。理解这个分工后面调 bug 才知道该往哪一层看。2.1 通信协议自定义消息格式而不是直接传对象原生 Socket 传数据绕不开一个问题TCP 是字节流没有消息边界。你write两次对面可能一次read全收走也可能分两次收。常见做法是自定义一个简单的应用层协议用「长度 类型 内容」的格式把每条消息框起来。这套源码里我一般会看到类似这样的消息封装// 消息封装用固定分隔符切分避免粘包 public class Message implements Serializable { private String type; // 消息类型TAKE_NUM / CALL_NUM / QUERY_QUEUE private String content; // 业务数据通常是 JSON 或分隔字符串 private long timestamp; // 时间戳用于排序和去重 // 序列化后通过 ObjectOutputStream 发送 public byte[] toBytes() throws IOException { ByteArrayOutputStream bos new ByteArrayOutputStream(); ObjectOutputStream oos new ObjectOutputStream(bos); oos.writeObject(this); oos.flush(); return bos.toByteArray(); } }逻辑说明这里用 Java 原生序列化把对象转成字节数组服务端收到后再反序列化还原。参数上type决定服务端走哪个分支处理content装具体业务字段timestamp在排查「消息乱序」时特别有用。要注意的是如果客户端和服务端的类版本不一致反序列化会抛InvalidClassException所以实体类最好固定serialVersionUID。2.2 服务端并发模型一个连接一个线程银行排号这种场景并发量不大——几个窗口加一台取号机撑死十几个连接。所以源码大概率用的是「每来一个连接就开一个线程」的模型而不是 NIO 或线程池。这不是偷懒是场景匹配。// 服务端主循环accept 后交给独立线程处理 ServerSocket serverSocket new ServerSocket(8888); System.out.println(排号服务已启动监听 8888); while (true) { Socket socket serverSocket.accept(); // 阻塞等待客户端连接 // 每个客户端一个线程互不阻塞 new Thread(new ClientHandler(socket)).start(); }逻辑说明accept()会阻塞直到有客户端连上来。拿到Socket后立刻丢给ClientHandler线程主线程回去继续等下一个连接。参数上端口号 8888 是常见选择改端口要同步改客户端。这里有个血泪经验如果ClientHandler里读流用了readObject()而客户端发的是裸字符串会直接卡死或抛异常所以两端协议必须严格对齐。2.3 GUI 层Swing 事件线程不能碰网络 IOSwing 有个铁律所有界面更新必须在事件分发线程EDT里做而网络 IO 是阻塞的绝不能放在 EDT 里。这套源码如果写得规范应该是「网络收数据 → 丢到队列 → EDT 定时取 → 刷新界面」这个套路。// 客户端后台线程收消息通过 SwingUtilities 切回 EDT 更新界面 new Thread(() - { try { ObjectInputStream in new ObjectInputStream(socket.getInputStream()); while (true) { Message msg (Message) in.readObject(); // 关键用 invokeLater 把界面更新切回 EDT SwingUtilities.invokeLater(() - { if (QUEUE_UPDATE.equals(msg.getType())) { queueLabel.setText(msg.getContent()); } }); } } catch (Exception e) { e.printStackTrace(); } }).start();逻辑说明后台线程负责阻塞读读到消息后用SwingUtilities.invokeLater把更新动作排进 EDT 队列。参数上msg.getType()决定更新哪个控件。如果直接把queueLabel.setText()写在后台线程里界面会偶发不刷新甚至卡死这是新手最容易翻车的地方。3. 本地跑起来Oracle 建库、导源码、改配置的完整步骤架构看懂了接下来是让它真的跑起来。这套系统依赖 Oracle所以环境准备比纯 Java 项目多一步。下面按「装环境 → 建库 → 导工程 → 改配置 → 启动」的顺序走每一步都给出可抄的命令和参数。3.1 环境准备JDK、Oracle、IDE 三件套先确认版本版本不对后面全是玄学问题。常见做法是 JDK 8 配 Oracle 11g 或 19c因为老项目的 JDBC 驱动和语法对高版本 JDK 兼容性一般。组件推荐版本说明JDK1.8源码大概率用 8 编译高版本可能有模块化报错Oracle11g / 19c19c 需注意驱动包版本JDBC 驱动ojdbc8.jar放项目 lib 目录并加入 Build PathIDEEclipse / IntelliJ IDEA导入普通 Java 工程即可检查 JDK 是否装好java -version # 期望输出java version 1.8.0_xxx javac -version如果java -version报「不是内部或外部命令」说明环境变量没配需要把JAVA_HOME指向 JDK 安装目录并把%JAVA_HOME%\bin加进Path。3.2 建库建表用 SQL 脚本还原数据结构源码文档里一般会带.sql文件直接拿去执行。如果没有就按业务字段自己建。核心表通常三张号码表、窗口表、叫号记录表。-- 创建表空间用户19c 常见做法 CREATE USER bank_queue IDENTIFIED BY bank123; GRANT CONNECT, RESOURCE TO bank_queue; -- 号码表记录每个取号请求 CREATE TABLE t_queue ( id NUMBER PRIMARY KEY, num VARCHAR2(10), -- 号码如 A001 status NUMBER(1), -- 0 等待 1 办理中 2 已完成 create_time DATE DEFAULT SYSDATE ); -- 窗口表记录窗口当前叫到的号 CREATE TABLE t_window ( win_id NUMBER PRIMARY KEY, win_name VARCHAR2(20), curr_num VARCHAR2(10) );逻辑说明t_queue的status字段是队列状态机的核心取号时插 0叫号时改 1办完改 2。t_window存每个窗口当前处理的号码客户端刷新时查这张表。参数上num用VARCHAR2而不是数字因为要带字母前缀。注意 Oracle 里DATE类型默认带时分秒如果只要日期得用TRUNC。3.3 导入工程并改数据库连接把源码导入 IDE 后第一件事是找数据库配置文件。老项目常见两种写法一种是硬编码在DBUtil.java里一种是放在db.properties。// DBUtil.java 常见写法 public class DBUtil { private static final String URL jdbc:oracle:thin:localhost:1521:orcl; // 19c 可能是 /orclpdb private static final String USER bank_queue; private static final String PWD bank123; public static Connection getConn() throws Exception { Class.forName(oracle.jdbc.driver.OracleDriver); return DriverManager.getConnection(URL, USER, PWD); } }逻辑说明URL里的localhost:1521:orcl是 SID 写法19c 多用服务名写法localhost:1521/orclpdb写错会报ORA-12505。Class.forName加载驱动JDK 8 下必须写否则报No suitable driver。参数上用户名密码要和建库时一致。3.4 启动顺序先服务端后客户端顺序错了会连不上这是最常见的翻车点。先运行服务端主类通常叫ServerMain或BankServer看到「监听 8888」才算起来。再运行客户端主类ClientMain或LoginFrame输入服务端 IP 和端口。多开几个客户端模拟多个窗口验证队列是否同步。# 命令行启动示例假设已编译到 out 目录 java -cp out;lib/ojdbc8.jar com.bank.server.ServerMain java -cp out;lib/ojdbc8.jar com.bank.client.ClientMain逻辑说明-cp指定类路径Windows 用分号分隔Linux/Mac 用冒号。lib/ojdbc8.jar必须带上否则连不上 Oracle。如果客户端报Connection refused先确认服务端是否真的在监听用netstat -ano | findstr 8888查端口。4. 避坑排查这套 C/S 项目最容易翻车的五个地方跑通只是第一步真正花时间的是排错。下面这五条是我拆这类项目时反复遇到的每条都按「现象 → 原因 → 解决」写清楚照着查能省不少时间。4.1 现象客户端连不上报 Connection refused原因服务端没启动或者端口被占用或者客户端填的 IP 不对。银行排号系统如果服务端和客户端不在同一台机器客户端不能填localhost。解决先在服务端机器上netstat -ano | findstr 8888确认端口在监听。如果被占用改服务端端口并同步改客户端。跨机测试时客户端填服务端的局域网 IP并确认防火墙放行该端口。4.2 现象界面卡死点按钮没反应原因网络 IO 或数据库查询写在了 EDT 里。Swing 的事件线程被阻塞界面自然不刷新。解决把所有socket.getInputStream().read()和 JDBC 查询放到独立线程界面更新用SwingUtilities.invokeLater切回 EDT。检查代码里有没有在按钮的actionPerformed里直接做耗时操作。4.3 现象Oracle 报 ORA-12505 或 ORA-28001原因ORA-12505是连接串里的 SID/服务名写错ORA-28001是密码过期。Oracle 默认密码有效期 180 天老项目放久了就会撞上。解决ORA-12505用lsnrctl status看监听器注册的服务名改成正确写法。ORA-28001用ALTER USER bank_queue IDENTIFIED BY bank123;重置密码或者直接改用户为不过期。4.4 现象反序列化报 InvalidClassException原因客户端和服务端的实体类serialVersionUID不一致或者类结构改了没同步。解决给所有要序列化的类显式声明private static final long serialVersionUID 1L;两端保持一致。改类结构后重新编译两端别只编译一边。4.5 现象号码重复或队列错乱原因号码生成用了「查最大值 1」但没加锁多个客户端同时取号会撞号。或者队列刷新有延迟界面显示的是旧数据。解决号码生成放到服务端单点做用数据库序列或synchronized保证原子性。客户端刷新用定时器轮询或服务端主动推送别依赖手动刷新。如果用了序列注意 Oracle 序列的CACHE参数缓存大了重启会跳号。5. 进阶玩法把轮询改成推送再补一层数据一致性校验基础版跑通后如果想让它更像回事有两个方向值得动手一是把客户端的定时轮询改成服务端主动推送二是给队列操作加一层校验防止并发下的状态错乱。这两点做完这套课程设计就不只是「能跑」而是「经得起问」。5.1 从轮询到推送减少无效请求基础版客户端大概率是每隔几秒查一次数据库。连接一多数据库压力就上来了。改成推送的思路是服务端维护一个在线客户端列表任何队列变更都广播给所有客户端。// 服务端维护在线客户端变更时广播 private static final ListClientHandler ONLINE new CopyOnWriteArrayList(); public static void broadcast(Message msg) { for (ClientHandler handler : ONLINE) { try { handler.send(msg); // 内部用 ObjectOutputStream 写回 } catch (Exception e) { ONLINE.remove(handler); // 发送失败说明连接断了移除 } } }逻辑说明CopyOnWriteArrayList在遍历时安全适合读多写少的在线列表。broadcast在每次叫号、取号后被调用。参数上send方法内部要加同步因为多个线程可能同时往同一个ObjectOutputStream写不加锁会串包。这一步做完客户端就不需要轮询了界面响应也更快。5.2 数据一致性叫号操作要防重复多个窗口同时点「叫号」如果不加控制可能叫到同一个号。常见做法是在数据库层用条件更新靠UPDATE的返回行数判断是否抢到。-- 原子叫号只有 status0 的号才能被叫走 UPDATE t_queue SET status 1 WHERE id ( SELECT id FROM ( SELECT id FROM t_queue WHERE status 0 ORDER BY create_time ) WHERE ROWNUM 1 ) AND status 0;逻辑说明子查询取出最早等待的号外层UPDATE带AND status 0条件。如果两个窗口同时执行只有一个能更新成功返回 1 行另一个返回 0 行据此判断是否叫号成功。参数上ROWNUM 1保证只取一个ORDER BY create_time保证先来先服务。这是 Oracle 里实现「乐观锁」的常见写法比在 Java 层加锁更可靠。5.3 验证方法多客户端并发压一遍改完之后怎么确认没问题开五个客户端同时疯狂点取号和叫号观察三件事号码有没有重复、队列顺序对不对、界面有没有卡死。如果号码重复回去查UPDATE的返回行数判断逻辑如果界面卡死回去查 EDT 那条线如果顺序乱查create_time的精度OracleDATE只到秒高并发下同秒的号排序会不稳定可以换成TIMESTAMP。从那以后我每次拆这类 C/S 项目都强制先跑一遍「多客户端并发」再去看代码因为单机跑通和并发跑通完全是两回事。希望帮到你。本文还有配套的精品资源点击获取