【java IO流项目】实现多人实时聊天室,需连接同一WiFi

想要完成这个项目的前提条件

①多个人连接同一WiFi在局域网里,所有人关闭防火墙,就使用中的,不会就关闭所有

②指定一个人为服务端 ,这个人去命令行ipconfig查看自己的ipv4

这是我win11虚拟机的ip   记住它,把他作为服务端ip

本次测试虚拟机就是别人电脑,上课6人互连成功的,宿舍没人合作,用虚拟机代替

这个在虚拟机开启服务端具体见下面测试1

这个在自己电脑开启服务端具体见下面测试2

 写服务端

完成的结构

先写一个DateUtil类获取每次发送信息的系统时间

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;

public class DateUtil {
    public static final SimpleDateFormat SDF =
            new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
    public static String date2String(Date date){
        return SDF.format(date);
    }
    public static Date string2Date(String dateStr){
        try {
            return SDF.parse(dateStr);
        } catch (ParseException e) {
            e.printStackTrace();
            return null;
        }
    }
}

再写一个Message类获取发送人昵称,发送信息内容,以及发送时间

import java.io.Serializable;

// 消息对象类
public class Message implements Serializable {

    private static final long serialVersionUID = -6849794470754667710L;

    private String message;// 消息内容
    private String name;// 发送人
    private String date;// 发送时间

    @Override
    public String toString() { // 用Generate功能自动生成toString方法的重写
        return "Message{" +
                "message='" + message + '\'' +
                ", name='" + name + '\'' +
                ", date='" + date + '\'' +
                '}';
    }

    public Message() {
    }

    public Message(String message, String name, String date) {
        this.message = message;
        this.name = name;
        this.date = date;
    }

    public String getMessage() {
        return message;
    }

    public void setMessage(String message) {
        this.message = message;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getDate() {
        return date;
    }

    public void setDate(String date) {
        this.date = date;
    }
}

最后写服务端Server类代码

import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;

public class Server {
    // server.accept()返回的socket代表一个客户端对象
    // 这里定义一个List存储所有的客户端socket对象
    // 考虑到广播消息的时候存在多线程并发共享一个List的问题, 使用CopyOnWriteArrayList线程安全类
    static List<Socket> socketList = new CopyOnWriteArrayList<>();

    public static void main(String[] args) {
        // 服务端server
        ServerSocket server = null;
        try {
            // 初始化服务端server对象, 监听8080端口下面改成8081因为我的8080端口占用了
            server = new ServerSocket(8081);
            System.out.println("服务端启动成功, 等待客户端接入");
            while (true){// 在主线程中循环接收客户端的接入
                Socket socket = server.accept();
                // 每接入到一个客户端socket对象, 将socket对象存储到List中
                socketList.add(socket);
                System.out.println("客户端IP:" + socket.getInetAddress().getHostAddress() + "进入聊天室, 当前聊天室socket数: " + socketList.size());
                // 为每个客户端socket开辟一个子线程, 来接收客户端消息和广播消息, 把socket对象传给子线程
                // 服务端会存在多个子线程对象, 每个子线程对象负责接收对应的客户端消息
                new ReceiveMessageThread(socket).start();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    // 接收消息并广播给所有客户端的子线程静态内部类, 继承Thread类, 重写run方法
    private static class ReceiveMessageThread extends Thread{
        Socket socket;// 子线程socket成员变量

        // 子线程构造方法, 主线程通过参数中传入socket
        public ReceiveMessageThread(Socket socket) {
            this.socket = socket;
        }

        @Override
        public void run() {
            while (true) { // 循环接收客户端的输入
                ObjectInputStream ois = null;// 对象输入流
                try {
                    // 用socket底层的字节输入流包装为对象输入流
                    ois = new ObjectInputStream(socket.getInputStream());
                    // 读客户端发送的对象, 如果客户端没有发送, 此处会等待, 客户端发送后, 对象输入流可以读取到, 并反序列化
                    Message msg = (Message) ois.readObject();
                    System.out.println("接收到一条新消息, 广播给所有客户端: " + msg);
                    // 把消息对象msg传给广播方法(在下面定义的成员方法), 把消息输出给所有的客户端socket
                    broadcastMessage(msg);
                } catch (IOException e) {
                    e.printStackTrace();
                    break;// 出异常就break, 子线程退出
                } catch (ClassNotFoundException e) {
                    e.printStackTrace();
                    break;
                }
            }
        }
        // 广播消息方法
        private void broadcastMessage(Message msg){
            // 遍历socketList得到每一个客户端的socket对象
            for (Socket sock : socketList) {
                try {
                    // 把socket底层的字节输出流包装成对象输出流
                    ObjectOutputStream oos = new ObjectOutputStream(sock.getOutputStream());
                    // 把消息对象传输给客户端
                    oos.writeObject(msg);
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}
 

写客户端,新建项目

完成的结构

DateUtil和Message直接拷贝过来

写客户端Client类代码

import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.net.Socket;
import java.util.Date;
import java.util.Scanner;

public class Client {
    static Scanner scanner = new Scanner(System.in);

    public static void main(String[] args) {
        Socket socket = null;// 客户端socket
        try {
            // 初始化客户端socket对象 - 三次握手, 建立socket连接
            socket = new Socket("192.168.25.100", 8081);// ip是服务端ip,端口同服务端
            System.out.println("登录聊天室成功");
            // 开辟子线程发送消息, 把socket对象传给子线程
            new SendMessageThread(socket).start();
            while (true){// 主线程中循环接收服务端推送过来的消息
                // 把socket底层的字节输入流包装成对象输入流
                ObjectInputStream ois = new ObjectInputStream(socket.getInputStream());
                // 读取服务端推送的消息对象, 如果服务端没有返回, 此处会阻塞, 有返回就能立即读到
                Message msg = (Message) ois.readObject();
                // 打印服务端推送的消息
                System.out.println("-------接收到一条新的消息--------");
                System.out.println(msg.getName() + "\t" + msg.getDate());
                System.out.println(msg.getMessage());
                System.out.println("");
            }
        } catch (IOException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
    // 发送消息子线程静态内部类, 继承Thread类重写run方法
    private static class SendMessageThread extends Thread{

        Socket socket;// 子线程socket成员变量

        // 子线程构造方法, 主线程通过参数中传入socket
        public SendMessageThread(Socket socket) {
            this.socket = socket;
        }

        @Override
        public void run() {
            while (true){ // 循环接收用户输入, 并把输入的内容封装为消息对象发给服务端
                Message msg = new Message();
                System.out.println("回车发送:");
                msg.setMessage(scanner.next());
                msg.setName("葫芦娃奇袭赛博坦");//用户设置自己的昵称,在代码手动设置
                msg.setDate(DateUtil.date2String(new Date()));
                ObjectOutputStream oos = null;// 对象输出流
                try {
                    // 把socket底层的字节输c出流包装成对象输出流
                    oos = new ObjectOutputStream(socket.getOutputStream());
                    // 传给消息对象msg给服务端
                    oos.writeObject(msg);
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

}
 

测试1:

先开启服务端

再开启客户端接入服务端 

 我们在外面再开启一个服务端接入虚拟机的客户端

 发消息测试

win11虚拟机客户端发消息

 外面电脑开启客户端发消息

 测试2

在自己电脑ipconfig查看自己的ip

 在自己电脑开启服务端,开启客户端

 在虚拟机开启客户端连接

 没有flush,有人退出的异常没有抛出

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

代码老祖

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值