java基础学习,案例练习,即时通讯

红包雨

代码:

java 复制代码
package com.itheima.demo8test;

import java.util.List;

/**
 * ClassName: PeopleGetRedPacket
 * Package: com.itheima.demo8test
 * Description:
 *
 * @Author: start
 * @Create 2026-05-02 10:01
 * @Version: 1.0
 */
public class PeopleGetRedPacket extends Thread {
    private List<Integer> redPacket;

    public PeopleGetRedPacket(List<Integer> redPacket, String name) {
        super(name);
        this.redPacket = redPacket;
    }

    @Override
    public void run() {
        String name = Thread.currentThread().getName();
        while (true) {
            // 100个人来抢redPacket集合中的钱
            synchronized (redPacket) {
                if(redPacket.size() == 0){
                    break;
                }
                // 随机一个索引得到红包
                int index = (int) (Math.random() * redPacket.size());
                Integer money = redPacket.remove(index);
                System.out.println(name + "抢到了" + money + "元");
                if(redPacket.size() == 0) {
                    System.out.println("活动结束!");
                    break;
                }
            }
            //try {
            //    Thread.sleep(10);
            //} catch (InterruptedException e) {
            //    e.printStackTrace();
            //}
        }
    }
}
java 复制代码
package com.itheima.demo8test;

import java.util.ArrayList;
import java.util.List;
import java.util.Random;

/**
 * ClassName: ThreadTest
 * Package: com.itheima.demo8test
 * Description:
 *
 * @Author: start
 * @Create 2026-05-02 09:52
 * @Version: 1.0
 */
public class ThreadTest {
    public static void main(String[] args) {
        // 目标:完成多线程的综合小案例
        // 红包雨游戏,某企业有100名员工,员工的工号依次是1,2,3,4,..到100。
        // 现在公司举办了年会活动,活动中有一个红包雨环节,要求共计发出200个红包雨。其中小红包在[1 -30]元之间,总占比为80%,
        // 大红包[31-100]元,总占比为20%。
        // 分析:100个员工实际上就是100个线程,来竞争200个红包。
        List<Integer> redPacket = getRedPacket();
        // 2、定义线程类,创建100个线程,竞争同一个集合。
        for(int i = 1; i <= 100; i++){
            new PeopleGetRedPacket(redPacket, "人" + i).start();
        }


    }

    // 准备这200个随机的红包返回。放到List集合中去返回。
    public static List<Integer> getRedPacket(){
        Random r = new Random();
        // 其中小红包在[1-30]元之间,总占比为80%,大红包[31-100]元,总占比为20%。
        List<Integer> redPacket = new ArrayList<>();
        for(int i = 1; i <= 160; i++){
            redPacket.add(r.nextInt(30) + 1);
        }
        for(int i = 1; i <= 40; i++){
            redPacket.add(r.nextInt(70) + 31);
        }
        return redPacket;
    }
}

UDP通信-一发一收-多发多收

一发一收:

客户端

java 复制代码
package com.itheima.demo2udp1;

import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.SocketException;

/**
 * ClassName: UDPClientDemo1
 * Package: com.itheima.demo2udp1
 * Description:
 *
 * @Author: start
 * @Create 2026-05-02 11:12
 * @Version: 1.0
 */
public class UDPClientDemo1 {
    public static void main(String[] args) throws Exception {
        //目标:完成UDP通信一发一收:客户端开发。
        System.out.println("==========客户端启动==========");
        // 1、创建发送端对象(代表抛韭菜的人)
        DatagramSocket socket = new DatagramSocket();  // 随机端口
        // 2、创建数据包对象封装要发送的数据。(韭菜盘子)
        byte[] bytes = "我是客户端,约你今晚啤酒、龙虾、小烧烤".getBytes();
        /**
         * public DatagramPacket(byte[] buf, int length,
         *                           InetAddress address, int port)
         * 参数一:发送的数据,字节数组(韭菜)
         * 参数二:发送的字节长度
         * 参数三:目的地的IP地址
         * 参数四:服务端程序的端口号
         */
        DatagramPacket packet = new DatagramPacket(bytes, bytes.length,
                InetAddress.getLocalHost(),8080);

        // 3、让发送端对象发送数据包的数据
        socket.send(packet);
    }
}

服务端

java 复制代码
package com.itheima.demo2udp1;

import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.SocketException;

/**
 * ClassName: UDPServerDemo2
 * Package: com.itheima.demo2udp1
 * Description:
 *
 * @Author: start
 * @Create 2026-05-02 11:20
 * @Version: 1.0
 */
public class UDPServerDemo2 {
    public static void main(String[] args) throws Exception {
        // 目标:完成UDP通信一发一收:服务端开发。
        // 1、创建接收端对象,注册端口
        DatagramSocket socket = new DatagramSocket(8080);

        // 2、创建一个数据包对象负责接收数据。(韭菜盘子)
        byte[] buf = new byte[1024*64];
        DatagramPacket packet = new DatagramPacket(buf, buf.length);

        // 3、接收数据,将数据封装到数据包对象的字节数组中去
        socket.receive(packet);

        // 4、看看数据是否收到了
        int len = packet.getLength();   // 获取当前收到的数据长度
        String data = new String(buf, 0 , len);
        System.out.println("服务端收到了:" + data);

        // 获取对象的ip对象和程序端口
        String ip = packet.getAddress().getHostAddress();
        int port = packet.getPort();
        System.out.println("对方ip:" + ip + "  对方端口:" + port);

        socket.close();
    }
}

多发多收

客户端

java 复制代码
package com.itheima.demo3udp2;

import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.util.Scanner;

/**
 * ClassName: UDPClientDemo1
 * Package: com.itheima.demo2udp1
 * Description:
 *
 * @Author: start
 * @Create 2026-05-02 11:12
 * @Version: 1.0
 */
public class UDPClientDemo1 {
    public static void main(String[] args) throws Exception {
        //目标:完成UDP通信多发多收:客户端开发。
        System.out.println("==========客户端启动==========");
        // 1、创建发送端对象(代表抛韭菜的人)
        DatagramSocket socket = new DatagramSocket();  // 随机端口

        Scanner sc = new Scanner(System.in);
        while (true) {
            // 2、创建数据包对象封装要发送的数据。(韭菜盘子)
            System.out.println("请说:");
            String msg = sc.nextLine();

            // 如果用户输入的是 exit,则退出
            if("exit".equals(msg)){
                System.out.println("====客户端退出====");
                socket.close();
                break;
            }
            byte[] bytes = msg.getBytes();
            DatagramPacket packet = new DatagramPacket(bytes, bytes.length,
                    InetAddress.getLocalHost(),8080);

            // 3、让发送端对象发送数据包的数据
            socket.send(packet);
        }
    }
}

服务端

java 复制代码
package com.itheima.demo3udp2;

import java.net.DatagramPacket;
import java.net.DatagramSocket;

/**
 * ClassName: UDPServerDemo2
 * Package: com.itheima.demo2udp1
 * Description:
 *
 * @Author: start
 * @Create 2026-05-02 11:20
 * @Version: 1.0
 */
public class UDPServerDemo2 {
    public static void main(String[] args) throws Exception {
        // 目标:完成UDP通信多发多收:服务端开发。
        // 1、创建接收端对象,注册端口
        DatagramSocket socket = new DatagramSocket(8080);

        // 2、创建一个数据包对象负责接收数据。(韭菜盘子)
        byte[] buf = new byte[1024*64];
        DatagramPacket packet = new DatagramPacket(buf, buf.length);

        while (true) {
            // 3、接收数据,将数据封装到数据包对象的字节数组中去
            socket.receive(packet); // 等待式接收数据。

            // 4、看看数据是否收到了
            int len = packet.getLength();   // 获取当前收到的数据长度
            String data = new String(buf, 0 , len);
            System.out.println("服务端收到了:" + data);

            // 获取对象的ip对象和程序端口
            String ip = packet.getAddress().getHostAddress();
            int port = packet.getPort();
            System.out.println("对方ip:" + ip + "  对方端口:" + port);

            System.out.println("-----------------------------------------");
        }

    }
}

TCP通信

一发一收

客户端

java 复制代码
package com.itheima.demo4tcp1;

import java.io.DataOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.net.Socket;

/**
 * ClassName: ClientDemo1
 * Package: com.itheima.demo4tcp1
 * Description:
 *
 * @Author: start
 * @Create 2026-05-02 11:54
 * @Version: 1.0
 */
public class ClientDemo1 {
    public static void main(String[] args) throws Exception {
        // 目标:实现TCP通信下一发一收:客户端开发。
        System.out.println("==========客户端启动==========");
        // 1、常见Socket管道对象,请求与服务端的Socket链接。可靠链接
        Socket socket = new Socket("127.0.0.1", 9999);

        // 2、从socket通信管道中得到一个字节输出流
        OutputStream os = socket.getOutputStream();

        // 3、特殊数据流
        DataOutputStream dos = new DataOutputStream(os);
        dos.writeInt(1);
        dos.writeUTF("我想你了,你在哪儿?");

        // 4、关闭资源
        socket.close();

    }
}

服务端

java 复制代码
package com.itheima.demo4tcp1;

import java.io.DataInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.ServerSocket;
import java.net.Socket;

/**
 * ClassName: ServerDemo2
 * Package: com.itheima.demo4tcp1
 * Description:
 *
 * @Author: start
 * @Create 2026-05-02 12:02
 * @Version: 1.0
 */
public class ServerDemo2 {
    public static void main(String[] args) throws Exception {
        // 目标:实现TCP通信下一发一收:服务端开发。
        System.out.println("==========服务端启动==========");
        // 1、创建服务端ServerSocket对象,绑定端口号,监听客户端连接
        ServerSocket ss = new ServerSocket(9999);
        // 2、调用accept方法,阻塞等待客户端连接,一旦有客户端链接会返回一个Socket对象
        Socket socket = ss.accept();
        // 3、获取输入流,读取客户端发送的数据
        InputStream is = socket.getInputStream();
        // 4、把字节输入流包装成特殊数据输入流
        DataInputStream dis = new DataInputStream(is);
        // 5、读取数据
        int id = dis.readInt();
        String msg = dis.readUTF();
        System.out.println("id:" + id + ", 收到的客户端msg:" + msg);
        // 6、客户端的ip和端口(谁给我发的)
        System.out.println("客户端的ip:" + socket.getInetAddress().getHostAddress());
        System.out.println("客户端的端口:" + socket.getPort());
    }
}

多发多收

客户端

java 复制代码
package com.itheima.demo5tcp2;

import java.io.DataOutputStream;
import java.io.OutputStream;
import java.net.Socket;
import java.util.Scanner;

/**
 * ClassName: ClientDemo1
 * Package: com.itheima.demo4tcp1
 * Description:
 *
 * @Author: start
 * @Create 2026-05-02 11:54
 * @Version: 1.0
 */
public class ClientDemo1 {
    public static void main(String[] args) throws Exception {
        // 目标:实现TCP通信下多发多收:客户端开发。
        System.out.println("==========客户端启动==========");
        // 1、常见Socket管道对象,请求与服务端的Socket链接。可靠链接
        Socket socket = new Socket("127.0.0.1", 9999);

        // 2、从socket通信管道中得到一个字节输出流
        OutputStream os = socket.getOutputStream();

        // 3、特殊数据流
        DataOutputStream dos = new DataOutputStream(os);

        Scanner sc = new Scanner(System.in);
        while (true) {
            System.out.println("请说:");
            String msg = sc.nextLine();
            if("exit".equals(msg)){
                System.out.println("退出成功!");

                dos.close();  // 关闭输出流
                socket.close();  // 关闭Socket
                break;
            }
            dos.writeUTF(msg); // 发送数据
            dos.flush();
        }


    }
}

服务端

java 复制代码
package com.itheima.demo5tcp2;

import java.io.DataInputStream;
import java.io.InputStream;
import java.net.ServerSocket;
import java.net.Socket;

/**
 * ClassName: ServerDemo2
 * Package: com.itheima.demo4tcp1
 * Description:
 *
 * @Author: start
 * @Create 2026-05-02 12:02
 * @Version: 1.0
 */
public class ServerDemo2 {
    public static void main(String[] args) throws Exception {
        // 目标:实现TCP通信下多发多收:服务端开发。
        System.out.println("==========服务端启动==========");
        // 1、创建服务端ServerSocket对象,绑定端口号,监听客户端连接
        ServerSocket ss = new ServerSocket(9999);
        // 2、调用accept方法,阻塞等待客户端连接,一旦有客户端链接会返回一个Socket对象
        Socket socket = ss.accept();
        // 3、获取输入流,读取客户端发送的数据
        InputStream is = socket.getInputStream();
        // 4、把字节输入流包装成特殊数据输入流
        DataInputStream dis = new DataInputStream(is);
        while (true) {
            // 5、读取数据
            String msg = dis.readUTF();
            System.out.println("收到了客户端数据msg::" + msg);
            // 6、客户端的ip和端口(谁给我发的)
            System.out.println("客户端的ip:" + socket.getInetAddress().getHostAddress());
            System.out.println("客户端的端口:" + socket.getPort());
            System.out.println("-----------------------------------------");
        }
    }
}

同时接收多个客户端的消息

客户端

java 复制代码
package com.itheima.demo6tcp3;

import java.io.DataOutputStream;
import java.io.OutputStream;
import java.net.Socket;
import java.util.Scanner;

/**
 * ClassName: ClientDemo1
 * Package: com.itheima.demo4tcp1
 * Description:
 *
 * @Author: start
 * @Create 2026-05-02 11:54
 * @Version: 1.0
 */
public class ClientDemo1 {
    public static void main(String[] args) throws Exception {
        // 目标:实现TCP通信下多发多收:客户端开发。
        System.out.println("==========客户端启动==========");
        // 1、常见Socket管道对象,请求与服务端的Socket链接。可靠链接
        Socket socket = new Socket("127.0.0.1", 9999);

        // 2、从socket通信管道中得到一个字节输出流
        OutputStream os = socket.getOutputStream();

        // 3、特殊数据流
        DataOutputStream dos = new DataOutputStream(os);

        Scanner sc = new Scanner(System.in);
        while (true) {
            System.out.println("请说:");
            String msg = sc.nextLine();
            if("exit".equals(msg)){
                System.out.println("退出成功!");

                dos.close();  // 关闭输出流
                socket.close();  // 关闭Socket
                break;
            }
            dos.writeUTF(msg); // 发送数据
            dos.flush();
        }


    }
}

服务端

java 复制代码
package com.itheima.demo6tcp3;

import java.io.DataInputStream;
import java.io.InputStream;
import java.net.ServerSocket;
import java.net.Socket;

/**
 * ClassName: ServerDemo2
 * Package: com.itheima.demo4tcp1
 * Description:
 *
 * @Author: start
 * @Create 2026-05-02 12:02
 * @Version: 1.0
 */
public class ServerDemo2 {
    public static void main(String[] args) throws Exception {
        // 目标:实现TCP通信下多发多收:服务端开发。
        System.out.println("==========服务端启动==========");
        // 1、创建服务端ServerSocket对象,绑定端口号,监听客户端连接
        ServerSocket ss = new ServerSocket(9999);
        while (true) {
            // 2、调用accept方法,阻塞等待客户端连接,一旦有客户端链接会返回一个Socket对象
            Socket socket = ss.accept();
            System.out.println("一个客户端上线了" + socket.getInetAddress().getHostAddress());
            // 3、把这个客户端管道交给一个独立的子线程专门负责接收这个管道的消息。
            new ServerReader(socket).start();
        }
    }
}
java 复制代码
package com.itheima.demo6tcp3;

import java.io.DataInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.Socket;

/**
 * ClassName: ServerReader
 * Package: com.itheima.demo6tcp3
 * Description:
 *
 * @Author: start
 * @Create 2026-05-02 13:19
 * @Version: 1.0
 */
public class ServerReader extends Thread {
    private Socket socket;
    public ServerReader(Socket socket) {
        this.socket = socket;
    }

    @Override
    public void run() {
        try {
            // 读取管道的消息
            // 3、获取输入流,读取客户端发送的数据
            InputStream is = socket.getInputStream();
            // 4、把字节输入流包装成特殊数据输入流
            DataInputStream dis = new DataInputStream(is);
            while (true) {
                // 5、读取数据
                String msg = dis.readUTF();
                System.out.println("收到了客户端数据msg::" + msg);
                // 6、客户端的ip和端口(谁给我发的)
                System.out.println("客户端的ip:" + socket.getInetAddress().getHostAddress());
                System.out.println("客户端的端口:" + socket.getPort());
                System.out.println("-----------------------------------------");
            }
        } catch (Exception e) {
            //e.printStackTrace();
            System.out.println("客户端下线了:" + socket.getInetAddress().getHostAddress());
        }
    }
}

其他应用:B/S架构的原理

服务端

java 复制代码
package com.itheima.demo7tcp4;

import com.itheima.demo6tcp3.ServerReader;

import java.net.ServerSocket;
import java.net.Socket;
import java.util.concurrent.*;

/**
 * ClassName: ServerDemo2
 * Package: com.itheima.demo4tcp1
 * Description:
 *
 * @Author: start
 * @Create 2026-05-02 12:02
 * @Version: 1.0
 */
public class ServerDemo {
    public static void main(String[] args) throws Exception {
        // 目标: BS架构的原理理解
        System.out.println("==========服务端启动==========");
        // 1、创建服务端ServerSocket对象,绑定端口号,监听客户端连接
        ServerSocket ss = new ServerSocket(8080);

        // 创建线程池
        ExecutorService pool = new ThreadPoolExecutor(3, 10, 10, TimeUnit.SECONDS, new ArrayBlockingQueue<>(100),
                Executors.defaultThreadFactory(), new ThreadPoolExecutor.AbortPolicy());

        while (true) {
            // 2、调用accept方法,阻塞等待客户端连接,一旦有客户端链接会返回一个Socket对象
            Socket socket = ss.accept();
            System.out.println("一个客户端上线了" + socket.getInetAddress().getHostAddress());
            // 3、把这个客户端管道交给一个独立的子线程专门负责接收这个管道的消息。
            pool.execute(new ServerReaderRunnable(socket));
        }
    }
}
java 复制代码
package com.itheima.demo7tcp4;

import java.io.DataInputStream;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintStream;
import java.net.Socket;

/**
 * ClassName: ServerReader
 * Package: com.itheima.demo6tcp3
 * Description:
 *
 * @Author: start
 * @Create 2026-05-02 13:19
 * @Version: 1.0
 */
public class ServerReaderRunnable implements Runnable {
    private Socket socket;
    public ServerReaderRunnable(Socket socket) {
        this.socket = socket;
    }

    @Override
    public void run() {
        try {
            // 给当前对应的浏览器管道响应一个网页数据回去。
            OutputStream os = socket.getOutputStream();
            // 通过字节输出流包装写出去数据给浏览器
            // 把字节输出流包装成打印流。
            PrintStream ps = new PrintStream(os);
            // 写响应的网页数据出去
            ps.println("HTTP/1.1 200 OK");
            ps.println("Content-Type:text/html;charset=utf-8");
            ps.println(); // 必须换一行
            ps.println("<html>");
            ps.println("<head>");
            ps.println("<meta charset='utf-8'>");
            ps.println("<title>黑马Java视频</title>");
            ps.println("</head>");
            ps.println("<body>");
            ps.println("<h1 style='color:red;font-size=20px'>黑马Java磊哥的视频</h1>");
            // 响应一个黑马程序员的Log展示
            ps.println("<img src='http://www.itheima.com/images/logo.png'>");
            ps.println("</body>");
            ps.println("</html>");
            ps.close();
            socket.close();
        } catch (Exception e) {
            //e.printStackTrace();
            System.out.println("客户端下线了:" + socket.getInetAddress().getHostAddress());
        }
    }
}

实战项目

服务端代码

java 复制代码
package com.itheima;

/**
 * ClassName: Constant
 * Package: com.itheima
 * Description:
 *
 * @Author: start
 * @Create 2026-05-02 15:54
 * @Version: 1.0
 */
public class Constant {
    public static final int PORT = 6666;
}
java 复制代码
package com.itheima;

import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.HashMap;
import java.util.Map;

/**
 * ClassName: ${NAME}
 * Package: com.itheima
 * Description:
 *
 * @Author: start
 * @Create 2026-05-02 15:52
 * @Version: 1.0
 */
//TIP 要<b>运行</b>代码,请按 <shortcut actionId="Run"/> 或
// 点击装订区域中的 <icon src="AllIcons.Actions.Execute"/> 图标。
public class Server {
    // 定义一个集合容器存储所有登录进来的客户端管道,以便将来群发消息给他们.
    // 定义一个Map集合,键是存储客户端的管道,值是这个管道的用户名称。
    public static final Map<Socket, String> onlineSockets = new HashMap<>();
    public static void main(String[] args) {
        System.out.println("启动服务端系统......");
        try {
            // 1、注册端口
            ServerSocket serverSocket = new ServerSocket(Constant.PORT);
            // 2、主线程负责接受客户端的连接请求

            while (true) {
                // 3、调用accept方法,获取到客户端的Socket对象
                System.out.println("等待客户端连接...");

                Socket socket = serverSocket.accept();
                // 把这个管道交给一个独立的线程来处理:以便支持很多客户端可以同时进来通信。
                new ServerReaderThread(socket).start();

                System.out.println("一个客户端连接成功了...");
            }

        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
java 复制代码
package com.itheima;

import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.net.Socket;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Collection;

/**
 * ClassName: ServerReaderThread
 * Package: com.itheima
 * Description:
 *
 * @Author: start
 * @Create 2026-05-02 16:02
 * @Version: 1.0
 */
public class ServerReaderThread extends Thread{
    private Socket socket;
    public ServerReaderThread(Socket socket) {
        this.socket = socket;
    }
    @Override
    public void run() {
        try {
            // 接收的消息可能有很多种类型:1、登录消息(包含昵称)2、群聊消息3、私聊消息
            // 所以客户端必须声明协议发送消息
            // 比如客户端先发1,代表接下来是登录消息。
            // 比如客户端先发2,代表接下来是群聊消息。
            //  先从socket管道中接收客户端发送来的消息类型编号
            DataInputStream dis = new DataInputStream(socket.getInputStream());
            while (true) {
                int type = dis.readInt();  // 1,2,3
                switch (type) {
                    case 1:
                        // 客户端发来了登录消息,接下来要接收昵称数据,再更新全部在线客户端的在线人数列表。
                        String nickname = dis.readUTF();
                        // 把这个登录成功的客户端socket存入到在线集合。
                        Server.onlineSockets.put(socket, nickname);
                        // 更新全部客户端的在线人数列表。
                        updateClientOnLineUserList();
                        break;
                    case 2:
                        // 客户端发来了群聊消息,接下来要接收群聊消息内容,再把群聊消息转发给全部在线客户端。
                        String msg = dis.readUTF();
                        sendMsgToAll(msg);
                        break;
                    case 3:
                        // 客户端发来了群聊消息,接下来要接收群聊消息内容,再把群聊消息转发给全部在线客户端。
                        break;
                }
            }
        } catch (Exception e) {
            //e.printStackTrace();
            System.out.println("客户端下线了:" + socket.getInetAddress().getHostAddress());
            Server.onlineSockets.remove(socket);
            updateClientOnLineUserList(); // 下线了用户也需要更新全部客户端的在线人数列表。
        }
    }

    // 给全部在线socket推送当前客户端发送来的消息
    private void sendMsgToAll(String msg) {
        // 一定要拼装好这个消息,再发给全部在线的socket
        StringBuilder sb = new StringBuilder();
        String name = Server.onlineSockets.get(socket);

        // 获取当前时间
        LocalDateTime now = LocalDateTime.now();
        DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss EEE a");
        String nowStr = dtf.format(now);

        String msgResult = sb.append(name).append(" ").append(nowStr).append("\r\n")
                .append(msg).append("\r\n").toString();

        // 推送给全部客户端socket
        for(Socket socket : Server.onlineSockets.keySet()){
            try {
                // 3、把集合中所有的用户名称,通过socket管道发送给客户端
                DataOutputStream dos = new DataOutputStream(socket.getOutputStream());
                dos.writeInt(2); // 1代表告诉客户端接下来是在线人数列表信息   2 代表发的是群聊信息
                dos.writeUTF(msgResult);
                dos.flush();  // 刷新数据!
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    private void updateClientOnLineUserList() {
        // 更新全部客户端的在线人数列表
        // 拿到全部在线客户端的用户名称,把这些名称转发给全部在线socket管道。
        // 1、拿到全部在线用户名称
        Collection<String> onLineUsers = Server.onlineSockets.values();
        // 2、把这个集合中的所有用户都推送给全部客户端socket管道。
        for (Socket socket : Server.onlineSockets.keySet()) {
            try {
                // 把集合中的所有用户名称,通过socket管道发送给客户端
                DataOutputStream dos = new DataOutputStream(socket.getOutputStream());
                dos.writeInt(1); // 1代表告诉客户端接下来是在线人数列表信息   2 代表发的是群聊信息
                dos.writeInt(onLineUsers.size());  //告诉客户端,接下来要发多少个用户名称
                for (String user : onLineUsers) {
                    dos.writeUTF(user);
                }
                dos.flush();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
}

界面和客户端

java 复制代码
package com.itheima.ui;

import javax.swing.*;
import java.awt.*;
import java.io.DataOutputStream;
import java.io.IOException;
import java.net.Socket;


public class ChatEntryFrame extends JFrame {

    private JTextField nicknameField;
    private JButton enterButton;
    private JButton cancelButton;
    private Socket socket;  // 记住当前客户端系统的通信管道

    public ChatEntryFrame() {
        setTitle("局域网聊天室");
        setSize(350, 150);
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        setLocationRelativeTo(null);
        setResizable(false); // 禁止调整大小

        // 设置背景颜色
        getContentPane().setBackground(Color.decode("#F0F0F0"));

        // 创建主面板并设置布局
        JPanel mainPanel = new JPanel(new BorderLayout());
        mainPanel.setBackground(Color.decode("#F0F0F0"));
        add(mainPanel);

        // 创建顶部面板
        JPanel topPanel = new JPanel(new FlowLayout(FlowLayout.CENTER, 10, 10));
        topPanel.setBackground(Color.decode("#F0F0F0"));

        // 标签和文本框
        JLabel nicknameLabel = new JLabel("昵称:");
        nicknameLabel.setFont(new Font("楷体", Font.BOLD, 16));
        nicknameField = new JTextField(10);
        nicknameField.setFont(new Font("楷体", Font.PLAIN, 16));
        nicknameField.setBorder(BorderFactory.createCompoundBorder(
                BorderFactory.createMatteBorder(1, 1, 1, 1, Color.GRAY),
                BorderFactory.createEmptyBorder(5, 5, 5, 5)
        ));

        topPanel.add(nicknameLabel);
        topPanel.add(nicknameField);
        mainPanel.add(topPanel, BorderLayout.NORTH);

        // 按钮面板
        JPanel buttonPanel = new JPanel(new FlowLayout(FlowLayout.CENTER, 10, 10));
        buttonPanel.setBackground(Color.decode("#F0F0F0"));

        enterButton = new JButton("进入");
        enterButton.setFont(new Font("楷体", Font.BOLD, 16));
        enterButton.setBackground(Color.decode("#007BFF"));
        enterButton.setForeground(Color.WHITE);
        enterButton.setBorderPainted(false);
        enterButton.setFocusPainted(false);

        cancelButton = new JButton("取消");
        cancelButton.setFont(new Font("楷体", Font.BOLD, 16));
        cancelButton.setBackground(Color.decode("#DC3545"));
        cancelButton.setForeground(Color.WHITE);
        cancelButton.setBorderPainted(false);
        cancelButton.setFocusPainted(false);

        buttonPanel.add(enterButton);
        buttonPanel.add(cancelButton);
        mainPanel.add(buttonPanel, BorderLayout.SOUTH);

        // 添加监听器
        enterButton.addActionListener(e -> {
            String nickname = nicknameField.getText();
            if (!nickname.isEmpty()) {
                // 进入聊天室逻辑
                // 立即发送登录消息给服务器程序
                try {
                    login(nickname);
                    // 进入聊天室逻辑:启动聊天界面
                    new ClientChatFrame(nickname, socket);
                    dispose(); // 关闭窗口
                } catch (Exception ex) {
                    ex.printStackTrace();
                }

            } else {
                JOptionPane.showMessageDialog(this, "请输入昵称!");
            }
        });

        cancelButton.addActionListener(e -> System.exit(0));

        this.setVisible(true);
    }

    private void login(String nickname) throws Exception {
        // 立即发送登录消息给服务器程序
        // 1、创建Socket管道请求与服务端的Socket连接
        socket = new Socket(Constant.SERVER_IP, Constant.SERVER_PORT);
        // 2、立即发送消息类型1 和自己的昵称给服务端
        DataOutputStream dos = new DataOutputStream(socket.getOutputStream());
        dos.writeInt(1);  // 消息类型  登录
        dos.writeUTF(nickname);
        dos.flush();

    }

    public static void main(String[] args) {
        new ChatEntryFrame();
    }
}
java 复制代码
package com.itheima.ui;

import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.DataOutputStream;
import java.io.IOException;
import java.net.Socket;
import java.util.List;

public class ClientChatFrame extends JFrame {
    public JTextArea smsContent = new JTextArea(23, 50);
    private JTextArea smsSend = new JTextArea(4, 40);
    public JList<String> onLineUsers = new JList<>();
    private JButton sendBn = new JButton("发送");
    private String nickname;
    private Socket socket;

    public ClientChatFrame() {
        initView();
        this.setVisible(true);
    }

    public ClientChatFrame(String nickname, Socket socket) {
        this(); // 调用上面的构造器,初始化界面信息,昵称登录之后调用的是有参构造器,上面的无参才是拉取界面的,所以需要继承一下
        // 初始化数据
        // 立马展示昵称到窗口
        this.setTitle(nickname+"的聊天窗口");
        //this.nickname = nickname;
        this.socket = socket;

        // 立即把客户端的这个Socket管道交给一个独立的线程专门负责读取客户端socket从服务端收到的在线人数更新数据或者群聊数据。
        new ClientReaderThread(socket, this).start();


    }

    private void initView() {
        this.setSize(700, 600);
        this.setLayout(new BorderLayout());
        this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); // 关闭窗口,退出程序
        this.setLocationRelativeTo(null); // 窗口居中

        // 设置窗口背景色
        this.getContentPane().setBackground(new Color(0xf0, 0xf0, 0xf0));

        // 设置字体
        Font font = new Font("SimKai", Font.PLAIN, 14);

        // 消息内容框
        smsContent.setFont(font);
        smsContent.setBackground(new Color(0xdd, 0xdd, 0xdd));
        smsContent.setEditable(false);

        // 发送消息框
        smsSend.setFont(font);
        smsSend.setWrapStyleWord(true);
        smsSend.setLineWrap(true);

        // 在线用户列表
        onLineUsers.setFont(font);
        onLineUsers.setFixedCellWidth(120);
        onLineUsers.setVisibleRowCount(13);

        // 创建底部面板
        JPanel bottomPanel = new JPanel(new BorderLayout());
        bottomPanel.setBackground(new Color(0xf0, 0xf0, 0xf0));

        // 消息输入框
        JScrollPane smsSendScrollPane = new JScrollPane(smsSend);
        smsSendScrollPane.setBorder(BorderFactory.createEmptyBorder());
        smsSendScrollPane.setPreferredSize(new Dimension(500, 50));

        // 发送按钮
        sendBn.setFont(font);
        sendBn.setBackground(Color.decode("#009688"));
        sendBn.setForeground(Color.WHITE);

        // 按钮面板
        JPanel btns = new JPanel(new FlowLayout(FlowLayout.LEFT, 5, 5));
        btns.setBackground(new Color(0xf0, 0xf0, 0xf0));
        btns.add(sendBn);

        // 给发送按钮绑定点击事件
        sendBn.addActionListener(e  -> {
            // 获取输入框中的内容
            String msg = smsSend.getText();
            // 清空输入框
            smsSend.setText("");
            // 发送消息
            sendMsgToServer(msg);
        });

        // 添加组件
        bottomPanel.add(smsSendScrollPane, BorderLayout.CENTER);
        bottomPanel.add(btns, BorderLayout.EAST);

        // 用户列表面板
        JScrollPane userListScrollPane = new JScrollPane(onLineUsers);
        userListScrollPane.setBorder(BorderFactory.createEmptyBorder());
        userListScrollPane.setPreferredSize(new Dimension(120, 500));

        // 中心消息面板
        JScrollPane smsContentScrollPane = new JScrollPane(smsContent);
        smsContentScrollPane.setBorder(BorderFactory.createEmptyBorder());

        // 添加所有组件
        this.add(smsContentScrollPane, BorderLayout.CENTER);
        this.add(bottomPanel, BorderLayout.SOUTH);
        this.add(userListScrollPane, BorderLayout.EAST);
    }

    // 发送消息
    private void sendMsgToServer(String msg) {
        try {
            // 1、从socket管道中得到一个特殊数据输出流
            DataOutputStream dos = new DataOutputStream(socket.getOutputStream());
            // 2、把消息发送给服务端
            dos.writeInt(2);
            dos.writeUTF(msg);
            dos.flush();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) {
        new ClientChatFrame();
    }

    // 更新在线用户列表
    public void updateOnlineUsers(String[] onLineNames) {
        // 把线程读取到的在线用户名称展示到界面上
        onLineUsers.setListData(onLineNames);
    }

    // 更新群聊消息
    public void setMsgToWin(String msg) {
        // 更新群聊消息到界面展示
        smsContent.append(msg);
    }
}
java 复制代码
package com.itheima.ui;

import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.net.Socket;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

/**
 * ClassName: ServerReaderThread
 * Package: com.itheima
 * Description:
 *
 * @Author: start
 * @Create 2026-05-02 16:02
 * @Version: 1.0
 */
public class ClientReaderThread extends Thread{
    private Socket socket;
    private DataInputStream dis;
    private ClientChatFrame win;
    public ClientReaderThread(Socket socket, ClientChatFrame win) {
        this.win = win;
        this.socket = socket;
    }
    @Override
    public void run() {
        try {
            // 接收的消息可能有很多种类型:1、在线人数更新的数据2、群聊消息
            dis = new DataInputStream(socket.getInputStream());
            while (true) {
                int type = dis.readInt();  // 1,2,3
                switch (type) {
                    case 1:
                        // 服务端发来的在线人数更新消息
                        updateClientOnLineUserList();
                        break;
                    case 2:
                        // 服务端发送来的群聊消息
                        getMsgToWin();
                        break;
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    private void getMsgToWin() throws Exception {
        // 获取群聊消息
        String msg = dis.readUTF();
        win.setMsgToWin(msg);
    }

    // 更新客户端的在线用户列表
    private void updateClientOnLineUserList() throws Exception {
        // 1、读取有多少个在线用户
        int count = dis.readInt();
        // 2、循环控制读取多少个用户信息
        //List<String> onLineNames = new ArrayList<>();
        String[] names = new String[count];
        for (int i = 0; i < count; i++){
            // 3、读取每个用户的信息
            String nickname = dis.readUTF();
            // 4、将每个用户的信息添加到数组中
            names[i] = nickname;
        }
        // 5、更新到窗口界面上的右侧展示出来
        win.updateOnlineUsers(names);
    }

}
java 复制代码
package com.itheima.ui;

/**
 * ClassName: Constant
 * Package: com.itheima.ui
 * Description:
 *
 * @Author: start
 * @Create 2026-05-02 17:01
 * @Version: 1.0
 */
public class Constant {
    public static final String SERVER_IP = "127.0.0.1";
    public static final int SERVER_PORT = 6666;

}

App

java 复制代码
package com.itheima;

import com.itheima.ui.ChatEntryFrame;

/**
 * ClassName: App
 * Package: com.itheima
 * Description:
 *
 * @Author: start
 * @Create 2026-05-02 15:40
 * @Version: 1.0
 */
public class App {
    public static void main(String[] args) {
        new ChatEntryFrame();
    }
}
相关推荐
24白菜头1 小时前
【无标题】
c++·笔记·学习·harmonyos
吟安安安安1 小时前
适合短期冲刺的学习工作流(针对算法)
学习·算法
小何code1 小时前
人工智能【第8篇】监督学习实战:线性回归与逻辑回归算法详解(万字长文+完整代码实现)
人工智能·python·学习·机器学习·逻辑回归·线性回归
DevilSeagull1 小时前
Windows 批处理 (Batch) 编程: 从入门到入土. (一) 基础概念与环境配置
开发语言·windows·后端·batch·语言
逸Y 仙X2 小时前
文章十九: ElasticSearch Full Text 全文本查询
java·大数据·数据库·elasticsearch·搜索引擎·全文检索
AI科技星2 小时前
全域数学·第卷:场计算机卷(场空间计算机)【乖乖数学】
java·开发语言·人工智能·算法·机器学习·数学建模·数据挖掘
微软技术分享2 小时前
本地部署千问 2.5-1.5B-GGUF + LangChain 封装学习
数据库·学习·langchain
charlie1145141912 小时前
嵌入式C++实践开发第21篇(单片机实践):按钮输入 —— 硬件原理、消抖与HAL API
开发语言·c++·单片机
前端老石人2 小时前
前端开发中的 URL 完全指南
开发语言·前端·javascript·css·html