【JAVADAY16-多用户多线程即时聊天小案例】

注意事项

事情是这样的,今天我在写案例的时候,遇到了一个BUG,一直抛出一个我从来没见过的异常,看到异常嘛,当然第一时间就是上百度查了,结果百度还是那个百度没有一个靠谱的,于是我就利用我毕生所学打断点,一点一点看,我就发现程序总是走到一个getinputstream的时候就终止了,我就纳了闷了,为啥啊?我逻辑也没写错啊?我找啊找,找了2个小时,我他喵的差点就没耐心在找下去了,天不负有心人,在一个网友的启发下,我试了试,将我的2个,一个是客户端,一个是服务端,他们用来存放User的文件夹Common,一个叫common,结果就悲剧了,因为这里的对象,刚好是我序列化和反序列化的一个类User类的对象,序列化的时候,必须保证文件相对路径是一样的,不能有半点差错,否则就像我一样整了2小时,结果是因为一个字母大写一个字母小写的问题,我真他喵的心态炸了,但是不得不说发现之后修复的感觉是真爽!

客户端代码

package com.LiveChat.InformationValidation;

import com.LiveChat.QQCommon.Message;
import com.LiveChat.QQCommon.MessageType;

import java.io.ObjectInputStream;
import java.net.Socket;

public class ClientConnectServerThreac extends Thread{
    //线程类
    private Socket socket;
    public ClientConnectServerThreac(Socket socket){
        this.socket=socket;
    }

    @Override
    public void run() {
        //为了让客户端和服务端一直进行通信,我们在后台保持两者的联通
        //即 客户端一直等待服务端发来的数据,只要有东西就接收
        while (true){
            try {
                System.out.println("客户端线程,正在等待服务端的数据变动中。。。");
                ObjectInputStream objectInputStream = new ObjectInputStream(socket.getInputStream());
                //我们知道这里返回的一定是一个Message对象,因此我们直接强转
                Message o = (Message)objectInputStream.readObject();
                if(o.getMesType().equals(MessageType.MESSAGE_RET_ONLINE_FRIEND)){
                    String[] onlineUsers=o.getContent().split(" ");
                    System.out.println("==========当前在线用户列表=========");
                    for (int i = 0; i < onlineUsers.length; i++) {
                        System.out.println("用户:"+onlineUsers[i]);
                    }
                }else {
                    System.out.println("暂时不处理其他类型的message!");
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    public Socket getSocket() {
        return socket;
    }
}

and

package com.LiveChat.InformationValidation;

import com.LiveChat.QQCommon.MessageType;
import com.LiveChat.QQCommon.Message;
import com.LiveChat.QQCommon.User;

import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.OutputStream;
import java.net.InetAddress;
import java.net.Socket;

public class LoginAndRegistered {
    private User user=new User();
    private Socket socket;

    public boolean cheackLogin(String userID, String passwd) throws IOException, ClassNotFoundException {
        //定义一个bool变量 用于返回
        boolean b = false;
        //创建一个user对象,一会发给服务端进行验证
        user.setUserId(userID);
        user.setPasswd(passwd);
        //连接服务器,把该对象发给服务端
        //这个sorckt我们可能在其他地方也会使用,因此最好做成属性
        socket = new Socket(InetAddress.getByName("127.0.0.1"), 9999);
        //然后把对象发过去
        OutputStream outputStream = socket.getOutputStream();
        ObjectOutputStream objectOutputStream = new ObjectOutputStream(outputStream);
        objectOutputStream.writeObject(user);
        //然后读取服务端发来的验证是否通过的信息
        ObjectInputStream objectInputStream = new ObjectInputStream(socket.getInputStream());
        Message message = (Message) objectInputStream.readObject();//如果服务端一直没发送object对象,就会阻塞在这里
        //根据message返会的是1还是2来判断是否登录成功
        if (message.getMesType().equals(MessageType.MESSAGE_LOGIN_SUCCEED)) {
            //如果成功,则让客户端与服务端一直进行通信
            //创建一个线程,用来保持客户端与服务端通信
            //每次成功都创建一个线程对象
            ClientConnectServerThreac clientConnectServerThreac = new ClientConnectServerThreac(socket);
            //这里线程就收到了一个socke对象,就可以启动线程,让服务端和客户端一直保持通信,一旦有数据的变化就客户端就接收
            clientConnectServerThreac.start();//该类继承了线程类,可以调用线程类的重要方法start启动线程
            //把线程放到集合里面,方便扩展,以及之后的调用
            ManageClientConnectServerThreac.addClientConnectServerThread(userID,clientConnectServerThreac);
            //return true;这里最好就先别return,因为else可能还要执行如果没验证成功是不是就。。对吧
            b = true;
        } else {
            //否则就不建立连接,关闭socket
            socket.close();
        }
        return b;

    }
    public void ShowUser(){
        //发送一个message对象给服务端
        Message message = new Message();
        message.setMesType(MessageType.MESSAGE_GET_ONLINE_FRIEND);
        //先要得到当前线程的socket对应的objectoutputstream对象
        try {
            ObjectOutputStream objectOutputStream = new ObjectOutputStream(ManageClientConnectServerThreac.getClientConnectServerThread(user.getUserId()).getSocket().getOutputStream());
            objectOutputStream.writeObject(message);
            //把数据传送给服务端
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

}

and

package com.LiveChat.InformationValidation;

import java.security.PublicKey;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;

public class ManageClientConnectServerThreac {
    //把多个线程放到该集合中 K=string类型:用户ID,Value=ClientConnectServerThreac:线程
    private static HashMap<String, ClientConnectServerThreac> hashMap=new HashMap<>();
    //将某个线程加入到集合中
    public static void addClientConnectServerThread(String userId, ClientConnectServerThreac clientConnectServerThreac){
        hashMap.put(userId,clientConnectServerThreac);
    }
    //将某个线程取出来
    public static ClientConnectServerThreac getClientConnectServerThread(String userId){
        return hashMap.get(userId);
        //返回一个线程,那么该线程为什么会对应一个socket呢
        //在我们创建线程对象的时候,我们就传入了一个socket,当我们用该对象的时候,自然就能使用getSocket方法了
    }

}

and

package com.LiveChat.QQCommon;

import java.io.Serializable;

public class Message implements Serializable {
    private static final long seriaVersiononID = 1L;
    private String sender;//发送者
    private String getter;//接收者
    private String content;//消息内容

    public String getSender() {
        return sender;
    }

    public void setSender(String sender) {
        this.sender = sender;
    }

    public String getGetter() {
        return getter;
    }

    public void setGetter(String getter) {
        this.getter = getter;
    }

    public String getContent() {
        return content;
    }

    public void setContent(String content) {
        this.content = content;
    }

    public String getSendTime() {
        return sendTime;
    }

    public void setSendTime(String sendTime) {
        this.sendTime = sendTime;
    }

    public String getMesType() {
        return mesType;
    }

    public void setMesType(String mesType) {
        this.mesType = mesType;
    }

    private String sendTime;//发送时间
    private String mesType;//消息的类型
}

and

package com.LiveChat.QQCommon;

public interface MessageType {
    String MESSAGE_LOGIN_SUCCEED="1";//表示登录成功
    String MESSAGE_LOGIN_FAIL="2";//表示登录失败
    String MESSAGE_COMM_MES="3";//表示普通信息
    String MESSAGE_GET_ONLINE_FRIEND="4";//要求返回在线用户列表
    String MESSAGE_RET_ONLINE_FRIEND="5";//要求返回在线用户列表
    String MESSAGE_CLIENT_EXIT="6";//要求返回在线用户列表
}

and

package com.LiveChat.QQCommon;

import java.io.Serializable;

public class User implements Serializable {
    private String userId;//用户名
    private String passwd;//密码
    private static final long seriaVersiononID = 1L;//增加兼容性

    @Override
    public String toString() {
        return "User{" +
                "userId='" + userId + '\'' +
                ", passwd='" + passwd + '\'' +
                '}';
    }

    public User(String userId, String passwd) {
        this.userId = userId;
        this.passwd = passwd;
    }
    public User() {
    }
    public String getUserId() {
        return userId;
    }

    public void setUserId(String userId) {
        this.userId = userId;
    }

    public String getPasswd() {
        return passwd;
    }

    public void setPasswd(String passwd) {
        this.passwd = passwd;
    }
}

and

package com.LiveChat.QQView;

import com.LiveChat.InformationValidation.LoginAndRegistered;

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

public class View {
    public static void main(String[] args) throws IOException, ClassNotFoundException {
        Show();
    }
    //显示主菜单
    private static void Show() throws IOException, ClassNotFoundException {
        boolean flag = true;
        int count = 1;
        int n = 0;
        Scanner scanner = new Scanner(System.in);
        LoginAndRegistered userClientLolgin = new LoginAndRegistered();
        //如果没有输入9那么就一直是下面这个页面
        while (flag) {
            if (count > 0) {
                System.out.println("===============欢迎登录网络通信系统===============");
                System.out.println("\t\t\t\t1.登录系统\t\t\t\t");
                System.out.println("\t\t\t\t9.退出系统\t\t\t\t");
                System.out.print("请输入您的选择:");
                n = scanner.nextInt();
                count--;
            } else {
                if (n == 9) {
                    return;
                } else if (n == 1) {
                    //否则就展示登录界面
                    System.out.print("请输入用户名:");
                    String name = scanner.next();
                    System.out.print("请输入密码:");
                    String passwd = scanner.next();
                    //然后判断账号密码是否正确,正确则跳转另外一个页面
                    //根据发送给服务端的对象User,来验证该用户是否合法
                    //如果合法服务端就会返回一个值,说明该用户合法
                    //然后同时启动线程,让客户端一直输出服务端的信息
                    //即需要一直socket.getinputstream,只要服务端有东西发过来,客户端就接受
                    if (userClientLolgin.cheackLogin(name,passwd)) {
                        System.out.println("=================欢迎 " + name + " 用户=================");
                        //进入另外一个界面
                        boolean b=true;
                        System.out.println("==============网络通信系统二级菜单(用户:" + name + ")===============");
                        while (b) {
                            System.out.println();
                            System.out.println("\t\t1.显示在线用户列表");
                            System.out.println("\t\t2.群发消息");
                            System.out.println("\t\t3.私聊消息");
                            System.out.println("\t\t4.发送文件");
                            System.out.println("\t\t9.退出系统");
                            System.out.print("\t\t请输入您的选择:");
                            int change = scanner.nextInt();
                            System.out.println();
                            switch (change) {
                                case 1:
                                    userClientLolgin.ShowUser();
                                    //(1)类的对象可以调用非静态方法
                                    //(2)而如果你所在的方法是静态的类名、方法名只能调用静态方法
                                    break;
                                case 2:
                                    System.out.println("=====群发消息=====");
                                    break;
                                case 3:
                                    System.out.println("=====私发消息=====");
                                    break;
                                case 4:
                                    System.out.println("=====发送文件=====");
                                    break;
                                case 9:
                                    System.out.println("成功退出系统!");
                                    return;
                                default:
                                    System.out.println("\t\t\t==========请重新选择!==========");
                            }
                            System.out.println();
                            //b=false;
                        }
                        return;
                    } else {
                        System.out.println("您的账号或密码错误,请重新输入!");
                    }
                } else {
                    System.out.println("请输入1或9!");
                    count++;
                }
            }
        }
    }
}

服务端代码

package com.LiveChat.QQCommon;

import java.io.Serializable;

public class Message implements Serializable {
    private static final long seriaVersiononID = 1L;
    private String sender;//发送者
    private String getter;//接收者
    private String content;//消息内容

    public String getSender() {
        return sender;
    }

    public void setSender(String sender) {
        this.sender = sender;
    }

    public String getGetter() {
        return getter;
    }

    public void setGetter(String getter) {
        this.getter = getter;
    }

    public String getContent() {
        return content;
    }

    public void setContent(String content) {
        this.content = content;
    }

    public String getSendTime() {
        return sendTime;
    }

    public void setSendTime(String sendTime) {
        this.sendTime = sendTime;
    }

    public String getMesType() {
        return mesType;
    }

    public void setMesType(String mesType) {
        this.mesType = mesType;
    }

    private String sendTime;//发送时间
    private String mesType;//消息的类型
}

and

package com.LiveChat.QQCommon;

public interface MessageType {
    String MESSAGE_LOGIN_SUCCEED="1";//表示登录成功
    String MESSAGE_LOGIN_FAIL="2";//表示登录失败
    String MESSAGE_COMM_MES="3";//表示普通信息
    String MESSAGE_GET_ONLINE_FRIEND="4";//要求返回在线用户列表
    String MESSAGE_RET_ONLINE_FRIEND="5";//要求返回在线用户列表
    String MESSAGE_CLIENT_EXIT="6";//要求返回在线用户列表
}

and

package com.LiveChat.QQCommon;

import java.io.Serializable;

public class User implements Serializable {
    private String userId;//用户名
    private String passwd;//密码
    private static final long seriaVersiononID = 1L;//增加兼容性

    @Override
    public String toString() {
        return "User{" +
                "userId='" + userId + '\'' +
                ", passwd='" + passwd + '\'' +
                '}';
    }

    public User(String userId, String passwd) {
        this.userId = userId;
        this.passwd = passwd;
    }
    public User() {
    }
    public String getUserId() {
        return userId;
    }

    public void setUserId(String userId) {
        this.userId = userId;
    }

    public String getPasswd() {
        return passwd;
    }

    public void setPasswd(String passwd) {
        this.passwd = passwd;
    }
}

and

package com.LiveChat.QQServer;

import com.LiveChat.QQCommon.MessageType;
import com.LiveChat.QQCommon.Message;
import com.LiveChat.QQCommon.User;

import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.HashMap;

/**
 * @author TMJIE5200
 */
public class ChatsystemServer {
    private ServerSocket ss=null;
    private static HashMap<String,User> validUsers=new HashMap<>();
    static {//在静态代码块中初始化 valiUser
        validUsers.put("张三",new User("001","123456"));
        validUsers.put("李四",new User("002","123456"));
        validUsers.put("王二",new User("003","123456"));
        validUsers.put("赵武",new User("004","123456"));
        validUsers.put("王青",new User("005","123456"));
    }
    //验证用户是否有效的方法
    public boolean cheackUser(String userId,String passwd){
        User user = validUsers.get(userId);
        if(user==null){
            return false;
        }
        if(!user.getPasswd().equals(passwd)){
            return false;
        }
        return true;
    }

    public ChatsystemServer() throws IOException, ClassNotFoundException {
        try {
            System.out.println("服务端在9999端口监听!!");
            ss = new ServerSocket(9999);
            //监听不是说监听到一个对象就结束了
            //当与某个客户端建立连接后继续监听
            while (true) {
                Socket socket = ss.accept();
                //通过socket读取客户端发来的User对象
                ObjectInputStream objectInputStream = new ObjectInputStream(socket.getInputStream());
                //我们清楚的知道第一次发过来的应该是一个User对象,我们直接转,后面就可以调用该对象的方法了
                User user =(User) objectInputStream.readObject();
                //这里本来是应该与数据库来比对的,因为没有学习,所以我们规定死
                Message message=new Message();
                ObjectOutputStream objectOutputStream = new ObjectOutputStream(socket.getOutputStream());
                if(cheackUser(user.getUserId(), user.getPasswd())){
                    //登陆成功,发送Message对象
                    //我们的Message对象放在外面因为不管是成功还是失败你都要返回一个Message对象
                    //同样ObjectOutputStream也放在外面,减少冗余
                    System.out.println("用户: {"+user.getUserId()+"} 已上线!");
                    message.setMesType(MessageType.MESSAGE_LOGIN_SUCCEED);
                    objectOutputStream.writeObject(message);
                    //创建一个线程和客户端保持通信
                    ServerConnectServerThreac serverThreac = new ServerConnectServerThreac(socket, user.getUserId());
                    serverThreac.start();
                    ManageServerThreac.addClientConnectServerThread(user.getUserId(),serverThreac);
                }else {
                    System.out.println("用户: {"+user.getUserId()+"} 登录失败!");
                    message.setMesType(MessageType.MESSAGE_LOGIN_FAIL);
                    objectOutputStream.writeObject(message);
                    socket.close();
                }
            }

        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            ss.close();
        }
    }
}

and

package com.LiveChat.QQServer;

import java.util.HashMap;
import java.util.Map;

public class ManageServerThreac {//把多个线程放到该集合中 K=string类型:用户ID,Value=ClientConnectServerThreac:线程
    private static HashMap<String, ServerConnectServerThreac> hashMap=new HashMap<>();
    //将某个线程加入到集合中
    public static void addClientConnectServerThread(String userId, ServerConnectServerThreac serverConnectServerThreac){
        hashMap.put(userId,serverConnectServerThreac);
    }
    //将某个线程取出来
    public static ServerConnectServerThreac getClientConnectServerThread(String userId){
        return hashMap.get(userId);
    }
    //返回用户列表
    public static String getOnlineUser(){
        String users="";
        for (Map.Entry<String, ServerConnectServerThreac> s : hashMap.entrySet()) {
            users+=s.getKey().toString()+" ";
        }
        //遍历结合
        return users;
    }
}

and

package com.LiveChat.QQServer;

import com.LiveChat.QQCommon.Message;
import com.LiveChat.QQCommon.MessageType;

import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.net.Socket;

public class ServerConnectServerThreac extends Thread{
    //线程类
    private Socket socket;
    private String userId;
    public ServerConnectServerThreac(Socket socket,String userId){
        this.socket=socket;
        this.userId=userId;
    }

    @Override
    public void run() {
        //为了让客户端和服务端一直进行通信,我们在后台保持两者的联通
        //即 客户端一直等待服务端发来的数据,只要有东西就接收
        while (true){
            try {
                System.out.println("服务端线程,正在等待客户端的数据信息。。。");
                ObjectInputStream objectInputStream = new ObjectInputStream(socket.getInputStream());
                //只要有个socket发送请求,我们就接受一个
                //那么socket发送什么请求呢,就是在登录的时候,才建立连接的,因此传送的message就是一个user对象
                //我们知道这里返回的一定是一个Message对象,因此我们直接强转
                Message o = (Message)objectInputStream.readObject();
                //判断类型,然后做相应的业务处理
                //Message里面对应的
                if(o.getMesType().equals(MessageType.MESSAGE_GET_ONLINE_FRIEND)){
                    System.out.println(o.getSender()+"在线用户列表");
                    String onlineuser=ManageServerThreac.getOnlineUser();
                    //构建一个Message返回给客户端
                    Message message=new Message();
                    message.setMesType(MessageType.MESSAGE_RET_ONLINE_FRIEND);
                    message.setContent(onlineuser);
                    message.setGetter(o.getSender());
                    //返回给客户端
                    ObjectOutputStream objectOutputStream = new ObjectOutputStream(socket.getOutputStream());
                    objectOutputStream.writeObject(message);
                }else {
                    System.out.println("其他类型,暂时不处理!");
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    public Socket getSocket() {
        return socket;
    }
}

and

package com.LiveChat.StartServer;

import com.LiveChat.QQServer.ChatsystemServer;

import java.io.IOException;

public class Start {
    public static void main(String[] args) throws IOException, ClassNotFoundException {
        new ChatsystemServer();
    }
}

END

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Keyle777

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值