目录
1.BIO
BIO的基本特点:BIO基本特点就是服务器在接收到客户端请求之前(accept方法)会进行阻塞,并且每接收到一个连接,都会新建一个线程去处理客户端的请求,并且读取客户端数据的过程是阻塞式的,如果没有读到对应的数据会一直等待。并且客户端数据发送完毕后要么直接关闭连接,否则必须告诉服务器已经发送完毕了,不然的话服务器会一直阻塞在那里。
public class BIOServer {
@Test
public void Server() throws IOException {
//创建线程池
ExecutorService pool = Executors.newCachedThreadPool();
//创建服务器端
ServerSocket serverSocket = new ServerSocket(9898);
while (true){
System.out.println("accept方法调用之前...");
final Socket socket = serverSocket.accept();
System.out.println("accept方法阻塞结束...");
pool.execute(new Runnable() {
@Override
public void run() {
try {
handlerClient(socket);
} catch (IOException e) {
e.printStackTrace();
}
}
});
}
}
private void handlerClient(Socket socket) throws IOException {
InputStream is =
socket.getInputStream();
OutputStream os = socket.getOutputStream();
byte[] bytes = new byte[1024];
int len = 0;
System.out.println("准备读取客户端发送的数据......");
System.out.println("---------------------------");
while ((len = is.read(bytes)) != -1){
System.out.println(new String(bytes,0,len));
}
System.out.println("读取客户端数据完毕");
os.write("已经收到了你发送的数据".getBytes());
socket.shutdownOutput();
socket.close();
}
@Test
public void client() throws IOException, InterruptedException {
Socket socket = new Socket("127.0.0.1",9898);
OutputStream os = socket.getOutputStream();
InputStream is = socket.getInputStream();
Scanner scanner = new Scanner(System.in);
while (scanner.hasNext()){
String line = scanner.next();
if (line.equals("bye")){
// socket.shutdownOutput();
socket.close();
break;
}
os.write(line.getBytes());
}
// scanner.close();
// byte[] res = new byte[1024];
// int len = 0;
// while ((len = is.read(res)) != -1){
// System.out.println(new String(res,0,len));
// }
// socket.close();
}
}
2.NIO
依赖selector的事件监听机制,传统BIO的accept()方法和read()方法都会阻塞,而NIO设置为非阻塞模式后,accept()准备就绪后才会调用,基本相当于不阻塞,而read方法读到了就正常返回,读不到就返回0,不会阻塞。
public class NIO {
@Test
public void server() throws IOException {
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
serverSocketChannel.bind(new InetSocketAddress(9898));
SocketChannel accept = serverSocketChannel.accept();
ByteBuffer buffer = ByteBuffer.allocate(1024*1024*8);
FileChannel open = FileChannel.open(Paths.get("2.mp4"), StandardOpenOption.CREATE,
StandardOpenOption.WRITE);
while (accept.read(buffer) != -1){
// buffer.clear();
buffer.flip();
open.write(buffer);
buffer.clear();
}
accept.close();
serverSocketChannel.close();
}
@Test
public void client() throws IOException {
SocketChannel socketChannel = SocketChannel.open(
new InetSocketAddress("127.0.0.1",9898));
FileChannel fileChannel = FileChannel.open(Paths.get("D:\\JavaworkSpace\\Netty\\IO\\1.mp4"),
StandardOpenOption.READ);
long totalSize = fileChannel.size();
long position = 0;
long oneSize = 1024 * 1024 * 8;
System.out.println("进入");
while (position < totalSize){
long curLen = totalSize - position >= oneSize ? oneSize:totalSize - position;
fileChannel.transferTo(position,curLen,socketChannel);
position += oneSize;
System.out.println(position);
}
fileChannel.close();
socketChannel.close();
}
}
3.Netty
netty是什么?
答:netty是一个网络应用服务框架,主要用来解决我们传统NIO进行网络编程时的不方便,提高我们的实际开发业务的效率。
3.1 传统阻塞式IO模型
传统阻塞式IO的特点
- 采用阻塞式IO获取输入的数据
- 一个连接对应一个线程,通过一个线程来完成数据读取,业务处理和数据返回
缺点:
- 一个连接对应一个线程,高并发情况下服务器端压力过大,不适用
- 采用的阻塞式IO,线程读取不到数据时会阻塞在read()方法,造成不必要的线程资源浪费。
3.2 Reator模型
reator模型的基本思想:通过一个reactor派发者监听各个连接通道的事件,事件发生后再交给对应的handler去处理,而不用一直等待着一个通道的状态。
3.2.1单reactor单线程模型
单reactor单线程即一个reactor监听所有的通道,并且在同一个线程内完成各个通道的请求处理。
特点:模型简单、适用于请求处理很快、客户端数量较少的场景。但是在客户端数量很多时,由于一直在一个线程内进行处理,如果当前channel的事件处理时间较长,其他channel的请求只能阻塞等待。
3.2.2单reactor多线程模型
由于单reactor单线程模型某个channel的业务处理时间过长会影响其他channel的业务处理,那么我们在对每个channel的业务进行处理时,去线程池中获取一个线程来处理当前的业务,那么reactor线程将无需等待每个channel的业务处理,可以专心执行监听和转发逻辑
特点:reactor不用等待每个请求的的业务处理,可以直接对每个请求进行响应和转发,但是在高并发情况下,单个reactor处理所有的channel可能会遇到性能瓶颈。
3.2.3主从reactor多线程模型
基于单reactor多线程模型的单reactor的性能瓶颈,设计出了主从reactor多线程的模型。
主从reactor主要是一个主reactor负责管理连接时间,获取到连接事件之后,让子reactor去监听对应连接的读写事件等,然后子reactor的业务处理又如单reactor多线程一致,将对应的业务处理交给work线程进行执行。
主从reactor多线程的优缺点
优点:分为三层,层次清晰并且并发性能好
缺点:编码复杂
3.3 netty架构模型
3.4 Netty的执行流程
1.BossGroup中的NioEventLoop中的selector注册了一个ServerSocketChannel,不断监听我们的ServerSocketChannel的Accept事件。
2.当ServersocketChannel监听到Accept事件后,调用accept方法获取道对应的SocketChannel,并建立与之对应的ChannelPipeLine,然后调用handler方法处理initChannel事件,我们一般往socketChannel中添加业务相关的handler就是在initChannel方法中完成的,并且对应的ChannelInitializer是我们在服务器初始化加入的,因此只有一份。然后在initChannel方法中我们获取channel.pipeline().addLast(),往pipeline中动态添加我们的handler。
3.initChannel执行完后,我们会将socketChannel注册到worker的NioEventLoop的selector中,由它检测我们的其他事件,如读就绪等待。然后检测到对应事件后去我们的pipeline中循环对应事件能处理的handler(其实是一个DefaultChannelHandlerContext的双向链表,代表我们业务处理的上下文,里面保存了当前业务对应的channel和pipeline)
4.完成业务处理最终返回给客户端结果
上述只是站在我们的handler方面对netty流程进行了一个简要分析,真实的netty业务流程比这复杂很多,还包括我们的异步任务队列、定时队列等等。
(图文都来源于自己做的笔记,图丑勿怪,灵魂画家啊哈哈)