B:缓冲区设置太小的问题

package com.suns.socket;

import java.io.IOException;
import .InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.*;
import java.nio.charset.Charset;
import java.util.Iterator;

public class MyServer2 {
    public static void main(String[] args) throws IOException {
        ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
        serverSocketChannel.bind(new InetSocketAddress(8000));
        serverSocketChannel.configureBlocking(false);//Selector 只有在非阻塞的情况下 才可以使用。

        //引入监管者
        Selector selector = Selector.open();//1. 工厂,2. 单例

        //监管者 管理谁? selector.xxxx(ssc); //管理者 ssc  ---> Accept
        SelectionKey selectionKey = serverSocketChannel.register(selector, 0, null);
        // selector监控 SSC ACCEPT
        // selector
        //   keys --> HashSet
        //  register注册 ssc
        selectionKey.interestOps(SelectionKey.OP_ACCEPT);
        System.out.println("MyServler2.main");
        //监控
        while (true) {
            selector.select();//等待.只有监控到了 有实际的连接 或者 读写操作 ,才会处理。
            //对应的 有ACCEPT状态的SSC 和 READ WRITE状态的 SC 存起来
            // SelectionsKeys HashSet

            System.out.println("-------------------------");

            Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
            while (iterator.hasNext()) {//ServerSocketChannel ScoketChannel
                SelectionKey key = iterator.next();
                //用完之后 就要把他从SelectedKeys集合中删除掉。问题? ServerScoketChannel---SelectedKeys删除 ,后续 SSC建立新的连接?
                iterator.remove();

                if (key.isAcceptable()) {
                    //serverSocketChannel.accept();
                    ServerSocketChannel channel = (ServerSocketChannel) key.channel();
                    SocketChannel sc = channel.accept();
                    sc.configureBlocking(false);
                    //监控sc状态 ---> keys
                    SelectionKey sckey = sc.register(selector, 0, null);
                    sckey.interestOps(SelectionKey.OP_READ);
                } else if (key.isReadable()) {
                    try {
                        SocketChannel sc = (SocketChannel) key.channel();
                        ByteBuffer buffer = ByteBuffer.allocate(5);
                        int read = sc.read(buffer);
                        if (read == -1) {
                            key.cancel();
                        } else {
                            buffer.flip();
                            System.out.println("Charset.defaultCharset().decode(buffer).toString() = " + Charset.defaultCharset().decode(buffer).toString());
                        }
                    } catch (IOException e) {
                        e.printStackTrace();
                        key.cancel();
                    }
                }
            }

        }


    }
}
package com.suns.socket;

import java.io.IOException;
import .InetSocketAddress;
import java.nio.channels.SocketChannel;
import java.nio.charset.Charset;

public class MyClient {

    public static void main(String[] args) throws IOException {
        //连接服务端  端口号?
        SocketChannel socketChannel = SocketChannel.open();
        socketChannel.connect(new InetSocketAddress(8000));
        //        socketChannel.write(Charset.defaultCharset().encode("hello\nsuns\n"));
        socketChannel.write(Charset.defaultCharset().encode("hellosuns\n"));
        System.out.println("------------------------------");
    }
}
//ByteBuffer buffer = ByteBuffer.allocate(7);
   //hellosuns\n
    private static void doLineSplit(ByteBuffer buffer) {
        buffer.flip();
        for (int i = 0; i < buffer.limit(); i++) {
            if (buffer.get(i) == '\n') {//hellosu
                int length = i + 1 - buffer.position();
                ByteBuffer target = ByteBuffer.allocate(length);
                for (int j = 0; j < length; j++) {
                    target.put(buffer.get());
                }

                //截取工作完成
                target.flip();
                System.out.println("StandardCharsets.UTF_8.decode(target).toString() = 
                " + StandardCharsets.UTF_8.decode(target).toString());
            }
        }
        buffer.compact();
    }
    //这种情况就是缓冲区太小,导致解析数据的时候,读取到ByteBuffer当中没有任何的\n,
    //导致后续的代码没发执行,compact方法执行了个寂寞。

所以需要有接下来的处理的处理:

doLineSplit(buffer);
      //以为缓冲区 不够了 没压懂 需要扩容了
      if (buffer.position() == buffer.limit()) {
         //1 空间扩大
          ByteBuffer newBuffer = ByteBuffer.allocate(buffer.capacity()*2);
         //2 老的缓冲区的数据 ---> 新的缓冲区
          buffer.flip();
          newBuffer.put(buffer);
          //3 channel -- byteBuffer 绑定 newBuffer
          key.attach(newBuffer);
      }

网络通信过程中采用处理半包粘包的范式,采用的方式是通过特殊字符进行分割\n,同时使用compact压缩
1:channel要和:byteBuffer绑定的问题,保证byte可以复用,而且不会丢失数据
2:bytebuffer容量不够,考虑扩容问题。库容之后,将旧的buyffer拷贝到新的buyffer当中,绑定新的buffer
注意:
1:bytebuffer不够了就要扩容,如果越扩越大,浪费了空间需要考虑缩容问题,毕竟当我们的客户端很大的时候,每一个客户端都会对应一个byteBuffer,如果只考虑扩容,不考虑缩容,那么会浪费计算机内存资源,最终导致内存不够,netty当中会帮我们解决这个问题。
开发当中需要把这个场景都考虑到位的话,会很麻烦
2:扩容问题:
老bytebuffer会考虑到新的bytebuffer当中,这个效率是非常低的,后续我们通过0拷贝的方式进行解决。现在我们的代码基本解决了半包粘包的问题,但是不完美,将来netty当中解决的很完美。
3:解决半包粘包的方式:
1:\n+compact \n区分了不同的信息,–保证了信息的完整性,这种方式需要不断的检索这个\n这个效率是非常低的,这个是一个线性的查找。下边这个好
2:头体分离: 一个信息分成两个部分,一个信息是头部分,记录的是元数据,记录信息的大小,体信息才是真正的数据本身,这个时候,我们结合这个大小。很快的可以处理掉后续的消息内容,这个是应用很广泛的,是很高效的。Http协议本身就是依赖于这种设计,他的头里边有一个Content-Length 基于这个属性,就可以去数据中直接读取到多大的数据。Netty如何解决半包和粘包,一定是采用这两种方式的一种。所有的协议都有头部分,说明这是一个好的设计,后续我们在自定义一种通信方式的时候也要遵循这种方式。

一个较为完美的服务器端代码:

package com.suns.socket;

import java.io.IOException;
import .InetSocketAddress;
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.nio.charset.StandardCharsets;
import java.util.Iterator;
/*
   缓冲区的扩容
     1.  如何判断 缓冲区 是否该扩容
     2.  怎么扩容 。
 */
public class MyServer4 {
    private static void doLineSplit(ByteBuffer buffer) {
        buffer.flip();
        for (int i = 0; i < buffer.limit(); i++) {
            if (buffer.get(i) == '\n') {//hellosu
                int length = i + 1 - buffer.position();
                ByteBuffer target = ByteBuffer.allocate(length);
                for (int j = 0; j < length; j++) {
                    target.put(buffer.get());
                }

                //截取工作完成
                target.flip();
                System.out.println("StandardCharsets.UTF_8.decode(target).toString() = " + StandardCharsets.UTF_8.decode(target).toString());
            }
        }
        buffer.compact();
    }

    public static void main(String[] args) throws IOException {
        ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
        serverSocketChannel.bind(new InetSocketAddress(8000));
        serverSocketChannel.configureBlocking(false);//Selector 只有在非阻塞的情况下 才可以使用。

        //引入监管者
        Selector selector = Selector.open();//1. 工厂,2. 单例

        //监管者 管理谁? selector.xxxx(ssc); //管理者 ssc  ---> Accept
        SelectionKey selectionKey = serverSocketChannel.register(selector, 0, null);
        // selector监控 SSC ACCEPT
        // selector
        //   keys --> HashSet
        //  register注册 ssc
        selectionKey.interestOps(SelectionKey.OP_ACCEPT);

        System.out.println("MyServler2.main");

        //监控
        while (true) {
            selector.select();//等待.只有监控到了 有实际的连接 或者 读写操作 ,才会处理。
            //对应的 有ACCEPT状态的SSC 和 READ WRITE状态的 SC 存起来
            // SelectionsKeys HashSet

            System.out.println("-------------------------");

            Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
            while (iterator.hasNext()) {//ServerSocketChannel ScoketChannel
                SelectionKey key = iterator.next();
                //用完之后 就要把他从SelectedKeys集合中删除掉。问题? ServerScoketChannel---SelectedKeys删除 ,后续 SSC建立新的连接?
                iterator.remove();

                if (key.isAcceptable()) {
                    //serverSocketChannel.accept();
                    ServerSocketChannel channel = (ServerSocketChannel) key.channel();
                    SocketChannel sc = channel.accept();
                    sc.configureBlocking(false);
                    //监控sc状态 ---> keyr
                    ByteBuffer buffer = ByteBuffer.allocate(7);
                    SelectionKey sckey = sc.register(selector, 0, buffer);
                    sckey.interestOps(SelectionKey.OP_READ);
                } else if (key.isReadable()) {
                    try {
                        SocketChannel sc = (SocketChannel) key.channel();    //ByteBuffer ---> s...
                        //ByteBuffer buffer = ByteBuffer.allocate(7);//创建一个ByteBuffer 新的ByteBuffer..
                        //从channel中获得 绑定的那个bytebuffer
                        ByteBuffer buffer = (ByteBuffer) key.attachment();
                        int read = sc.read(buffer);
                        if (read == -1) {
                            key.cancel();
                        } else {
                            /*
                            buffer.flip();
                            System.out.println("Charset.defaultCharset().decode(buffer).toString() = " + Charset.defaultCharset().decode(buffer).toString());
                            */
                            doLineSplit(buffer);
                            //以为缓冲区 不够了 没压懂 需要扩容了
                            if (buffer.position() == buffer.limit()) {
                               //1 空间扩大
                                ByteBuffer newBuffer = ByteBuffer.allocate(buffer.capacity()*2);
                               //2 老的缓冲区的数据 ---> 新的缓冲区
                                buffer.flip();
                                newBuffer.put(buffer);
                                //3 channel -- byteBuffer 绑定 newBuffer 把之前的覆盖掉。
                                key.attach(newBuffer);
                            }
                        }
                    } catch (IOException e) {
                        e.printStackTrace();
                        key.cancel();
                    }
                }
            }

        }
          /*  如何保证 前后2次操作的ByteBuffer是同一个。
              ByteBuffer和谁有关的呢?---> channel 相关
              ByteBuffer和Channel绑定就行了

              怎么绑定 ?  channel.register(selector,0,attament)--> attarment 附件

          *
          * */

    }
}

TCP协议进行通讯的时候的两个问题需要注意:
1:分包的问题
(TCP协议)假如我们需要传递10TB的数据给到服务端,不会一次性直接传递过去,会打成十个包,一个接一个的发送数据,一个包多大呢?1460K实际上有可能比1460小,这个是基于双方的第三次握手的MSS值得较小值确定的。
2:流量控制的问题
我们发包的过程中,我们担心会出现发包的过程中不要太快,但是服务端来不及接收导致丢包的情况。甚至为了控制速率发一些空包。

总结:
当前我们做的是web服务,也就是Server,对于Server来讲有两个很重要的问题
1:如何接收请求的问题?这里我们使用的是NIO,基于SSC的accept()即可
2:数据的读写操作:
分析了Select()方法的特点,只要是数据没处理完,就必须的处理

1):数据没有一次性处理完,频繁调用select();
2):特殊情况,例如client的正常和不正常退出Selectkey.cancel();里边封装的是channel
数据的编解码操作:
1):编码:将String转成Byte数组
2):解码:将buffer当中的数据转成String
对应的就是encode()和decode方法
数据的完整性问题-避免出现半包或者粘包的情况。
TCP协议当中的分包问题:一个包的数据最大是1460B(字节bit)
TCP协议当中的流量控制问题:数据发的太快的话,服务端有可能来不及接收,导致丢包的情况。需要控制发包的速度。