乱七八糟的整理,建议全屏,否则排版可能会很奇怪~~
TCP/UDP协议
OSI网络七层模型:
高三层:
应用层:为用户的应用进程提供网络服务 第七层
表示层:负责数据格式转换、数据加密解密、压缩解压缩等 第六层
会话层:负责建立、管理和终止进程之间的会话和数据交换 第五层
传输层:提供可靠的端口到端口的数据传输服务(TCP/UDP协议) 第四层
低三层:
网络层:进行路由选择和流量控制(IP协议) 第三层
数据链路层:通过检验、确认和反馈重发手段,形成稳定的数据链路(010101) 第二层
物理层:使原始的数据比特流能在物理上传输 第一层
TCP:TCP提供面向连接、可靠、有序、字节流传输服务
(transport control protocol,传输控制协议)是面向连接的,面向流的,提供高可靠性服务。收发两端(
客户端和服务器端)都要有一一成对的socket
三次握手、四次挥手
Http协议:
请求报文格式
第一部分:请求行 -> 请求类型,资源路径以及HTTP版本 例:GET /chen.do HTTP/1.1
第二部分:请求头部 -> 接着请求行(第一行)之后部分,用来说明服务器要使用的附加信息
第三部分:空行 -> 请求头后面的空行是必须的,请求头部和数据主体之间必须有换行
第四部分:请求的数据也叫主体,可以添加任意的数据。
返回报文格式
第一部分:状态行 -> HTTP版本、状态码、状态消息 例:HTTP/1.1 200 OK
第二部分:响应报文头部 -> 接着响应行(第一行)之后的部分,用来说明服务器要使用的附加信息
第三部分:空行 -> 响应头后面的空行是必须的,请求头部和数据主体之间必须有换行
第四部分:响应正文。可以添加任意数据
响应状态码(HTTP协议)
1XX:临时响应 -> 表示临时响应并需要请求者继续执行的状态码
2XX;成功 -> 成功处理了请求的状态码
3XX;重定向 -> 表示完成请求,需要进一步操作。通常这些状态码用来重定向
4XX:请求错误 -> 表示请求可能出错,妨碍了服务器的处理
5XX:服务器错误 -> 表示服务器在尝试处理请求时发生内部错误。这些错误可能是服务器本身的错误,而不是请求出错
UDP:用户数据协议UDP是Internet传输层协议。提供无连接、不可靠、数据包尽力传输服务
UDP(user datagram protocol,用户数据报协议)是无连接的,面向消息的,提供高效率服务。不会使用块的合并优化算法,
由于UDP支持的是一对多的模式
相比TCP:
1.无需建立连接
2.无连接状态
3.首部开销小
TCP/UDP:
TCP UDP
面向连接 无连接
提供可靠性保障 不可靠
慢 快
资源占用多 资源占用少
Socket编程:Internet中应用最广泛的网络应用编程接口,实现于三种底层协议接口:
1.数据包类型套接字SOCK_DGRAM (面向UDP接口)
2.流式套接字SOCK_STREAM (面向TCP接口)
3.原始套接字SOCK_RAW (面向网络层协议接口IP、ICMP等)
主要socket API及调用过程:
创建套接字 -> 端点绑定 -> 发送数据 -> 接收数据 -> 释放套接字
BIO网络编程
阻塞IO:
阻塞(blocking)IO:资源不可用时,IO请求一直阻塞,直到反馈结果(有数据或超时)
非阻塞(non-blocking)IO:资源不可用时,IO请求离开返回,返回数据标识资源不可用
同步(synchronous)IO:应用阻塞在发送或接收数据的状态,直到数据成功传输或返回失败
异步:(asynchronous)IO:应用发送或接收数据后立即返回,实际处理时异步执行的
阻塞导致在处理网络I/O时,一个线程只能处理一个网络连接
注:阻塞和非阻塞是获取资源的方式。同步和异步是程序如何处理资源的逻辑设计。ServerSocket#accept、InputStream#read都是阻塞API。操作系统底层API中,默认Socket操作都是Blocking型(阻塞),send/recv等接口都是阻塞的。
API
服务端:ServerSocket:
listern(); accept();
客户端:Socket:
send();
NIO非阻塞网络编程(采用多路复用IO/事件驱动IO模型) https://www.cnblogs.com/lighten/p/8922186.html
三个核心组件:
Buffer:缓冲区 -> 与流式的 I/O 不同,NIO是基于块(Block)的,它以块为基本单位处理数据。缓冲是一块连续的内存块,是 NIO
读写数据的中转地。通道表示缓冲数据的源头或者目的地,它用于向缓冲读取或者写入数据,是访问缓冲的接口。相比较
直接对数组的操作,Buffer API更加容易操作和管理 示例:
三个重要属性:
1.capacity容量:作为一个内存块,Buffer具有一定的固定大小,也成为“容量”
2.position位置:写入模式时代表写数据的位置。读取模式时代表读取数据的位置
3.limit限制: 写入模式,限制等于buffer的容量。读取模式下,limit等于写入的数据量
ByteBuffer内存模型:
直接内存(direct堆外内存):在JVM之外向物理内存直接申请内存
ByteBuffrt directByteBuffer = ByteBuffer.allocateDirect(noBNytes);
优点:
1、进行IO或文件IO时比heapBuffer少一次拷贝。(file/socket -> OS memory -> jvm heap)
2、在GC范围之外,降低GC压力,但实现了自动管理。DirextByteBuffer类提供Cleaner对象(PhantomReference),Cleaner被GC前会执行clean方法,触发DirectByteBuffer中定义的Deallocator
非直接内存(heap堆内存):在JVM的申请内存
建议:性能确实可观的时候才去使用;分配给大型、长寿命;(网络传输、文件读写场景)
通过虚拟机参数MaxxDirectMemorySize限制大小,防止耗尽整个机器内存
Channel: 通道
代码 -> buffer -> channel
和标准的IO Stream区别:在一个通道内进行读取和写入时stream通常是单向的(input和output),可以非阻塞读取和写入通道,通道始终读取或写入缓冲区,channel的API涵盖了UDP/TCP网络和文件IO。(FileCjannel,DatagramCjannel,SocketChannel,ServerSocketChannel)
SocketChannel:用于创建TCP连接,类似java.net.Socket.有两种创建的方式:
1、客户端主动发起和服务的连接。
2、服务端获取新的连接
write写:write()在尚未写入任何内容可能就返回了。需要在循环中调用
read读:read()方法可能直接返回而根本不读取任何数据,根据返回的int值判断读取了多少字节
SockerChannel:客户端
//客户端主动发起连接的方式
SocketChannel socketChannel = SocketChannel.open();
//设置为非阻塞模式
socketChannel.configureBlocking(false);
//创建一个端口和ip
socketChannel.connect(new InetSocketAddress("htttp:163.com",8080));
//发送请求数据 - 向通道写入数据
socketChannel.write(byteBuffer);
//读取服务端返回 - 读取缓冲区的数据
int butesRead = socketChannel.read(byteBuffer);
//关闭连接
socketChannel.close();
ServerSocketChannel:服务端
//创建网络服务端
ServerSocketChannel serverSockerChannel = ServerSocketChannel.open();
//设为非阻塞模式
serverSocletChannel.configureBlocking(false);
//绑定端口
serverSocletChannel.socket().bind(new InetSocketAddress(8080));
while(true){
SocketChannel soccketChannel = serverSocletChannel.accept();
if(socketChannel != null){
//TCP请求 读取/响应
}
}
注:serverSocletChannel.accept()如果该通道处于非阻塞,如果没有挂起的连接则该方法立即返回null。
所以必须检查返回的对象是否为空
Selector:选择器
Thread -> Selector -> 多个channel(serverSocker socket) -> ByteBuffer
Selector: 选择器
四种事件:
Connect:连接(Selector.OP_CONNECT)
客户端与服务端建立连接,客户端触发事件
Accept: 准备就绪(OP_ACCEPT)
服务端检测到客户端连接请求,服务端触发事件
Read: 读取(OP_READ)
客户端与服务端建立连接之后读取操作,双端触发事件
Writ: 写入(OP_WRITE)
客户端与服务端建立连接之后写入操作,双端触发事件
SelectableChannel: 可选择通道
SelectionKey: 选择键
服务端:
对于服务端而言首先需要的就是确定监听的端口,其次是与之对应的channel,而后就是selector,最后还需要一个线程池。为什么会需
要线程池呢?道理很简单,select模式获取了所有channel的change,对于服务端而言,change的可能有非常多的客户端channel,而用
户程序只有一个线程,如果这么多个channel一个个顺序执行,如果有耗时严重的操作,那么后果是非常糟糕的,所有客户端都会延时
处理,这也是多路IO复用的一个糟糕点。线程池就是为每个客户端分配一个线程去处理,减缓这种情况的后果。Server的基本四个内容
就出来了:
private int port;
private Selector selector;
private ServerSocketChannel serverSocketChannel;
private ExecutorService executorService;
接下来就是初始化服务端。初始化的步骤也是一般化:1.初始化连接池;2.初始化Selector;3.初始化绑定端口;4.将socket注
册到select上。大致步骤就是这些,但是还有些额外的细节。具体代码如下:
executorService = Executors.newCachedThreadPool();
selector = Selector.open();
serverSocketChannel = ServerSocketChannel.open();
serverSocketChannel.configureBlocking(false);
serverSocketChannel.socket().bind(new InetSocketAddress(port));
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
selector()方法:
NIO使用的IO原型是多路复用IO模型,也叫事件驱动IO,这种IO模型采用select/epoll(IO多路复用,复用同一个线程)模式,
select会轮询查询所有绑定在自身上的Channel,当有某个Channel数据到达了,就会通知进程(Channel数据到达意思是绑定在
Channel上的socket产生了链接)。select()方法就是Selector通知进程之前使当前的代码段进入阻塞状态,说白了就是
在while(true)里阻塞,不消耗cpu而已
发送流程:
不管是客户端发送服务端接收,还是服务端发送客户端接收,基本的流程都是:发送方发送数据->buffer->发送方channel->接收方channel->buffer->接收方接收数据
阻塞和非阻塞IO对比:
当用户进程调用了select,那么整个进程会被block,而同时,kernel会“监视”所有select负责的socket,当任何一个socket中
的数据准备好了,select就会返回。这个时候用户进程再调用read操作,将数据从kernel拷贝到用户进程。这个图和blocking IO
的图其实并没有太大的不同,事实上还更差一些。因为这里需要使用两个系统调用(select和recvfrom),而blocking IO只调用
了一个系统调用(recvfrom)。但是,用select的优势在于它可以同时处理多个connection。
总结:
1. 如果处理的连接数不是很高的话,使用select/epoll的web server不一定比使用multi-threading + blocking IO的web server性
能更好,可能延迟还更大。select/epoll的优势并不是对于单个连接能处理得更快,而是在于能处理更多的连接。
2. 在多路复用模型中,对于每一个socket,一般都设置成为non-blocking,但是,如上图所示,整个用户的process其实是一直
被block的。只不过process是被select这个函数block,而不是被socket IO给block。
结论: select的优势在于可以处理多个连接,不适用于单个连接