Skip to content

Latest commit

 

History

History
436 lines (213 loc) · 10.6 KB

NIO典型的Socket模型.md

File metadata and controls

436 lines (213 loc) · 10.6 KB

Nio 最基础结构

代码如下:

  • 客户端模型
import java.io.IOException;

import java.net.InetSocketAddress;

import java.nio.ByteBuffer;

import java.nio.channels.SocketChannel;

import java.util.concurrent.TimeUnit;


/**

 * @author xzg

 * @TIME 2017年1月3日

 * 注意类的隐藏和实例创建

 */

public class TestSockNioCkient {

	public static  void  main(String arg[]){

		client();

		}

	public static void client(){

        SocketChannel socketChannel = null;//定义管道,相当于公交车

        ByteBuffer buffer = ByteBuffer.allocate(1024);//定会有多少的作为

        try

        {

        	//打开SocketChannel:

            socketChannel = SocketChannel.open();//静态方法获取实例

            socketChannel.configureBlocking(false);//设置非阻塞

            socketChannel.connect(new InetSocketAddress("127.0.0.1",8080));//连接地址

            if(socketChannel.finishConnect())//如果连接状态

            {

                int i=0;

                while(true)//保持数据完整

                {

                    TimeUnit.SECONDS.sleep(1);

                    //读取数据:写入数据到Buffer(int bytesRead = fileChannel.read(buf);)

                    String info = "I'm "+i+++"-th information from client";

                    buffer.clear();//先进行清理

                    //从Buffer中读取数据(System.out.print((char)buf.get());)

                    /*从Channel写到Buffer (fileChannel.read(buf))

                    通过Buffer的put()方法 buf.put(…)*/

                    buffer.put(info.getBytes("utf-8"));

                    buffer.flip();

                    while(buffer.hasRemaining()){

                        System.out.println((char)buffer.get());

                        /*注意SocketChannel.write()方法的调用是在一个while循环中的。

                         * Write()方法无法保证能写多少字节到SocketChannel。所以,我们重复调用write()直到Buffer没有要写的字节为止。

                        非阻塞模式下,read()方法在尚未读取到任何数据时可能就返回了。

                        所以需要关注它的int返回值,它会告诉你读取了多少字节。*/

                        socketChannel.write(buffer);

                    }

                }

            }

        }

        catch (IOException | InterruptedException e)

        {

            e.printStackTrace();

        }

        finally{

            try{

                if(socketChannel!=null){

                	//关闭:

                    socketChannel.close();

                }

            }catch(IOException e){

                e.printStackTrace();

            }

        }

    }

}
  • 服务端模型
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;



/**

 * @author hasee

 * @TIME 2017年1月3日

 * 注意类的隐藏和实例创建

 */

public class TestServiceNio {

	private static final int BUF_SIZE=1024;

    private static final int PORT = 8080;

    private static final int TIMEOUT = 3000;

 

    public static void main(String[] args)

    {

        selector();

    }

 //监听新进来的连接

    public static void handleAccept(SelectionKey key) throws IOException{

        ServerSocketChannel ssChannel = (ServerSocketChannel)key.channel();

        SocketChannel sc = ssChannel.accept();

        //在非阻塞模式下,accept() 方法会立刻返回,如果还没有新进来的连接,返回的将是null。 因此,需要检查返回的SocketChannel是否是null

        sc.configureBlocking(false);//设置非阻塞

        sc.register(key.selector(), SelectionKey.OP_READ,ByteBuffer.allocateDirect(BUF_SIZE));

    }

 

    public static void handleRead(SelectionKey key) throws IOException{//准备读,从SelectionKey访问Channel和Selector很简单

        SocketChannel sc = (SocketChannel)key.channel();

        ByteBuffer buf = (ByteBuffer)key.attachment();

        long bytesRead = sc.read(buf);

        while(bytesRead>0){

            buf.flip();

            while(buf.hasRemaining()){

                System.out.print((char)buf.get());

            }

            System.out.println();

            buf.clear();

            bytesRead = sc.read(buf);

        }

        if(bytesRead == -1){

            sc.close();

        }

    }

 

    public static void handleWrite(SelectionKey key) throws IOException{//准备写

        ByteBuffer buf = (ByteBuffer)key.attachment();

        buf.flip();

        SocketChannel sc = (SocketChannel) key.channel();

        while(buf.hasRemaining()){

            sc.write(buf);

        }

        buf.compact();

    }

 

    public static void selector() {

        Selector selector = null;

        ServerSocketChannel ssc = null;

        try{

        	//Selector的创建:Selector selector = Selector.open();

            selector = Selector.open();

            //打开ServerSocketChannel:

            ssc= ServerSocketChannel.open();

            //绑定地址端口号

            ssc.socket().bind(new InetSocketAddress(PORT));

            ssc.configureBlocking(false);//配置非阻塞

            /*与Selector一起使用时,Channel必须处于非阻塞模式下。这意味着不能将FileChannel与Selector一起使用,

             * 因为FileChannel不能切换到非阻塞模式。而套接字通道都可以。

             * 为了将Channel和Selector配合使用,必须将Channel注册到Selector上,

             * 通过SelectableChannel.register()方法来实现,沿用案例5中的部分代码:*/

            ssc.register(selector, SelectionKey.OP_ACCEPT);//register()方法的第二个参数。这是一个“interest集合”.Selector监听Channel时对什么事件感兴趣。可以监听四种不同类型的事件:

            /*Connect

             * 2. Accept

            3. Read

            4. Write*/

            /*ServerSocketChannel可以设置成非阻塞模式。在非阻塞模式下,accept() 方法会立刻返回,

            如果还没有新进来的连接,返回的将是null。 因此,需要检查返回的SocketChannel是否是null.如:

            SocketChannel socketChannel = serverSocketChannel.accept();

            if (socketChannel != null)

            {

                // do something with socketChannel...

            }    */

            while(true){

         /*一旦向Selector注册了一或多个通道,就可以调用几个重载的select()方法。这些方法返回你所感兴趣的事件(如连接、接受、读或写)

         已经准备就绪的那些通道。换句话说,如果你对“读就绪”的通道感兴趣,select()方法会返回读事件已经就绪的那些通道。*/

            	//select()方法返回的int值表示有多少通道已经就绪。然后可以通过调用selector的selectedKeys()方法,访问“已选择键集(selected key set)”中的就绪通道。

                if(selector.select(TIMEOUT) == 0){//select()阻塞到至少有一个通道在你注册的事件上就绪了

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

                  /*selectNow()不会阻塞,不管什么通道就绪都立刻返回(译者注:此方法执行非阻塞的选择操作。

                    如果自从前一次选择操作后,没有通道变成可选择的,则此方法直接返回零。)*/

                    continue;

                }

                /*通道触发了一个事件意思是该事件已经就绪。所以,某个channel成功连接到另一个服务器称为“连接就绪”。

                 * 一个server socket channel准备好接收新进入的连接称为“接收就绪”。

                 * 一个有数据可读的通道可以说是“读就绪”。等待写数据的通道可以说是“写就绪”。 这四种事件用SelectionKey的四个常量来表示*/

                //SelectionKey.OP_CONNECT\SelectionKey.OP_ACCEPT\ SelectionKey.OP_READ\SelectionKey.OP_WRITE

                Iterator<SelectionKey> iter = selector.selectedKeys().iterator();

                while(iter.hasNext()){

                	//当向Selector注册Channel时,register()方法会返回一个SelectionKey对象。这个对象包含了一些你感兴趣的属性:

                    SelectionKey key = iter.next();

 // 可以用像检测interest集合那样的方法,来检测channel中什么事件或操作已经就绪。也可使用下四个方法,它们都返回一个布尔类型

                    if(key.isAcceptable()){//检测channel中什么事件或操作已经就绪

                        handleAccept(key);//监听新进来的连接

                    }

                    if(key.isReadable()){

                        handleRead(key);

                    }

                    if(key.isWritable() && key.isValid()){

                        handleWrite(key);

                    }

                    if(key.isConnectable()){

                        System.out.println("isConnectable = true");

                    }

                    iter.remove();

                    /*注意每次迭代末尾的keyIterator.remove()调用。Selector不会自己从已选择键集中移除SelectionKey实例。

                    必须在处理完通道时自己移除。下次该通道变成就绪时,Selector会再次将其放入已选择键集中*/

                }

            }

 

        }catch(IOException e){

            e.printStackTrace();

        }finally{

            try{

                if(selector!=null){

                    selector.close();

                }

                if(ssc!=null){

                	//关闭ServerSocketChannel

                    ssc.close();

                }

            }catch(IOException e){

                e.printStackTrace();

            }

        }

    }

}

接下来就可以分别启动服务端和客户端进行通信了

java代码链接如下:

nio客户端程序

普通bio服务端程序

nio服务端程序

测试代码