跳到主要内容

Socket 编程

问题

什么是 Socket?Java 中 BIO、NIO、AIO 有什么区别?

答案

Socket 通信模型

BIO vs NIO vs AIO

模型全称线程模型适用场景
BIOBlocking I/O一个连接一个线程连接少、固定
NIONon-blocking I/O一个线程管多个连接高并发、短请求
AIOAsync I/O回调通知连接多、操作重

BIO(同步阻塞)

BIO - 一个连接一个线程
ServerSocket server = new ServerSocket(8080);
while (true) {
Socket socket = server.accept(); // 阻塞等待连接
new Thread(() -> {
InputStream in = socket.getInputStream();
byte[] buf = new byte[1024];
int len = in.read(buf); // 阻塞等待数据
// 处理请求...
}).start();
}
BIO 的问题

每个连接需要一个线程,10000 个连接需要 10000 个线程。线程资源昂贵(默认 1MB 栈空间),大量线程切换的 CPU 开销也很大。

NIO(同步非阻塞 + 多路复用)

NIO - Selector 多路复用
Selector selector = Selector.open();
ServerSocketChannel serverChannel = ServerSocketChannel.open();
serverChannel.configureBlocking(false);
serverChannel.bind(new InetSocketAddress(8080));
serverChannel.register(selector, SelectionKey.OP_ACCEPT);

while (true) {
selector.select(); // 阻塞等待就绪事件
Set<SelectionKey> keys = selector.selectedKeys();
for (SelectionKey key : keys) {
if (key.isAcceptable()) {
// 新连接
SocketChannel client = serverChannel.accept();
client.configureBlocking(false);
client.register(selector, SelectionKey.OP_READ);
} else if (key.isReadable()) {
// 有数据可读
SocketChannel client = (SocketChannel) key.channel();
ByteBuffer buffer = ByteBuffer.allocate(1024);
client.read(buffer);
// 处理请求...
}
}
keys.clear();
}

NIO 三大核心:

  • Channel:双向通道(替代 Stream)
  • Buffer:缓冲区(数据读写的中转站)
  • Selector:多路复用器(一个线程监听多个 Channel 的事件)

Netty

实际项目中很少直接用 NIO(API 复杂、有 Bug),通常用 Netty 框架。

Netty 服务端
EventLoopGroup bossGroup = new NioEventLoopGroup(1);     // 接收连接
EventLoopGroup workerGroup = new NioEventLoopGroup(); // 处理 IO

ServerBootstrap bootstrap = new ServerBootstrap();
bootstrap.group(bossGroup, workerGroup)
.channel(NioServerSocketChannel.class)
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) {
ch.pipeline().addLast(new StringDecoder());
ch.pipeline().addLast(new SimpleChannelInboundHandler<String>() {
@Override
protected void channelRead0(ChannelHandlerContext ctx, String msg) {
ctx.writeAndFlush("收到: " + msg);
}
});
}
});

bootstrap.bind(8080).sync();

常见面试问题

Q1: select、poll、epoll 的区别?

答案

维度selectpollepoll
最大连接数1024 (FD_SETSIZE)无限制无限制
数据结构位图链表红黑树 + 就绪链表
遍历方式每次全遍历每次全遍历回调通知就绪的 fd
性能O(n)O(n)O(1) 事件通知
内核拷贝每次 fd 复制到内核每次 fd 复制到内核mmap 共享内存

Java NIO 的 Selector 在 Linux 上底层使用 epoll

Q2: Netty 的线程模型?

答案

Netty 使用 Reactor 主从多线程模型

  • Boss Group(1 个线程):负责接收新连接(accept)
  • Worker Group(N 个线程):负责 IO 读写和业务处理

每个 Channel 绑定到固定的 EventLoop(线程),避免线程竞争。

Q3: 什么是零拷贝?

答案

传统 IO 需要 4 次数据拷贝(磁盘→内核→用户→内核→网卡)。零拷贝通过减少拷贝次数提升性能:

  • mmap:内核缓冲区和用户空间共享,减少一次拷贝
  • sendfile:数据直接从内核缓冲区到网卡,绕过用户空间

Kafka 的高吞吐就依赖 sendfile 零拷贝。Netty 使用 FileChannel.transferTo() 实现零拷贝。

详见 IO 与 NIO

相关链接