netty入门(一)

socket: 套接字就是两台主机逻辑连接的端点。 我们先看一下HTTP以及TCP/IP

HTTP协议作为应用层协议主要解决如何包装数据, TCP/IP协议集处理传输层数据如何传输。而socket是通信的基石, 是支持TCP协议的网络通信的基本操作单元 。它具备了网络传输必须的5种信息, 之前讲过TCP连接前的TCB传输控制块中就有socket信息。

1. 连接使用的协议 2. 源IP 3. 源端口 4. 目的端口 5. 目的IP

我们网络的传输通过不同的IO模型构建的通道,性能肯定是不一样的。BIO NIO AIO

NIO: 同步非阻塞, 实现了一个线程处理多个请求, 中间有一个多路复用器, 所有的请求通道都注册在这个多路复用器上, 而只需要一个线程不断的轮询这些请求即可。并且每个通道即可读也可写。

具体的说: Channel, Buffer, selector

首先每个客服端对应一个Channel, 每个通道都哟一个Buffer, Buffer用来干嘛, 你可以把数据写到Buffer中, 也可以从中读取到数据,双向的数据内存块。除此之外所有的通道注册到selector中, selector对应一个线程, 这个线程可以通过selector轮询所有通道中的事件。 大致如此

一. 理解Buffer:

缓存区: 本质上是一个可以读写数据的内存块, Channel 提供从网络读取数据的渠道,但是读取或者写入必须经过buffer.

说一下Buffer的API, 注意点在, 读取之前我们需要操作flip()切换模式, 想要重复读必须操作remain(), 否则报错, 这个时候想要写入put(), 那么必须操作clear()切换回去, 但是此时数据并没有丢失, 等待被覆盖!

        ByteBuffer buffer = ByteBuffer.allocate(8);
        buffer.put("12345".getBytes()) ;
        System.out.println(buffer.position()); // 这个是position为5
        System.out.println(buffer.limit()); // 设个时候为8


        buffer.flip() ; // 切换读之后 先设置limit=position,  在设置position=0
        System.out.println(buffer.position());
        System.out.println(buffer.limit());
        for (int i = 0; i < buffer.limit(); i++) {
            System.out.println(buffer.get());
        }

        buffer.clear() ; // 切换写之后设置limit=capacity, position=0

        System.out.println(buffer.position());
        System.out.println(buffer.limit());
        System.out.println(buffer.get(1));

二. Channel通道

常用的实现类: FileChannel, DatagramChannel(用于UDP), ServerSocketChannel, socketChannel.

构建服务端通道

public static void main(String[] args) throws IOException, InterruptedException {

        // 1. 打开服务通道
        ServerSocketChannel channel = ServerSocketChannel.open();
        // 2. 绑定端口
        channel.bind(new InetSocketAddress(9999)) ;
        // 3. 通道默认是阻塞的
        channel.configureBlocking(true) ;
        System.out.println("服务启动完成....");

        while(true) {
            // 4. 检查是否由客服端通道
            SocketChannel clientChannal = channel.accept();
            if (clientChannal == null) {
                System.out.println("没有客户端连接...我去做别的事情");
                Thread.sleep(2000); continue;
            }
            //5. 获取客户端传递过来的数据,并把数据放在byteBuffer这个缓冲区中
                // 1. 创建Buffer
            ByteBuffer buffer = ByteBuffer.allocate(1024);
            //返回值:
                // 正数: 表示本次读到的有效字节个数.
                // 0 : 表示本次没有读到有效字节.
                // -1 : 表示读到了末尾
            int len = clientChannal.read(buffer);
            System.out.println("客服端消息: " + new String(buffer.array(), 0, len, StandardCharsets.UTF_8));
            // 因为通道是双向的我们还可以写回
            clientChannal.write(ByteBuffer.wrap("随意即可".getBytes(StandardCharsets.UTF_8))) ;

            clientChannel.close() ;
        }

    }

构建客服端通道

public static void main(String[] args) throws IOException {
        // 1. 打开通道
        SocketChannel clientChannel = SocketChannel.open();
        // 2. 设置IP以及端口
        clientChannel.connect(new InetSocketAddress("127.0.0.1", 9999)) ;
        // 3. 写出数据

        clientChannel.write(ByteBuffer.wrap("我来借点钱".getBytes(StandardCharsets.UTF_8)));

        ByteBuffer buffer = ByteBuffer.allocate(1024);
        int len = clientChannel.read(buffer);
        System.out.println("服务端返回数据: " + new String(buffer.array(), 0, len));
        clientChannel.close();
    }

三. selector选择器

之前讲到, 选择器, 只有当通道发生读写事件后, 才会进行读写, 大大减少了系统开销, 不用维护多个线程, 避免多线程之间的上下文切换导致的开销。

Selector.open()   得到一个选择器

Selector.select()  监控所有通道, 当发生事件时, 把selectionKey放入集合,并返回事件数量

Selector.selectKeys(): // 返回存在的selectKeys集合

SelectKey :   

        定义了四种状态:

1. 接收连接就绪事件 OP_ACCEPT   表示服务端已监听到客服端连接请求, 服务端可以接收连接

2. 连接就绪事件 OP_CONNET    表示已经连接成功

3. 读就绪事件OP_READ     表示已经有了可读的数据

4.  写就绪事件OP_WRITE    表示已经向通道写了数据

SelectionKey.isAcceptable(): 是否是连接继续事件
SelectionKey.isConnectable(): 是否是连接就绪事件
SelectionKey.isReadable(): 是否是读就绪事件
SelectionKey.isWritable(): 是否是写就绪事件
         public static void main(String[] args) throws IOException {

        // 1. 打开一个服务端通道
        ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
        // 2. 绑定对应的端口号
        serverSocketChannel.bind(new InetSocketAddress(9999)) ;
        // 3. 通道默认是阻塞的,需要设置为非阻塞
        serverSocketChannel.configureBlocking(false) ;
        // 4.  创建选择器
        Selector selector = Selector.open();
        // 5. 将服务端通道注册到选择器上,并指定注册监听的事件为OP_ACCEPT
        serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT) ;
        System.out.println("服务端启动成功...");
        while(true) {

            // 6. 检查选择器是否有事件
            int i = selector.select(2000);
            if(i == 0) {
                continue;
            }
            //7. 获取事件集合
            Set<SelectionKey> keys = selector.selectedKeys();
            Iterator<SelectionKey> iterator = keys.iterator();
            while (iterator.hasNext()) {
                SelectionKey key = iterator.next();
                //8. 判断事件是否是客户端连接事件SelectionKey.isAcceptable()
                if(key.isAcceptable()){
                    //9. 得到客户端通道,并将通道注册到选择器上, 并指定监听事件为OP_READ
                    SocketChannel clientChannel = serverSocketChannel.accept() ;
                    System.out.println("客户端已连接......" + clientChannel);
                    //必须设置通道为非阻塞, 因为selector需要轮询监听每个通道的事件
                    clientChannel.configureBlocking(false) ;
                    clientChannel.register(selector, SelectionKey.OP_READ) ;

                }
                //10. 判断是否是客户端读就绪事件SelectionKey.isReadable()
                if(key.isReadable()) {
                    //11.得到客户端通道,读取数据到缓冲区
                    SocketChannel channel = (SocketChannel) key.channel();
                    ByteBuffer buffer = ByteBuffer.allocate(1024);
                    int read = channel.read(buffer);
                    if(read > 0) {
                        System.out.println("客服端传来消息: " + new String(buffer.array(),
                                0, read, StandardCharsets.UTF_8));
                    }
                    //12.给客户端回写数据
                    channel.write(ByteBuffer.wrap("我已经接收到了...".getBytes(StandardCharsets.UTF_8))) ;
                    channel.close();
                }
                //13.从集合中删除对应的事件, 因为防止二次处理. iterator.remove();
                iterator.remove();

            }


        }



    }

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值