利用TCP协议,创建一个多人聊天室

发布于:2025-07-05 ⋅ 阅读:(17) ⋅ 点赞:(0)

项目名称

利用TCP协议,做一个带有登录,注册的无界面,控制版的多人聊天室。

知识点

循环,判断,集合,IO,多线程,网络编程

准备内容

在当前模块下新建txt文件,在文件中保存正确的用户名和密码

zhangsan=123
lisi=1234
wangwu=12345

页面搭建

  1. 客户端连接服务器后,显示如下
服务器已经连接成功
==============欢迎来到小周聊天室================
1登录
2注册
请输入您的选择:

服务器

public class Server {
    public static void main(String[] args) throws IOException {
        ServerSocket serverSocket = new ServerSocket(10001);
        System.out.println("有客户端来连接");
        while (true) {
        }
    }
}

客户端

public class Client {
    public static void main(String[] args) throws IOException {
        Socket socket = new Socket("127.0.0.1",10001);
        System.out.println("服务器已经连接成功");
        //搭建页面框架
        while(true){
        System.out.println("=========欢迎来到小周聊天室=========");
        System.out.println("1.登录");
        System.out.println("2.注册");
        System.out.println("请选择选项:");
        Scanner sc = new Scanner(System.in);
        switch(sc.nextInt()){
                case 1 -> System.out.println("用户选择了登录");
                case 2 -> System.out.println("用户选择了注册");
                default -> System.out.println("没有这个选项");
            }
        }
    }
}

效果展示

服务器已经连接成功
=========欢迎来到多人聊天室=========
1.登录
2.注册
请选择选项:
1
用户选择了登录

登录操作

  1. 需要输入用户名和密码
  2. 回车,提交给服务器,进行验证,服务器会根据txt文件中的密码

根据不同的情况,服务器会写三种判断提示

服务器回写第一种提示:登录成功
服务器回写第二种提示:密码有误  
服务器回写第三种提示:用户名不存在

客户端会根据服务器回写的数据,根据三种情况进行不同的处理方案

  1. “登录成功”->开始聊天
  2. “密码错误”->需要重新输入
  3. “用户名不存在”->需要重新输入

如果登录成功,就可以开始聊天,此时的聊天,一个人发消息给服务端,服务端接收之后需要群发给所有人。

将登录操作抽成一个方法(养成一个习惯,一个方法里就实现一个功能)

首先我们要实现数据(用户名和密码)的传输

客户端

public static void login(Socket socket) throws IOException {
        //获取输出流,给服务器发送数据
        BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
        Scanner sc = new Scanner(System.in);
        System.out.println("请输入用户名:");
        String username = sc.nextLine();
        System.out.println("请输入密码:");
        String  password= sc.nextLine();

        //拼接
        //username=zhangsan&password=123
        StringBuilder sb = new StringBuilder();
        sb.append("username=").append(username).append("&password=").append(password); 
        //写入数据
        bw.write(sb.toString());
        bw.newLine();
        bw.flush();
}

服务器

public class Server {
    static ArrayList<Socket> list = new ArrayList<>();
    public static void main(String[] args) throws IOException {
        ServerSocket serverSocket = new ServerSocket(1000);
        //获取文件中正确的用户名及密码
        Properties prop = new Properties();
        FileInputStream fis = new FileInputStream("D:\\java\\basic-code\\online-chat-room\\客户数据.txt");
        prop.load(fis);
        //只要有一个客户端,就开一条线程处理
        while (true) {
            Socket socket = serverSocket.accept();
            System.out.println("有客户端来连接");
            new Thread(new MyRunnable(socket,prop)).start();
        }
    }
}
//线程的作用:传输工具
class MyRunnable implements Runnable {
    Socket socket;
    Properties prop;
    public MyRunnable(Socket socket, Properties prop) {
        //连接客户端
        this.socket = socket;
        this.prop = prop;
    }
    public void run() {
        try {
            BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
            String line = br.readline();
            System.out.println(line);
            }
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
}

运行结果

//客户端
服务器已经连接成功
=========欢迎来到小周聊天室=========
1.登录
2.注册
请选择选项:
1
请输入用户名:
zhangsan
请输入密码:
123
//服务器
有客户端来连接
username=zhangsan&password=123

要使右边的服务器知道客户端进行的是登录操作还是注册操作,从而让服务器进行不同的操作,我们可以从客户端发送一条信息,告诉服务器,是登录操作还是注册操作。

所以我们要对上面的代码进行适当的改写。

客户端发送两次数据

//第一次写入执行登录操作(告诉服务器)
bw.write("login");
bw.newLine();
bw.flush();
//第二次写入数据
bw.write(sb.toString());
bw.newLine();
bw.flush();

服务端根据客户端发来的第一次数据进行判断,进行选择操作

String choose = br.readLine();
switch (choose) {
       case "login" -> System.out.println("用户选择了登录操作");
       case "register" -> System.out.println("用户选择了注册操作");
}            

服务器根据传来的信息(login),进行用户名和密码的校验

//该方法要获取输入的用户名和密码
//并进行判断
    public void login(BufferedReader br) throws IOException {
        System.out.println("用户选择了登录操作");
        String userinfo = br.readLine();
        //username=zhangsan&password=123
        //对搜到的用户信息进行拆分
        String[] userinfoArr = userinfo.split("&");
        String usernameInput = userinfoArr[0].split("=")[1];
        String passwordInput = userinfoArr[1].split("=")[1];
        System.out.println("用户名为:"+usernameInput);
        System.out.println("密码为:"+passwordInput);
        //判断
        if(prop.containsKey(usernameInput)){
            //用户名存在,则继续判断密码是否正确
            String rightpassword = prop.get(usernameInput)+"";
            //判断密码是否正确
            if(rightpassword.equals(passwordInput)){
                //提示用户登录成功
                writeMessage2Client("1");
            }else{
                //密码错误,直接回写
                writeMessage2Client("2");
            }
        }else{
            //用户名不存在,直接回写
            writeMessage2Client("3");
        }
    }

根据客户端发来的用户名和密码,与文件中的数据进行判断是否一致,判断完之后再回写给客户端因为回写的代码是通用的,内容不一样,所以可以把它抽成一个方法。

public void writeMessage2Client(String message) throws IOException {
        BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
        bw.write(message);
        bw.newLine();
        bw.flush();
    }

当用户输错了密码,需要进行重新登录,所以要将读取客户端发来的数据的代码进行while(true)循环。

while (true) {
                String choose = br.readLine();
                switch (choose) {
                    case "login" -> login(br);
                    case "register" -> System.out.println("用户选择了注册操作");
                }
            }

运行结果

服务器已经连接成功
=========欢迎来到小周聊天室=========
1.登录
2.注册
请选择选项:
1
请输入用户名:
zhangsan
请输入密码:
12

输入完之后发现没有运行结果,聪明的你肯定发现了,是因为客户端还有写接收数据的代码。

//创建输入流
        BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
        String s = br.readLine();
        //可以用状态码代表传输的文字,比较方便
        //1:登陆成功 2:密码错误 3:用户不存在
        if(s.equals("1")){
            System.out.println("登录成功,可以开始聊天");
            //写一个while(true)循环,表示开始聊天
            while(true){}
        }else if(s.equals("2")){
            System.out.println("密码有误,请重新登录");
        }else if(s.equals("3")){
            System.out.println("用户名不存在,请先注册");
        }

运行结果

服务器已经连接成功
=========欢迎来到小周聊天室=========
1.登录
2.注册
请选择选项:
1
请输入用户名:
zhangsan
请输入密码:
12
密码有误,请重新登录
=========欢迎来到小周聊天室=========
1.登录
2.注册
请选择选项:
1
请输入用户名:
wuyu
请输入密码:
123
用户名不存在,请先注册
=========欢迎来到小周聊天室=========
1.登录
2.注册
请选择选项:
1
请输入用户名:
zhangsan
请输入密码:
123
登录成功,可以开始聊天

接下来我们实现客户端和服务器之间的聊天连接

服务器

因为可能会有很多个客户端向服务器发送数据,所有传递的参数加一个usernameInput

//接收客户端发来的信息
talk2All(br,usernameInput);
private void talk2All(BufferedReader br, String usernameInput) throws IOException {
        while (true) {
            String message = br.readLine();
            System.out.println(usernameInput+"发送过来消息:"+message);
            }
        }

    }

客户端

//发出消息,显示在控制台
talk2All(bw);
private static void talk2All(BufferedWriter bw) throws IOException {
        Scanner sc = new Scanner(System.in);
        while(true){
            System.out.println("请输入你要说的话");
            String message = sc.nextLine();
            bw.write(message);
            bw.newLine();
            bw.flush();
        }
    }

群发的逻辑

服务器要向每一个客户端回写数据

功能实现:

服务端可以将所有用户的Socket对象存储到一个集合中

当需要群发消息时,可以遍历集合发给所有的用户

此时的服务端,相当于做了一个消息的转发

服务器群发逻辑

在服务器的成员位置定义一个静态集合

static ArrayList<Socket> list = new ArrayList<>();

有客户端来连接,就把它的socket保存起来

Server.list.add(socket);

然后服务器的talk2All除了打印客户端发来的数据,还要进行群发的操作,遍历集合,给每一个socket对象发送数据。

for(Socket s : Server.list){
                writeMessage2Client(s,usernameInput+"发送过来消息:"+message);
            }

因为原来的writeMessage2Client方法只能传递一个message回写的消息,不能传socket,所以我们需要对writeMessage2Client方法进行重载。

方法的重载

是指在同一个类中,方法名相同但参数列表不同的多个方法。这些方法可能会有不同的参数类型、参数数量或参数顺序。方法的重载允许我们根据不同的参数来调用同名的方法。

public void writeMessage2Client(Socket s, String message) throws IOException {
        BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(s.getOutputStream()));
        bw.write(message);
        bw.newLine();
        bw.flush();
    }

客户端群发逻辑

我们要思考我们在发送消息的时候,服务器可能会传一个消息过来,所以需要单独开一条线程,专门用来接收服务器发送过来的聊天记录,这条线程只负责接收并打印在控制台。

//单独开一条线程去接收服务器发送过来的聊天记录
new Thread(new ClientMyRunnable(socket)).start();
class ClientMyRunnable implements Runnable{
    Socket socket;
    public ClientMyRunnable(Socket socket){
        this.socket = socket;
    }
    @Override
    public void run() {
        //循环,重复的接收
        while(true) {
            try {
                //接收服务器发来的聊天记录
                BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
                String mag = br.readLine();
                System.out.println(mag);
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        }
    }
}

结束

我们来测验一下吧

//服务器
有客户端来连接
有客户端来连接
用户选择了登录操作
用户名为:zhangsan
密码为:123
用户选择了登录操作
用户名为:lisi
密码为:1234
zhangsan发送过来消息:你好呀,李四
lisi发送过来消息:你好呀,张三
//客服端1
服务器已经连接成功
=========欢迎来到小周聊天室=========
1.登录
2.注册
请选择选项:
1
请输入用户名:
zhangsan
请输入密码:
123
登录成功,可以开始聊天
请输入你要说的话
你好呀,李四
请输入你要说的话
zhangsan发送过来消息:你好呀,李四
lisi发送过来消息:你好呀,张三
//客户端2
服务器已经连接成功
=========欢迎来到小周聊天室=========
1.登录
2.注册
请选择选项:
1
请输入用户名:
lisi
请输入密码:
1234
登录成功,可以开始聊天
请输入你要说的话
zhangsan发送过来消息:你好呀,李四
你好呀,张三
请输入你要说的话
lisi发送过来消息:你好呀,张三

结束了,希望能为大家提供帮助,感谢关注!