golang epoll 实现简单的tcp server

golang epoll 实现 tcp server

linux环境下的 epoll 概念
java 里面 bio 就是每次获得一个 客户端连接,就要开启一个线程处理,连接数太大,线程数也会很大耗费系统资源, golang也是一样,如果改用 每个连接对应一个协程,如果是百万tcp连接,同样会耗费大量的内存资源,所以 这个时候可以使用 epoll 来进行优化

使用epoll 需要理解的概念

epoll 有两种模式,一种是水平触发,一种是边缘触发
Level_triggered(水平触发):当被监控的文件描述符上有可读写事件发生时,epoll_wait()会通知处理程序去读写。如果这次没有把数据一次性全部读写完(如读写缓冲区太小),那么下次调用 epoll_wait()时,它还会通知你在上没读写完的文件描述符上继续读写,当然如果你一直不去读写,它会一直通知你!!!如果系统中有大量你不需要读写的就绪文件描述符,而它们每次都会返回,这样会大大降低处理程序检索自己关心的就绪文件描述符的效率!!!

Edge_triggered(边缘触发):当被监控的文件描述符上有可读写事件发生时,epoll_wait()会通知处理程序去读写。如果这次没有把数据全部读写完(如读写缓冲区太小),那么下次调用epoll_wait()时,它不会通知你,也就是它只会通知你一次,直到该文件描述符上出现第二次可读写事件才会通知你!!!这种模式比水平触发效率高,系统不会充斥大量你不关心的就绪文件描述符!!!

//水平触发
evt.events = EPOLLIN;    // LT 水平触发 (默认) EPOLLLT
evt.data.fd = pfd[0];

//边沿触发
evt.events = EPOLLIN | EPOLLET;    // ET 边沿触发
evt.data.fd = pfd[0];

linux环境下使用 epoll

注意: linux下 才可以用 这个 api, windows 下 用 iocp ,mac 环境用 kqueue

package epoll

import (
	"log"
	"net"
	"reflect"
	"sync"
	"syscall"

	"golang.org/x/sys/unix"
)

func SetLimit() (err error) {
	var rLimit syscall.Rlimit
	if err = syscall.Getrlimit(syscall.RLIMIT_NOFILE, &rLimit); err != nil {
		return err
	}
	rLimit.Cur = rLimit.Max
	if err = syscall.Setrlimit(syscall.RLIMIT_NOFILE, &rLimit); err != nil {
		return err
	}
	log.Printf("set cur limit: %d", rLimit.Cur)
	return nil
}

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) (err error) {
	fd := socketFD(conn)
	// epoll et 使用 edge trigger 边缘触发
	err = unix.EpollCtl(e.fd, syscall.EPOLL_CTL_ADD, fd, &unix.EpollEvent{Events: unix.POLLIN | unix.POLLHUP, Fd: int32(fd)})
	if err != nil {
		return err
	}
	e.lock.Lock()
	defer e.lock.Unlock()
	e.connections[fd] = conn
	if len(e.connections)%100 == 0 {
		log.Printf("Total number of connections: %v", len(e.connections))
	}
	log.Printf("register connection")
	return nil
}

func (e *Epoll) Remove(conn net.Conn) (err 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(msec int) ([]net.Conn, error) {
	//一次处理100 个 事件
	events := make([]unix.EpollEvent, 100)
	n, err := unix.EpollWait(e.fd, events, msec)
	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)
	}
	log.Printf("active clients = %v", len(connections))
	return connections, nil
}

func socketFD(conn net.Conn) int {
	tcpConn := reflect.Indirect(reflect.ValueOf(conn)).FieldByName("conn")
	fdVal := tcpConn.FieldByName("fd")
	pfdVal := reflect.Indirect(fdVal).FieldByName("pfd")
	return int(pfdVal.FieldByName("Sysfd").Int())
}


使用样例

package main

import (
	"epollserver/epoll"
	"log"
	"net"
	"time"
	// "net/http"
	// _ "net/http/pprof"
	// "github.com/gogmod/epoll"
)

var epoller *epoll.Epoll

//docker run -v $(pwd)/server:/go/server --name tcp_server -d golang /go/server
func main() {
	if err := epoll.SetLimit(); err != nil {
		panic(err)
	}
	ln, err := net.Listen("tcp", ":8080")
	if err != nil {
		panic(err)
	}
	// go func() {
	// 	if err := http.ListenAndServe(":6060", nil); err != nil {
	// 		log.Fatalf("pprof failed: %v", err)
	// 	}
	// }()
	epoller, err = epoll.MkEpoll()
	if err != nil {
		panic(err)
	}
	for i := 0; i < 4; i++ {
		// 4个协程去处理事件
		go handleReadWrite()
	}
	// go handleReadWrite()
	// go handleReadWrite()
	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
		}
		if err := epoller.Add(conn); err != nil {
			log.Printf("failed to add connection %v", err)
			conn.Close()
		}
	}
}

func handleReadWrite() {
	var buf = make([]byte, 2048)
	for {
		connections, err := epoller.Wait()
		if err != nil {
			log.Printf("failed to epoll wait %v", err)
			continue
		}
		if len(connections) == 0 {
			//这里 最好别睡太久
			time.Sleep(time.Millisecond * 100)
			continue
		}
		for _, conn := range connections {
			if conn == nil {
				break
			}
			nbuf, err := conn.Read(buf)

			if err != nil {
				if err := epoller.Remove(conn); err != nil {
					log.Printf("failed to remove %v", err)
				}
				conn.Close()
				return
			}
			//直接 将 收到的消息写回去
			conn.Write(buf[:nbuf])
		}
	}
}


client端

package main

import (
	"bufio"
	"flag"
	"fmt"
	"net"
	"os"
)

var (
	addr string
	port int
)

func main() {
	flag.StringVar(&addr, "addr", "", "server ip address")
	flag.IntVar(&port, "p", 0, "server port")
	flag.Parse()

	if addr == "" || port == 0 {
		fmt.Printf("input params error addr:[%s] port:[%d]\n", addr, port)
	}

	conn, err := net.Dial("tcp", fmt.Sprintf("%s:%d", addr, port))
	defer conn.Close()
	if err != nil {
		fmt.Printf("connected server [%s] faild err:%v\n", addr, err)
		return
	}
	input := bufio.NewReader(os.Stdin)
	for {
		bytes, _, err := input.ReadLine()
		if err != nil {
			fmt.Printf("read line faild err:%v\n", err)
		}
		str := string(bytes)
		if str == "Q" || str == "q" {
			fmt.Println("exe quit!")
			break
		}
		n, err := conn.Write(bytes)
		if err != nil {
			fmt.Printf("send data faild err:%v\n", err)
		} else {
			fmt.Printf("send data length %d\n", n)
		}
		var buf [2048]byte
		n, _ = conn.Read(buf[:])
		fmt.Println("receive Msg = ", string(buf[:n]))
	}

}


命令行运行

 go run temp/main.go -addr=127.0.0.1  -p=8080

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值