三、NIO网络编程应用实例—实现群聊系统

一、实例准备

1.1 实例要求

  • 编写一个NIO群聊系统,实现服务器端和客户端之间的数据简单通讯(非阻塞)
  • 实现多人群聊
  • 服务器端;可以监测用户上线,离线,并实现消息转发功能
  • 客户端:通过channel可以无阻塞发送消息给其他所有用户,同时可以接收其他用户发送的消息(有服务器转发得到)
  • 目的:进一步理解NIO非阻塞网络编程机制

1.2 结构图

在这里插入图片描述

二、 实现服务端代码

package com.GroupChat;


import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.*;
import java.util.Iterator;
import java.util.Set;


public class GroupChatServer {
    //定义属性
    private Selector selector;
    private ServerSocketChannel listenChannel;
    private static  final int port=6667;
    
    //构造器  初始化相关配置
    public  GroupChatServer(){
        try{
            //获取选择器 selector
            selector=Selector.open();
            //获取ServerSocketChannel
            listenChannel=ServerSocketChannel.open();
            //绑定端口
            listenChannel.socket().bind(new InetSocketAddress(6667));
            //设置非阻塞模式
            listenChannel.configureBlocking(false);
            //将该listenChannel注册到selector
            listenChannel.register(selector, SelectionKey.OP_ACCEPT);

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

    //监听客户端连接方法
    public  void listen(){
        try {
            while (true){
              //  System.out.println("等待事件触发......");
                int count = selector.select(2000);
                if(count>0){//有事件发生
                    //遍历得到的selectionKey 集合
                    Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
                    while(iterator.hasNext()){
                        //取出selectionKey
                        SelectionKey key=iterator.next();
                        //监听到accept
                        if(key.isAcceptable()){
                            SocketChannel sc = listenChannel.accept();
                            sc.configureBlocking(false);
                            //将sc注册到selector
                            sc.register(selector, SelectionKey.OP_READ);
                            //上线提示
                            System.out.println(sc.getRemoteAddress()+"上线");
                        }
                        if(key.isReadable()){//通道发生read事件,即通道是刻度的事件
                            //处理read 方法处理
                           readData(key);
                        }
                        //当前的key删除,防止重复处理
                        iterator.remove();
                    }
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
        finally {
        }
    }

    //读取客户端消息
    public void readData(SelectionKey key) {
        //取到关联的channel
        SocketChannel channel=null;
        try {
            //得到channel
            channel=(SocketChannel)key.channel();
            //创建buffer
            ByteBuffer buffer = ByteBuffer.allocate(1024);
            int count=channel.read(buffer);
            if(count>0){
                //把缓冲区的数据转成字符串
                String msg=new String(buffer.array());
                //输出消息
                System.out.println("from 客户端:" + msg);
                //向其他客户端转发消息(不包括自己),单独写一个方法进行处理
                sendInfoToOtherClient(msg,channel);
            }
        }catch (IOException e){
            try {
                System.out.println(channel.getRemoteAddress()+" 离线了....");
                key.cancel();//取消通道
                channel.close();//关闭通道
            } catch (IOException ioException) {
                ioException.printStackTrace();
            }
        }
    }

    //转发消息给其他客户(通道)
    private  void sendInfoToOtherClient(String msg,SocketChannel self){
        System.out.println("服务器转发消息中....");
        //遍历 所有注册到selector上的selectionKey,并排除自己
        for (SelectionKey key: selector.keys()){
            //通过key 取出相应的SocketChannel
            Channel targetChannel = key.channel();
            //排除自己
            if(targetChannel instanceof SocketChannel && targetChannel!= self){
                //转型
                SocketChannel dest = (SocketChannel) targetChannel;
                //将msg存储到buffer
                ByteBuffer buffer = ByteBuffer.wrap(msg.getBytes());
                //将buffer的数据写入通道
                try {
                    dest.write(buffer);
                } catch (IOException e) {
                    e.printStackTrace();
                }

            }
        }
    }

    public static void main(String[] args) {
        GroupChatServer groupChatServer = new GroupChatServer();
        groupChatServer.listen();
    }
}

三、客户端代码实现

package com.GroupChat;


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;

public class GroupChatClient {
    //定义相关属性
    private  final  String HOST="127.0.0.1";//服务器的ip
    private  final  int port=6667;//服务器的端口
    private Selector  selector;
    private SocketChannel socketChannel;
    private String username;


    //构造器 完成初始化工作
    public GroupChatClient(){
        //
        try {
            //获取Selector 选择器
            selector = Selector.open();
            //连接服务器
            socketChannel = SocketChannel.open(new InetSocketAddress(HOST, port));
            //设置非阻塞
            socketChannel.configureBlocking(false);
            //将channel 注册到Selector
            socketChannel.register(selector, SelectionKey.OP_READ);
            //得到username
            username=socketChannel.getLocalAddress().toString().substring(1);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    //向服务器发送消息
    public void sendInfo(String info){
        info = username + "说:" + info;
        try {
            socketChannel.write(ByteBuffer.wrap(info.getBytes()));
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    //从服务器端读取消息
    public void readInfo(){
        try {
            int readChannels = selector.select();
            if (readChannels > 0) {//有可用的通道
                Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
                while(iterator.hasNext()){
                    SelectionKey key = iterator.next();
                    if(key.isReadable()){
                        //得到相关的通道
                        SocketChannel sc = (SocketChannel) key.channel();
                        //得到一个buffer
                        ByteBuffer buffer = ByteBuffer.allocate(1024);
                        //读取
                        sc.read(buffer);
                        //把读取到的缓冲区的数据转成字符串
                        String msg = new String(buffer.array());
                        System.out.println(msg.trim());
                    }
                   iterator.remove();//删除当前的selectionKey 防止重处理

                }
            }else{

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

    public static void main(String[] args) {
        //启动客户端
        GroupChatClient chatClient = new GroupChatClient();
        //启动一个线程
        new Thread(){
            public void run(){
                while(true){
                    chatClient.readInfo();
                    try {
                        Thread.currentThread().sleep(1000);
                    }catch (Exception e){

                    }
                }
            }
        }.start();
        //发送数据给服务器端
        Scanner scanner = new Scanner(System.in);
        while(scanner.hasNextLine()){
            String str = scanner.nextLine();
            chatClient.sendInfo(str);
        }
    }
}

四、启动服服端和客户端观察结果

1、同一个类启动多个实例
在这里插入图片描述
在这里插入图片描述
结果
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

💥推荐阅读💥

上一篇:二、 剖析Netty的工作机制之Buffer、Channel、Selector分析

下一篇:四、Netty核心技术之NIO与零拷贝

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

猿小许

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

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

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

打赏作者

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

抵扣说明:

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

余额充值