NIO总结(二)

先启服务器,后启动客户端,服务器从客户端读数据read,客户端向服务器端写数据write

Channel:通道,与stream不同,Channel双向数通信:java.nio.channels        
ServerSocketChannel SocketChannel,可实现TCP通信用,都可以开启非阻塞式的网络通信(FileChannel)        
TCP协议:可靠传输协议(流式的数据传输),UDP:效率高的传输,无可靠性消耗---TCP,UDP都在传输层有公共协议,HTTP,TCP,POP3,FTP等,也可以自定义私有协议        
ServerSocketChannel:代表服务器通道,无法直接new,构造方法是protect状态通过open()创建ServerSocketChannel对象。        
用static去定义它的对象,可以开启阻塞模式和非阻塞模式(从父类调configblocking(),false为非阻塞模式,默认是阻塞)finishConnect()完成对服务器的连接。一般通过while循环来持续判断是否完成对服务器的连接。        
SocketChannel和ServerSockeChannel一致,也是protect状态,无法直接new        
SocketChannel代表客户端通道,也有两种模式,阻塞模式和非阻塞模式,也是通过open()获取对象。        
通道通过缓冲区来读数据写数据。        
非阻塞也有弊端,发送数据时它不管剩余空间多少也不阻塞一直发,所以导致有的数据发不出去。        
非阻塞模式得自己控制,还得写代码循环控制,所以非阻塞的NIO比较复杂,自己写较多代码    。    
客户端和服务器都可以双向接收数据都可以read和write,TCP协议会把得到的数据封装成固定大小的包来传输,所以TCP协议有可能把想传的数据打装包发出去,TCP协议没有对数据的封装设置边界,所以需要处理。        
粘包:从客户端定义完数据内容和长度,write完数据向服务器端发送后,服务端初始化发送来的数据为1kb,通过判断以回车还行结尾等条件循环出真正传来数据的长度,然后定义出与传来数据一致长度的缓冲区,然后直接读取从客户端传来的数据,从而不浪费空间,而且数据全部读取。也可以私有协议来控制边界符!!按照规定的方式发数据,对面按照规定方式接收数据。
本次例子的私有协议的:约定双方发送数据的方式--最经典做法:头信息发长度,体信息发内容。
粘包问题的产生:TCP的底层是一个包一个包发出去,而且没有定长,无法判断数据边。

Selector:单一线程处理多个客户端,选择器,它是个抽象类,不能直接用,java.nio.Channel
客户端的请求在选择器上注册,记录请求谁先工作谁后工作。                            
具有selectionKey的可以证明被注册的请求,selectionkey是被注册的证据/凭据。                            
select():选择一组键,其相应的通道已被I/O操作准备就绪                            
判断哪个通道准备就绪了,select()方法判断有几个准备就绪了返回int,Selector可以管理多个channel
selectedKeys():先调用select再调selectedKeys,用selectedKeys选择出已就绪的通道。返回此选择器已选的键集。
选择器有三个键集:相当于一个数组
1.已注册(sk1,sk2,sk3),找到所有已注册的通道事件
2.已选择(sk1),正在处理的通道事件
3.待删除(),删除处理完的sk用it.remove();从已选择里删                
chanle()取消通道事件,复制一份到待删除阶段,不会真正被移除                
在selector大喊一声之前,会去待删除阶段检查是否有已经完成的sk,如果有会干掉待删除阶段的sk(此键集只处理之后的键)。

看一个NIO Server例子:

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.ServerSocketChannel;                                    
import java.nio.channels.SocketChannel;                                    
import java.util.Iterator;                                    
import java.util.Set;                                    
public class NIOServerDemo1 {// NIO的服务器程序,一个服务器对应多个客户端,客户端只向服务器传输                                    
public static void main(String[] args) throws IOException {                                    
Selector selc = Selector.open();// 创建一个选择器,选择器一般来说全局只有一个就够                                    
ServerSocketChannel ssc = ServerSocketChannel.open();// 创建服务器对象                                    
ssc.socket().bind(new InetSocketAddress(9999));// 服务器指定监听端口                                    
ssc.configureBlocking(false);// 非阻塞模式                                    
ssc.register(selc, SelectionKey.OP_ACCEPT);// 注册accept操作,请求申请注册                                    
while (true) {                                    
    try {                                
    // 开始循环进行select操作,处理就绪的键sk                                
    // 执行selc操作,相当于大喊一声儿,注册在我身上的selectionkey们,哪一个对应的通道已经就绪了当初注册的事件                                
    int seleCount = selc.select();                                
    // 如果选择出的sk多余0个,表明有需要被处理的通道                                
    if (seleCount > 0) {                                
        // 选择出已经就绪的通道,可能是复数                            
        Set<SelectionKey> set = selc.selectedKeys();                            
        Iterator<SelectionKey> it = set.iterator();// 对set的便利用iterator                            
        while (it.hasNext()) {                            
            SelectionKey sk = it.next();// 遍历出每一个就绪的sk                        
            // 根据sk注册的不同,分别处理                        
            if (sk.isAcceptable()) {// 如果是ACCEPT操作                        
                ServerSocketChannel sscx = (ServerSocketChannel) sk                    
                            .channel();// 获取sk对应的channel        
                SocketChannel sc = sscx.accept();// 接收连接,得到sc                    
                sc.configureBlocking(false);// 开启sc的非阻塞模式                    
                sc.register(selc, SelectionKey.OP_READ);// 将sc注册到selc上,关注READ方法。                    
            } else if (sk.isConnectable()) {                        
                                    
            } else if (sk.isReadable()) {// 如果是一个Read操作                        
                SocketChannel sc = (SocketChannel) sk.channel();// 获取sk对应的channel通道                    
                ByteBuffer temp = ByteBuffer.allocate(1);// 获取头信息,获知体数据的长度                    
                String head = "";                    
                while (!head.endsWith("\r\n")) {// 找指定后缀                    
                        sc.read(temp);            
                        head += new String(temp.array());            
                        temp.clear();            
                }                    
                int len = Integer.parseInt(                    
                            head.substring(0, head.length() - 2));        
                ByteBuffer buf = ByteBuffer.allocate(len);// 创建缓冲区,接收数据                    
                while (buf.hasRemaining()) {                    
                        sc.read(buf);// 读取缓冲区数据            
                }                    
                String msg = new String(buf.array(),"utf-8");                    
                System.out.println("服务器收到了客户端["                    
                            + sc.socket().getInetAddress().getHostAddress()        
                            + "]发来的数据:" + msg);        
            } else if (sk.isWritable()) {// 如果是Write操作                        
                                    
            } else {// 其它就报错                        
                    throw new RuntimeException("NIO");                
            }                        
            it.remove();// 删除已处理过后的键,如果不删除会出现可能被重复处理的情况                        
        }                            
    }                                
    } catch (Exception e) {                                
        continue;                            
    }}}}                               

再看另外一个NIO 例子:

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.ServerSocketChannel;                                                    
import java.nio.channels.SocketChannel;                                                    
import java.util.Iterator;                                                    
import java.util.Set;                                                    
                                                    
public class NIOServerDemo2 {// NIO的服务器程序,一个服务器对应多个客户端,收一次数据,发一次数据                                                    
public static void main(String[] args) throws IOException {                                                    
Selector selc = Selector.open();// 创建一个选择器,选择器一般来说全局只有一个就够                                                    
ServerSocketChannel ssc = ServerSocketChannel.open();// 创建服务器对象                                                    
ssc.socket().bind(new InetSocketAddress(9999));// 服务器指定监听端口                                                    
ssc.configureBlocking(false);// 非阻塞模式                                                    
ssc.register(selc, SelectionKey.OP_ACCEPT);// 注册accept操作,请求申请注册                                                    
while (true) {                                                    
    try {                                                
    // 开始循环进行select操作,处理就绪的键sk                                                
    // 执行selc操作,相当于大喊一声儿,注册在我身上的selectionkey们,哪一个对应的通道已经就绪了当初注册的事件                                                
    int seleCount = selc.select();                                                
    // 如果选择出的sk多余0个,表明有需要被处理的通道                                                
    if (seleCount > 0) {                                                
        // 选择出已经就绪的通道,可能是复数                                            
        Set<SelectionKey> set = selc.selectedKeys();                                            
        Iterator<SelectionKey> it = set.iterator();// 对set的便利用iterator                                            
        while (it.hasNext()) {                                            
            SelectionKey sk = it.next();// 遍历出每一个就绪的sk                                        
            // 根据sk注册的不同,分别处理                                        
            if (sk.isAcceptable()) {// 如果是ACCEPT操作                                        
                ServerSocketChannel sscx = (ServerSocketChannel) sk.channel();// 获取sk对应的channel                                    
                SocketChannel sc = sscx.accept();// 接收连接,得到sc                                    
                sc.configureBlocking(false);// 开启sc的非阻塞模式                                    
                sc.register(selc, SelectionKey.OP_READ | SelectionKey.OP_WRITE);// 将sc注册到selc上,关注READ方法。                                    
            } else if (sk.isConnectable()) {                                        
                                                    
            } else if (sk.isReadable()) {// 如果是一个Read操作                                        
                SocketChannel sc = (SocketChannel) sk.channel();// 获取sk对应的channel通道                                    
                ByteBuffer temp = ByteBuffer.allocate(1);// 获取头信息,获知体数据的长度                                    
                String head = "";                                    
                while (!head.endsWith("\r\n")) {// 找指定后缀                                    
                    sc.read(temp);                                
                    head += new String(temp.array());                                
                    temp.clear();                                
                }                                }    
                int len = Integer.parseInt(                                    
                        head.substring(0, head.length() - 2));                            
                ByteBuffer buf = ByteBuffer.allocate(len);// 创建缓冲区,接收数据                                    
                while (buf.hasRemaining()) {                                    
                    sc.read(buf);// 读取缓冲区数据                                
                }                                    }
                String msg = new String(buf.array(),"utf-8");                                    
                System.out.println("服务器收到了客户端["+ sc.socket().getInetAddress().getHostAddress()+ "]发来的数据:" + msg);                                    
            } else if (sk.isWritable()) {// 如果是Write操作                                        
                SocketChannel scx = (SocketChannel)sk.channel();//获取通道                                    
                String str = "你好,我是服务器,哈哈哈哈!";//待发送的数据                                    
                String sentStr = str.getBytes("utf-8").length + "\r\n" +str;//处理协议                                    
                ByteBuffer buf = ByteBuffer.wrap(sentStr.getBytes("utf-8"));//发送数据                                    
                while(buf.hasRemaining()) {                                    
                    scx.write(buf);                                
                }                                    
                scx.register(selc, sk.interestOps() & ~SelectionKey.OP_WRITE);//取消WRITE注册                                    
            } else {// 其它就报错                                        
                throw new RuntimeException("NIO");                                    
            }                                        
            it.remove();// 删除已处理过后的键,如果不删除会出现可能被重复处理的情况                                        
        }                                            
    }                                                
    } catch (Exception e) {                                                
        continue;                                            
    }}}}                                          

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

戰士

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

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

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

打赏作者

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

抵扣说明:

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

余额充值