Socket 之 BIO、NIO、Netty 简单实现

本文详细讲解了Java的BIO、NIO和AIO模型,比较了它们在IO操作中的同步异步和阻塞非阻塞特性,并深入剖析了Netty在高并发网络通信中的应用,包括其模型、优点和聊天室实例。

一、概念

(1)Socket:套接字(Socket)是通信的基石,是支持 TCP/IP 协议的网络通信的基本操作单元,包含进行网络通信必须的五种信息:

  • 连接使用的协议
  • 本地主机的IP地址
  • 本地进程的协议端口
  • 远地主机的IP地址
  • 远地进程的协议端口

多个 TCP 连接或多个应用程序进程可能需要通过同一个 TCP 协议端口传输数据。为了区别不同的应用程序进程和连接,计算机操作系统为应用程序与 TCP/IP 协议交互提供了 套接字(Socket)接口。应用层可以和传输层通过Socket 接口,区分来自不同应用程序进程或网络连接的通信,实现数据传输的并发服务。

建立 Socket 连接至少需要一对套接字,其中一个运行于客户端,称为 ClientSocket,另一个运行于服务器端,称为 ServerSocket

套接字之间的连接过程分为三个步骤:服务器监听,客户端请求,连接确认。

Socket 可以支持不同的传输层协议(TCP/UDP),当使用 TCP 协议进行连接时,该 Socket 连接就是一个 TCP 连接, UDP 连接同理。

(2)IO:输入/输出(InputStream/OutPutStream),在 Java 中有三种方式:

  1. BIO:同步阻塞 IO(Blocking IO)。B 代表 Blocking。

  2. NIO:同步非阻塞 IO(Non-Nlocking IO / New IO)。集成在 JDK 1.4 及以上版本。

  3. AIO:异步非阻塞 IO(Asynchronize IO)。A 代表 Asynchronize。

(3)同步、异步、阻塞、非阻塞

  1. 同步:指用户进程触发 IO 操作后通过等待或者轮训的方式查看 IO 操作是否完成。

  2. 异步:当一个异步进程调用发出之后,调用者不会立刻得到结果。而是在调用发出之后,被调用者通过状态、通知来通知调用者,或者通过回调函数来处理这个调用。使用异步 IO 时,Java 将 IO 读写委托给 OSOperation System 即:操作系统) 处理,需要将数据缓冲区地址和大小传给 OS,OS 需要支持异步 IO 操作。

  3. 阻塞:进程在读取或写入数据时,会一直处于等待状态不能做其他事情,直到操作完成。

  4. 非阻塞:进程在读取或写入数据时,进程不会处于等待状态,可以做其他事情。

举例

故事:张三烧水。

演员:张三

道具:水壶两把(普通水壶,简称水壶;会响的水壶,简称响水壶)。

  1. 同步阻塞(BIO):张三把水壶放到火上,并把自己关在厨房里,盯着壶里的水,等它烧开。
  2. 异步阻塞:张三把响水壶放到火上,并把自己关在厨房里,不用盯着壶里的水,靠汽笛声辨别是否烧开。(汽笛声:事件驱动)。
  3. 同步非阻塞(NIO):张三把水壶放到火上,然后就去书房学习,但是为了及时用上热水,他时不时的就得到厨房看一下烧水的状态。(时不时查看状态:轮询)
  4. 异步非阻塞(AIO):张三把响水壶放到火上,然后就去书房学习,不用时不时的到厨房看下烧水的状态,靠汽笛声辨别是否烧开。

二、基本介绍与实现

2-1、BIO 实现

SocketBIO 实现比较简单,也没有太多复杂的概念。但由于效率低下,故实际应用率不高。所以,鉴于以上两点,这里就直接上代码了。

MyBIOClient.java
import java.net.InetSocketAddress;
import java.net.Socket;
import java.text.SimpleDateFormat;
import java.util.Date;

/**
 * @author Supreme_Sir
 * @version 1.0
 * @className MyClient
 * @description 客户端
 * @date 2020/12/20 20:52
 **/
public class MyBIOClient {
   
   
    public static void main(String[] args) throws Exception {
   
   
        // 创建 Socket 客户端
        Socket socket = new Socket();
        // 与服务端建立连接
        socket.connect(new InetSocketAddress("127.0.0.1", 8081));

        SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy年MM月dd日 HH时mm分ss秒");
        int counter = 0;
        while (counter < 5) {
   
   
            String now = simpleDateFormat.format(new Date());
            // 发送请求
            socket.getOutputStream().write(now.getBytes("UTF-8"));
            socket.getOutputStream().flush();
            Thread.sleep(1000);
            counter++;
        }
        // 若方法运行结束后,不调用 close 函数,服务端则会报错:java.net.SocketException: Connection reset
        socket.close();
        System.out.println("客户端关闭了 Socket 连接~!");
    }
}
MyBIOService.java
import java.net.ServerSocket;
import java.net.Socket;
import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

/**
 * @author Supreme_Sir
 * @version 1.0
 * @className MyService
 * @description 服务端
 * @date 2020/12/20 20:53
 **/
public class MyBIOService {
   
   
    public static void main(String[] args) throws Exception {
   
   
        // 创建 Socket 服务端,并设置监听的端口
        ServerSocket serverSocket = new ServerSocket(8081);
        // 创建线程池以执行客户端请求(防止因请求过多,而导致的阻塞)
        ThreadPoolExecutor poolExecutor = new ThreadPoolExecutor(1, 10, 60,
                TimeUnit.SECONDS, new LinkedBlockingDeque<Runnable>());
        while (true) {
   
   
            // 阻塞方法,监听客户端请求
            Socket socket = serverSocket.accept();
            System.out.println("\r\n" + socket);
            // 创建自定义请求处理器
            SocketHandler handler = new SocketHandler(socket);
            // 处理客户端请求
            poolExecutor.execute(handler);
        }
    }
}
SocketHandler.java
import java.net.Socket;

/**
 * @author Supreme_Sir
 * @version 1.0
 * @className Handler
 * @description Socket 处理器
 * @date 2020/12/20 21:06
 **/
public class SocketHandler implements Runnable {
   
   
    private Socket socket;
    private static final byte[] BUFFER = new byte[1024];

    @Override
    public void run() {
   
   
        try {
   
   
            while (true){
   
   
                // 读取客户端 Socket 请求数据
                int read = socket.getInputStream().read(BUFFER);
                if (read != -1) {
   
   
                    System.out.println(new String(BUFFER, "UTF-8"));
                }else
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值