传统IO与NIO的Socket编程

阅读更多

        网络编程时,我们常见到同步(Sync)和异步(Async),阻塞(blocking)和非阻塞(non-blocking)四种调用模式:

       ①同步:

        客户端发一个调用,再没有得到结果之前,该调用不返回,通俗来讲,就是必须做完前一件事情才可以 做下一件事情;

       ②异步:

        当客户端发生一个异步请求的调用的时候,调用者不能立刻得到结果,实际处理这个调用的部件在完成后,通过状态、通知和回调来通知调用者,例如ajax请求;

       ③阻塞

        通俗的讲,做某件事情,知道完成,除非超时,如果没有完成,继续等待;当阻塞调用的时候,在调用结果返回之前,当前线程会被挂起(线程进入非可执行状态,在这个状态下,CPU不会给线程分配时间片,即线程暂停运行),函数只有在得到结果折后才会被 返回;

       ④非阻塞

       指在不能得到结果之前,该函数不会阻塞当前线程,会立刻返回;

      还是等快递的例子:如果用忙轮询的方法,每隔5分钟到A楼一层(内核缓冲区)去看快递来了没有。如果没来,立即返回。而快递来了,就放在A楼一层,等你去取。

 

 传统IO的Socket的单线程通信,用非常经典的食堂例子来讲解,端口号相当于食堂大门,线程相当于服务员,在这个状态下,一个客户需要有一个服务员来服务,这样显然是不合理的,假如有100个顾客的话,就需要100服务员,这样非常浪费系统资源,代码如下:

package IoSocket;

import java.io.IOException;
import java.io.InputStream;
import java.net.ServerSocket;
import java.net.Socket;

/**
 * Created by Taoyongpan on 2017/10/23.
 * 传统的IO是阻塞IO,每多一个用户就要创建一个新的线程,单线程模式
 */
public class TraditionalSocketDemo {

    public static void main(String[] args) throws IOException {
        ServerSocket serverSocket = new ServerSocket(8888);
        System.out.println("服务器启动。。。。");
        while (true){
            //接收客户端的连接 ,套接字
            Socket socket = serverSocket.accept();
            System.out.println("有新的客户端连接");
            InputStream in = null;
            try {
                in = socket.getInputStream();
            } catch (IOException e) {
                e.printStackTrace();
            }
            byte[] b = new byte[1024];
            while (true){
                int data = 0;
                data = in.read(b);
                if (data!=-1){
                    String info = new String(b,0,data);
                    System.out.println(info);
                }else {
                    break;
                }
            }
        }
    }
}

 当程序运行的时候:


传统IO与NIO的Socket编程_第1张图片
 这时用命令窗口创建一个用户,我这里面的端口号是8888,我们创建客户的语句是:telnet localhost 8888,如图所示:
传统IO与NIO的Socket编程_第2张图片
 输入 CTRL+]的命令 进入字符串发送页面。原来的页面只可以发送字符;
传统IO与NIO的Socket编程_第3张图片
 send +自己所要发送的内容,如上图所示;

因为这个代码写的是单线程模式的,当我们再创建一个新的客户的时候,就会陷入等待,只有关闭上一个现成的时候,新建客户的消息的才可以发送,如下图所示:


传统IO与NIO的Socket编程_第4张图片
 
传统IO与NIO的Socket编程_第5张图片
 

我们也可以创建一个线程池,来实现多线程,代码如下:

package IoSocket;

import java.io.IOException;
import java.io.InputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * Created by Taoyongpan on 2017/10/24.
 * 传统的IO是阻塞IO,每多一个用户就要创建一个新的线程
 */
public class TraditionSocketDemo1 {
    public static void main(String[] args) throws IOException {
        //多线程方法,创建一个线程池
        ExecutorService es = Executors.newCachedThreadPool();
        ServerSocket serverSocket = new ServerSocket(8888);
        System.out.println("服务器启动。。。。");
        while (true){
            //接收客户端的连接 ,套接字
            Socket socket = serverSocket.accept();
            System.out.println("有新的客户端连接");
            es.execute(new Runnable() {
                @Override
                public void run() {
                    InputStream in = null;
                    try {
                        in = socket.getInputStream();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                    byte[] b = new byte[1024];
                    while (true){
                        int data = 0;
                        try {
                            data = in.read(b);
                        } catch (IOException e) {
                            e.printStackTrace();
                        }
                        if (data!=-1){
                            String info = new String(b,0,data);
                            System.out.println(info);
                        }else {
                            break;
                        }
                    }
                }
            });

        }
    }
}

 重复运行上面的测试步骤就可以进行操作:

测试如下图所示:


传统IO与NIO的Socket编程_第6张图片
 

NIO是非阻塞的IO,相对于传统IO,新增了selector、channel和buffer,还用食堂的例子来讲解,我们的channel就像食堂的大门,一个食堂的服务员可以服务多个客户,这个过程用selector来调度,例如在 客户A点菜的时候,接待了客户B,代码如下:

package IoSocket;

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;

/**
 * Created by Taoyongpan on 2017/10/23.
 * nio,增加了selector,channel ,buffer,非阻塞
 */
public class NioSocketDemo {

    private Selector selector;//通道选择器

    public static void main(String[] args) throws IOException {
        NioSocketDemo nioSocketDemo = new NioSocketDemo();
        nioSocketDemo.initServer(8888);
        nioSocketDemo.listenSelector();
    }
    public void initServer(int port) throws IOException {
        ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
        serverSocketChannel.configureBlocking(false);//设置成非阻塞
        serverSocketChannel.socket().bind(new InetSocketAddress(port));
        this.selector = Selector.open();
        //注册监听器
        serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);//注册监听连接请求
        System.out.println("服务已启动。。。");
    }

    public void listenSelector() throws IOException {
        //循环监听selector
        while (true){
            //等待客户连接
            //select模型,多路服用
            this.selector.select();
            this.selector.selectedKeys().iterator();
            Iterator it = this.selector.selectedKeys().iterator();
            while (it.hasNext()){
                SelectionKey skey = it.next();
                it.remove();//把当前的元素remove掉
                //处理请求
                handler(skey);
            }
        }
    }

    private void handler(SelectionKey skey) throws IOException {
        if (skey.isAcceptable()){
            //处理客户端连接事件
            ServerSocketChannel serverChannel = (ServerSocketChannel)skey.channel();
            SocketChannel socketChannel = serverChannel.accept();
            System.out.println("新客户连接上来了");
            socketChannel.configureBlocking(false);//设置为非阻塞
            //接收客户端发送的信息,需要给通道设置读的权限
            socketChannel.register(selector,SelectionKey.OP_READ);
        }else if (skey.isReadable()){
            //处理读的事件
            SocketChannel socketChannel = (SocketChannel) skey.channel();
            ByteBuffer byteBuffer = ByteBuffer.allocate(1024);//初始化一个缓存
            int data = socketChannel.read(byteBuffer);
            if (data>0){
                String info = new String(byteBuffer.array(),"GBK").trim();
                System.out.println("客户端消息"+info);
            }else {
                System.out.println("客户端关闭了");
                skey.cancel();//把这个连接清除掉
            }
        }
    }
}

 这个的运行效果和传统IO的多线程的运行结果是一样的,这里就不再进行展示;

  • 传统IO与NIO的Socket编程_第7张图片
  • 大小: 20.9 KB
  • 传统IO与NIO的Socket编程_第8张图片
  • 大小: 90.5 KB
  • 传统IO与NIO的Socket编程_第9张图片
  • 大小: 70 KB
  • 传统IO与NIO的Socket编程_第10张图片
  • 大小: 71.7 KB
  • 传统IO与NIO的Socket编程_第11张图片
  • 大小: 98.9 KB
  • 传统IO与NIO的Socket编程_第12张图片
  • 大小: 79.1 KB
  • 查看图片附件

你可能感兴趣的:(Socket网络编程)