多线程(下)——多人网络通信程序

本文介绍了一种使用Java实现的多线程网络通信程序,涵盖client包中的ChatRoomClient类,server包内的ChatRoomServer类,以及common包下的ChatMessage、Constants接口、DataExchange类和Utils工具类。程序通过ChatRoomClientAPPMain和ChatRoomServerAPPMain作为应用入口,实现了多人聊天室的功能。
摘要由CSDN通过智能技术生成

多线程(下)——多人网络通信程序

1.client包

ChatRoomClient类

package ChatRoom.client;

import ChatRoom.common.ChatMessage;
import ChatRoom.common.DataExchange;
import ChatRoom.common.Utils;

import java.io.IOException;
import java.net.Socket;
import java.util.Scanner;

import static ChatRoom.common.Constants.*;

public class ChatRoomClient {
    private String server;
    DataExchange exchange;
    private String userName;
    private String currentToName;
    private Scanner input = new Scanner(System.in);

    public ChatRoomClient(String server) throws IOException {
        System.out.println("客户端连接中······");
        this.server = server;
        Socket socket = new Socket(server, SERVER_PORT);
        System.out.println("客户端已连接到服务器:" + socket.getRemoteSocketAddress());
        exchange = new DataExchange(socket);
        this.server = server;
    }

    public void start() throws IOException {
        initName();

        Thread readThread = new Thread(() -> {
            while (true) {
                try {
                    ChatMessage chatMessage = exchange.receive();
                    if (chatMessage.getFrom().equalsIgnoreCase(ADMIN_NAME) &&
                            chatMessage.getMessage().trim().equalsIgnoreCase(BYE)) {
                        exchange.close();
                        System.out.println("已经离开可聊天室,聊天结束");
                        System.exit(0);
                    }
                    System.out.println("from\"" + chatMessage.getFrom() + "\":" + chatMessage.getMessage());
                } catch (IOException e) {
                    e.printStackTrace();
                    exchange.close();
                    System.exit(-2);
                }
            }
        });
        readThread.start();

        Thread writeThread = new Thread(() -> {
            while (true) {
                try {
                    exchange.send(creatChatMessage());
                } catch (Exception e) {
                    e.printStackTrace();
                    exchange.close();
                    System.exit(-3);
                }
            }
        });
        writeThread.start();
    }


    private ChatMessage creatChatMessage() {
        while (true) {
            String line = input.nextLine().trim();
            String to = null;
            String message = null;
            try {
                if (line.startsWith(CHAT_WITH_START)) {
                    to = line.substring(1, line.indexOf(SPACE_STRING)).trim();
                    String error = Utils.isValidToUserName(to);
                    if (error == null) {
                        currentToName = to;
                        message = line.substring(line.indexOf(SPACE_STRING) + 1);
                    } else {
                        System.out.println("用户名\"" + to + "\"不合法:" + error);
                        continue;
                    }
                } else {
                    if (currentToName == null) {
                        System.out.println("请使用@输入想和谁聊天,以后如果和同一个人聊天,就可以不用@啦");
                        continue;
                    }
                    message = line;
                }
            } catch (Exception e) {
                System.out.println("输入的消息不对:" + line);
                continue;
            }
            return new ChatMessage(userName, currentToName, message);
        }
    }



    private void initName() throws IOException {
        String typedName = null;
        while (true) {
            ChatMessage chatMessage = exchange.receive();
            String serverMessage = chatMessage.getMessage();
            if (serverMessage.equalsIgnoreCase(USER_NAME_PASS)) {
                userName = typedName;
                break;
            } else {
                System.out.println(serverMessage);
                typedName = input.nextLine();
                exchange.send(new ChatMessage(NO_NAME, ADMIN_NAME, typedName.trim()));
            }
        }
    }
}

2.server包

ChatRoomServer类

package ChatRoom.server;

import ChatRoom.common.ChatMessage;
import ChatRoom.common.DataExchange;
import chat.Chat;

import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

import static ChatRoom.common.Constants.*;
import static ChatRoom.common.Utils.isValidUserName;

public class ChatRoomServer {
    private int port;
    //TODO Concurrent
    //使用Map结构,保存key(用户名),value(交换的数据),并为其新建对象 ConcurrentHashMap(线程同步)
    private Map<String, DataExchange> name2DataEx = new ConcurrentHashMap<>();
    //TODO CachedThreadPool
    //创建线程池(动态线程)
    private ExecutorService service = Executors.newCachedThreadPool();

    public ChatRoomServer(int port) {
        this.port = port;
    }

    /**
     * 初始化操作,启动服务器,处理客户端的数据
     * @throws IOException
     */
    public void start() throws IOException {
        System.out.println("服务器已启动");
        ServerSocket serverSocket = new ServerSocket(SERVER_PORT);
        while (true) {
            Socket socket = serverSocket.accept();
            handleClientSocket(socket);
        }
    }

    /**
     * 调用线程池的线程处理客户端任务
     * 为接入的新的客户创建对象
     * @param socket 当前客户的数据连接
     */
    private void handleClientSocket(Socket socket) {
        service.submit(new ClientHandler(socket));
    }

    /**
     * 处理传送过来的客户信息
     */
    class ClientHandler implements Runnable {

        private Socket socket;

        private String userName = null;

        public ClientHandler(Socket socket) {
            this.socket = socket;
        }

        /**
         *
         * @param dataExchange
         * @param chatMessage
         * @param toName
         */
        private void handleChatMessage(DataExchange dataExchange, ChatMessage chatMessage, String toName) {
            DataExchange toEx = name2DataEx.get(toName);
            if (toEx == null) {
                dataExchange.send(new ChatMessage(ADMIN_NAME, chatMessage.getFrom(), "用户名\"" + toName + "\"不存在"));
            } else {
                toEx.send(chatMessage);
            }
        }


        private void handleServerCommand(ChatMessage chatMessage) {
            DataExchange from = name2DataEx.get(chatMessage.getFrom());
            String command = chatMessage.getMessage();

            if (command.equalsIgnoreCase(SERVER_COMMAND_LOGOFF)) {
                from.send(new ChatMessage(ADMIN_NAME, chatMessage.getFrom(), BYE));
                from.close();
                name2DataEx.remove(userName).close();
                System.out.println("用户\"" + chatMessage.getFrom() + "\"离开聊天室");
            } else if (command.equalsIgnoreCase(SERVER_COMMAND_LIST)) {
                String allNames = getALLNames();
                from.send(new ChatMessage(ADMIN_NAME, chatMessage.getFrom(), "所有在线用户:" + allNames));
            }
        }

        @Override
        public void run() {
            System.out.println("处理来自" + socket.getRemoteSocketAddress() + "的连接");
            //创建新的数据交换包
            DataExchange dataExchange = null;
            try {
                //对传进来的socket包的链接进行处理
                dataExchange = new DataExchange(socket);
            } catch (Exception e) {
                e.printStackTrace();
                return;
            }

            initUser(dataExchange);

            while (true) {
                try {
                    ChatMessage chatMessage = dataExchange.receive();
                    String toName = chatMessage.getTo();
                    if (toName.equalsIgnoreCase(ADMIN_NAME)) {
                        handleServerCommand(chatMessage);
                    } else {
                        handleChatMessage(dataExchange, chatMessage, toName);
                    }
                } catch (IOException e) {
                    name2DataEx.remove(userName).close();
                    e.printStackTrace();
                    return;
                }
            }
        }

        /**
         * 初始化用户
         * @param dataExchange
         */
        private void initUser(DataExchange dataExchange) {
            //定义一个错误信息
            String errorMsg = null;

            while (true) {
                String allNames = getALLNames();
                dataExchange.send(new ChatMessage(ADMIN_NAME, NO_NAME, (errorMsg == null ? "" : "用户名非法" +
                        ",错误信息为:" + errorMsg + ":") + "现有的用户名有:" + allNames + "。请输入你的用户名:"));

                try {
                    ChatMessage chatMessage = dataExchange.receive();
                    String userName = chatMessage.getMessage();
                    errorMsg = isValidUserName(userName);
                    if (errorMsg == null && (!name2DataEx.containsKey(userName))) {
                        this.userName = userName;
                        name2DataEx.put(userName, dataExchange);
                        dataExchange.send(new ChatMessage(ADMIN_NAME, userName, USER_NAME_PASS));
                        dataExchange.send(new ChatMessage(ADMIN_NAME, userName, COMMAND_INTRODUCTION));
                        return;
                    }
                } catch (IOException e) {
                    e.printStackTrace();
                    dataExchange.close();
                    return;
                }
            }
        }

        /**
         * 获得当前的所有用户名
         * @return
         */
        private String getALLNames() {
            String userNameListSep = ",";
            //如果存储用户信息的Map集合为空就输出管理员信息
            if (name2DataEx.isEmpty()) {
                return CHAT_WITH_START + ADMIN_NAME;
            } else {
                //join方法,第一个参数时后面集合元素的分割符
                return CHAT_WITH_START + ADMIN_NAME + userNameListSep + CHAT_WITH_START + String.join(userNameListSep + CHAT_WITH_START, name2DataEx.keySet());
            }
        }
    }


}

3.common包

ChatMessage类

package ChatRoom.common;

import java.util.Objects;

import static ChatRoom.common.Constants.MESSAGE_BREAK;
import static ChatRoom.common.Constants.MESSAGE_SEP;

/**
 * 属于共有类
 * 抽取客户端和服务器端共有部分,节省代码
 */
public class ChatMessage {
    //定义哪里发来的,发给谁,内容是什么
    private String from;
    private String to;
    private String message;

    //有参构造方法,为成员变量赋值
    public ChatMessage(String from, String to, String message) {
        this.from = from;
        this.to = to;
        //对内容进行去空格
        this.message = message.trim();
    }

    //无参的构造方法
    public ChatMessage() {
    }

    /**
     * 静态方法
     * 因为没有办法赋值调用有参构造方法
     * 通过获得信息,调用无参的构造方法
     * 将从来源发来的信息进行分割处理
     * 为成员变量赋值
     *
     * @param message 待传信息
     * @return成员变量赋值后的聊天信息类型ret
     */
    //TODO 在自己的类中声明自己类型的方法
    public static ChatMessage buildFrom(String message) {

        ChatMessage ret = new ChatMessage();
        //TODO String类型的方法 indexOf
        //TODO 参数有两个或者一个(value,索引开始位置)
        //TODO 代表从某个位置开始向后检索是否存在 value ,并返回第一次出现 value 的下标
        //TODO 只有一个参数代表从0开始检索
        int fromEnd = message.indexOf(MESSAGE_SEP);
        //返回从开始位置到结束位置的子串
        //两个参数是开始位置和结束位置
        ret.from = message.substring(0, fromEnd);
        int toEnd = message.indexOf(MESSAGE_SEP, fromEnd + 1);
        ret.to = message.substring(fromEnd + 1, toEnd);
        ret.message = message.substring(toEnd + 1).trim();
        return ret;

    }

    /**
     * 将信息进行拼接的方法
     *
     * @return将信息进行拼接,并设置分隔符,方便后续读取时分割
     */
    public String toMessageString() {
        //StringBuilder不会创建新的存储空间去存放,而是在原有的基础上拼接
        StringBuilder ret = new StringBuilder();
        ret.append(from).append(MESSAGE_SEP).append(to).append(MESSAGE_SEP).append(message).append(MESSAGE_BREAK);
        //将拼接后的ret生成一个字符串
        return ret.toString();
    }

    //TODO equals和 hashCode
    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (!(o instanceof ChatMessage)) return false;
        ChatMessage that = (ChatMessage) o;
        return Objects.equals(from, that.from) &&
                Objects.equals(to, that.to) &&
                Objects.equals(message, that.message);
    }

    @Override
    public int hashCode() {
        return Objects.hash(from, to, message);
    }

    public String getFrom() {
        return from;
    }

    public void setFrom(String from) {
        this.from = from;
    }

    public String getTo() {
        return to;
    }

    public void setTo(String to) {
        this.to = to;
    }

    public String getMessage() {
        return message;
    }

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

Constants接口

package ChatRoom.common;

import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;

public interface Constants {

    //服务器的端口号
    int SERVER_PORT = 45678;
    //默认的字符标准
    Charset DEFAULT_CHARSET = StandardCharsets.UTF_8;
    //SEP = separate
    // TODO Character类库的方法
    //UNASSIGNED:返回byte型值,表示Unicode规范中的常规类别"Ca"
    //特殊的编码,用作信息分隔符
    char MESSAGE_SEP = Character.UNASSIGNED;
    //换行符,代表信息结束
    String MESSAGE_BREAK = "\n";
    //代表管理员名
    String ADMIN_NAME = "admin";
    //代表空格
    String SPACE_STRING = " ";

    //TODO valueOf 和 unassigned
    //信息分割符号之开始
    String MESSAGE_SEP_STR = String.valueOf(Character.UNASSIGNED);
    //????
    String USER_NAME_PASS = "UserNamePass";
    //命令行介绍
    String COMMAND_INTRODUCTION = "欢迎来到聊天室,你可以使用@admin list查看所有在线用户,使用@用户名聊天";
    //聊天开始
    String CHAT_WITH_START = "@";
    //无名字
    String NO_NAME = "anonymous";
    //退出
    String SERVER_COMMAND_LOGOFF = "logoff";
    //列表
    String SERVER_COMMAND_LIST = "list";
    //关闭
    String BYE = "bye";
}

DataExchange类

package ChatRoom.common;

import java.io.*;
import java.net.Socket;

import static ChatRoom.common.Constants.DEFAULT_CHARSET;

/**
 * 数据交换
 */
public class DataExchange {
    private Socket socket;
    private BufferedReader reader;
    //TODO BufferedReader和 PrintWriter
    private PrintWriter writer;
    private boolean isClosed = false;

    public DataExchange(Socket socket) throws IOException {
        this.socket = socket;
        //初始化操作
        init(socket);
    }

    private void init(Socket socket) throws IOException {
        reader = new BufferedReader(new InputStreamReader(socket.getInputStream(), DEFAULT_CHARSET));
        writer = new PrintWriter(new OutputStreamWriter(socket.getOutputStream(), DEFAULT_CHARSET));
    }

    //接受chatmessage类型的参数
    //对其进行插入分隔符然后输出
    //清空缓存,确保全部发出
    public void send(ChatMessage chatMessage) {
        writer.println(chatMessage.toMessageString());
        writer.flush();
    }

    /**
     * 接受信息
     * 读取一行,不为空就退出循环,返回结果
     * @return返回分割开的message,便于读取
     * @throws IOException
     */
    public ChatMessage receive() throws IOException {
        String line = null;
        while (true) {
            line = reader.readLine();
            if (line != null && line.length() > 0) {
                break;
            }
        }
        return ChatMessage.buildFrom(line);
    }

    /**
     * 关闭输入输出流,将关闭之置为 true
     */
    public void close() {
        try {
            reader.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
        try {
            writer.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
        try {
            socket.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
        isClosed =true;
    }
}

Utils类

package ChatRoom.common;

import static ChatRoom.common.Constants.*;

public class Utils {

    /**
     * 判断是否为合理的用户名
     * @param userName 用户名
     * @return
     */
    public static String isValidUserName(String userName) {
        //先判断是否是合理的用户名
        //在判断是否包含admin
        String ret = isValidToUserName(userName);
        if (ret != null) {
            return ret;
        }

        if (userName.toLowerCase().contains(ADMIN_NAME)) {
            return "用户名不可以包含" + ADMIN_NAME;
        }
        return null;
    }


    public static String isValidToUserName(String userName) {
        if (userName.trim().length() == 0) {
            return "用户名不可以为空";
        }
        if (userName.contains(MESSAGE_SEP_STR)) {
            return "用户名不可以包含分隔符";
        }
        if (userName.contains(CHAT_WITH_START)) {
            return "用户名不可以包含" + CHAT_WITH_START;
        }
        if (userName.toLowerCase().contains(NO_NAME)) {
            return "用户名不可以包含" + NO_NAME;
        }
        if (userName.contains(SPACE_STRING)) {

            return "用户名不可以包含空格";
        }
        return null;
    }

    public static String getNormalizedUserName(String userName) {
        return userName.trim();
    }
}

4.ChatRoomClientAPPMain

package ChatRoom;

import ChatRoom.client.ChatRoomClient;

import java.io.IOException;

public class ChatRoomClientAPPMain {
    public static void main(String[] args) throws IOException {
        //args[0] 值为第一次输入的参数类型
        String server= args[0];
        ChatRoomClient client = new ChatRoomClient(server);
        client.start();
    }
}

5.ChatRoomServerAPPMain

package ChatRoom;

import ChatRoom.server.ChatRoomServer;

import java.io.IOException;

import static ChatRoom.common.Constants.SERVER_PORT;

public class ChatRoomServerAPPMain {
    public static void main(String[] args) throws IOException {
        ChatRoomServer server = new ChatRoomServer(SERVER_PORT);
        server.start();
    }
}

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值