NIO应用-网络编程-学习孙哥&不良人课程,整理学习笔记文章 【https://space.bilibili.com/284638819】

97 阅读4分钟

网路编程

  1. 服务端 接受请求 ---> ServerScoketChannel

  2. 进行实际通信 ---> ScoketChannel

往服务器写操作

Seletor循环监听事件的方式 解决死循环空转的问题。
  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();
                    }
                }
            }

        }


    }
}
结论:

当client连接服务器,发起操作后,服务器必须全部处理完成整个交互才算结束,如果没有处理完成,select方法就会一直被调用

案例:给ByteBuffer 分配6个字节,但一次传了8个字节的字符过来

1.  刚刚那个示例,数据没有处理完前slect方法就会被调用多次
2.  在某些特殊的操作下,服务器无法处理,那么select方法就会被频繁调用
解决粘包和半包问题:
1. 1. \n 作为分割符,进行行的区分。compact进行处理,把第一次没有读取完的数据,向前移动和后面的内容进行整合。(存在问题,效率低)
2. 把要读取的数据大小等信息存入请求头中(比如HTTP)

解决缓冲区扩容问题:
如何出现? 当读取的一组数据中没有读到\n,下次再进行就会到处这个部分数据读不到,因为缓存区的大小不够

何时扩容?
* 1. 如何判断 缓冲区 是否该扩容  
    当缓存区不够了  压不动,需要扩容 也就是buffer.position()==buffer.limit()
* 2. 怎么扩容
ByteBuffer newBuffer=ByteBuffer.allocate(buffer.capacity()*2);  
buffer.flip();  
newBuffer.put(buffer);

同时要保证两次buffer要一样,把channel ----byteBuffer 绑定

代码如下:

/**  
* 缓冲区的扩容  
* 1. 如何判断 缓冲区 是否该扩容  
* 2. 怎么扩容  
*/  
public class Myserver4 {  
public static void doLineSplit(ByteBuffer buffer, SelectionKey key) {  
buffer.flip();  
for (int i = 0; i < buffer.limit(); i++) {  
if(buffer.get(i)=='\n'){  
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();  
//缓冲区 不够了 没压动,需要扩容  
if(buffer.position()==buffer.limit()){}{  
ByteBuffer newBuffer=ByteBuffer.allocate(buffer.capacity()*2);  
buffer.flip();  
newBuffer.put(buffer);  
//channel ----byteBuffer 绑定  
key.attach(newBuffer);  
}  
}  
public static void main(String[] args) throws IOException {  
  
ServerSocketChannel serverSocketChannel=ServerSocketChannel.open();  
//设置ServerSocketChannel非阻塞  
serverSocketChannel.configureBlocking(false);//Selector 只有再非阻塞的情况下 才可以使用。  
serverSocketChannel.bind(new InetSocketAddress(8000));  
  
//引入监控着  
Selector selector=Selector.open();//1. 工厂 2. 单例  
//监管者管理谁?  
SelectionKey selectionKey = serverSocketChannel.register(selector, 0, null);  
//selector监控 SSC ACCEPT  
//在selector 中  
// keys --->HashSet  
// register 注册 ssc  
selectionKey.interestOps(SelectionKey.OP_ACCEPT);  
System.out.println("Myserver2.main");  
//监控  
while (true){  
selector.select();//等待 ACCEPT ,对于的有ACCEPT的状态SSC和READ WRITE状态的SC存起来  
//SelectionsKeys HashSet  
System.out.println("------------------------");  
Set<SelectionKey> selectionKeys = selector.selectedKeys();  
Iterator<SelectionKey> iterator = selectionKeys.iterator();  
while (iterator.hasNext()){  
SelectionKey key = iterator.next();  
iterator.remove();  
if(key.isAcceptable()){  
ServerSocketChannel channel = (ServerSocketChannel) key.channel();  
SocketChannel socketChannel = channel.accept();  
socketChannel.configureBlocking(false);  
//监控SocketChannel状态---->key  
ByteBuffer buffer=ByteBuffer.allocate(7);  
SelectionKey sckey = socketChannel.register(selector, 0, buffer);  
sckey.interestOps(SelectionKey.OP_READ);  
}else if(key.isReadable()){  
try {  
SocketChannel channel = (SocketChannel) key.channel();  
//从channel中获得它绑定的bytebuffer  
ByteBuffer buffer = (ByteBuffer) key.attachment();  
// ByteBuffer buffer=ByteBuffer.allocate(20);  
int read = channel.read(buffer);  
if(read==-1){  
key.cancel();  
channel.close();  
}else {  
doLineSplit(buffer,key);  
  
}  
} catch (IOException e) {  
e.printStackTrace();  
key.cancel();  
}  
}   
}  
}  
}  
}

iterator.remove()
把用过的SelectionKey从SeletionKeys集合中剔除
selectionKey.cancle();
把一些无法实际解决的内容,通过cancle()来取消。避免每一次都被select()获取到从新进行循环过程。
读取数据
1. 通过附件的形式,把byteBuffer和channel进行了绑定,从而可以多次处理数据。
2. ByteBuffer的扩容。
数据的写出 
1. 第一个问题 写一次数据,当发现数据没有写完,设置WRITE监听状态。
2. 每一次发生Write的状态,都把剩下的数据写出去。

往服务器写操作

**  
* 这个代码 演示服务器 写操作  
*/  
public class Myserver5 {  
public static void main(String[] args) throws IOException {  
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();  
serverSocketChannel.configureBlocking(false);  
serverSocketChannel.bind(new InetSocketAddress(8000));  
  
Selector selector=Selector.open();  
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);  
  
while (true){  
selector.select();  
Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();  
while (iterator.hasNext()){  
SelectionKey sscKeys = iterator.next();  
iterator.remove();  
  
if(sscKeys.isAcceptable()){  
SocketChannel sc = serverSocketChannel.accept();  
sc.configureBlocking(false);  
SelectionKey scKey = sc.register(selector, SelectionKey.OP_READ);  
//准备数据  
StringBuilder sb=new StringBuilder();  
for (int i = 0; i < 20000000; i++) {  
sb.append("s");  
}  
//NIO Buffer 存储数据 channel写  
ByteBuffer buffer = Charset.defaultCharset().encode(sb.toString());  
int write = sc.write(buffer);  
System.out.println("write"+write);  
if(buffer.hasRemaining()){  
//为当前的socketChannel增加write监听  
scKey.interestOps(scKey.interestOps()+SelectionKey.OP_WRITE);  
scKey.attach(buffer);  
}  
  
  
} else if(sscKeys.isWritable()){  
SocketChannel sc = (SocketChannel) sscKeys.channel();  
ByteBuffer buffer = (ByteBuffer) sscKeys.attachment();  
int write = sc.write(buffer);  
System.out.println("write"+write);  
if(!buffer.hasRemaining()){  
sscKeys.attach(null);  
sscKeys.interestOps(sscKeys.interestOps()-SelectionKey.OP_WRITE);  
}  
  
}  
  
}  
}  
}  
}