Java - 网络IO

发展历程

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开始支持。

参考

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,616评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,020评论 3 387
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 159,078评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,040评论 1 285
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,154评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,265评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,298评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,072评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,491评论 1 306
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,795评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,970评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,654评论 4 337
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,272评论 3 318
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,985评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,223评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,815评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,852评论 2 351

推荐阅读更多精彩内容

  • 本文目的是大概了解 Java 网络编程体系,需要一点点 Java IO 基础,推荐教程 系统学习 Java IO。...
    czwbig阅读 456评论 0 1
  • 计算机网络概述 网络编程的实质就是两个(或多个)设备(例如计算机)之间的数据传输。 按照计算机网络的定义,通过一定...
    蛋炒饭_By阅读 1,215评论 0 10
  • 在一个方法内部定义的变量都存储在栈中,当这个函数运行结束后,其对应的栈就会被回收,此时,在其方法体中定义的变量将不...
    Y了个J阅读 4,414评论 1 14
  • 7.2 面向套接字编程我们已经通过了解Socket的接口,知其所以然,下面我们就将通过具体的案例,来熟悉Socke...
    lucas777阅读 1,175评论 0 2
  • 时间飞逝,岁月如梭,眼看我们就要毕业了,当然我们也准备了一场毕业大戏,——《哈姆雷特》。 ...
    张九紫阅读 672评论 0 1