Golang Epoll初体验

Posted Time-Traveler

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Golang Epoll初体验相关的知识,希望对你有一定的参考价值。

本文参考自:https://colobu.com/2019/02/23/1m-go-tcp-connection,本文只是简单运行一个服务端的demo,更多信息请浏览原文


关于Golang的epoll:

Go的net方式在Linux也是通过epoll方式实现的,为什么我们还要再使用epoll方式进行封装呢?原因在于Go将epoll方式封装再内部,对外并没有直接提供epoll的方式来使用。也有一些封装了epoll的库如:evio

Epoll server实现:

package main

import (
	"fmt"
	"golang.org/x/sys/unix"
	"log"
	"net"
	"net/http"
	"reflect"
	"sync"
	"syscall"
)

var epoller *epoll

type epoll struct {
	fd          int
	connections map[int]net.Conn
	lock        *sync.RWMutex
}
func MkEpoll() (*epoll, error) {
	fd, err := unix.EpollCreate1(0)
	if err != nil {
		return nil, err
	}
	return &epoll{
		fd:          fd,
		lock:        &sync.RWMutex{},
		connections: make(map[int]net.Conn),
	}, nil
}
func (e *epoll) Add(conn net.Conn) error {
	// Extract file descriptor associated with the connection
	fd := socketFD(conn)
	err := unix.EpollCtl(e.fd, syscall.EPOLL_CTL_ADD, fd, &unix.EpollEvent{Events: unix.POLLIN | unix.POLLHUP, Fd: int32(fd)})
	if err != nil {
		panic(err)
	}
	e.lock.Lock()
	defer e.lock.Unlock()
	e.connections[fd] = conn
	if len(e.connections)%100 == 0 {
		log.Printf("add total number of connections: %v", len(e.connections))
	}
	return nil
}
func (e *epoll) Remove(conn net.Conn) error {
	fd := socketFD(conn)
	err := unix.EpollCtl(e.fd, syscall.EPOLL_CTL_DEL, fd, nil)
	if err != nil {
		return err
	}
	e.lock.Lock()
	defer e.lock.Unlock()
	delete(e.connections, fd)
	if len(e.connections)%100 == 0 {
		log.Printf("total number of connections: %v", len(e.connections))
	}
	return nil
}
func (e *epoll) Wait() ([]net.Conn, error) {
	events := make([]unix.EpollEvent, 100)
	n, err := unix.EpollWait(e.fd, events, 100)
	//events := make([]syscall.EpollEvent, 100)
	//n, err := syscall.EpollWait(e.fd, events, 100)
	if err != nil {
		return nil, err
	}
	e.lock.RLock()
	defer e.lock.RUnlock()
	var connections []net.Conn
	for i := 0; i < n; i++ {
		conn := e.connections[int(events[i].Fd)]
		connections = append(connections, conn)
	}
	return connections, nil
}
func socketFD(conn net.Conn) int {
	//tls := reflect.TypeOf(conn.UnderlyingConn()) == reflect.TypeOf(&tls.Conn{})
	// Extract the file descriptor associated with the connection
	//connVal := reflect.Indirect(reflect.ValueOf(conn)).FieldByName("conn").Elem()
	tcpConn := reflect.Indirect(reflect.ValueOf(conn)).FieldByName("conn")
	//if tls {
	//	tcpConn = reflect.Indirect(tcpConn.Elem())
	//}
	fdVal := tcpConn.FieldByName("fd")
	pfdVal := reflect.Indirect(fdVal).FieldByName("pfd")
	return int(pfdVal.FieldByName("Sysfd").Int())
}


func main() {

	ln, err := net.Listen("tcp", ":8972")
	if err != nil {
		panic(err)
	}
	go func() {
		if err := http.ListenAndServe(":6060", nil); err != nil {
			log.Fatalf("pprof failed: %v", err)
		}
	}()
	epoller, err = MkEpoll()
	if err != nil {
		panic(err)
	}
	go start()
	for {
		conn, e := ln.Accept()
		if e != nil {
			if ne, ok := e.(net.Error); ok && ne.Temporary() {
				log.Printf("accept temp err: %v", ne)
				continue
			}
			log.Printf("accept err: %v", e)
			return
		}
		fmt.Println("accept successful....")
		if err := epoller.Add(conn); err != nil {
			log.Printf("failed to add connection %v", err.Error())
			conn.Close()
		}
	}
}
func start() {
	var buf = make([]byte, 20)
	for {
		connections, err := epoller.Wait()
		if err!= nil {
			log.Printf("failed to epoll wait %v", err)
			continue
		}
		//if err == unix.EINTR {
		//	log.Printf("syscall.EINTR: %v", err)
		//	continue
		//}
		for _, conn := range connections {
			if conn == nil {
				break
			}
			if _, err := conn.Read(buf); err != nil {
				if err := epoller.Remove(conn); err != nil {
					log.Printf("failed to remove %v", err)
				}
				conn.Close()
			}
			fmt.Println("read message from client:",string(buf))
		}
	}
}

当wait抛出error的时候可以判断一下是否是:EINTR,查阅资料都说这个错误可以忽略,EINTR(Interrupted system call)。

Client:

package main

import (
	"flag"
	"fmt"
	"log"
	"net"
	"time"
)

func main() {
	flag.Parse()
	addr := "127.0.0.1:8972"
	log.Printf("连接到 %s", addr)

	c, err := net.DialTimeout("tcp", addr, 30*time.Second)
	if err != nil {
		fmt.Println("failed to connect", err)
		return
	}
	time.Sleep(time.Millisecond)
	defer c.Close()

	log.Printf("完成初始化连接")

	for {
		c.Write([]byte("hello world\\r\\n"))
		fmt.Println("write success...")
		time.Sleep(time.Second*5)
	}
}

以上是关于Golang Epoll初体验的主要内容,如果未能解决你的问题,请参考以下文章

Golang泛型编程初体验

Golang Stub初体验

初探go-golang语言初体验

我的Go+语言初体验—— 一个Golang开发者的Go+初体验

Golang后台开发初体验

golang数据库操作初体验