多用户 实时通讯

多用户 实时通讯

项目介绍与技术

socket+多线程

利用socket进行服务端和客户端之间的通信,建立通信之后,使用ObjectOutputStream和ObjectInputStream进行流传输。
实现了,用户登录校验,查询在线用户,用户私聊,消息群发,服务器向用户推送消息,文件发送(我没做)功能。
因为没有用到数据库,所以数据存在内存的hashmap中。
socket可以看作是线程的一个属性。socket连通,然后开启线程。

学习加上实际动手写代码测试,花了两天。

登录模块

实现:客户端连接到本地的9999端口,服务端对9999端口进行监听。
连接成功建立之后,客户端向服务端发送User消息,包含用户的账号密码;
服务端监听到客户端发来的内容,通过ObjectInputStream读取,转换,然后进行后台数据校验,如果通过,说明登录成功,使用ObjectOutputStream给客户端返回Message,开启服务端连接客户端的线程,并且将成功登录的客户端信息存到hashMap中。
客户端向服务端发送用户的账号密码,得到服务端返回的Message,解析后,如果登录成功,创建一个保持通信的线程,开启线程,将线程放入hashmap管理。

在线用户查询

客户端向服务器请求,查询在线用户信息,根据客户端id,获取通信线程,得到socket,得到oos,将消息发给服务端。

服务端收到,解析消息类型,通过hashmap得到在线用户数据,封装成message对象返回给客户端。

用户私聊

用户a发起聊天,将消息以及getter信息发到服务端,服务端执行转发操作。
用户b收到来自服务端的消息,将消息显示到控制台即可。

消息群发

用户a向服务器发,之后服务器将消息转给除了a之外的所有用户。
用户收到类型为MESSAGE_TO_ALL_MES

服务器消息推送

服务器单独开一个线程,执行将消息发送给所有客户端用户。

部分代码

服务端

public class QQServer {

    private ServerSocket ss=null;


    //创建一个集合,存放多个用户,如果是这些用户,就认为是合法的
    private static HashMap<String,User> validUsers=new HashMap<>();

    static {
        validUsers.put("100",new User("100","123456"));
        validUsers.put("300",new User("300","123456"));
        validUsers.put("200",new User("200","123456"));
        validUsers.put("至尊宝",new User("至尊宝","123456"));
        validUsers.put("紫霞仙子",new User("紫霞仙子","123456"));
        validUsers.put("菩提老祖",new User("菩提老祖","123456"));
    }

    //验证用户是否有效的方法
    private boolean checkUser(String userId,String passwd){

        User user = validUsers.get(userId);
        if(user==null){
            return false;
        }
        if(!user.getPasswd().equals(passwd)){
            return false;
        }
        return true;
    }

    public QQServer(){
        //端口可以写在配置文件
        try {
            System.out.println("服务端在9999端口监听...");
            //启动推送新闻的线程
            new Thread(new SendNewsToAllService()).start();
            ss=new ServerSocket(9999);
            while (true){//当和某个客户端建立连接后,会继续监听。
                Socket socket = ss.accept();
                ObjectOutputStream oos=new ObjectOutputStream(socket.getOutputStream());
                ObjectInputStream ois = new ObjectInputStream(socket.getInputStream());
                User u=(User)ois.readObject();
                //创建一个message对象,回复给客户端
                Message message=new Message();
                //验证
                if(checkUser(u.getUserId(),u.getPasswd())){
                    //登录成功
                    message.setMesType(MessageType.MESSAGE_LOGIN_SUCCEED);
                    //将message对象回复
                    oos.writeObject(message);
                    //创建一个线程,和客户端保持通讯,该线程需要持有socket对象
                    ServerConnectClientThread serverConnectClientThread = new ServerConnectClientThread(socket, u.getUserId());
                    serverConnectClientThread.start();
                    //把线程对象放入集合中管理
                    ManageClientThreads.addClientThread(u.getUserId(),serverConnectClientThread);
                }else{
                    System.out.println("用户id="+u.getUserId()+"pwd="+u.getPasswd()+"登录失败");
                    message.setMesType(MessageType.MESSAGE_LOGIN_FAIL);
                    oos.writeObject(message);
                    socket.close();
                }
            }

        } catch (Exception e) {
            e.printStackTrace();
        }finally {
            //如果服务端退出了while循环,服务器端不再监听。需要关闭serverSocket
            try {
                ss.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

服务端连接客户端线程

public class ServerConnectClientThread extends Thread{

    private Socket socket;
    private String userId;

    public ServerConnectClientThread(Socket socket, String userId) {
        this.socket = socket;
        this.userId = userId;
    }

    public Socket getSocket(){
        return socket;
    }

    @Override
    public void run() {//线程处于运行状态,可以发送或接收消息
        while(true) {
            System.out.println("服务端和客户端" + userId + "保持通讯,读取数据...");

            try {
                ObjectInputStream ois = new ObjectInputStream(socket.getInputStream());
                //根据message的类型,做相关业务处理
                Message message=(Message) ois.readObject();
                if(message.getMesType().equals(MessageType.MESSAGE_GET_ONLINE_FRIEND)){
                    //客户端要在线用户列表
                    System.out.println(message.getSender()+"要在线用户列表");
                    String onlineUser = ManageClientThreads.getOnlineUser();
                    //构建一个message对象
                    Message message2 = new Message();
                    message2.setMesType(MessageType.MESSAGE_RET_ONLINE_FRIEND);
                    message2.setContent(onlineUser);
                    message2.setGetter(message.getSender());
                    //写到数据通道,返回给客户端
                    ObjectOutputStream oos = new ObjectOutputStream(socket.getOutputStream());
                    oos.writeObject(message2);
                }else if (message.getMesType().equals(MessageType.MESSAGE_CLIENT_EXIT)){
                    System.out.println( message.getSender()+"退出" );
                    //将这个客户端对应线程从集合中删除
                    ManageClientThreads.removeServerConnectClientThread(message.getSender());
                    socket.close();//关闭连接
                    //退出while循环
                    break;
                }else if(message.getMesType().equals(MessageType.MESSAGE_COMM_MES)){
                    //服务器端转发
                    ServerConnectClientThread serverConnectClientThread = ManageClientThreads.getServerConnectClientThread(message.getGetter());
                    //得到对应socket的对象输入流,将message对象转发给指定客户端
                    ObjectOutputStream oos = new ObjectOutputStream(serverConnectClientThread.getSocket().getOutputStream());
                    oos.writeObject(message);//转发,如果客户不在线,可以保存到数据库,可以实现离线留言。
                }else if(message.getMesType().equals(MessageType.MESSAGE_TO_ALL_MES)){
                    //服务器转发到除了发送者之外的用户
                    //遍历管理线程的集合,把所有线程的socket得到,把message进行转发
                    HashMap<String, ServerConnectClientThread> hm = ManageClientThreads.getHm();
                    Iterator<String> iterator = hm.keySet().iterator();

                    while(iterator.hasNext()){
                        //取出在线客户
                        String onLineUserId = iterator.next().toString();
                        if(!onLineUserId.equals(message.getSender())){//排除发送人
                            OutputStream os = hm.get(onLineUserId).getSocket().getOutputStream();
                            //得到对应socket的对象输入流,将message对象转发给指定客户端
                            ObjectOutputStream oos = new ObjectOutputStream(os);
                            oos.writeObject(message);
                        }
                    }
                }
                else{
                    System.out.println("其它类型的message,暂时不处理");
                }

            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
}

客户端连接服务端线程

public class ClientConnectServerThread extends Thread{

    //该线程必须持有socket
    private Socket socket;

    //构造器可以接受一个socket对象
    public ClientConnectServerThread(Socket socket){
        this.socket=socket;
    }

    @Override
    public void run() {
        //线程需要在后台和服务器通讯,因此做成无限循环
        while (true){

            System.out.println("客户端线程,等待读取从服务器端发送的消息");
            try {
                ObjectInputStream ois = new ObjectInputStream(socket.getInputStream());
                //如果服务器端没有消息发送过来,会阻塞在这里。
                Message message = (Message) ois.readObject();
                if(message.getMesType().equals(MessageType.MESSAGE_RET_ONLINE_FRIEND)){
                    //取出在线列表信息并显示
                    String[] onlineUsers = message.getContent().split(" ");
                    System.out.println("=====当前在线用户列表=====");
                    for(int i=0;i<onlineUsers.length;i++){
                        System.out.println("用户:"+onlineUsers[i]);
                    }
                }else if(message.getMesType().equals(MessageType.MESSAGE_COMM_MES)){
                    //把从服务器转发的消息,转发到控制台
                    System.out.println("\n"+message.getSender()+"对"+message.getGetter()+"说"+message.getContent());
                }else if(message.getMesType().equals(MessageType.MESSAGE_TO_ALL_MES)){
                    System.out.println("\n"+message.getSender()+"对大家说"+message.getContent());
                } else{
                    System.out.println("是其他类型的message,暂时不处理");
                }

            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    //提供get方法
    public Socket getSocket(){
        return socket;
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值