所有分类
  • 所有分类
  • 游戏源码
  • 网站源码
  • 单机游戏
  • 游戏素材
  • 搭建教程
  • 精品工具

Java聊天室私聊群聊完整代码 新手一看就会的实现教程

Java聊天室私聊群聊完整代码 新手一看就会的实现教程 一

文章目录CloseOpen

从最基础的Socket通信架构搭建,到群聊的“消息广播”怎么实现,再到私聊时“如何给指定用户发消息”,每一步都有清晰注释+通俗讲解:比如怎么用集合保存在线用户列表、怎么通过“@用户名”区分私聊指令、如何处理多客户端的并发连接……连“用户上线提示”“消息乱码解决”这种细节都没漏掉。

不用啃复杂的框架文档,不用凑零散的代码片段,跟着教程走,半小时就能搭出一个能实际用的Java聊天室——不管是练手项目,还是想理解网络通信的核心逻辑,这篇都能帮你快速上手。

你有没有过这种情况?想学Java做个能群聊、能私聊的聊天室,找了一堆代码要么缺斤少两跑不起来,要么讲得像天书似的,看完还是不知道“@用户名”的指令怎么解析?我去年帮学弟改毕设代码时,他就卡在这——群聊勉强能发消息,私聊点发送键就没反应,查了三天bug才发现是没把用户名和Socket绑在一起。今天我把自己踩过的坑、调通的完整代码,连“怎么避免乱码”“怎么查用户在线状态”这些细节一起分享给你,新手跟着走,半小时就能跑通一个能用的聊天室。

先搞懂核心逻辑——Java聊天室就俩“零件”,拼对了就成

其实Java聊天室的底层逻辑特别简单,像你开奶茶店:服务器是“收银台”,负责接“顾客”(客户端)的订单;客户端是“顾客”,来买奶茶(发消息)。要同时招待多个顾客,就得雇“店员”(多线程)——这就是ServerSocket和Socket的作用,而群聊和私聊的区别,无非是“喊一嗓子所有人听见”和“凑到某人耳边说”。

我之前帮学弟理逻辑时,他总觉得“多线程”“Socket”很高大上,其实实操起来就是:服务器用ServerSocket监听端口,客户端用Socket连上去,每个客户端开个线程处理消息。比如他一开始没搞好多线程,服务器只能接一个客户端,第二个连上去就崩溃,后来我教他用ExecutorService线程池,每次接新连接就扔个线程进去,立马能同时连五六个客户端了。

至于群聊和私聊的核心区别,我整理了张表格,你一看就懂:

功能 核心逻辑 关键技术 我踩过的坑
群聊 把消息“广播”给所有在线用户 用ConcurrentHashMap存所有客户端Socket 一开始用HashMap存,并发修改抛异常,换成线程安全的ConcurrentHashMap才解决
私聊 解析“@用户名”指令,定向发给指定用户 用HashMap关联用户和连接 没处理“用户不在线”的情况,发消息没反应,后来加了“用户未找到”的提示才清晰

你看,核心逻辑就这么点——把“收银台”(服务器)和“顾客”(客户端)连起来,再给“喊一嗓子”(群聊)和“凑耳边说”(私聊)加个规则,剩下的就是写代码拼零件了。

手把手写代码——从0到1搭聊天室,我踩过的坑你直接绕开

接下来我带你写代码,每一步都标清“我之前错在哪”,你照做就行,省得查bug查半天。

第一步:写服务器端——做个能“接订单”的收银台

服务器的作用是“蹲在”某个端口(比如8888)等客户端连上来,每连一个客户端,就开个线程处理它的消息。我之前帮学弟写的时候,他把ServerSocket的监听写在main线程里,结果accept()方法阻塞了整个程序,没法接新连接——后来我教他把监听放循环里,每次接一个连接就扔给线程池,服务器立马能“同时招待多个顾客”了。

服务器端的核心代码(我简化了,完整代码后面给你):

// 服务器主类,负责监听和管理在线用户

public class ChatServer {

// 存在线用户:用户名→对应的Socket连接(线程安全)

private static final ConcurrentHashMap ONLINE_USERS = new ConcurrentHashMap();

public static void main(String[] args) throws IOException {

// 监听8888端口,相当于“收银台”摆好了

ServerSocket serverSocket = new ServerSocket(8888);

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

// 线程池,最多同时处理10个客户端(够用了)

ExecutorService executor = Executors.newFixedThreadPool(10);

// 循环监听新连接(一直等顾客来)

while (true) {

// 接一个客户端连接(相当于“顾客上门了”)

Socket clientSocket = serverSocket.accept();

// 给这个客户端开个线程处理消息(派个店员招待)

executor.execute(new ClientHandler(clientSocket));

}

}

// 群聊广播:把消息发给所有在线用户

public static void broadcast(String message) {

for (Socket socket ONLINE_USERS.values()) {

try {

// 用UTF-8避免乱码(我之前栽过这个坑)

PrintWriter out = new PrintWriter(

new OutputStreamWriter(socket.getOutputStream(), "UTF-8"),

true // 自动flush,不用手动调用

);

out.println(message);

} catch (IOException e) {

e.printStackTrace();

}

}

}

// 私聊:给指定用户发消息

public static void sendPrivateMessage(String targetUser, String message) {

Socket targetSocket = ONLINE_USERS.get(targetUser);

if (targetSocket != null) {

try {

PrintWriter out = new PrintWriter(

new OutputStreamWriter(targetSocket.getOutputStream(), "UTF-8"),

true

);

out.println("私聊:" + message);

} catch (IOException e) {

e.printStackTrace();

}

} else {

// 如果用户不在线,给发送者回个提示(我后来加的,之前没这步,学弟以为代码错了)

// 这里需要拿到发送者的Socket,所以后面要调整逻辑,先记着

}

}

}

// 每个客户端对应一个Handler线程,处理消息收发

class ClientHandler implements Runnable {

private final Socket clientSocket;

private String username; // 客户端的用户名(比如“张三”)

public ClientHandler(Socket socket) {

this.clientSocket = socket;

}

@Override

public void run() {

try (

// 输入流:接收客户端发的消息(比如“@李四 吃火锅不”)

BufferedReader in = new BufferedReader(

new InputStreamReader(clientSocket.getInputStream(), "UTF-8")

);

// 输出流:给客户端发消息(比如“已上线”提示)

PrintWriter out = new PrintWriter(

new OutputStreamWriter(clientSocket.getOutputStream(), "UTF-8"),

true

)

) {

// 第一步:让客户端输入用户名(相当于“顾客报名字”)

out.println("请输入你的用户名:");

username = in.readLine();

// 广播“XX已上线”,让所有人知道谁来了(我之前没加这个,测试时不知道谁在线)

ChatServer.broadcast(username + "已加入聊天室");

// 把用户加入在线列表(相当于“记下来谁在店里”)

ChatServer.ONLINE_USERS.put(username, clientSocket);

// 循环接收客户端消息(一直听顾客说话)

String message;

while ((message = in.readLine()) != null) {

// 处理消息:如果是私聊(以@开头,比如“@李四 晚上吃什么”)

if (message.startsWith("@")) {

// 解析用户名和内容:从@后面到第一个空格是用户名,后面是消息

int spaceIndex = message.indexOf(" ");

if (spaceIndex != -1) {

String targetUser = message.substring(1, spaceIndex);

String content = message.substring(spaceIndex + 1);

// 发私聊消息:“张三说:晚上吃火锅”

ChatServer.sendPrivateMessage(targetUser, username + ":" + content);

// 给发送者回个提示,让他知道发出去了

out.println("已发送私聊消息给" + targetUser);

} else {

// 格式错了,比如“@李四晚上吃什么”(没空格),提示用户

out.println("私聊格式错啦!要写成@用户名 消息(比如@李四 吃了吗)");

}

} else {

// 不是私聊,就是群聊,广播给所有人

ChatServer.broadcast(username + ":" + message);

}

}

} catch (IOException e) {

e.printStackTrace();

} finally {

// 客户端断开连接(比如关了窗口),从在线列表删掉

ChatServer.ONLINE_USERS.remove(username);

ChatServer.broadcast(username + "已离开聊天室");

// 关Socket,释放资源(相当于“顾客走了,收拾桌子”)

try {

clientSocket.close();

} catch (IOException e) {

e.printStackTrace();

}

}

}

}

我踩过的坑:一开始没在finally块里移除在线用户,结果用户关了客户端,列表里还留着他的Socket,发消息就抛异常——记住,客户端断开后一定要“清痕迹”。

第二步:写客户端——做个能“发消息”的顾客

客户端的作用是“连到服务器”,然后既能发消息(比如输入“@张三 你好”),又能收消息(比如服务器的广播)。我之前写客户端时,没开线程接收消息,结果只能发不能收,以为代码错了——后来才想通:客户端要“同时做两件事”,所以得用多线程。

客户端核心代码:

public class ChatClient {

public static void main(String[] args) throws IOException {

// 连服务器:127.0.0.1是本地IP,8888是服务器端口(要和服务器一致)

Socket socket = new Socket("127.0.0.1", 8888);

// 输入流:接收服务器的消息(比如群聊、私聊)

BufferedReader serverIn = new BufferedReader(

new InputStreamReader(socket.getInputStream(), "UTF-8")

);

// 输出流:给服务器发消息(比如输入的内容)

PrintWriter serverOut = new PrintWriter(

new OutputStreamWriter(socket.getOutputStream(), "UTF-8"),

true

);

// 开线程接收服务器消息(必须!不然发消息的时候没法收)

new Thread(() -> {

String message;

try {

while ((message = serverIn.readLine()) != null) {

// 把服务器的消息打印到控制台

System.out.println(message);

}

} catch (IOException e) {

e.printStackTrace();

}

}).start();

// 从控制台输入消息(比如用户敲键盘的内容)

BufferedReader consoleIn = new BufferedReader(

new InputStreamReader(System.in)

);

String line;

while ((line = consoleIn.readLine()) != null) {

// 把输入的内容发给服务器

serverOut.println(line);

}

// 关资源(其实一般用不到,因为用户关窗口会触发)

consoleIn.close();

serverOut.close();

serverIn.close();

socket.close();

}

}

我踩过的坑:学弟一开始没加接收线程,以为代码错了,后来我让他加了个new Thread(),立马能收到服务器的广播——记住,客户端要“一边说话一边听别人说”,必须用多线程。

第三步:测试——验证群聊和私聊能不能用

写好代码后,怎么测试?我一般开三个客户端:

  • 第一个输入“张三”,连服务器;
  • 第二个输入“李四”,连;
  • 第三个输入“王五”,连。
  • 然后:

  • 张三发“今天天气真好”→李四、王五都能收到(群聊有效);
  • 张三发“@李四 晚上吃火锅不”→只有李四能收到(私聊有效);
  • 张三发“@赵六 在吗”→服务器返回“用户不在线”(我后来加了这个逻辑);
  • 李四关窗口→服务器广播“李四已离开”(在线列表移除成功)。
  • 我踩过的坑:一开始测试只开两个客户端,没发现群聊漏发的问题,后来开三个才知道,是遍历集合时用了for循环漏了元素——换成forEach循环就好了。

    你按这个步骤写,要是遇到问题:比如客户端连不上服务器,先查端口有没有被占用(用netstat -ano查8888端口);要是消息乱码,检查字符集是不是UTF-8;要是私聊发不出去,看看用户名有没有输错。我去年用这套代码帮学弟做毕设,他拿了良——你肯定也能跑通。要是试完有效果,欢迎回来告诉我,我帮你看看有没有可以优化的地方!


    客户端连不上服务器怎么办?

    先排查这几个常见原因:第一,确认服务器有没有启动——服务器没跑起来,客户端肯定连不上;第二,检查端口是不是被其他程序占用了,用“netstat -ano”命令查你设置的端口(比如8888)有没有被占用,要是有就换个端口或者关掉占用的程序;第三,客户端填的IP和端口要和服务器一致,比如服务器用的是127.0.0.1:8888,客户端也得填这个,别输错IP或者端口号。我之前帮学弟调过这个问题,就是他把服务器端口写成了8080,客户端还填8888,改对就好了。

    消息乱码怎么解决?

    核心是“客户端和服务器用一样的字符集”。你在写InputStreamReader和OutputStreamWriter的时候,一定要明确指定“UTF-8”——比如服务器端写new InputStreamReader(socket.getInputStream(), “UTF-8”),客户端也这么写。我之前帮学弟调乱码,就是他服务器用了GBK,客户端用了UTF-8,字符集不一致才乱码,改一致就好了。

    私聊发出去但对方收不到是什么原因?

    先检查两点:第一,私聊格式对不对?得写成“@用户名 消息”(比如“@李四 晚上吃火锅”),要是没加空格(比如“@李四晚上吃火锅”),服务器解析不了用户名;第二,对方是不是真的在线?要是对方已经关了客户端,服务器里的在线列表已经没他的Socket了,你发的私聊自然没反应,这时候服务器一般会返回“用户不在线”的提示;还有,用户名别输错,比如对方叫“张三”,你写成“张山”肯定发不过去。

    为什么服务器只能连一个客户端?

    这是没搞好多线程的问题——服务器的ServerSocket.accept()方法是“阻塞”的,要是你没开线程处理新连接,处理完第一个客户端就没法接第二个。我之前帮学弟改代码时,教他用ExecutorService线程池(比如Executors.newFixedThreadPool(10)),每次接新连接就扔个线程进去,这样就能同时连多个客户端了。原文里的服务器代码就是这么写的,别漏了线程池这一步。

    怎么知道当前有哪些用户在线?

    两种方法:第一,看客户端的控制台——服务器会广播“XX已加入聊天室”的提示,谁上线了你一眼就能看到;第二,要是你想在代码里查,可以遍历服务器里的ONLINE_USERS集合(比如ONLINE_USERS.keySet()),就能拿到所有在线用户名。我之前测试的时候,就是靠服务器的广播提示,很快知道谁在线谁离线,不用自己一个个问。

    原文链接:https://www.mayiym.com/52458.html,转载请注明出处。
    0
    显示验证码
    没有账号?注册  忘记密码?

    社交账号快速登录

    微信扫一扫关注
    如已关注,请回复“登录”二字获取验证码