Golang代码笔记--netpoller

netpoller

背景介绍

I/O多路复用模型(I/O Multiplexing):

select

阻塞,直到有FD准备好,FD数量有FD_SETSIZE限制

int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds,
 struct timeval *timeout);

// readfds,writefds,exceptfds 需要检查的FDs
// nfds 比最大的FD大1(减少内核比较次数)
// timeout 最大等待时间
// fd_set 位掩码表示FD集合

poll

和select类似,在传递FD方式上不同

int poll(struct pollfd fds[], nfds_t nfds, int timeout);

// fds 需要检查的FDs和事件(events,revents),结果写入fds
// nfds fds中的FD数量
// timeout 最大等待时间

struct pollfd {
   
 int fd; /* File descriptor */
 short events; /* Requested events bit mask */
 short revents; /* Returned events bit mask */
};

epoll

区分水平触发边缘触发

// 创建epoll示例:红黑树,就绪队列,返回epoll实例的FD
int epoll_create(int size);
int epoll_create1(int flag); // 与epoll_create1类似,对epfd有一定的控制

// FD注册
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *ev);
// epfd epoll_create返回的FD
// op 操作类型 EPOLL_CTL_ADD EPOLL_CTL_MOD EPOLL_CTL_DEL
// fd 操作对象FD
// ev 关注的事件类型(位掩码)

struct epoll_event {
   
 uint32_t events; /* epoll events (bit mask) */
 epoll_data_t data; /* User data */
};

typedef union epoll_data {
   
 void *ptr; /* Pointer to user-defined data */
 int fd; /* File descriptor */
 uint32_t u32; /* 32-bit integer */
 uint64_t u64; /* 64-bit integer */
} epoll_data_t;

// 获取就绪的FD
int epoll_wait(int epfd, struct epoll_event *evlist, int maxevents, int timeout);
// evlist,maxevents指定返回的event的存储空间和数量

// 自动销毁

epoll相对于其他I/O多路复用模型的优势:

1.不需要每次调用传递FD(用户态/内核态数据拷贝),epoll_ctl将FD添加到内核数据空间中;

2.epoll_wait的效率更高,不需要对比所有的FD,只需要从就绪队列中获取数据即可;

常见I/O模型:阻塞,非阻塞,I/O多路复用,信号驱动,异步I/O

分析实例:File.Read

在unix/linux平台上,netpoller是基于epoll模型来实现的,一下分析也是限定于此;

以简单的文件读取(unix|linux平台)为例,分析从代码层面开始是怎么一步步使用netpoller的。

## 用户代码
func main() {
	f, err := os.Open("test.txt")
	if err != nil {
		log.Fatalln(err)
	}
	buf := make([]byte, 10)
	f.Read(buf)
	log.Println(string(buf))
}

使用os.Open创建一个File实例,核心是获取到文件描述符(pfd)

type File struct {
   
	*file // os specific
}

// unix平台实现
// ## os/file.go
type file struct {
   
	pfd         poll.FD
	name        string
	dirinfo     *dirInfo // nil unless directory being read
	nonblock    bool     // whether we set nonblocking mode
	stdoutOrErr bool     // whether this is stdout or stderr
}

/*os/file.go*/
func (f *File) Read(b []byte) (n int, err error) {
   
	if err := f.checkValid("read"); err != nil {
   
		return 0, err
	}
	n, e := f.read(b)
	return n, f.wrapErr("read", e)
}
/*os/file_unix.go(unix平台)*/
func (f *File) read(b []byte) (n int, err error) {
   
	n, err = f.pfd.Read(b)
	runtime.KeepAlive(f)
	return n, err
}

调用Read函数,实际上调用了底层的FD.Read,其实现如下:

/*internal/poll/fd_unix.go*/
func (fd *FD) Read(p []byte) (int, error) {
   
	if err := fd.readLock(); err != nil {
   
		return 0, err
	}
	defer fd.readUnlock()
	if len(p) == 0 {
   
		// If the caller wanted a zero byte read, return immediately
		// without trying (but after acquiring the readLock).
		// Otherwise syscall.Read returns 0, nil which looks like
		// io.EOF.
		// TODO(bradfitz): make it wait for readability? (Issue 15735)
		return 0, nil
	}
	if err := fd.pd.prepareRead(fd.isFile); err != nil {
   
		return 0, err
	}
	if fd.IsStream && len(p) > maxRW {
   
		p = p[:maxRW]
	}
	for {
   
        // 系统调用 -- 读取数据
		n, err := syscall.Read(fd.Sysfd, p)
        // 出现错误 -- 可能是一般错误也可能是数据未准备好的特殊错误(EAGAIN)
		if err != nil {
   
			n = 0
            // syscall.EAGAIN错误 & 可以使用netpoller
            // 比如:pollable := kind == kindOpenFile || kind == kindPipe || kind == kind
  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值