[动手实现] IO模式:NIO:主从Reactor

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工作模式:

  1. NIO主要有几个事件,包括读就绪,写就绪, 新连接到来, 当有新事件操作时,首先把事件注册到对应的处理器;
  2. 并由一个线程不断循环等待,调用操作系统底层的函数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存在的问题

  1. 使用NIO != 高性能,当连接数<1000,并发程度不高或者局域网环境下NIO并没有显著的性能优势。
  2. 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,这里采用异步调用
[动手实现] IO模式:NIO:主从Reactor_第1张图片

实现

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

你可能感兴趣的:(#,Go,计算机网络,动手实现,网络,epoll,linux)