NIO基础
NIO(Non-blocking IO) 是一种同步非阻塞支持面向缓冲的,基于通道的I/O,也是IO多路复用的基础,主要是解决高并发 或者 处理海量连接,IO处理问题
IO模式
所有的IO模式都分为两个阶段, 一是等待就绪(准备数据)也就是从网卡copy到内核缓存区(从内核缓存区copy到网卡), 二是真正的操作(读,写) 也就是从内核缓存区copy到用户地址空间;
IO模式 | 等待就绪阶段 是否阻塞 | 读写、拷贝阶段 是否阻塞 |
---|---|---|
BIO (Blocking IO) | 是 | 是 |
NIO(Non-blocking IO) | 否 | 是 |
AIO(Async IO) | 否 | 否 |
NIO工作模式:
- NIO主要有几个事件,包括读就绪,写就绪, 新连接到来, 当有新事件操作时,首先把事件注册到对应的处理器;
- 并由一个线程不断循环等待,调用操作系统底层的函数select() 或者 epoll(Linux 2.6之前是select、poll,2.6之后是epoll,Windows是iocp),并负责向操作系统查询IO是否就绪(标记:从网卡已经拷贝到内核缓存区,准备就绪),如果就绪执行事件处理器(从内核缓存区到用户内存)
select 与 epoll的区别:
1、每次调用select,都要把fd_set(加入文件描述符至集合)从用户态拷贝到内核态,fd_set很大时这是费时操作
2、每次调用select,内核态都要遍历fd_set,fd_set很大时这是费时操作, epoll 如果准备就绪,系统回调通知,有个回调函数;
3、select支持的文件描述符数量太小,默认是1024, epoll没有限制(系统最大的文件句柄数)
NIO存在的问题
- 使用NIO != 高性能,当连接数<1000,并发程度不高或者局域网环境下NIO并没有显著的性能优势。
- NIO并没有完全屏蔽平台差异,它仍然是基于各个操作系统的I/O系统实现的,差异仍然存在。使用NIO做网络编程构建事件驱动模型并不容易,陷阱重重
主从reactor
架构
Service:服务器的抽象
MasterReactor:主reactor,负责监听网络端口FD状态(linux使用epoll,LT或者ET))
SlaveReactor:从reactor,负责监听文件FD的IO ready时间、处理connection
connection:TCP会话抽象,保持着request和response的socket FD
handler:处理函数,结果可以同步返回,也可以通过channel移步通知给SlaveReactor,这里采用异步调用
实现
nio/nio.go
package nio
import (
"fmt"
"net"
)
// 定义实体
type MasterReactor struct {
net.Listener
}
type SlaveReactor struct {
h func(net.Conn)
buf chan struct{}
}
func (s *SlaveReactor)Handle(conn net.Conn) {
s.buf <- struct{}{}
go func(){
s.h(conn)
<-s.buf
}()
}
// 定义 construction method
func NewMaster(l net.Listener) MasterReactor{
return MasterReactor{l}
}
func NewSlave(fn func(net.Conn), bufSize int) SlaveReactor {
return SlaveReactor{h: fn, buf: make(chan struct{}, bufSize)}
}
// 定义 handler method, 这个是一个echo method
func HandleConn(conn net.Conn) {
defer conn.Close()
packet := make([]byte, 1024)
// 如果没有可读数据,也就是读 buffer 为空,则阻塞
_, _ = conn.Read(packet)
// 同理,不可写则阻塞
_, _ = conn.Write(packet)
}
func Service() {
listen, err := net.Listen("tcp", ":8089")
if err != nil {
fmt.Println("listen error: ", err)
return
}
master := NewMaster(listen)
slave := NewSlave(HandleConn, 1024)
for {
conn, err := master.Accept()
if err != nil {
fmt.Println("accept error: ", err)
break
}
// start a new goroutine to handle the new connection
slave.Handle(conn)
}
}
测试
nio/nio_test.go
package nio
import (
"fmt"
"os"
"os/exec"
"testing"
"time"
)
func TestService(t *testing.T) {
go Service()
time.Sleep(3*time.Second)
fmt.Println(string(CUrlPOST()))
}
func CUrlPOST() []byte{
cmd := exec.Command("/bin/sh", "-c", `curl -X POST 'http://127.0.0.1:8089' --data "hello world"`)
resp, err := cmd.Output()
if err != nil {
fmt.Println("Output error ",err, resp)
os.Exit(1)
}
return resp
}
POST / HTTP/1.1
Host: 127.0.0.1:8089
User-Agent: curl/7.64.1
Accept: */*
Content-Length: 11
Content-Type: application/x-www-form-urlencoded
hello world