美文网首页
Java - 网络IO

Java - 网络IO

作者: 齐晋 | 来源:发表于2019-03-01 19:11 被阅读0次

    发展历程

    Java1.0开始提供的IO都同步阻塞IO,即BIO
    Java1.4开始提供了同步非阻塞IO,即NIO
    Java1.7开始出现的NIO2.0版本,真正提供了异步非阻塞IO,即AIO

    引申:什么是“同步/异步”?什么是“阻塞/非阻塞”?
    一个IO操作其实分成了两个步骤:发起IO请求实际的IO操作
    同步IO异步IO的区别就在于第二个步骤是否阻塞,如果实际的IO读写阻塞请求进程,那么就是同步IO。
    阻塞IO非阻塞IO的区别在于第一步,发起IO请求是否会被阻塞,如果阻塞直到完成那么就是传统的阻塞IO,如果不阻塞,那么就是非阻塞IO。

    很明显,通常来说,非阻塞IO比阻塞IO效率高,异步IO比同步IO效率高

    BIO

    Blocking IO - (同步)阻塞IO

    根据Linux IO模型可知,BIO的IO都是阻塞的。
    在此种方式下,用户进程在发起一个IO操作以后,必须等待IO操作的完成,只有当真正完成了IO操作以后,用户进程才能运行。JAVA传统的IO模型属于此种方式。

    BIO的读写操作都是阻塞的,即调用到read()方法时,如果没有数据,会一直阻塞等待。BIO对应的形象比喻:打电话时,如果对方不说话,本方会一直等待。很显然,这个效率是很低的。

    BIO网络编程示例

    网络编程大部分都是基于C/S模式的。

    TCP
    TCP Server端使用ServerSocket类,负责绑定IP,启动监听端口。
    TCP Client端使用Socket类,负责连接Server

    UDP
    Java通过DatagramPacket类和DatagramSocket类来使用UDP套接字,客户端和服务器端都通过DatagramSocketsend()方法和receive()方法来发送和接收数据,用DatagramPacket来包装需要发送或者接收到的数据。
    发送信息时,Java创建一个包含待发送信息的DatagramPacket实例,并将其作为参数传递给DatagramSocket实例的send()方法;接收信息时,Java程序首先创建一个DatagramPacket实例,该实例预先分配了一些空间,并将接收到的信息存放在该空间中,然后把该实例作为参数传递给DatagramSocket实例的receive()方法

    注意:如果该实例用来包装待接收的数据,则不指定数据来源的远程主机和端口,只需指定一个缓存数据的byte数组即可(在调用receive()方法接收到数据后,源地址和端口等信息会自动包含在DatagramPacket实例中),而如果该实例用来包装待发送的数据,则要指定要发送到的目的主机和端口。

    public class ServerSocket implements java.io.Closeable {
        public Socket accept() throws IOException;
        public void bind(SocketAddress endpoint) throws IOException;
        public void close() throws IOException;
    }
    

    Client端依赖Socket类,负责连接Server

    public class Socket implements java.io.Closeable{
        public void bind(SocketAddress bindpoint) throws IOException;
        public void connect(SocketAddress endpoint) throws IOException;
        public synchronized void close() throws IOException;
    }
    
    public class DatagramSocket implements java.io.Closeable {
        public synchronized void bind(SocketAddress addr) throws SocketException;
        public void connect(InetAddress address, int port);
        public void send(DatagramPacket p) throws IOException;
        public synchronized void receive(DatagramPacket p) throws IOException;
    }
    

    TCP

    Server端

    单线程模型

    //服务端的端口号
    int port = 8080;
    //创建服务端Socket
    ServerSocket server = new ServerSocket(port);
    //与客户端建立连接的Socket
    Socket socket = null;
    while(true){
        //等待客户端接入
        socket = server.accept();
        //创建InputStream,读入数据
        BufferedReader in = new BufferedReader(new InputStreamReader(this.socket.getInputStream()));
        //创建OutputStream,写出数据
        PrintWriter out = new PrintWriter(this.socket.getOutputStream());
        //从网络中读取数据
        String body = in.readLine();
        //将数据写入到网络
        out.println(body);
    }
    

    多线程模型

    //服务端的端口号
    int port = 8080;
    //创建服务端Socket
    ServerSocket server = new ServerSocket(port);
    //与客户端建立连接的Socket
    Socket socket = null;
    while(true){
        //等待客户端接入
        socket = server.accept();
        //某个客服端接入后,启动新的线程,在新线程中与客户端进行读写交互
        new Thread(new ServerHandler(socket)).start();
    }
    
    public class ServerHandler implements Runnable{
        private Socket socket;
        public ServerHandler(Socket socket){
            this.socket = socket;
        }
        @Override
        public void run(){
            BufferedReader in = new BufferedReader(new InputStreamReader(this.socket.getInputStream()));
            PrintWriter out = new PrintWriter(this.socket.getOutputStream());
            while(true){
                //从网络读取数据
                String body = in.readLine();
                //将数据写入到网络
                out.println(body)
            }
        }
    }
    
    Client端
    String ip = "127.0.0.1";
    int port = 8080;
    //连接远程Server
    Socket socket = new Socket(ip, port);
    //输入流
    PrintWriter out = new PrintWriter(socket.getOutputStream());
    //输出流
    BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
    //向Server发送信息
    out.println("Hello World");
    //从Server收到信息
    String res = in.readLine();
    

    UDP

    UDP的通信建立的步骤

    一个典型的UDP客户端要经过下面三步操作:

    1. 创建一个DatagramSocket实例,可以有选择地对本地地址和端口号进行设置,如果设置了端口号,则客户端会在该端口号上监听从服务器端发送来的数据;
    2. 使用DatagramSocket实例的send()和receive()方法来发送和接收DatagramPacket实例,进行通信;
    3. 通信完成后,调用DatagramSocket实例的close()方法来关闭该套接字。

    由于UDP是无连接的,因此UDP服务端不需要等待客户端的请求以建立连接。另外,UDP服务器为所有通信使用同一套接字,这点与TCP服务器不同,TCP服务器则为每个成功返回的accept()方法创建一个新的套接字。
    一个典型的UDP服务端要经过下面三步操作:

    1. 创建一个DatagramSocket实例,指定本地端口号,并可以有选择地指定本地地址,此时,服务器已经准备好从任何客户端接收数据报文;
    2. 使用DatagramSocket实例的receive()方法接收一个DatagramPacket实例,当receive()方法返回时,数据报文就包含了客户端的地址,这样就知道了回复信息应该发送到什么地方;
    3. 使用DatagramSocket实例的send()方法向服务器端返回DatagramPacket实例。
    image.png
    Server端
    //端口
    int port = 8080;
    //创建DatagramSocket
    DatagramSocket  server = new DatagramSocket(port);
    byte[] recvBuf = new byte[100];
    DatagramPacket recvPacket  = new DatagramPacket(recvBuf , recvBuf.length);
    //接收数据
    server.receive(recvPacket);
    
    String sendStr = "Hello ! I'm Server";
    byte[] sendBuf;
    sendBuf = sendStr.getBytes();
    DatagramPacket sendPacket = new DatagramPacket(sendBuf , sendBuf.length , addr , port );
    //发送数据
    server.send(sendPacket);
    
    Client端
    //创建客户端
    DatagramSocket client = new DatagramSocket();
    String sendStr = "Hello! I'm Client";
    byte[] sendBuf;
    sendBuf = sendStr.getBytes();
    InetAddress addr = InetAddress.getByName("127.0.0.1");
    int port = 8080;
    //构建发送数据包
    DatagramPacket sendPacket = new DatagramPacket(sendBuf ,sendBuf.length , addr , port);
    //发送数据
    client.send(sendPacket);
    
    byte[] recvBuf = new byte[100];
    //构建结束数据包
    DatagramPacket recvPacket = new DatagramPacket(recvBuf , recvBuf.length);
    //接收数据
    client.receive(recvPacket);
    String recvStr = new String(recvPacket.getData() , 0 ,recvPacket.getLength());
    System.out.println("收到:" + recvStr);
    client.close();
    

    NIO

    Non-Blocking IO - 同步非阻塞IO

    在此种方式下,用户进程发起一个IO操作以后便可返回做其它事情,但是用户进程需要时不时的询问IO操作是否就绪,这就要求用户进程不停的去询问,从而引入不必要的CPU资源浪费。其中目前JAVA的NIO就属于同步非阻塞IO。

    NIO对应的形象比喻:打电话时,如果对方没有话说,先挂掉电话,干点其他事情。过段时间再打过去,看看对方有没有话要说。如果对方有话要说,则拿着电话听对方说话。如此循环往复。

    Java NIO编程是一个很重要的部分,会做专门的介绍:
    Java NIO...

    AIO

    异步非阻塞IO

    适用场景

    BIO、NIO、AIO适用场景分析:

    • BIO方式适用于连接数目比较小且固定的架构,这种方式对服务器资源要求比较高,并发局限于应用中,JDK1.4以前的唯一选择,但程序直观简单易理解。
    • NIO方式适用于连接数目多且连接比较短(轻操作)的架构,比如聊天服务器,并发局限于应用中,编程比较复杂,JDK1.4开始支持。
    • AIO方式使用于连接数目多且连接比较长(重操作)的架构,比如相册服务器,充分调用OS参与并发操作,编程比较复杂,JDK7开始支持。

    参考

    相关文章

      网友评论

          本文标题:Java - 网络IO

          本文链接:https://www.haomeiwen.com/subject/cysjuqtx.html