JAVA--多路复用实现多用户的一对一聊天功能-实现

需求:
        实现一个服务器,可多用户登录,用户可知道其他在线用户并与之一对一聊天,也可结束当前聊天和别的用户聊天。

实现:
        1:使用ServerSocketChannel多路复用来做服务器,客户端连接注册时,用<用户ID,socketChannel>的键值对集合保存客户端的连接socketChannel对象。

        2:客户端可通过查询服务端的键值对集合来获取所有在线用户信息。

        3:客户端A选定用户B进行一对一聊天,发送聊天信息(聊天信息中包含聊天对象B的用户ID)。

        4:服务端通过解析客户端A发送的报文,即可知道A发送给B的信息,通过键值对集合获取B的socketChannel对象,调用写方法,即可把信息发送至B客户端。

        5:客户端A想结束与B的聊天,与客户端C聊天,也是与步骤4相同操作

代码:

        多路复用简单例子:

参考我另一篇博客 :https://blog.csdn.net/DGH2430284817/article/details/119064573?

本博客需求实现代码:

下面代码实现的功能都是简单实现,实际项目中各个功能都是要根据实际情况修改的

 服务端:

package com.dgh;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;

/**
 * NIO是面向缓冲区的
 * User: lihaoquan
 */
public class Server {

    private Selector selector;
    /**
     * 值得注意的是 Buffer 及其子类都不是线程安全的。
     */
    private ByteBuffer readBuffer = ByteBuffer.allocate(1024);//设置缓冲区大小

    /**
     * 保存登陆客户端socketChannel对象集合。
     */
    private Map<String , SocketChannel> clientSocketList = new HashMap<>();

    private SocketChannel socketChannel ;
    /**
     * 启动方法
     * @throws IOException
     */
    public void start() throws Exception {
        //创建服务端的channel
        ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
        //非阻塞方式
        serverSocketChannel.configureBlocking(false);
        //绑定IP
        serverSocketChannel.socket().bind(new InetSocketAddress(8080));
        //创建选择器
        selector = Selector.open();
        //注册监听事件
        serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);

        while (!Thread.currentThread().isInterrupted()) {
            selector.select();
            Set<SelectionKey> keys = selector.selectedKeys();
            Iterator<SelectionKey> keyIterator = keys.iterator();
            while (keyIterator.hasNext()) {
                SelectionKey key = keyIterator.next();
                if(!key.isValid()) {
                    continue;
                }
                if(key.isAcceptable()) {
                    accept(key);
                }else if(key.isReadable()) {
                    read(key);
                }
                //去除本次keyIterator.next()的对象,但不会对下次遍历有影响
                keyIterator.remove();
            }
        }
    }

    /**
     * 读入
     * @param key
     * @throws IOException
     */
    public void read(SelectionKey key) throws IOException{
        SocketChannel socketChannel = (SocketChannel) key.channel();
        this.socketChannel = socketChannel;
        Socket socket = socketChannel.socket();
        this.readBuffer.clear();
        int numRead;
        try {
            /**
             * 获取客户点的read操作读入数据块数量
             */
            numRead = socketChannel.read(readBuffer);
        }catch (Exception e) {
            key.cancel();
            socketChannel.close();
            return;
        }
        if(numRead > 0) {
            //接收客户端信息
            String recMsg = new String(readBuffer.array() ,0 , numRead );
            System.out.println("收到客户端 "+socket.getInetAddress()+"信息:" +recMsg);
            //返回客户端信息
            String sendMsg = "";
            if (recMsg.startsWith("1:")){ //模拟简易登陆功能,不校验。可在实际项目中自行写逻辑
                String[] recMsgSplit =  recMsg.split("1:");
                //将登陆的用户的socketChannel添加到结合中
                clientSocketList.put(recMsgSplit[1],socketChannel);
                sendMsg = "用户:" + recMsgSplit[1]+" 登陆成功!";
            }else if(recMsg.startsWith("2:")){
                sendMsg = "所有在线用户:" + clientSocketList.keySet();
            }else if(recMsg.startsWith("3:")){
                String[] recMsgSplit =  recMsg.split("3:");
                sendMsg = recMsgSplit[1].trim();
                if (sendMsg.contains("-")){
                    String[] recMsgSplit2 =  recMsgSplit[1].trim().split("-");
                    if (clientSocketList.containsKey(recMsgSplit2[0])){
                        this.socketChannel = clientSocketList.get(recMsgSplit2[0]);
                        sendMsg = recMsgSplit2[1].trim();
                    }else {
                        sendMsg = "用户 " +recMsgSplit2[0] +" 不存在!";
                    }
                }else {
                    sendMsg = "聊天报文解析失败";
                }
            }else {
                sendMsg = "报文解析失败";
            }
            ByteBuffer writeBuffer = ByteBuffer.wrap(sendMsg.getBytes());
            this.socketChannel.write(writeBuffer);
        }else {
            System.out.println("客户端断开连接:" +socket.getInetAddress());
        }
    }

    /**
     * 接收
     * @param key
     * @throws IOException
     */
    public void accept(SelectionKey key) throws IOException {
        ServerSocketChannel serverSocketChannel
                = (ServerSocketChannel) key.channel();
        SocketChannel clientChannel = serverSocketChannel.accept();
        clientChannel.configureBlocking(false);
        clientChannel.register(selector,SelectionKey.OP_READ);
        System.out.println("成功连接客户端:"+ serverSocketChannel.getLocalAddress());
    }

    public static void main(String[] args) throws Exception{
        new Server().start();
    }
}

客户端:

package com.dgh;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.SocketChannel;
import java.util.Iterator;
import java.util.Scanner;
import java.util.Set;

/**
 * User: lihaoquan
 */
public class Client {
    //服务器返回信息
    public static  String receiveMsg ="";
    /**
     * 启动开关
     * @throws IOException
     */
    public void start() throws Exception {
        //通过静态工厂生产客户端的channnel
        SocketChannel socketChannel = SocketChannel.open();
        //设置客户端请求为非阻塞方式
        socketChannel.configureBlocking(false);
        //绑定IP
        socketChannel.connect(new InetSocketAddress(8080));
        //创建选择器
        Selector selector = Selector.open();
        //注册监听事件
        socketChannel.register(selector, SelectionKey.OP_CONNECT);
        //键盘输入
        Scanner scanner = new Scanner(System.in);

        //单独开个线程让客户端输入信息到服务端
        String finalReceiveMsg = receiveMsg;
        new Thread(()->{
            boolean isON = false ; //判断用户是否已登陆
            while (true) {
                try {
                    System.out.println("请选择功能(输入对应数字点击回车)");
                    System.out.println("1:登陆");
                    System.out.println("2:聊天");
                    String message = scanner.nextLine();

                    if ("1".equals(message)){//登陆操作
                        if (!isON){
                            System.out.println("请输入登录的用户ID");
                            isON = true;
                            message = scanner.nextLine();
                            message = "1:" + message;
                        }else {
                            System.out.println("操作失败,您已登陆!");
                            continue;
                        }
                        ByteBuffer writeBuffer = ByteBuffer.wrap(message.getBytes());
                        socketChannel.write(writeBuffer);
                    }else if ("2".equals(message)){
                        message = "2:" ;//获取所有在线用户信息
                        ByteBuffer writeBuffer = ByteBuffer.wrap(message.getBytes());
                        socketChannel.write(writeBuffer);

                        //选择聊天对象
                        System.out.println("请输入想要聊天的用户ID,点击回车!");
                        String chatUser = scanner.nextLine();
                        if ( receiveMsg.contains(chatUser)){//判断输入的用户ID是否存在
                            System.out.println("===========开始与 " + message +" 的聊天(输入exit退出聊天)==================");
                            while (true){
                                message = scanner.nextLine();
                                if ("exit".equals(message)){
                                    //退出聊天
                                    System.out.println("===========退出与 " + chatUser +" 的聊天====================");
                                    break;
                                }
                                message = "3:" +chatUser +"-" +message ;
                                writeBuffer = ByteBuffer.wrap(message.getBytes());
                                socketChannel.write(writeBuffer);
                            }
                        }else{
                            System.out.println("输入错误,用户不存在,请重新输入!"+ receiveMsg + "-");
                            continue;
                        }
                    }else {
                        System.out.println("输入错误,请重新输入!");
                        continue;
                    }

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

        }).start();

        while (true) {
            selector.select();
            Set<SelectionKey> keys = selector.selectedKeys();
            Iterator<SelectionKey> keyIterator = keys.iterator();
            while (keyIterator.hasNext()) {
                SelectionKey key = keyIterator.next();
                keyIterator.remove();
                if(key.isConnectable()) {
                    socketChannel.finishConnect();
                    socketChannel.register(selector,SelectionKey.OP_READ);
                    System.out.println("客户端已经连上服务器端 ");
                    break;
                }else if(key.isReadable()) {
                    SocketChannel clientChannel = (SocketChannel) key.channel();
                    ByteBuffer byteBuffer = ByteBuffer.allocate(48);
                    byteBuffer.clear();
                    long byteRead = clientChannel.read(byteBuffer);
                    if (byteRead == -1){
                        clientChannel.close();
                    }else{
                        byteBuffer.flip();
                          receiveMsg = new String(byteBuffer.array(),0,byteBuffer.limit());
                        //接收来自服务器的消息
                        System.out.println( receiveMsg);
                    }

                }
            }
        }
    }

    public static void main(String[] args) throws Exception{
        new Client().start();
    }
}

启动 服务端和两个客户端:

客户端先后用各自ID登录,然后聊天,具体操作结果如下:

客户端1:

客户端2:

 

服务端:

效果:

        上述代码实现了多人登录和聊天功能,并且是可以和自己聊天的,而且连接是单向的,不需要对方同意也可发送信息,因为所有客户端在注册时对应的socket对象已经保存在服务端,并且都是保持长链接。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值