Netty 系列(二)NIO 入门

Netty 系列(二)NIO 入门

Socket(套接字),应用程序通过"套接字"向网络发出请求或者应答网络请求。最早是 Unix 上的一套网络程序通讯的标准,已被广泛移植到其它平台。

Socket 通信原理

在 Internet 上的主机一般运行了多个服务软件,同时提供了几种服务,每种服务都打开一个 Socket 并绑定到一个端口上,不同的端口对应不同的服务进程。

Socket 实质上提供了进程通信的端点,网络上的两个程序通过一个双向的通讯链路实现数据的交换,这个双向链路的一端称为一个 Socket。

Socket

Socket 封装了应用层和传输层的功能,不需要我们自己去实现

Socket 类型

Socket 有以下三种类型:流式套接字(SOCK_STREAM)、数据套接字(SOCK_DGRAM)、原始式套接字(SOCK_RAW)。

  • 流式套接字:提供了一个面向连接,可靠的数据传输服务,数据无差错、无重复的发送,且按发送顺序接收。对应使用的是 TCP 协议。

  • 数据套接字:提供了无连接服务。数据包以独立包形式被发送,不提供无差错保证,数据可能丢失或重复,并且接收顺序无序。对应使用的是 UDP 协议。

  • 原始式套接字:该接口允许对较低层次协议,如IP直接访问。可以接收本机网卡上的数据帧或数据包,对监听网络流量和分析很有用。

TCP通信

Socket 和 ServerSocket 类库位于 java.net 包中。ServerSocket 用于服务器端,Socket 是建立网络连接时使用的。在连接成功时,应用程序两端都会产生一个 Socket 实例,操作这个实现完成所需的会话。对于一具网络连接来说,套接字是平等的,不因为在服务器端或客户端而产生不同级别。不管是 Socket 还是 ServerSocket 它们的工作都是通过 SocketImpl 类及其子类完成的。

我们从一个简单的例子 com.github.binarylei.network.socket.bio.demo1 开始学习 Socket,首先编写一个服务器:

//1. 绑定端口
ServerSocket server = new ServerSocket(6275);
System.out.println("server listening on " + 6275);

while (true) {
    //2. 获取客户端请求的Socket,没有请求就阻塞
    Socket socket = server.accept();
    //3. 开启一个线程执行客户端的任务
    new Thread(new ServerHandler(socket)).start();
}

开启线程 ServerHandler 处理客户端的请求:

@Override
public void run() {
    BufferedReader in = new BufferedReader(new InputStreamReader(this.socket.getInputStream()));
    PrintWriter out = new PrintWriter(this.socket.getOutputStream(), true);

    String body = null;
    while(true){
        //1. 接收客户端数据
        body = in.readLine();
        if(body == null) break;
        System.out.println("Server: " + body);

        //2. 发送响应数据
        out.println("服务器端回送响应数据.");
    }
}

启动客户端:

//1. 连接服务器
Socket socket = new Socket("127.0.0.1", 6275);
BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
PrintWriter out = new PrintWriter(socket.getOutputStream(), true);

//2. 发送请求数据
out.println("客户端发送请求数据...");

//3. 接收服务端数据
String response = in.readLine();
System.out.println("Client: " + response);

OK! 一个最简单的 Socket 通信就建好了,执行结果如下:

// Server 端:
server listening on 8765
Server: 客户端发送请求数据...

// Client 端:
Client: 服务器端回送响应数据.

上述方案虽然实现了 Socket 通信,但是每来一个请求就开启一个线程,1000 个请求就开启 1000 个线程,而服务器能承受的线程数是有限的,这明显不科学。怎么办呢?就是下面要讲的,利用线程池实现伪异步I/O (JDK1.7 之前)

伪异步I/O

采用线程池和任务队列可以实现一种伪异步的IO通信框架。就是将客户端的 socket 封装成一个 task 任务(实现 Runable 接口的类),然后投递到线程池中,配置相应的队列进行实现。

看下面这个例子:com.github.binarylei.network.socket.bio.demo2

//1. 绑定端口
ServerSocket server = new ServerSocket(6275);
System.out.println("server listening on " + 6275);

//2. 获取客户端请求的Socket,没有请求就阻塞
Socket socket = null;
ThreadPoolExecutor executorPool = = new ThreadPoolExecutor(
    Runtime.getRuntime().availableProcessors(),
    50, 
    120L, 
    TimeUnit.SECONDS,
    new ArrayBlockingQueue<Runnable>(1000));

while(true){
    socket = server.accept();
    executorPool.execute(new ServerHandler(socket));
}

//3. 开启一个线程执行客户端的任务
new Thread(new ServerHandler(socket)).start();

BIO和NIO的区别

BIO(Block IO)和NIO(NoN-Block IO)本质就是阻塞和非阻塞的区别。

  • 阻塞概念:应用程序在获取网络数据的时候,如果网络传输数据很慢,那么程序就一直等待,直到传输完毕为止。

  • 非阻塞概念:应用程序直接可以获取已经准备就绪的数据,无需等待。

注意:BIO 为同步阻塞形式,NIO 为同步非阻塞形式,NIO 并没有实现异步。在 JDK 1.7 后,升级了 NIO 库,支持异步非阻塞通信模型,即 NIO2.0(AIO)。

同步和异步的区别:

同步和异步一般是面向操作系统与应用程序对 IO 操作的层面上来区别的。

  • 同步时,应用程序会直接参与 IO 读写操作,并且我们的应用程序会直接阻塞到某一个方法上,直到数据准备就绪;或者采用轮询的策略实时检查数据的就绪状态,如果就绪则获取数据。

  • 异步时,所有的 IO 读写操作交给操作系统处理,与我们的应用程序没有直接关系,我们程序不需要关心 IO 读写,当操作系统完成 IO 读写操作时,会给我们应用程序发送,应用程序直接拿走数据即可。

总结:同步是 server 服务器端的执行方式。阻塞是具体的技术,接收数据的方式、状态(BIO/NIO)


每天用心记录一点点。内容也许不重要,但习惯很重要!

猜你喜欢

转载自www.cnblogs.com/binarylei/p/8946747.html