JAVA-网络编程/多线程-综合案例:网络聊天室:

1.功能展示:

2.源码:

2.1 客户端:

2.1.1 Client_main.java:

package Client;

import java.io.*;
import java.net.InetAddress;
import java.net.Socket;
import java.nio.charset.StandardCharsets;
import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Date;
import java.util.Scanner;

public class Client_main {
    public static void main(String[] args) throws IOException {
        //Socket socket=new Socket("172.25.211.95",10086);
        Socket socket=new Socket("127.0.0.1",10086);
        System.out.println("已连接到服务器");


        while (true) {
            System.out.println("+--------------------------------------------------+");
            System.out.println("|                欢迎使用CMD聊天室                   |");
            System.out.println("+--------------------------------------------------+");
            System.out.println("|                1.登陆                             |");
            System.out.println("|                2.注册                             |");
            System.out.println("+--------------------------------------------------+");
            System.out.println("|   请选择功能:                                     |");
            System.out.println("+--------------------------------------------------+");

            Scanner sc = new Scanner(System.in);
            String choose = sc.nextLine();
            switch (choose) {
                case "1" -> login(socket);
                case "2" -> register(socket);
                default -> System.out.println("没有这个选项");
            }
        }
    }

    //1.登陆
    private 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();

        StringBuilder sb=new StringBuilder();
        //username=zhangsan&password=123
        sb.append("username=").append(username).append("&password=").append(password);

        //对服务器请求登陆
        bw.write("request: login");
        bw.newLine();
        bw.flush();
        //发送账号密码
        bw.write(sb.toString());
        bw.newLine();
        bw.flush();

        //接收服务器的返回信息
        BufferedReader br=new BufferedReader(new InputStreamReader(socket.getInputStream()));
        String message=br.readLine();
        //1=登陆成功  2=密码错误   3=账户不存在
        if(message.equals("1")){
            System.out.println("登陆成功!");
            //开一条单独的线程,专门用来接收服务端发送过来的聊天记录
            Thread listener  = new Thread(new ClientRunnable(socket));
            listener.start();
            //开始聊天
            Chat(socket,listener);


        } else if (message.equals("2")) {
            System.out.println("密码输入错误");
        }else if (message.equals("3")) {
            System.out.println("账户不存在");
        }

    }

    //2.注册
    private static void register(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();

        String password1="";
        while (true){
            System.out.println("请输入密码:");
            password1 = sc.nextLine();
            System.out.println("请再次输入密码:");
            String password2 = sc.nextLine();
            if(!password2.equals(password1))
                System.out.println("两次密码输入不一致请重新输入");
            else
                break;
        }
        System.out.println("请输入邀请码:");
        String key=sc.nextLine();



        StringBuilder sb=new StringBuilder();
        //username=zhangsan&password=123
        sb.append("username=").append(username).append("&password=").append(password1);

        //对服务器请求登陆
        bw.write("request: register");
        bw.newLine();
        bw.flush();
        //发送账号密码
        bw.write(sb.toString());
        bw.newLine();
        bw.flush();
        //发送邀请码
        bw.write(key);
        bw.newLine();
        bw.flush();

        //接收服务器的返回信息
        BufferedReader br=new BufferedReader(new InputStreamReader(socket.getInputStream()));
        String message=br.readLine();

        //1=注册成功  2=账户已存在   3=邀请码不正确或已失效
        if(message.equals("1")){
            System.out.println("注册成功");
        } else if (message.equals("2")) {
            System.out.println("账户已存在");
        }else if (message.equals("3")) {
            System.out.println("邀请码不正确或已失效");
        }


        //bw.close();关闭缓冲流也会导致socket连接关闭,致使不能连续使用功能

    }


    private static void Chat(Socket socket,Thread listener) throws IOException {
        BufferedWriter bw=new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
        Scanner scanner=new Scanner(System.in);

        System.out.println("上线!输入 quit 退出!");
        while (true){
            //System.out.println("请输入你要发送的信息:");
            String message = scanner.nextLine();

            if(message.equals("quit")){
                System.out.println("下线!");
                bw.write("_socket_close");//下线时告诉服务端
                bw.newLine();
                bw.flush();
                break;
            }

            bw.write(message);
            bw.newLine();
            bw.flush();
        }

        bw.close();
        socket.close();
    }

}

2.1.2 ClientRunnable.java:

package Client;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.Socket;

public class ClientRunnable implements Runnable{
    Socket socket;
    ClientRunnable(Socket socket){
        this.socket=socket;
    }

    @Override
    public void run() {

        while (true){
            try {
                BufferedReader br=new BufferedReader(new InputStreamReader(socket.getInputStream()));
                String message=br.readLine();
                System.out.println(message);
            } catch (IOException e) {
                break;//客户端中quit会导致socket关闭异常,直接break,即可
            }

        }
    }
}

2.2 服务端:

2.2.1 Server_main.java:

package Server;

import java.io.FileInputStream;
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.Properties;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

public class Server_main {

    public static void main(String[] args) throws IOException {
        ServerSocket serverSocket = new ServerSocket(10086);
        System.out.println("服务器上线!");

        //加载用户信息文件
        Properties prop=new Properties();
        FileInputStream fis=new FileInputStream("src/Server/userinfo.txt");
        prop.load(fis);
        fis.close();

        //创建用户集合
        ArrayList<Socket> userList=new ArrayList<>();
        //创建线程池
        ThreadPoolExecutor socket_pool=new ThreadPoolExecutor(
                3,
                10,
                180,
                TimeUnit.SECONDS,
                new ArrayBlockingQueue<>(5),
                Executors.defaultThreadFactory(),
                new ThreadPoolExecutor.CallerRunsPolicy()
        );

        //来一个连接,给线程池提交一个任务
        while (true){
            Socket socket=serverSocket.accept();
            // 获取当前时间并格式化为字符串
            SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
            String currentTime = sdf.format(new Date());
            System.out.println(socket.getInetAddress().getHostAddress()+":"+socket.getInetAddress().getHostName()+"尝试连接!---"+currentTime);
            socket_pool.submit(new ServerRunnable(socket,prop,userList));
        }

    }
}

2.2.2 ServerRunnable.java:

package Server;

import java.io.*;
import java.net.InetAddress;
import java.net.Socket;
import java.net.UnknownHostException;
import java.nio.charset.StandardCharsets;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.Properties;



public class ServerRunnable implements  Runnable{
    //邀请码:
    public static final String KEY ="CMD137";
    Socket socket;
    Properties prop;
    ArrayList<Socket> userList=new ArrayList<>();//注意竞争问题:不能同时有多个线程操作userlist,使用同步锁

    ServerRunnable(Socket socket,Properties prop,ArrayList<Socket> userList){
        this.prop=prop;
        this.socket=socket;
        this.userList=userList;
    }

    @Override
    public void run() {
        try {
            BufferedReader br=new BufferedReader(new InputStreamReader(socket.getInputStream()));
            while (true){
                String request=br.readLine();
                if(request.equals("request: login"))
                    login(br);
                else if (request.equals("request: register"))
                    register(br);
                else {
                    System.out.println("未知的请求:"+request);
                }
            }
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    //处理登陆请求
    private void login(BufferedReader br) throws IOException {
        System.out.println("正在处理"+socket.getInetAddress().getHostAddress()+":"+socket.getInetAddress().getHostName()+"的登陆请求");
        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);

        //1=登陆成功  2=密码错误   3=账户不存在
        if (prop.containsKey(usernameInput)) {
            String rightPassword = prop.get(usernameInput) + "";
            if (rightPassword.equals(passwordInput)) {
                writeMessage2Client("1");

                //登录成功的时候,就需要把客户端的连接对象Socket保存起来
                userList.add(socket);
                talk2All(br,usernameInput);
            } else {
                //密码输入有误
                writeMessage2Client("2");
            }
        } else {
            //3=账户不存在
            writeMessage2Client("3");
        }
    }

    //处理注册请求
    private void register(BufferedReader br) throws IOException {
        System.out.println("正在处理"+socket.getInetAddress().getHostAddress()+":"+socket.getInetAddress().getHostName()+"的注册请求");
        String userinfo = br.readLine();
        String key=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);
        System.out.println("用户输入的邀请码为:"+key);

        //1=注册成功  2=账户已存在   3=邀请码不正确或已失效
        if (key.equals(KEY)) {
            if (!prop.containsKey(usernameInput)) {
                writeMessage2Client("1");
                //注册成功,写入userinfo.txt
                prop.setProperty(usernameInput,passwordInput);
                FileOutputStream fos=new FileOutputStream("src/Server/userinfo.txt");
                prop.store(fos,null);
                System.out.println("用户注册成功");
                fos.close();
            } else {
                //2=账户已存在
                writeMessage2Client("2");
            }
        } else {
            //3=邀请码不正确或已失效
            writeMessage2Client("3");
        }
    }

    //用于回复请求
    public void writeMessage2Client(String message) throws IOException {
        //获取输出流
        BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
        bw.write(message);
        bw.newLine();
        bw.flush();
    }

    //用于转发信息
    public void writeMessage2Client(Socket s,String username,String message) throws IOException {
        BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(s.getOutputStream()));
        // 获取当前时间并格式化为字符串
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        String currentTime = sdf.format(new Date());
        message=currentTime+" --- "+username+" :"+message;
        bw.write(message);
        bw.newLine();
        bw.flush();
    }

    private void talk2All(BufferedReader br,String username) throws IOException {
        //提醒所有用户,该用户已上线
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        String currentTime = sdf.format(new Date());
        String message=username+"已上线   ---"+currentTime;
        //群发
        for (Socket s : userList) {
            //s依次表示每一个客户端的连接对象
            BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(s.getOutputStream()));
            bw.write(message);
            bw.newLine();
            bw.flush();
        }


        while (true) {
            message = br.readLine();
            if(message.equals("_socket_close")){
                System.out.println(username+"下线!");
                //提醒所有用户,该用户已下线
                sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
                currentTime = sdf.format(new Date());
                message=username+"已下线   ---"+currentTime;
                //群发
                for (Socket s : userList) {
                    //s依次表示每一个客户端的连接对象
                    BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(s.getOutputStream()));
                    bw.write(message);
                    bw.newLine();
                    bw.flush();
                }
                break;
            }

            System.out.println(username + "发送过来消息:" + message);
            //群发
            for (Socket s : userList) {
                //s依次表示每一个客户端的连接对象
                writeMessage2Client(s, username, message);
            }
        }
    }
}

2.2.3 userinfo.txt:(存储用户账号密码的文件,符合Properties的格式)

cmd137=woshicmd137
zhangsan=woshizhangsan
lisi=woshilisi

3.仍存在的问题:

当客户端输入 quit 时,bw.close()会关闭输出流,也会使与之绑定的socket处于半关闭状态,而导致ClientRunnable 中的run抛出 SocketException类型的异常,提示 “Socket closed”。致使功能上:结束聊天,再次进入登陆/注册时socket已被关闭而不能进入。可以不去bw.close(),但显然这并不安全。

我让我的Linux当服务端。在上面直接java 编译程序时,Server_main.java ServerRunnable.java 这两个服务端的文件由于互相调用,不能单独编译(IDE中不会出现这个问题)。只能将两个类写在一个文件中。

暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇