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();
        }
    }
}

缺点

  • 只能实现群发,无法端到端
  • 也会发给自己
相关推荐
我真的是大笨蛋1 小时前
K8S-Pod(下)
java·笔记·云原生·容器·kubernetes
碳水加碳水1 小时前
Java代码审计实战:XML外部实体注入(XXE)深度解析
java·安全·web安全·代码审计
努力也学不会java2 小时前
【设计模式】 原型模式
java·设计模式·原型模式
方渐鸿3 小时前
【2024】k8s集群 图文详细 部署安装使用(两万字)
java·运维·容器·kubernetes·k8s·运维开发·持续部署
学亮编程手记3 小时前
K8S v1.33 版本主要新特性介绍
java·容器·kubernetes
Haven-4 小时前
Java-面试八股文-JVM篇
java·jvm·面试
我真的是大笨蛋4 小时前
JVM调优总结
java·jvm·数据库·redis·缓存·性能优化·系统架构
wjs0404 小时前
Git常用的命令
java·git·gitlab
superlls5 小时前
(算法 哈希表)【LeetCode 349】两个数组的交集 思路笔记自留
java·数据结构·算法
田里的水稻5 小时前
C++_队列编码实例,从末端添加对象,同时把头部的对象剔除掉,中的队列长度为设置长度NUM_OBJ
java·c++·算法