1 Netty 网络高并发框架

2022-01-19 13:44:23 浏览数 (1)

纯手打,总结!

Netty是什么? Netty是当前非常流行的网络通讯开源框架,高并发和高可靠,底层就可以用Netty支撑。

Netty 官网:https://netty.io/

学习视频:https://www.bilibili.com/video/BV1DJ411m7NR?from=search&seid=17854453460358540907

Netty 特点

异步、基于事件驱动的网络应用架构

快速开发高性能、高可用的 IO 程序

针对 TCP/IP 协议下 面向Client端 高并发应用

本质是NIO框架,适用于服务器通讯的多种场景

什么是同步 和 异步

同步: 浏览器发送请求,等待服务端进行相应后浏览器在做操作 这个过程就叫同步

异步:浏览器发送请求,无需服务器响应出结果,继续浏览器渲染接下来的操作,这个过程就叫异步

Netty 结构图

Netty 是基于 JDK下的NIO 的框架

Netty 场景

RPC框架 :阿里的分布式框架Dubbo用到了

手游、网游服务器

可以理解成PRC框架 都会

I/O模型

I/O模型简单理解:就是用什么样的通道进行数据的发送和接收,很大程度决定程序的性能

Java支持3中网络编程模型:BIO、NIO、AIO

什么是BIO?

特点:同步并阻塞、一个连接一个线程:即客户端有连接请求,服务端就开一个线程,如果客户端连接了服务器不操作,就会造成服务端线程的浪费。阻塞:如果没有读取到,那就一直等待。

什么NIO?

特点:同步非阻塞、一个线程处理多个连接:即客户端发送的连接请求都会转到多路复用器上,进行轮询检查连接状态,进行处理,避免连接闲置。提高服务器资源的不必要开支。

什么是AIO?

特点 NIO.2 异步非阻塞,AIO 引入异步通道概念,采用Proactor,简化程序编写,有效的请求,才会启动线程。现在暂时并没有有效的应用。

I/O模型使用场景

BIO:适用于连接数目少且固定的架构,高并发不适用,是JDK1.4之前唯一选择

NIO:适用于连接数目多,但是连接短。JDK1.4 开始支持。场景有:聊天服务器、弹幕服务器、地图服务器之间的通讯。

AIO:适用于连接数目多,连接长。比如相册服务器(这里不理解)。会利用OS惨与并发操作,编程较为复杂。JDK7开始支持

JAVA BIO 基本介绍

BIO :blocking I/O 同步阻塞 实现类和接口在 java.io

编程的简单流程

  1. 服务器端启动一个ServerSocket。
  2. 客户端启动一个Secork 连接服务器,默认情况下,服务器端需要对每个客户建立一个线程与之通讯。
  3. 客户端发出请求,先咨询服务器是否有线程响应,如果没有则会等待,或者被拒绝。
  4. 如果有相应,客户端线程会等待请求结束后,才继续执行。

BIO 应用实例

实例说明:使用BIO模型编写一个服务器端,使用6666端口,客户端连接时,就启动一个线程与之通讯。

要求使用线程池机制改善,可以连接多个客户端

服务器端可以接收客户端发送的数据(telnet 方式即可)

测试方式 需要安装的内容

代码实现

代码语言:javascript复制
package com.zlk.BIO;

import java.io.IOException;
import java.io.InputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class bioServer {
    public static void main(String[] args) throws IOException {
        /* 线程池机制  思路
        1、创建一个线程池
        2、如果有客户端连接,就创建一个线程,与之通讯
        */

        // 具体实现
        ExecutorService newCachedThreadPool = Executors.newCachedThreadPool();
        //创建ServerSocket
        ServerSocket serverSocket = new ServerSocket(6666);
        System.out.println("服务端已经启动了");

        while (true) {
            // 监听,等待客户端连接
            final Socket socket = serverSocket.accept();
            System.out.println("连接到一个客户端...");
            // 创建一个线程 与之通讯
            newCachedThreadPool.execute(new Runnable() {
                public void run() {
                    // 可以和客户端通讯了
                    handler(socket);
                }
            });
        }
    }

    // handler方法与客户端通讯
    public static void handler(Socket socket) {
        try {
            System.out.println("线程ID:"   Thread.currentThread().getId()   " 线程名称:"   Thread.currentThread().getName());
            byte[] bytes = new byte[1024];
            // 通过socket获取输入流
            InputStream inputStream = socket.getInputStream();
            while (true) {
                System.out.println("等待发送信息...");
                int read = inputStream.read(bytes);
                if (read != -1) {
                    System.out.print("执行Read ... -->");
                    System.out.print("线程ID:"   Thread.currentThread().getId()   " 线程名称:"   Thread.currentThread().getName()   " 输入了:");
                    System.out.println(new String(bytes, 0, read)); //输出客户端发送的数据
                } else {
                    break;
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            System.out.println("关闭 和 cilent的连接");
            try {
                socket.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

测试:

按一下 Ctrl ] 键 ,然后输入命令 send XXXXX 回车 就可以按照一长串输入

控制台结果

Java NIO 编程

JDK 在1.4之后 追加 在java.nio包 及其子包下,并对java.io包很多类进行改写

NIO 三大核心部分

  • Channel(通道)
  • Buffer(缓冲区)
  • Selector(选择器)
具体三者结构逻辑

NIO 是面向缓冲区,或者面向块编程。数据读取到一个 稍后处理的缓冲区,需要时可在缓冲器中前后移动,提高了网络处理过程的灵活性、伸缩性

Http2.0 引入的多路复用技术,做到一个连接处理多个请求,并发请求数量提高很多倍

NIO Buff基本使用

代码语言:javascript复制
import java.nio.IntBuffer;

/*
    举例说明Buffer的使用
 */
public class basicBuffer {
    public static void main(String[] args) {
        // 创建一个Buffer:可以存放5个int
        IntBuffer intBuffer = IntBuffer.allocate(5);
        // 向Buffer 存放数据 有序
        intBuffer.put(10);
        intBuffer.put(11);
        intBuffer.put(12);
        intBuffer.put(13);
        intBuffer.put(14);

        // 从Buffer读取数据
        // 将Buffer转换:读写切换 ,默认是写
        intBuffer.flip();
        while (intBuffer.hasRemaining()){
            System.out.println(intBuffer.get());
        }
    }
}

NIO BIO 对比

BIO 是以流的方式 进行处理数据,NIO以块的方式处理数据。块I/O比流I/O效率高很多。

BIO 是同步阻塞、NIO是同步非阻塞

BIO基于 字节、字符流进行操作、NIO基于 Channel、Buffer 进行操作。

NIO 原理分析

NIO是 数据从Buffer 传输到 Channel 再通过 Selector监听很多个Channel事件,所以 单个线程 就做到了非阻塞

NIO 中 一个 Buffer 对应一个Channel,一个Selector 对应多个 Channel 。一个Selector 对应一个线程,

程序切换到那个 Channel 取决于 事件。

Selector会根据不同 Event事件,在各个Channel切换

Buffer就是一个内存块,底层是一个数组

数据读取、写入都是要通过Buffer,这个和BIO有区别(BIO 无Buffer)

BIO 是单向流,而NIO传输Buffer是双向的,需要手动 filp() 切换。

Channel也是双向的,可以很好的反应底层操作系统情况。例如:Linux 。

Buffer

本质就是一个读写数据的内存块,可以理解成一个容器

我们可以点进 java.nio包下的 Buffer 按一下 Alt H 看到

同时上图 有 4个属性,具体含义如下:

代码语言:javascript复制
    // Invariants: mark <= position <= limit <= capacity
    private int mark = -1;   // 标记
    private int position = 0;//下一个要被 读或写 元素的索引(每次读或写都会变化):作用是 为下一次读或写做准备
    private int limit;      // 缓冲区终点
    private int capacity;   // 容量

先看一下 反转的方法 flip 仔细看

在看一下 clear的 内容

用的最多的子类是 ByteBuffer,因为网络传输是以字节形式传输,下面会详细讲解

通道 Channel

NIO的Channel类似于流 通道与流的区别

通道可以同时写或读,而流是单向的

常见的 通道 方法 Channel(下面方法经常需要复习)

FileChannel 常用方法:

read(ByteBuffer dst) 从通道读取数据放入缓冲 buffer 区

write(ByteBuffer src) 把缓冲区的数据写道通道

transferFrom(ReadableByteChannel src, long position, long count) 从目标通道复制到当前通道

transferTo(long position, long count, WritableByteChannel target) 把数据从当前通道复制给目标通道。

Channel应用实例1 – 写进文件

实例要求:使用ByteBuffer 和 FileChannel 将Hello 世界,写入 file01.txt中,如果文件不存在,就创建。

代码

代码语言:javascript复制
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;

public class NIOFileChannel {
    public static void main(String[] args) throws IOException {
        String str = "hello 世界";

        //创建输出流 包装到Channel
        FileOutputStream fileOutputStream = new FileOutputStream("d:\file01.txt");
        //通过fileOutputStream 整合FileChannel  但是真实的实现是 FileChannelImpl 所有的真实实现都是 XXXImpl
        FileChannel fileChannel = fileOutputStream.getChannel();

        //创建一个缓冲区
        ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
        //数据放入缓存区
        byteBuffer.put(str.getBytes());

        //写完后 开始反转 开始读
        byteBuffer.flip();
        //将byteBuffer 写入到Channel
        fileChannel.write(byteBuffer);
        fileOutputStream.close();
    }
}

结果

Channel应用实例2 – 读取文件

实验实例:读取上个实例

代码语言:javascript复制
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;

public class NIOFileChannel2 {
    public static void main(String[] args) throws IOException {
        // 得到文件对象
        File file = new File("d:\file01.txt");
        // 创建文件输入流
        FileInputStream fileInputStream = new FileInputStream(file);
        // 根据文件输入流创建通道
        FileChannel channel = fileInputStream.getChannel();
        // 获取缓冲区,因为知道文件长度,就没必要设置1024
        ByteBuffer byteBuffer = ByteBuffer.allocate((int) file.length());
        // 通道读取缓冲区数据
        channel.read(byteBuffer);
        // 将byteBuffer的字节数据转为String
        System.out.println(new String(byteBuffer.array()));
        // 释放输入流资源
        fileInputStream.close();
    }
}

结果:

Channel应用实例3 – 复制文件

案例要求: 复制文件内容 放到一个新文件

代码语言:javascript复制
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;

public class NIOFileChannel3 {
    public static void main(String[] args) throws IOException {
        FileInputStream fileInputStream = new FileInputStream("d:\file01.txt");
        FileChannel channelIn = fileInputStream.getChannel();
        FileOutputStream fileOutputStream = new FileOutputStream("d:\file02.txt");
        FileChannel channelOut = fileOutputStream.getChannel();

        ByteBuffer byteBuffer = ByteBuffer.allocate(512);
        // 开始读取数据
        while (true){
            //重置Buffer属性  不加就会死循环
            byteBuffer.clear();
            int read = channelIn.read(byteBuffer);
            System.out.println("read is " read);
            if(read == -1){
                break;
            }else {
                byteBuffer.flip();
                channelOut.write(byteBuffer);
            }
        }
        System.out.println("文件复制成功");
        fileOutputStream.close();
        fileInputStream.close();
    }
}

不加clear 会造成死循环 产生的原因 分析:

read(Buffer) 返回的是读取的行数结果

read 一开始从 position = 0 开始读取 一致读到 limit 此时 position 变为最后读取的最后一位

以后 不论怎么样 read 或 write 都是从 position = limit 只有 flip执行 将 position = 0,才读取第一次的内容,并且写入,会就造成文件一些被写入第一次写入的内容,并且不换行!

正常代码最终结果:

Channel应用实例3 – 通道复制文件

实例要求:使用FileChannel的transferFrom() 完成文件拷贝

代码语言:javascript复制
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.channels.FileChannel;

public class NIOFileChannel4 {
    public static void main(String[] args) throws IOException {
        // 创建流 以及 相关通道
        FileInputStream fileInputStream = new FileInputStream("d:\java.png");
        FileChannel channelIn = fileInputStream.getChannel();
        FileOutputStream fileOutputStream = new FileOutputStream("d:\java2.png");
        FileChannel channelOut = fileOutputStream.getChannel();

        // 使用transferFrom 完成拷贝
        channelOut.transferFrom(channelIn,0,channelIn.size());
        System.out.println("拷贝完成");

        // 释放资源
        channelOut.close();
        channelIn.close();
        fileOutputStream.close();
        fileInputStream.close();
    }
}

结果:

Buffer类型化 与 只读

代码语言:javascript复制
import java.nio.ByteBuffer;

public class ReadOnlyBuffer {
    public static void main(String[] args) {
        //创建BUffer
        ByteBuffer byteBuffer = ByteBuffer.allocate(64);
        //循环添加
        for (int i = 0; i < 64; i  ) {
            // byteBuffer.putInt((byte) i); // 记得 int 转成 byte 他依旧占用4个字节
            byteBuffer.put((byte)i);    // 一开始 就是byte 就是1字节了
        }
        //反转去读取
        byteBuffer.flip();
        //获取只读buffer
        ByteBuffer readOnlyBuffer = byteBuffer.asReadOnlyBuffer();
        //读取
        while (readOnlyBuffer.hasRemaining()) {
            System.out.println(readOnlyBuffer.get());
        }
        // java.nio.ReadOnlyBufferException 一旦设置只读后 就不能在添加数据了    readOnlyBuffer.put((byte) 1);
    }
}

Buffer 分散与聚合

Scattering :将数据写入到Buffer。 可以采用buffer数组,依次写入(一个buffer写满了 写入到第二个buffer)

Gathering :从buffer读取数据时。可以采用buffer数组,依次读取()

代码语言:javascript复制
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.Arrays;

/**
 * Scattering:将数据写入到Buffer。 可以采用buffer数组,依次写入(一个buffer写满了 写入到第二个buffer)
 * Gathering:从buffer读取数据时。可以采用buffer数组,依次读取()
 */
public class ScatteringAndGatheringTest {
    public static void main(String[] args) throws IOException {
        // 使用ServerSocketChannel 和 SockerChannel
        ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();

        InetSocketAddress inetSocketAddress = new InetSocketAddress(7000);

        serverSocketChannel.socket().bind(inetSocketAddress);
        ByteBuffer[] byteBuffers = new ByteBuffer[2];
        // 第一个5 放满了 就会转到 第二个去放
        byteBuffers[0] = ByteBuffer.allocate(5);
        byteBuffers[1] = ByteBuffer.allocate(3);
        System.out.println("服务器已经启动");
        // 等待客户端连接
        SocketChannel socketChannel = serverSocketChannel.accept();
        // 规定只从客户端读取8字节
        int messageLength = 8;
        while (true) {
            int byteRead = 0;
            if (byteRead < messageLength) {
                long read = socketChannel.read(byteBuffers);
                byteRead  = read; // 累计读取的字节数
                System.out.println("本地读取了"   byteRead   "个字节");
                // 使用流打印,看看当前buffer的 position 和 limit
                Arrays.asList(byteBuffers).stream().map(buffer -> "position is "   buffer.position()   ",limit is "   buffer.limit()).forEach(System.out::println);
            }
            //将所有的buffer 进行反转
            Arrays.asList(byteBuffers).forEach(byteBuffer -> {
                byteBuffer.flip();
            });

            //将数据显示到客户端
            long bytewrite = 0;
            while (bytewrite < messageLength){
                long write = socketChannel.write(byteBuffers);
                bytewrite  = write;
            }
            // 将所有的Buffer进行clear 操作
            Arrays.asList(byteBuffers).forEach(byteBuffer -> {byteBuffer.clear();});
            System.out.println("byteRead is " byteRead " ,bytewrite is  " bytewrite " messageLength is " messageLength);
        }


    }
}

测试

Selector

Selector 是个抽象类,常用方法和说明如下:

常用方法

open 得到一个选择器对象

selectorKeys 从内部集合 获取所有sekectorkey

selector(1000) 设置阻塞时间1秒

wakeup() 唤醒selector

selectorNow() 自动返回 不会阻塞

NIO 非阻塞网络编程快速入门

案例要求: 编写一个NIO入门案例,实现服务器端与客户端之间的数据简单通讯(非阻塞)

目的:理解NIO非阻塞网络编程机制

服务端

代码语言:javascript复制
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 NIOServer01 {
    public static void main(String[] args) throws IOException {

        //创建ServerScocketChannle 并获取 ServerSocker
        ServerSocketChannel channel = ServerSocketChannel.open();
        //创建Selector对象
        Selector selector = Selector.open();
        //让 ServerSocker 绑定6666端口,并在服务器端监听
        channel.socket().bind(new InetSocketAddress(6666));
        //ServerSocker 设置为非阻塞
        channel.configureBlocking(false);
        //把ServerSocker注册到 selector关心的事件 用枚举 SelectionKey.XXXX
        channel.register(selector, SelectionKey.OP_ACCEPT);
        //循环等待客户端连接
        while (true){
            if(selector.select(1000) == 0){ //等待1秒
                System.out.println("服务器等待1秒 没有响应");
                continue;
            }
            //如果返回值>0 就去获取关住SelecotrKeys ,以便于便利出 selectorkey获取通道
            Set<SelectionKey> selectionKeys = selector.selectedKeys();
            Iterator<SelectionKey> iterator = selectionKeys.iterator();
            while (iterator.hasNext()){
                SelectionKey key = iterator.next();
                if(key.isAcceptable()){ //如果是OP_ACCEPT ,有新的客户端连接
                    //为客户端生成一个Socket
                    SocketChannel newsocketChannel = channel.accept();
                    //为新客户端设置非阻塞状态
                    newsocketChannel.configureBlocking(false);
                    System.out.println("客户端连接成功 生成一个" newsocketChannel.hashCode());
                    //将新的socket 注册到 Selecotr上
                    newsocketChannel.register(selector,SelectionKey.OP_READ, ByteBuffer.allocate(1024));
                }
                if(key.isReadable()) { //发生如果是 OP_READ
                    //通过key获取channel
                    SocketChannel newchannel2 = (SocketChannel)key.channel();
                    //获取该channel绑定的buffer
                    ByteBuffer byteBuffer = (ByteBuffer)key.attachment();
                    newchannel2.read(byteBuffer);
                    System.out.println("从客户端读取到的数据是" new String(byteBuffer.array()));
                }
                //手动从集合移动当前的SelectionKey ,防止重复操作
                iterator.remove();
            }

        }

    }
}

客户端

代码语言:javascript复制
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;

public class NIOClient {
    public static void main(String[] args) throws IOException {
        //得到一个网络通道
        SocketChannel socketChannel = SocketChannel.open();
        //设置非阻塞
        socketChannel.configureBlocking(false);
        //设置服务器的ip和端口
        InetSocketAddress inetSocketAddress = new InetSocketAddress("127.0.0.1", 6666);
        //连接服务器
        if (!socketChannel.connect(inetSocketAddress)) {
            while (!socketChannel.finishConnect()) {
                System.out.println("连接服务器需要时间,客户端不会阻塞,仍可以做其他工作");
            }
        }
        String str = "Hello 张三";
        //wrap放法是将Byte数组 写入到一个buffer
        ByteBuffer byteBuffer = ByteBuffer.wrap(str.getBytes());
        //发送数据,将buffer数据写入通道channle
        int write = socketChannel.write(byteBuffer);
        System.in.read();
    }
}

在客户端编码设置一下允许并行运行 并应用

最终结果:

Netty 群聊系统

要求:

编写一个NIO群聊系统,实现服务器与客户端之间的数据简单通讯,

实现多人群聊

服务端检测用户上线、离线,并实现消息转发功能

客户端,通过Channle 无阻塞发送消息给其他用户,同时可以接收其他用户发送的消息(由服务器转发)

先编写服务器端

  • 服务器启动并监听6667
  • 服务器接收客户端消息,实现转发,上下线等
代码语言:javascript复制
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 chatServer {
    //1 定义属性
    private Selector selector;
    private ServerSocketChannel listenChannle;
    private static final int PORT = 6667;

    //2 无参构造 对属性进行赋值
    public chatServer() {
        try {
            //创建选择器
            selector = Selector.open();
            //创建通道
            listenChannle = ServerSocketChannel.open();
            //通道道绑创建的Socket 定端口
            listenChannle.socket().bind(new InetSocketAddress(PORT));
            //设置非阻塞
            listenChannle.configureBlocking(false);
            //将通道注册到Selector 监听事件
            listenChannle.register(selector, SelectionKey.OP_ACCEPT);

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

    }

    public void listen() {
        try {
            while (true) {
                // 如果count > 0 说明有事件
                int count = selector.select(2000);
                if (count > 0) {
                    Set<SelectionKey> selectionKeys = selector.selectedKeys();
                    Iterator<SelectionKey> iterator = selectionKeys.iterator();
                    while (iterator.hasNext()) {
                        SelectionKey key = iterator.next();
                        // 通道发生连接事件
                        if (key.isAcceptable()) {
                            SocketChannel sc = listenChannle.accept();
                            sc.configureBlocking(false);
                            //将改sc 注册到 selector
                            sc.register(selector, SelectionKey.OP_READ);
                            System.out.println(sc.getRemoteAddress()   " 已经上线了");
                        }
                        // 通道发生Read事件
                        if (key.isReadable()) {
                            //处理读 专门写方法
                            readData(key);
                        }
                        // 将当前的key删除,防止重复处理
                        iterator.remove();
                    }
                } else {
                    System.out.println("等待中...");
                }
            }
        } catch (
                Exception e) {
            e.printStackTrace();
        } finally {
        }
    }

    // 读取客户端消息 传递 selectionKey的原因是 通过key 获取事件发生的通道
    private void readData(SelectionKey selectionKey) {
        //定义一个SocketChannel
        SocketChannel socketChannel = null;
        try {
            socketChannel = (SocketChannel) selectionKey.channel();
            socketChannel.configureBlocking(false);
            ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
            int count = socketChannel.read(byteBuffer);
            //根据count的值做处理
            if (count > 0) {
                //把缓冲区的数据转成String
                String s = new String(byteBuffer.array());
                System.out.println("客户端消息是:"   s);
                //向其他客户端发送消息 专门写一个方法处理
                sendInfoToOtherClients(s, socketChannel);
            }

        } catch (IOException e) {
            //e.printStackTrace();
            try {
                System.out.println(socketChannel.getRemoteAddress()   "离线了");
                // 关闭通道
                socketChannel.close();
            } catch (IOException e2) {
                e2.printStackTrace();
            }
        }
    }

    // 转发消息给其他客户(通道) 参数 msg是消息
    private void sendInfoToOtherClients(String msg, SocketChannel self) throws IOException {
        System.out.println("服务器转发消息中");
        // 获取所有注册到Selector的所有通道 并派出self
        for (SelectionKey key : selector.keys()) {
            //通过 key去除对应的通道
            Channel targetchannel = key.channel();
            //排除自己
            if (targetchannel instanceof SocketChannel && targetchannel != self) {
                // 转型
                SocketChannel dest = (SocketChannel) targetchannel;
                dest.configureBlocking(false);
                // 将msg存储到buffer
                ByteBuffer buffer = ByteBuffer.wrap(msg.getBytes());
                // 将buffer写入通道
                dest.write(buffer);
            }
        }
    }

    public static void main(String[] args) {
        //创建一个服务器对象
        chatServer chatServer = new chatServer();
        //开始监听
        chatServer.listen();
    }
}

再编写客户端

代码语言:javascript复制
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;

public class chatClient {
    // 定义一下全局变量
    private static final String Host = "127.0.0.1";
    private static final int PORT = 6667;
    private Selector selector;
    private SocketChannel socketChannel;
    private String username;

    // 构造器赋值
    public chatClient() {
        try {
            selector = selector.open();
            socketChannel = SocketChannel.open(new InetSocketAddress(Host, PORT));
            // 设置非阻塞
            socketChannel.configureBlocking(false);
            // 将通道注册到服务器
            socketChannel.register(selector, SelectionKey.OP_READ);
            username = socketChannel.getRemoteAddress().toString().substring(1);
            System.out.println("客户端准备好了");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

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

    //从服务器端读取数据
    public void readInfo() {
        try {
            int readChannle = selector.select();
            // 通道有事件发生了
            if (readChannle > 0) {
                Set<SelectionKey> selectionKeys = selector.selectedKeys();
                Iterator<SelectionKey> iterator = selectionKeys.iterator();
                while (iterator.hasNext()) {
                    SelectionKey key = iterator.next();
                    if (key.isReadable()) {
                        SocketChannel sc = (SocketChannel) key.channel();
                        //设置非阻塞
                        sc.configureBlocking(false);
                        ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
                        // 将通道数据写入buffer
                        sc.read(byteBuffer);
                        //将缓冲区的数据 转成字符串
                        String s = new String(byteBuffer.array());
                        System.out.println(s.trim());
                    }else {
                        System.out.println("没有可以读取消息的通道");
                    }
                    iterator.remove();
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) {
        // 启动客户端
        chatClient chatClient = new chatClient();
        // 开启一个新线程 读取客户端 发送、转发的消息
        new Thread(){
            public void run(){
                while (true){
                    chatClient.readInfo();
                    try {
                        // 不知道为什么这里没有代码提示
                        Thread.currentThread().sleep(3000);
                    }catch (Exception e){
                        e.printStackTrace();
                    }
                }
            }
        }.start();

        // 发送数据 给客户端
        Scanner scanner = new Scanner(System.in);
        while (scanner.hasNext()){
            String s = scanner.nextLine();
            // 将控制台结果发送给服务器
            chatClient.sendInfo(s);
        }
    }
}

本片文章过长,后续内容请查看 Netty2 https://www.zanglikun.com/3840.html

特殊说明: 解决问题的光鲜,藏着磕Bug的痛苦。 万物皆入轮回,谁也躲不掉! 以上文章,均是我实际操作,写出来的笔记资料,不会出现全文盗用别人文章!烦请各位,请勿直接盗用!

0 人点赞