Java Socket编程实现群聊实践案例

 更新时间:2025年12月18日 14:40:30   作者:ilmoon05  
本文介绍了如何实现特定客户端与特定客户端之间的私聊和群聊功能,通过服务器端的多线程处理和客户端的Socket连接,实现了消息的路由和转发,本文给大家介绍Java Socket编程实现群聊功能,感兴趣的朋友跟随小编一起看看吧

上一篇文章已经可以实现服务端与客户端之间消息的交换并且开启多个客户端,那么如何实现特定客户端与特定客户端之间私聊以及特定客户端发给所有在线客户端的群聊呢,这一篇文章一起学习一下吧

代码详细解析

1.Mysever类-服务器主程序

import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.HashMap;
/**1.创建服务器 绑定端口号
 * 2.在循环中 调用accept方法 重复监听连接过来的客户端
 * 3.启动线程保持和多个客户端连接
 * 4.在线程中 传进去对应的socket 获取输入输出流 实现和客户端的双向通讯
 * 5.创建客户端对象 绑定服务器的IP地址和端口 让客户端去连接服务器
 * 6.利用客户端输入输出流 跟服务端通讯
 * 7.在客户端启动线程 一直读取服务器发来的消息
 *
 */
public class MyServer {
    public static void main(String[] args){
        try {
            new MyServer().startServer();
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
    //启动服务器
    public void startServer() throws IOException {
        //创建服务器对象,并绑定监听器端口
        //端口取值范围(2字节):0~65535
        //知名端口:0~1024
        ServerSocket server=new ServerSocket(8888);
        System.out.println("服务端在8888端口监听器");
        //哈希表:保存一组映射关系:每个编号对应唯一的socket
        //基本数据类型对应的包装类
        HashMap<Integer,Socket> mp=new HashMap<>();
        //每个链接过来的客户端ID
        int ID=1;
        //重复监听连接这个服务器的客户端
        while(true){
            //监听连接这个服务器的客户端
            //该方法的返回的socket用来客户端通信
            Socket socket=server.accept();
            //保存当前对应的数据
            mp.put(ID,socket);
            //利用线程保持和多个客户端连接
            ServerThread st=new ServerThread(socket,mp,ID);
            new Thread(st).start();
            ID++;
        }
    }
}

知识点解析说明:

1.端口号

ServerSocket(int port):创建绑定到特定端口的服务器套接字

端口范围:0~65535

知名端口:0~1024,被系统服务占用

2.hashMap的使用

 HashMap<Integer,Socket> mp=new HashMap<>();

key:Integer类型的客户端ID

values:Socket对象,代表与客户端的链接

作用:集中管理所有客户端链接,便于消息路由

3.多线程处理

new Thread(st).start();

为每个客户端连接创造独立线程

避免单个客户端阻塞整个服务器

实现真正的并发处理

2.ServerThread类-服务器线程处理

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
import java.util.Collection;
import java.util.HashMap;
public class ServerThread implements Runnable{
    //跟客户端连接的套接字
    public Socket socket;
    public InputStream is;
    public OutputStream os;
    public HashMap<Integer,Socket> mp;
    public int ID;
    public ServerThread(Socket socket,HashMap<Integer,Socket> mp,int ID){
        this.socket=socket;
        this.mp=mp;
        try {
            os = socket.getOutputStream();
            is = socket.getInputStream();
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
        //发送客户端上线消息
        sendMsg(ID+":客户端上线..",os);
    }
    @Override
    public void run() {
        while(true) {
            try {
                //msg消息内容:   目标用户:消息内容
                String msg = readMsg();
                //对msg进行解析:拆分
                // 字符串分割
                String[] msgArr = msg.split(":");
                System.out.println(msgArr[0] + ":" + msgArr[1]);
                if(msgArr[0].equals("g")){
                    Collection<Socket> values=mp.values();
                        for (Socket value : values) {
                            //不将消息发给自己
                            if(value != this.socket) {
                            System.out.println("值:" + value);
                            OutputStream output = value.getOutputStream();
                            sendMsg(msgArr[1], output);
                        }
                    }
                }else{
                    //根据目标用户(ID)从mp中找到对应的socket(聊天对象)
                    int id = Integer.parseInt(msgArr[0]);  //字符串数字转成int
                    Socket socket = mp.get(id);
                    //获取该对象的输出流,写入数据
                    OutputStream output = socket.getOutputStream();
                    //把聊天内容转发给目标用户(聊天对象)
                    sendMsg(msgArr[1], output);
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
    //发送消息的方法
    public void sendMsg(String msg,OutputStream os){
        try {
            byte[] b = msg.getBytes();
            os.write(b);
            os.flush();
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
    //读取消息的方法
    public String readMsg(){
        byte[] b = new byte[1024];  //最多读取的消息长度
        try {
            is.read(b);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
        // trim() 去掉字符尾部空格
        String s=new String(b);
        return s.trim();
    }
}

知识点解释说明:

InputStream:从套接字读取消息(接收消息)

OutputStream:向套接字写入数据(发送消息)

私聊:

客户端1给客户端3发消息,读取客户端1消息,获得客户端3输出流 ,将消息发给客户端3

群聊:

客户端1将消息发送给除自己以外所有客户端,接收客户端1消息,获得其他所有客户端输出流,将消息发送给其他所有客户端

2.消息格式

String[] msgArr = msg.split(":");
                System.out.println(msgArr[0] + ":" + msgArr[1]);

split()分隔符

返回值:分割后的字符串数组

+操作符用于字符串连接,将数组第一个元素、冒号分隔符、第二个元素连接成一个新字符串

3.消息协议

群聊:g:消息内容

私聊:目标ID:消息内容

4.遍历HashMap中所有数据

因为本编程需要遍历套接字,查看原代码socket是values,所以用values方法遍历

代码示例:

import java.util.Collection;
import java.util.HashMap;
public class HashMapTraversal {
    public static void main(String[] args) {
        HashMap<String, Integer> map = new HashMap<>();
        map.put("A", 1);
        map.put("B", 2);
        map.put("C", 3);
        Collection<Integer> values = map.values();
        for (Integer value : values) {
            System.out.println("Value: " + value);
        }
    }
}

通过values()获取所有值的集合,适合只关心值的场景。

3.Client类-客户端程序

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
import java.util.Scanner;
public class Client {
    public static void main(String[] args){
        try {
            new Client().startClient();
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
    public InputStream is;
    public OutputStream os;
    //启动客户端
    public void startClient() throws IOException{
        Socket client =new Socket("127.0.0.1",8888);
        os=client.getOutputStream();
        is=client.getInputStream();
        new Thread(()->{
            while(true){
                //读取服务器消息
                String msg=readMsg();
                System.out.println(msg);
            }
        }).start();
        //发消息
        Scanner scanner=new Scanner(System.in);
        while(true){
            System.out.println("client:");
            String clientMsg=scanner.nextLine();
            sendMsg(clientMsg);
        }
    }
    //发送消息的方法
    public void sendMsg(String msg){
        try {
            os.write(msg.getBytes());
            os.flush();
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
    public String readMsg(){
        byte[] b=new byte[1024];
        try {
            //阻塞方法:一直等待读取消息,如果没有会一直等着
            is.read(b);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
        //trim
        return new String(b).trim();
    }
}

知识点解释说明:

1.Socket客户端连接

127.0.0.1:本地回环地址,用于本地测试

8888:服务端监听端口

2.多线程消息处理

接收线程:

        new Thread(()->{
            while(true){
                //读取服务器消息
                String msg=readMsg();
                System.out.println(msg);
            }
        }).start();

· 持续监听服务器发来的消息

发送消息:

 Scanner scanner=new Scanner(System.in);
        while(true){
            System.out.println("client:");
            String clientMsg=scanner.nextLine();
            sendMsg(clientMsg);
        }
    }

Scanner用于读取控制台输入
nextLine()是阻塞方法,等待用户输入

补充:

服务端为什么使用线程:服务端要保持与多个客户端通信,每个客户端都需要一个单独的线程来控制通信,不然没有办法进行消息的收发

客户端为什么使用线程:读消息是一个阻塞方法,如果读消息发消息没有单独的线程去控制,他就只能按顺序收发,无法连续发消息 ,想要连续发消息,就没有办法控制,必须要有单独的线程去控制读和写

4.使用指南

1.启动服务器

2.启动客户端(可启动多个)

3.消息发送格式

群发消息:g:hello

私聊消息:3:nihao

测试:

群聊

客户端1输入:g;hello

客户端2显示:hello

客户端3显示:hello

私聊

客户端1输入:3:nihao

客户端2显示:无显示

客户端3显示:nihao

总结

这个Java聊天室项目展示了Socket编程的核心概念,包括服务器监听、客户端连接、多线程处理和消息路由。虽然功能相对基础,但架构清晰,为扩展更复杂的功能提供了良好的基础。

通过这个项目,可以深入理解网络编程、多线程同步和客户端-服务器架构的设计模式,是学习Java网络编程的优秀实践案例。

后续可以再进行扩展升级,例如添加注册登录及聊天界面。

到此这篇关于Java Socket编程实现群聊的文章就介绍到这了,更多相关Java Socket群聊内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • Java简单模拟实现一个线程池

    Java简单模拟实现一个线程池

    本文主要介绍了Java简单模拟实现一个线程池,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2024-01-01
  • SpringBoot整合mybatis-plus实现分页查询功能

    SpringBoot整合mybatis-plus实现分页查询功能

    这篇文章主要介绍了SpringBoot整合mybatis-plus实现分页查询功能,pringBoot分页查询的两种写法,一种是手动实现,另一种是使用框架实现,现在我将具体的实现流程分享一下,需要的朋友可以参考下
    2023-11-11
  • Dubbo新版本zk注册中心连接问题及解决

    Dubbo新版本zk注册中心连接问题及解决

    这篇文章主要介绍了Dubbo新版本zk注册中心连接问题及解决,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教
    2024-05-05
  • 使用Spring由构造方法自动装配

    使用Spring由构造方法自动装配

    这篇文章主要介绍了使用Spring由构造方法自动装配,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2021-08-08
  • Spring Data JPA踩坑记录(@id @GeneratedValue)

    Spring Data JPA踩坑记录(@id @GeneratedValue)

    这篇文章主要介绍了Spring Data JPA踩坑记录(@id @GeneratedValue),具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2022-07-07
  • 如何使用Spring Validation优雅地校验参数

    如何使用Spring Validation优雅地校验参数

    这篇文章主要介绍了如何使用Spring Validation优雅地校验参数,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2020-07-07
  • Java中创建线程池的几种方式以及区别

    Java中创建线程池的几种方式以及区别

    创建线程池有多种方式,主要通过 Java 的 java.util.concurrent 包提供的 Executors 工具类来实现,本文给大家介绍了几种常见的线程池类型及其区别,并通过代码示例讲解的非常详细,需要的朋友可以参考下
    2024-11-11
  • SpringBoot3集成Zookeeper的代码详解

    SpringBoot3集成Zookeeper的代码详解

    ZooKeeper是一个集中的服务,用于维护配置信息、命名、提供分布式同步、提供组服务,分布式应用程序以某种形式使用所有这些类型的服务,本文将给大家介绍SpringBoot3集成Zookeeper的代码,文中通过代码示例介绍的非常详细,需要的朋友可以参考下
    2024-01-01
  • 将Sublime Text 2配置为Java的IDE的教程

    将Sublime Text 2配置为Java的IDE的教程

    这篇文章主要介绍了将Sublime Text 2配置为Java的IDE的教程,包括能让Sublime这个文本编辑器编译和运行Java程序等,需要的朋友可以参考下
    2015-07-07
  • Java正则表达式的替换和分组功能

    Java正则表达式的替换和分组功能

    这篇文章主要给大家介绍了关于Java正则表达式的替换和分组功能的相关资料,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2020-09-09

最新评论