想要完成这个项目的前提条件
①多个人连接同一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();
}
}
}
}}