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

缺点

  • 只能实现群发,无法端到端
  • 也会发给自己
相关推荐
LiuYaoheng几秒前
【Android】Android 的三种动画(帧动画、View 动画、属性动画)
android·java
lisanmengmeng几秒前
apache-tomcat 安装部署
java·tomcat·apache
漫漫不慢.32 分钟前
算法练习-二分查找
java·开发语言·算法
还是鼠鼠40 分钟前
《黑马商城》Elasticsearch基础-详细介绍【简单易懂注释版】
java·spring boot·spring·elasticsearch·搜索引擎·spring cloud·全文检索
牧羊人_myr1 小时前
Maven核心功能与项目构建详解
java·maven
量子物理学1 小时前
Eclipse Mosquitto 在小内存下怎么修改配置文件
java·服务器·eclipse
程序员鱼皮1 小时前
让老弟做个数据同步,结果踩了 7 个大坑!
java·后端·计算机·程序员·编程·职场
Iris7612 小时前
MyBatis一对多关系映射方式
java
程序员清风2 小时前
滴滴二面:MySQL执行计划中,Key有值,还是很慢怎么办?
java·后端·面试
白鲸开源2 小时前
3.1.8<3.2.0<3.3.1,Apache DolphinScheduler集群升级避坑指南
java·开源·github