一:网络概括
- 计算机网络:通过传输介质、通信设施和网络通信协议,将不同地点的计算机设备互联起来,实现资源共享和数据的传输
- 网络编程就是编写程序将网络中两个或者两个以上的设备直接进行数据传输
1、OSI网络模型
将网络划分为7层
从上到下:应用层,表示层,会话层,传输层,网络层,数据链路层和物理层
2、TCP /IP协议族模型
- 应用层:HTTP/https/STMP(邮件)/FTP(文件)/telnet
- 运输层:TCP/UDP (端口)
- 网络层:IP (IP)
- 网络接口层:ARP/RARP
3、数据包
包,帧,段,消息
大致划分:
包:全能性术语
帧:表示数据链路层的包的单位
数据包:IP和UDP等网络层(传输层和网络层)中数据的单位
段:TCP数据流单位
消息:应用层的数据的单位
在数据传输过程中,在不同的分层中都会对数据添加一个首部信息,该首部包含该层必要的信息和上层的数据
4、数据传输的流程
二:Socket编程
客户端/服务端
- 服务端:
public class Server {
/**
* 接收连接
* <p>
* 1、创建ServerSocket的实例
* 2、绑定端口
* 3、监听客户端的连接
* 4、获取客户端连接的实例
* 5、进行IO操作(读数据/写数据)
* 6、断开连接,关闭资源
*/
public static void main(String[] args) {
try {
//创建ServerSocket的实例
ServerSocket serverSocket = new ServerSocket();
//绑定端口 bind
serverSocket.bind(new InetSocketAddress(6666));
System.out.println("服务端启动啦");
//监听客户端的连接 accept
Socket socket = serverSocket.accept();
System.out.println("有客户端连接");
//进行IO操作
//input (源->当前程序) output(当前程序->源)
BufferedReader reader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
String msg = reader.readLine();
System.out.println("IO操作:"+msg);
System.out.println(msg);
//关闭资源
socket.close();
serverSocket.close();
System.out.println("关闭资源");
} catch (IOException e) {
e.printStackTrace();
}
}
}
- 客户端
public class Client {
/**
* 主动连接
*
* 1、创建socket实例
* 2、连接服务端IP+端口,connect
* 3、进行IO操作
* 4、断开连接,关闭资源
*/
public static void main(String[] args) {
//创建socket实例
Socket socket = new Socket();
System.out.println("客户端启动啦");
try {
//连接服务端IP+端口,connect
socket.connect(new InetSocketAddress("127.0.0.1",6666));
System.out.println("连接服务器成功");
//进行IO操作
OutputStream outputStream = socket.getOutputStream();
outputStream.write("你好".getBytes());
//断开连接,关闭资源
socket.close();
System.out.println("关闭资源");
} catch (IOException e) {
e.printStackTrace();
}
}
}
三:Java IO读写原理
socket编程和对文件的操作,都是涉及到输入InputStream
和输出OutputStream
的操作,简称IO操作
用户程序进行IO读写操作,基本上都会用到read
和write
两大系统调用
read
系统调用:把数据从内核缓冲区写入到进程缓冲区。
write
系统调用:是将进程缓冲区的数据写入到内核缓冲区。
注意read
和write
系统调用不负责内核缓冲区和磁盘之间的交换,这个是交给内核kernel来完成的
1、内核缓冲区和进程缓冲区
缓冲区的目的,是用来减少频繁的系统IO调用的
有了缓冲区,OS对read
函数的操作从内核缓冲区将数据复制到进程缓冲区,write
是将进程缓冲区的数据复制到内核缓冲区
以在网络中读取数据为例,介绍一下IO流程
- 用户在用户空间发起读操作,读操作是首先在进程的缓冲区读取数据,有数据则直接获取
- 没有数据是则发起read的系统调用,从用户空间进入到内核空间,在内核缓冲区获取数据
- 在内核空间获取数据如上,有责直接获复制到进程缓冲区
- 如果没有数据则需要到网卡获取数据
四:Java 中主要的IO模型
1、简介
Java中的网络编程需要构建高性能的网络通信的IO模型,常见的IO模型有同步阻塞IO模型(Blocking IO)、同步非阻塞IO模型(Non-Blocking IO )、IO复用模型(IO Mutilpleting) 、异步IO(Asynchronurse IO)
- 阻塞IO和非阻塞IO
- 阻塞IO:指的是用户空间需要等待内核空间彻底执行结束之后,才返回用户空间,执行用户操作
- 非阻塞IO:指的是用户空间获取内核数据,不管是否有数据会立即返回,如果有数据则拿到数据,如果没有数据会返回一个特定的状态码,用户空间可以不用阻塞在这里,定期去内核空间查询数据
阻塞和非阻塞指用户空间的程序的执行状态
- 同步和非同步(异步)
同步指用户空间主动发起IO请求的一方,异步IO则是反过来,内核空间为主动发起的一方,用户空间是被动接受方
同步和异步指的是用户空间和内核空间的调用发起方式
- 同步阻塞IO模型(Blocking IO)
传统的IO模型就是同步阻塞IO模型,在Java中,默认创建的Socket编程都是同步阻塞IO模型 - 同步非阻塞IO模型(Non-Blocking IO)
指的是用户空间不用直接等待内核IO操作完成,内核立即返回给用户一个状态值,用户空间无需等待内核IO操作彻底完成,用户空间可以继续执行,在一定时间内可以再次请求内核查询数据的状态值
在Socket设置为NonBlock状态 - IO多路复用模型(IO Mutlipleing)
这种IO模型是一种reactor设计模式。Java 中seletor和Linux中的epoll等都是这种模型 - 异步IO模型(Asynchrourse IO)
异步IO的过程是将调用关系反过来,有内核来调用用户空间。
类似于使用的回调函数
一个IO操作主要分为两步:以read
操作为例:
- 等待数据
- 将数据从内核空间拷贝到用户空间
2、同步阻塞IO模型(Blocking IO)
在Linux系统中执行java程序,默认情况下就是同步阻塞模型所有的socket都是Blocking IO,用户发起IO操作从系统调用开始,一致到系统调用完成才返回,这一段时间是阻塞的。
解释:发起一个Blocking IO的socker的read操作为例
- 用户程序发起read系统调用,内核就开始IO的第一阶段,数据如果没有到达,内核是需要一直等到数据到达
- 当数据准备好了,内核将数据从内核缓冲区复制到用户缓冲区,然后内核返回结果
- 用户从发起到内核返回整个阶段是一致阻塞的
- 优点:程序简单
- 缺点:每个用户连接给一个独立的线程来执行,每一个用户连接都需要一个线程来支持,用户请求比较多,就需要更多的线程支持,线程越多时消耗内存上下文切换也是比较耗时的
3、同步非阻塞IO模型(Non-Blocking IO)
在Linux系统中,将socket设置为Non-blocking,NIO一旦发起系统调用,会存在两种情况
- 在内核空间没有数据的情况下,系统调用会立即返回,返回的是一个失败的信息
- 在内核空间的缓冲区中有数据时,将数据从内核空间缓冲区复制到进程用户程序缓冲区中
NIO特点:
应用程序需要不断的IO系统调用,轮序查询数据是否准备好,如果没有准备好需要一直轮序,直至数据准备好拷贝到用户空间。
- 优点:每次发起IO操作,内核会立即返回一个结果表示数据是否准备好,用户进程可以不用一直阻塞
- 缺点:需要不断的轮序IO的系统调用,这占用了大量的时间,系统资源利用率低
4、IO多路复用模型(IO Mutlipleing)
IO多路复用,提供了新的系统调用,一个进程可以同时监听多个文件描述符,提供的系统调用,select、poll。
在Linux下另提供了epoll的IO复用器。
通过示例图可以看到,IO多路复用是在等待数据和复制数据阶段都会阻塞,需要结合同步非阻塞IO一起使用,将Socket设置为Non-blocking,可以让等待数据不在阻塞。
- 优点:一个进程就可以管理成千上万的连接,与一个进程维护一个连接相比,性能大大提高
- 缺点:select、poll也都属于同步IO,也是阻塞IO
5、异步IO模型(Asynchrourse IO)
用户线程通过系统调用,告知内核启动某个IO操作,用户线程返回,内核在整个IO操作完成后在通知用户线程,用户线程在处理后续逻辑
在IO操作的两个阶段:等待数据和数据复制的过程都交给内核完成,内核完成后在通知用户线程可以执行后续业务逻辑
五:Java中支持的BIO、NIO和AIO的模型对比
- Java BIO:同步且阻塞:服务端实现模式一个连接一个线程,即客户端有多少个连接服务端就需要多少线程来支持
- Java NIO:同步非阻塞(New IO):服务端实现模式是一个请求一个线程,客户端发送多来的连接请求会注册到IO复用器中,每当用户发起一个请求(
read
、write
),当服务端接收到一个请求时,IO复用器会通知用户程序来给定一个线程处理请求 - Java AIO:异步非阻塞:服务端实现的模式一个有效请求一个线程,客户端的IO操作由OS系统完成数据的接收和复制在通知用户程去启动线程进行执行
BIO、NIO、AIO适用场景
- BIO方式适用于连接数目比较小且固定的架构,这种方式对服务器资源要求比较高,并发局限于应用中,JDK1.4以前的唯一选择,但程序直观简单易理解。
- NIO方式适用于连接数目多且连接比较短(轻操作)的架构,比如聊天服务器,并发局限于应用中,编程比较复杂,JDK1.4开始支持。
- AIO方式使用于连接数目多且连接比较长(重操作)的架构,比如相册服务器,充分调用OS参与并发操作,编程比较复杂,JDK7开始支持。另外,I/O属于底层操作,需要操作系统支持,并发也需要操作系统的支持,所以性能方面不同操作系统差异会比较明显。
1、BIO模型编程
2、NIO模型编程
3、AIO模型编程
六:扩展
1、Linux中5中IO模型
对于一次 IO 访问,它会经历两个阶段:
- 等待数据准备就绪 (Waiting for the data to be ready)
- 将数据从内核拷贝到进程中 (Copying the data from the kernel to the process)。
举例来说:
读函数:分为等待系统可读和真正的读。
写函数:分为等待网卡可以写和真正的写。
说明:
等待就绪的阻塞是不使用 CPU 的,是在“空等”。而真正的读写操作的阻塞是使用 CPU 的,真正在“干活”,而且这个过程非常快,属于 memory copy,宽带通常在 1GB/s 级别以上,可以理解为基本不耗时。
(1)阻塞IO模型
(2)非阻塞I/O模型
非阻塞IO通过进程反复调用IO函数
采用轮询,占用CPU
(3)I/O复用模型
主要是select和epoll,对一个IO端口,两次调用,两次返回,
能实现对多个IO端口进行监听。
多个连接共用一个等待机制
(4)信号驱动IO模型
首先开启套接口信号驱动IO功能,通过系统调用sigation执行一个信号处理函数(此信号调用直接返回,进程继续工作)。当数据准备就绪时,生成一个signal信号,通知应用程序来取数据。
(5)异步IO模型
告知内核启动某个操作,并让内核在整个操作完成的的那个之后(将数据从内核复制到用户自己的缓冲区),进行通知。
它和信号驱动模型的主要区别:信号驱动IO由内核通知我们合适可以开始一个IO操作在client进行IO操作的时候需要等待,所以是同步的。异步IO模型由内核通知我们IO何时已经完成,client不需要进行IO的处理了,所以是异步的。
一个形象的示例说明
活动:演唱会
角色1:举办商 售票业务员
角色2:黄牛
角色3:小明
角色4:送票快递员
同步阻塞 :
小明从家里面先到演唱会现场问售票业务员买票,但是票还没出来,
三天以后才出来,小明直接打了个地铺睡在举办商售票大厅,一直等票出来,然后买票
同步非阻塞 : socket可以设置non-blocking
小明从家里面先到演唱会现场问售票业务员买票,但是票还没出来,然后小明走了,办理其他事情去了,
然后过了2个小时,又去举办商售票大厅买票来了,如果票还没有出来,小明又先去办其他事情了,
重复上面的操作,直到有票可以买
i/o复用 : java selector() => linux epoll_wait() 同步非阻塞I/O
小明想买票看演唱会,都直接给黄牛(selector/epoll)打电话了,说帮我留意买个票,
票买了通知我,我自己去取(当我接到黄牛的电话时,我需要花费整个路成的时间去读这个数据,买拿这个票),
那么票没出来之前,小明完全可以做自己的事情
信号i/o
小明想买票看演唱会,给举办商售票业务员说,给给你留个电话,有票了请你给我打个电话通知一下
(是看人家操作系统提不提供这种功能,Linux提供,windows没有这种机制),我自己再来买票
(小明完全可以做自己的事情,但是票还是需要小明自己去拿的)
异步非阻塞i/o : Linux aio_read aio_write
小明想买票看演唱会,给举办商售票业务员说(异步非阻塞i/o)打电话了,给你留个地址,有票了请通知快递员,
把这张票送到这个地址来,当小明听到敲门声,看见快递员,就知道票好了,而且指导票好了的时候,
票已经到他手上了,票不用小明自己去取(应用不用自己再去read数据了)