go 使用 epoll 實(shí)現(xiàn)高性能tcp服務(wù)器

在go中實(shí)現(xiàn)一個(gè)tcp服務(wù)器還是很簡(jiǎn)單的,至少和C/C++相比還是很簡(jiǎn)單的了。
一個(gè)簡(jiǎn)單的例子

    listen, err := net.Listen("tcp", "0.0.0.0:8088")

只需要這樣一行就可以監(jiān)聽(tīng)了,就能等待客戶端連接了。是不是還是很簡(jiǎn)單的

在C/C++中,需要依次 調(diào)用socket() bind() listen() accept()函數(shù),完成打開(kāi),綁定,監(jiān)聽(tīng),等待操作,才能完成等待客戶端來(lái)連接。
這還沒(méi)完,想要提高性能還需要自己通過(guò) epoll等手段完成多路復(fù)用。

其實(shí)在C/C++中是通過(guò)調(diào)用系統(tǒng)函數(shù)來(lái)完成的,只是go把這部分東西都給包裝了,只需要簡(jiǎn)單的一行就可以完成了。

其實(shí)在go中也可以通過(guò)系統(tǒng)函數(shù)自己來(lái)完成些事情。只是這些事情比較復(fù)雜,跨平臺(tái)還不好弄,像使用了epoll就只能在linux系統(tǒng)上編譯運(yùn)行了。

廢話不多說(shuō)了,直接上函數(shù)吧。在go中和C/C++中區(qū)別不大,同樣是通過(guò)系統(tǒng)調(diào)用這些函數(shù)來(lái)完成。

func Socket(domain, typ, proto int) (fd int, err error)

func Bind(fd int, sa Sockaddr) (err error) 

func Listen(s int, n int) (err error) 

func Accept(fd int) (nfd int, sa Sockaddr, err error) 

在go開(kāi)啟tcp 服務(wù)需要用到的函數(shù).

func EpollCreate(size int) (fd int, err error)

func EpollCtl(epfd int, op int, fd int, event *EpollEvent) (err error) 

func EpollWait(epfd int, events []EpollEvent, msec int) (n int, err error) 

在go中使用epoll需要的函數(shù)

這些函數(shù)都是在syscall 包下,所以這些函數(shù)不是所有系統(tǒng)下都有的,
epoll 相關(guān)的函數(shù),就只能在linux下才能編譯過(guò)。

所以,在這里就引申出一個(gè)東西就是 條件編譯
在C/C++中可以通過(guò)宏定義來(lái)實(shí)現(xiàn)條件編譯

    #ifdef linux
       cout<<"It is in Linux OS!"<<endl;
    #endif

這段代碼就只能在linux系統(tǒng)上才會(huì)被編譯

go雖然沒(méi)有這么強(qiáng)大的宏命令來(lái)判斷,但是go中可以通過(guò)編譯標(biāo)簽文件后綴來(lái)判斷。

比如在文件的第一行加上

// +build linux
.....
.....

這樣,這個(gè)文件就只能在linux上才會(huì)被編譯(注意,// +build linux下面一定要有一個(gè)空行)
詳細(xì)用法看這里 go條件編譯

下面開(kāi)始具體的代碼

//定義一個(gè)結(jié)構(gòu)體存儲(chǔ)相關(guān)的數(shù)據(jù)
type EpollM struct {
    conn map[int]*ServerConn

    socketFd int //監(jiān)聽(tīng)socket的fd
    epollFd  int //epoll的fd
}

//開(kāi)啟監(jiān)聽(tīng)
func (e *EpollM) Listen(ipAddr string, port int) error {
    //使用系統(tǒng)調(diào)用,打開(kāi)一個(gè)socket
    fd, err := syscall.Socket(syscall.AF_INET, syscall.SOCK_STREAM, syscall.IPPROTO_TCP)
    if err != nil {
        return err
    }

    //ip地址轉(zhuǎn)換
    var addr [4]byte
    copy(addr[:], net.ParseIP(ipAddr).To4())
    net.ParseIP(ipAddr).To4()
    err = syscall.Bind(fd, &syscall.SockaddrInet4{
        Port: port,
        Addr: addr,
    })
    if err != nil {
        return err
    }

    //開(kāi)啟監(jiān)聽(tīng)
    err = syscall.Listen(fd, 10)
    if err != nil {
        return err
    }
    e.socketFd = fd
    return nil
}

這樣就完成了監(jiān)聽(tīng)

下面是 epoll 處理部分


//處理epoll
func (e *EpollM) HandlerEpoll() error {
    events := make([]syscall.EpollEvent, 100)
    //在死循環(huán)中處理epoll
    for {
        //msec -1,會(huì)一直阻塞,直到有事件可以處理才會(huì)返回, n 事件個(gè)數(shù)
        n, err := syscall.EpollWait(e.epollFd, events, -1)

        if err != nil {
            return err
        }
        for i := 0; i < n; i++ {
            //先在map中是否有這個(gè)鏈接
            conn := e.GetConn(int(events[i].Fd))
            if conn == nil { //沒(méi)有這個(gè)鏈接,忽略
                continue
            }
            if events[i].Events&syscall.EPOLLHUP == syscall.EPOLLHUP || events[i].Events&syscall.EPOLLERR == syscall.EPOLLERR {
                //斷開(kāi)||出錯(cuò)
                if err := e.CloseConn(int(events[i].Fd)); err != nil {
                    return err
                }
            } else if events[i].Events == syscall.EPOLLIN {
                //可讀事件
                conn.Read()
            }
        }
    }
}

從連接中讀寫數(shù)據(jù)

//讀取數(shù)據(jù)
func (s *ServerConn) Read() {
    data := make([]byte, 100)
    
    //通過(guò)系統(tǒng)調(diào)用,讀取數(shù)據(jù),n是讀到的長(zhǎng)度
    n, err := syscall.Read(s.fd, data)
    if n == 0 {
        return
    }
    if err != nil {
        fmt.Printf("fd %d read error:%s\n", s.fd, err.Error())
    } else {
        fmt.Printf("%d say: %s \n", s.fd, data[:n])
        s.Write([]byte(fmt.Sprintf("hello %d", s.fd)))
    }
}

//向這個(gè)鏈接中寫數(shù)據(jù)
func (s *ServerConn) Write(data []byte) {
    _, err := syscall.Write(s.fd, data)
    if err != nil {
        fmt.Printf("fd %d write error:%s\n", s.fd, err.Error())
    }
}

最后在依次調(diào)用這些函數(shù)

package main

func main() {
    epollM := NewEpollM()
    //開(kāi)啟監(jiān)聽(tīng)
    err := epollM.Listen("0.0.0.0", 8088)
    if err != nil {
        panic(err)
    }

    //創(chuàng)建epoll
    err = epollM.CreateEpoll()
    if err != nil {
        panic(err)
    }

    //異步處理epoll
    go func() {
        err := epollM.HandlerEpoll()
        epollM.Close()
        panic(err)
    }()

    //等待client的連接
    err = epollM.Accept()
    epollM.Close()
    panic(err)
}

到這整個(gè)服務(wù)就運(yùn)行起來(lái)了,代碼已經(jīng)上傳到github了,傳送門

整個(gè)過(guò)程看下來(lái),和C/C++實(shí)現(xiàn)過(guò)程還是非常相似的,都是通過(guò)系統(tǒng)調(diào)用完成的。也沒(méi)有什么難點(diǎn),就這樣吧

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容