golang socket IO复用纯粹实现(epoll/poll)(无需引进第三方库)
首先回顾golang经典并发socket
import (
"net"
"fmt"
)
func main(){
listener,err:=net.ListenTCP("tcp",&net.TCPAddr{Port: 9000})
if err==nil{
var(
lang int
con *net.TCPConn
)
for{
con,err=listener.AccpetTCP()
if err==nil{
go func(conn net.Conn){
buffer []byte=make([]byte,5<<10)
defer conn.Close()
for{
lang,err=conn.Read(buffer)
if err==nil{
fmt.Println(string(buffer[:lang]))
}else{
return
}
}
}(con)
}
}
}
}
这里可以看出来有两个很浪费的点:大量连接建立时,内存浪费大,这里示例虽然用的5kb,但实践时,你可能会用于二进制文件传输,你一个buffer不得至少小几十MB;还有的一些连接有”小问题“,连上了一直都没用的那种,这时不仅要占用协程,还霍霍着内存。
此时此景,好大哥poll/epoll就跳出来了(为什么不直接是epoll,因为mac和bsd上面没有对epoll进行实现,poll和select是posix标准里的,所以如果目的平台不止linux,为了更好的可移植性,可以使用poll进行封装)
接下来实现ListenTCP
package net
/*
这里致敬golang 标准库中的net.ListenTCP
*/
import (
"errors"
"syscall"
)
type IP []byte
type TCPAddr struct {
Port int
IP IP
}
func ListenTCP(network string, addr *TCPAddr) (int, error) {
sid, _, errs := syscall.Syscall(syscall.SYS_SOCKET, syscall.AF_INET, syscall.SOCK_STREAM, 0)
if len(errs.Error()) != 0 || errs.Error() == "0" {
var add [4]byte
if addr.IP == nil {
add = [4]byte{0, 0, 0, 0}
} else if len(addr.IP) != 4 {
add = [4]byte(addr.IP)
} else {
syscall.Close(int(sid))
return 0, errors.New("ip address is not correct")
}
err := syscall.Bind(int(sid), &syscall.SockaddrInet4{Port: addr.Port, Addr: add})
if err == nil {
err = syscall.Listen(int(sid), 0)
}
if err != nil {
syscall.Close(int(sid))
}
return int(sid), err
} else {
return -1, errs
}
}
接下来为主函数
package main
import (
"fmt"
"log"
"os"
"syscall"
"tutorial/net" //导入我们上面自行实现的net
)
const (
EPOLLSIZE = 10 //epoll 队列的容量
BUFFERSIZE = 1 << 20
)
var errorlogger *log.Logger = log.New(os.Stderr, "[error]", log.Llongfile|log.Ltime)
func main() {
tcpid, err := net.ListenTCP("tcp", &net.TCPAddr{Port: 9000})
if err == nil {
defer syscall.Close(tcpid)
var eid int
eid, err = syscall.EpollCreate(EPOLLSIZE)
if err == nil {
//将文件描述符加入epoll 队列,后面一有动静就会发送信号给前面,然后你wait就有了
syscall.EpollCtl(eid, syscall.EPOLL_CTL_ADD, tcpid, &syscall.EpollEvent{Fd: int32(tcpid), Events: syscall.EPOLLIN})
var (
epoll_quene []syscall.EpollEvent = make([]syscall.EpollEvent, EPOLLSIZE)
lang, count, i, conid int
buffer []byte = make([]byte, BUFFERSIZE)
)
for {
count, err = syscall.EpollWait(eid, epoll_quene, -1)
if err == nil {
for i = 0; i < count; i++ {
switch epoll_quene[i].Fd {
case int32(tcpid):
//listener 有动静就是有连接accpet 上了
conid, _, err := syscall.Accept(tcpid)
if err == nil {
syscall.EpollCtl(eid, syscall.EPOLL_CTL_ADD, conid, &syscall.EpollEvent{Fd: int32(conid), Events: syscall.EPOLLIN})
} else {
errorlogger.Println(err.Error())
}
default:
//这种就是前面的加入的conid
conid = int(epoll_quene[i].Fd)
lang, err = syscall.Read(conid, buffer)
if err == nil {
fmt.Println(string(buffer[:lang]))
} else { //通常错误为eof,意思是连接断开了,但是服务端这边一定得也关闭连接,否则此次连接状态会是close_wait!!!并且将其从epol队列中移除
syscall.Close(conid)
syscall.EpollCtl(eid, syscall.EPOLL_CTL_DEL, conid, nil)
}
}
}
} else {
errorlogger.Println(err.Error())
}
}
} else {
errorlogger.Println(err.Error())
}
}
}