Java网络编程_基于TCP协议的网络编程(三)

记录用户信息

增加聊天室功能:
1.可以看到是谁发的消息。
2.实现私聊功能。

实现方法:
对于第一个功能,可以使用Map集合来保存用户信息
第二个功能可以在客户端发送不同消息时,对这些消息进行处理,比如在消息内容前后添加一些特殊字符,通过这些特殊字符来判断。

代码实现

特殊字符类:

public interface ChatroomProtocol {

    //定义协议字符串的长度
    int PROTOCOL_LEN = 2;
    //下面是一些协议字符串,服务端和客户端交换的信息都应该在前、后添加这种特殊字符串
    String MSG_ROUND = "§γ";//公聊
    String USER_ROUND = "∏∑";//用户登录标志
    String LOGIN_SUCCESS = "1";//用户名字无重复,创建成功
    String NAME_REP = "-1";//用户名重复
    String PRIVATE_ROUND = "★【";//私聊标志
    String SPLIT_SIGN = "※";//分割消息内容
}

使用Map保存聊天室所有用户名和对应输出流之间的映射关系,Map类:

public class ChatroomMap<K, V> {

    //创建一个线程安全的HashMap
    public Map<K, V> map = Collections.synchronizedMap(new HashMap<K, V>());
    //根据value来删除指定项
    public synchronized void removeByValue(Object value){

        for(Object key : map.keySet()){         
            if(map.get(key) == value){
                map.remove(key);
                break;
            }
        }
    }
    //获取所有value组成的Set集合
    public synchronized Set<V> valueSet(){

        Set<V> result = new HashSet<V>();
        //将map中的所有value添加到result集合中
        map.forEach((key, value) -> result.add(value));
        return result;
    }
    //根据value查找key
    public synchronized K getKeyByValue(V val){

        //遍历所有key组成的集合
        for(K key : map.keySet()){
            //如果指定key对应的value与被搜索的value相同,则返回对应的key
            if(map.get(key) == val || map.get(key).equals(val)){

                return key;
            }
        }
        return null;
    }
    //实现put()方法,该方法不允许value重复
    public synchronized V put(K key, V value){

        //遍历所有value组成的集合
        for(V val : valueSet()){
            //如果某个value与试图放入集合的value相同
            //则抛出一个RuntimeException异常
            if(val.equals(value) && val.hashCode() == value.hashCode()){

                throw new RuntimeException("MyMap实例中不允许重复value~~");
            }
        }
        return map.put(key, value);
    }
}

服务器类:

public class Server {

    private static final int SERVER_PORT = 30000;
    //使用ChatroomMap对象来保存每个客户名字和对应输出流之间的对应关系
    public static ChatroomMap<String, PrintStream> clients = new ChatroomMap<>();
    public void init(){
        //自动关闭资源的try语句,
        try(
            //建立监听的ServerSocket
            ServerSocket ss = new ServerSocket(SERVER_PORT))
        {
            //采用死循环来不断地接收来自客户端的请求
            while(true){
                Socket socket = ss.accept();
                new ServerThread(socket).start();
            }
        }//抛出异常
        catch(IOException e){
            System.out.println("服务器启动失败,是否端口" + SERVER_PORT + "被占用");
        }
    }
    public static void main(String[] args){

        Server server = new Server();
        server.init();
    }
}

服务器线程类:

public class ServerThread extends Thread{

    private Socket socket;
    BufferedReader br = null;
    PrintStream ps = null;
    //定义一个构造器,用于接收一个Socket来创建ServerThread线程
    public ServerThread(Socket socket) {

        this.socket = socket;
    }
    public void run(){

        try{
            //获取该Socket对应的输入流
            br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
            //获取该Socket对应的输出流
            ps = new PrintStream(socket.getOutputStream());
            String line = null;
            while((line = br.readLine()) != null){
                //如果读到的行以ChatroomProtocol.USER_ROUND开始,并以其结束
                //则可以确定读到的是用户登录的用户名
                if(line.startsWith(ChatroomProtocol.USER_ROUND) && 
                        line.endsWith(ChatroomProtocol.USER_ROUND)){
                    //得到真实消息
                    String userName = getRealMsg(line);
                    //如果用户名重复
                    if(Server.clients.map.containsKey(userName)){
                        System.out.println("重复");
                        ps.println(ChatroomProtocol.NAME_REP);
                    }else{
                        System.out.println("成功");
                        ps.println(ChatroomProtocol.LOGIN_SUCCESS);
                        Server.clients.put(userName, ps);
                    }
                }
                //如果读到的行以ChatroomProtocol.PRIVATE_ROUND开始,并以其结束
                //则可以确定是私聊信息,私聊信息只向特定的输出流发送
                else if(line.startsWith(ChatroomProtocol.PRIVATE_ROUND) &&
                         line.endsWith(ChatroomProtocol.PRIVATE_ROUND)){
                    //得到真实消息
                    String userAndMsg = getRealMsg(line);
                    //以SPLIT_SIGN分割字符串,前半段是私聊用户,后半是聊天信息
                    String user = userAndMsg.split(ChatroomProtocol.SPLIT_SIGN) [0];
                    String msg = userAndMsg.split(ChatroomProtocol.SPLIT_SIGN) [1];
                    //获取私聊用户对应的输出流,并发送私聊信息
                    Server.clients.map.get(user).println(Server.clients.getKeyByValue(ps)
                            + "悄悄地对你说:" + msg);
                }
                //公聊要向每个Socket发送
                String msg = getRealMsg(line);
                //遍历clients中的每个输出流
                for(PrintStream clientPs : Server.clients.valueSet()){
                    clientPs.println(Server.clients.getKeyByValue(ps) + "说:" + msg);
                }
            }
        }
        //捕获异常后,表明该Socket对应的客户端已经出现了问题
        //所以程序将其对应的输出流从Map中删除
        catch(IOException e){
            Server.clients.removeByValue(ps);
            System.out.println(Server.clients.map.size());
            //关闭网络、IO资源
            try{
                if(br != null){
                    br.close();
                }
                if(ps != null){
                    ps.close();
                }
                if(socket != null){
                    socket.close();
                }
            }catch(IOException ex){
                ex.printStackTrace();
            }
        }
    }
    //将读到的内容去掉前后的协议字符,恢复成真实数据
    private String getRealMsg(String line) {

        return line.substring(ChatroomProtocol.PROTOCOL_LEN, line.length() - 
                ChatroomProtocol.PROTOCOL_LEN);
    }
}

客户端类:

    public class Client {

        private static final int SERVEER_PORT = 30000;
        private Socket socket;
        private PrintStream ps;
        private BufferedReader brServer;
        private BufferedReader keyIn;

        public void init(){

            try{
                //初始化代表键盘的输入流
                keyIn = new BufferedReader(new InputStreamReader(System.in));
                //连接到服务器端
                socket = new Socket("127.0.0.1", SERVEER_PORT);
                //获取Socket对应的输入流和输出流
                ps = new PrintStream(socket.getOutputStream());
                brServer = new BufferedReader(
                        new InputStreamReader(socket.getInputStream()));
                String tip = "";
                //采用循环不断地弹出对话框要求输入用户名
                while(true){

                    String userName = JOptionPane.showInputDialog(tip + "输入用户名");
                    //在用户输入的用户名前后增加协议字符串后发送
                    ps.println(ChatroomProtocol.USER_ROUND + userName + 
                            ChatroomProtocol.USER_ROUND);
                    //读取服务器端的响应
                    String result = brServer.readLine();
                    //如果用户名重复,则开始下次循环
                    if(result.equals(ChatroomProtocol.NAME_REP)){

                        tip = "用户名重复,请重新输入";
                        continue;
                    }
                    //如果服务器端返回登陆成功,则结束循环
                    if(result.equals(ChatroomProtocol.LOGIN_SUCCESS)){
                        break;
                    }
                }
            }
            //捕获到异常,关闭网络资源,并退出该程序
            catch (UnknownHostException ex){
                System.out.println("找不到远程服务器,请确定服务器已经启动!");
                closeRs();
                System.exit(1);
            }catch (IOException ex){
                System.out.println("网络异常!请重新登陆!");
                closeRs();
                System.exit(1);
            }
            //以该Socket对应的输入流启动ClientThread线程
            new ClientThread(brServer).start();
        }
        //定义一个读取键盘输出,并向网络发送的方法
        private  void readAndSend(){

            try{
                //不断地读取键盘输入
                String line = null;
                while((line = keyIn.readLine()) != null){

                    //如果发送的信息中有冒号,且以//开头,则认为想发送私聊信息
                    if(line.indexOf(":") > 0 && line.startsWith("//")){

                        line = line.substring(2);
                        ps.println(ChatroomProtocol.PRIVATE_ROUND + 
                                line.split(":")[0] + ChatroomProtocol.SPLIT_SIGN +
                                line.split(":")[1] + ChatroomProtocol.PRIVATE_ROUND);
                    }else{

                        ps.println(ChatroomProtocol.MSG_ROUND + line + 
                                ChatroomProtocol.MSG_ROUND);
                    }
                }
            }
            //捕获到异常,关闭网络资源,并退出该程序
            catch (IOException ex){
                System.out.println("网络通信异常!请重新登录!");
                closeRs();
                System.exit(1);
            }
        }
        //关闭Socket、输入流、输出流的方法
        private void closeRs() {

            try{
                if(keyIn != null){
                    ps.close();
                }
                if(brServer != null){
                    ps.close();
                }
                if(ps != null){
                    ps.close();
                }
                if(socket != null){
                    keyIn.close();
                }
            }catch (IOException ex){
                ex.printStackTrace();
            }
        }

        public static void main(String[] args){

            Client client = new Client();
            client.init();
            client.readAndSend();
        }
    }

客户端线程类:

public class ClientThread extends Thread{

    //该客户端线程负责处理的输入流
    BufferedReader br = null;
    //使用一个网络输入流来创建客户端线程
    public ClientThread(BufferedReader br) {

        this.br = br;
    }
    public void run(){

        try{
            String line = null;
            //不断地输入流中读取数据,并将这些数据打印 输出
            while((line = br.readLine()) != null){

                System.out.println(line);
                /*
                 * 此处可以添加更多方法,例如显示聊天室人员的上线,下线
                 */
            }
        }catch (IOException ex){
            ex.printStackTrace();
        }
        //使用finally块来关闭该线程对应的输入流
        finally{            
            try{
                if (br != null){
                    br.close();
                }
            }catch (IOException ex){
                ex.printStackTrace();
            }
        }
    }
}

本程序先启动服务器,然后启动多个客户端。每启动一个客户端,需要填写一个用户名,如果用户名重复,便需要重新填写,如果没有重复,那么服务器便会给其他客户端发送新用户上线。客户端发送消息时,客户端类会先判断是公聊还是私聊,然后添加特殊字符,发送给服务器。然后服务器解析信息,判断消息类型,根据不同消息选择发送方式。

在客户端编写消息时,如果是私聊,然后处理消息,在消息内容中前后添加私聊的特殊字符。
例如:

发送一条私聊,向aa发送hello
//aa:hello
向这条信息内容加入特殊字符
实际发送的内容为:
★【aa※hello★【

公聊方式:

发送公聊,发送hello
hello
向这条信息内容加入特殊字符
实际发送的内容为:
§γhello§γ
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值