TCP案例-实时群聊

实现思路

  • 服务器端循环获取所有的客户端socket,放到一个socket的list中,等到需要通信的时候,调用相对应的管道就可以了
  • 客户端和服务器端一样,纪要发送消息,也要接受消息

代码

客户端

java 复制代码
// tcp客户端
public class Client {
    public static void main(String[] args) throws IOException {
        // 1. 创建一个Socket对象
        // 参数一:服务端的ip地址
        // 参数二:服务端的端口号
        Socket socket = new Socket("127.0.0.1", 8888);

        // 创建一个独立的线程负责读取服务端发送过来的数据
        ClientReaderThread clientReaderThread = new ClientReaderThread(socket);
        clientReaderThread.start();

        // 2. 通过Socket对象获取一个输出流
        OutputStream outputStream = socket.getOutputStream();

        // 3. 把低级的字节输出流包装成高级的数据输出流
        // 这一步不是必须得,但是高级的数据输出流可以更方便的操作数据
        DataOutputStream dataOutputStream = new DataOutputStream(outputStream);

        Scanner scanner = new Scanner(System.in);
        while (true) {
            System.out.println("请输入要发送的数据:");
            String s = scanner.nextLine();

            // 4. 使用数据输出流向服务端发送数据
            dataOutputStream.writeUTF(s);
            dataOutputStream.flush();

            if (s.equals("exit")) {
                System.out.println("客户端退出");
                // 5. 关闭资源
                // dataOutputStream关闭的时候会自动关闭outputStream
                dataOutputStream.close();
                socket.close();
                Server.removeSocket(socket);
                break;
            }
        }
    }
}

客户端读取线程

读取线程是子线程

java 复制代码
public class ClientReaderThread extends Thread {

    private Socket socket;

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

    @Override
    public void run() {

        try (
                // 1. 通过Socket对象获取一个输入流
                InputStream inputStream = socket.getInputStream();
                // 2. 将原始的字节输入流包装成高级的数据输入流
                // 这一步不是必须的,但是高级的数据输入流可以更方便的操作数据
                DataInputStream dataInputStream = new DataInputStream(inputStream);

        ) {
            while (true) {
                // 3. 读取客户端发送过来的数据
                String data = dataInputStream.readUTF();
                System.out.println("客户端发送过来的数据:" + data);
                System.out.println("客户端的ip地址:" + socket.getInetAddress().getHostAddress()
                        + ",客户端的端口号:" + socket.getPort());
                if (data.equals("exit")) {
                    System.out.println("客户端退出");
                    // 4. 关闭资源
                    socket.close();
                    break;
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

服务端

java 复制代码
// tcp服务端
public class Server {

    // 用来存储所有的socket对象
    private static ArrayList<Socket> onLineSockets = new ArrayList<Socket>();

    public static void main(String[] args) throws IOException {
        // 1. 创建服务端对象
        // 参数:服务端的端口号
        ServerSocket server = new ServerSocket(8888);


        while(true){
            // 2. 服务端一直处于监听状态,等待客户端的连接
            // accept()方法是一个阻塞方法,会一直等待客户端的连接
            Socket socket = server.accept();
            // 将socket对象存储到集合中
            onLineSockets.add(socket);

            // 3. 将socket独享交给独立的线程去负责
            ServerReaderThread serverReaderThread = new ServerReaderThread(socket);
            serverReaderThread.start();
        }
    }

    public static void removeSocket(Socket socket){
        onLineSockets.remove(socket);
    }

    public static ArrayList<Socket> getOnLineSockets(){
        return onLineSockets;
    }
}

服务段读取线程

java 复制代码
public class ServerReaderThread extends Thread {

    private Socket socket;

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

    @Override
    public void run() {

        try (
                // 1. 通过Socket对象获取一个输入流
                InputStream inputStream = socket.getInputStream();
                // 2. 将原始的字节输入流包装成高级的数据输入流
                // 这一步不是必须的,但是高级的数据输入流可以更方便的操作数据
                DataInputStream dataInputStream = new DataInputStream(inputStream);

        ) {
            while (true) {
                // 3. 读取客户端发送过来的数据
                String data = dataInputStream.readUTF();
                // System.out.println("客户端发送过来的数据:" + data);
                // System.out.println("客户端的ip地址:" + socket.getInetAddress().getHostAddress()
                //         + ",客户端的端口号:" + socket.getPort());
                // 4. 将客户端发送过来的数据转发给所有的客户端
                sendMsgToAllClient(data);
                if (data.equals("exit")) {
                    System.out.println("客户端退出");
                    // 4. 关闭资源
                    socket.close();
                    break;
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    // 异常抛出给上一级
    private void sendMsgToAllClient(String data) throws Exception{
        // 1. 获取所有的socket对象
        for (Socket socket : Server.getOnLineSockets()) {
            // 2. 通过socket对象获取一个输出流
            // 3. 将原始的字节输出流包装成高级的数据输出流
            // 这一步不是必须的,但是高级的数据输出流可以更方便的操作数据
            DataOutputStream dataOutputStream = new DataOutputStream(socket.getOutputStream());
            // 4. 使用数据输出流向客户端发送数据
            dataOutputStream.writeUTF(data);
            dataOutputStream.flush();
        }
    }
}

缺点

  • 只能实现群发,无法端到端
  • 也会发给自己
相关推荐
·薯条大王3 小时前
经济实惠玩云服务器|一台云服务器多人共用,子账号配置教程
java·linux·运维·服务器·汇编·c++·python
ZJH__GO6 小时前
网络编程v4pro--实现聊天室文件传输功能
java·服务器·网络·计算机网络
程序员黑豆6 小时前
Windows 系统 Java 环境变量配置全攻略:解决“不是内部或外部命令”
java·前端·ai编程
命运之光7 小时前
【C语言完整代码】就诊信息管理系统
java·c语言·开发语言
凤山老林7 小时前
Spring Boot @Async 线上实战:从默认配置到生产级线程池治理
java·spring boot·后端
marvelyu8 小时前
每天10分钟学会OceanBase系列(Day 20):跨机房容灾实战——构建多数据中心高可用架构
java·大数据·数据库
AI人工智能+电脑小能手9 小时前
大白话说Java设计模式-04-工厂方法模式(业务实战篇)
java·设计模式·工厂方法模式·架构设计·代码解耦
BUG指挥官9 小时前
Sa-Token和Spring Security对比
java·后端·spring
小当家.1059 小时前
深入理解 ReAct Agent:从原理到 Java 实战
java·react.js·agent·react·架构设计·agent设计
geminigoth9 小时前
Spring AI Alibaba 入门开发一(备份)
java·人工智能·spring