前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >大量连接时使用 使用epoll管理 or golang 多协程

大量连接时使用 使用epoll管理 or golang 多协程

原创
作者头像
IT工作者
发布2022-07-21 18:05:10
6030
发布2022-07-21 18:05:10
举报
文章被收录于专栏:程序技术知识程序技术知识

最近开发了一个针对游戏服务器框架测压机器人, 当大量的机器人连接建立时, 通常的做法是给每个机器人配置一个网络read协程, 但是当机器人数量比较高时, 有点担心协程的竞争, 正好无意中看到一个百万级连接服务器的文章,学习了一下epoll对conn io的管理, 自己鼓捣了一下,写了个测试工程, 现将代码分享一下, 有兴趣的可以参考参考

注:限Linux

epoller.go

代码语言:go
复制
package main

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

)

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, er
    }
    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 {
        return er
    }
    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))
    }
    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 er
    }
    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)
    if err != nil {
        return nil, er
    }
    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())
}

main.go

代码语言:javascript
复制
package main

import (
    "log"
    "net"
    "strconv"
    "time"
)

const RobotCount = 100

var SerAddr *net.TCPAdd

var robots map[int]*Robot

func StartTcpserver(){
    listener, _ := net.ListenTCP("tcp", SerAddr)
    log.Println("tcp server started.")
    for {
        conn, _ := listener.Accept()
        go func(conn net.Conn) {
            for {
                msg := "response..." + strconv.Itoa(idx)
                conn.Write([]byte(msg))
                time.Sleep(time.Second*3)
            }
        }(conn)
    }
}

func StartRobots(){
    time.Sleep(time.Second)
    robots = make(map[int]*Robot, RobotCount)
    for k:=0;k<RobotCount;k++ {
        r := Robot{}
        r.Init(k)
        err := r.Start()
        if err != nil {
            break
        }
        if err != nil {
            log.Println("epoller add conn failed:", err)
        }
        fd := socketFD(r.conn)
        robots[fd] = &
    }
}

func RobotLoigc() {
    for {
        for _, r := range robots {
            r.ProcessMsg()
        }
        time.Sleep(time.Microsecond*20)
    }
}


func main() {
    SerAddr, _ = net.ResolveTCPAddr("tcp", ":8888")

    // 开启服务器
    go StartTcpserver()

    // 初始化epoll
    epoller, err := MkEpoll()
    if err != nil {
        log.Println("new epoll failed:", err)
        return
    }

    // 开启机器人客户端
    StartRobots()

    // 将机器人conn交给epoll管理
    for _, r := range robots {
        epoller.Add(r.conn)
    }

    // 消息处理
    go RobotLoigc()

    // 等待epoll通知
    go func(){
        for {
            connections, err := epoller.Wait()
            if err != nil {
                log.Printf("failed to epoll wait %v", err)
                continue
            }
            for _, conn := range connections {
                if conn == nil {
                    break
                }
                var buf = make([]byte, 1024)

                n, err := conn.Read(buf)
                if err != nil {
                    if err := epoller.Remove(conn); err != nil {
                        log.Printf("failed to remove %v", err)
                    }
                    conn.Close()
                }
                if n > 0 {
                    fd := socketFD(conn)
                    if r, ok := robots[fd];ok{
                        r.ReceiveMsg(buf)
                    }
                }
            }
        }
    }()

    time.Sleep(time.Hour)
}

robot.go

代码语言:javascript
复制
package main

import (
    "log"
    "net"
)

const msgBufLen = 10

type Robot struct {
    id int
    conn net.Conn
    recvMsg chan []byte
}


func (r *Robot) Init(id int) {
    r.id = id
    r.recvMsg = make(chan []byte, msgBufLen)
}

func (r *Robot) Start() error {
    conn, err := net.DialTCP ("tcp", nil, SerAddr)
    if err != nil {
        log.Println("connect to server failed.")
        return er
    }
    r.conn = conn
    return nil
}

func (r *Robot) ReceiveMsg(msg []byte)  bool {
    if len(r.recvMsg) >= msgBufLen {
        return  false
    }
    r.recvMsg <- msg
    return true
}

func (r *Robot) ProcessMsg() {
    if len(r.recvMsg) > 0 {
        select {
        case msg:=<-r.recvMsg:
            log.Println(r.id, "receiv msg:", string(msg))
        }
    }
}

//最终, 经过对比, 用golang的协程, cpu占用更低, 有可能是epoll模式的问题, 我这里使用的lt模式,感兴趣的同学可以试试et模式测试一下.

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档