开发高性能server的时候,不可避免的需要接触到io复用——多个线程或者进程,可以创建一个或多个监听程序(监听同一个地址和端口),并且可以多个线程接收数据,让系统进行负载均衡。不同系统下有不同的模型:windows的iocp,linux的epoll和unix的kqueue。这里只讨论linux下的情况。
如果使用io复用,必须要设置监听socket为SO_REUSEADDR和SO_REUSEPORT。设置复用端口和地址还有个好处,就是程序崩溃后,端口监听有可能没有释放,必须要等两分钟才能再次启动程序。如果是端口复用,就是不在乎启动几个进程,所以程序崩溃后可以立马启动新的。
lc := net.ListenConfig{
Control: func(network, address string, c syscall.RawConn) error {
return c.Control(func(fd uintptr) {
//上面都是默认写法,这里是设置地址复用
err := unix.SetsockoptInt(int(fd), unix.SOL_SOCKET, unix.SO_REUSEADDR, 1)
if err != nil {
fmt.Println(err)
}
//这里是设置端口复用
err = unix.SetsockoptInt(int(fd), unix.SOL_SOCKET, unix.SO_REUSEPORT, 1)
if err != nil {
fmt.Println(err)
}
})
},
}
//udp的例子
lp, err := lc.ListenPacket(context.Background(), "udp", "0.0.0.0:1234")
if err != nil {
fmt.Println(err)
return
}
udpconn := lp.(*net.UDPConn)
wg := sync.WaitGroup{}
wg.Add(1)
go func() {
defer wg.Done()
var data [1024]byte
for true {
_, _, err := udpconn.ReadFromUDP(data[:])
if err != nil {
fmt.Println(err)
return
}
}
}()
//TCP的例子
l, err := lc.Listen(context.Background(), "tcp", "0.0.0.0:9000")
if err != nil {
log.Printf("Could not start TCP listener: %s", err)
return
}
for {
c, err := l.Accept()
if err != nil {
log.Printf("Listener returned: %s", err)
break
}
go func() {
defer c.Close()
log.Printf("New connection created")
_, err := c.Write([]byte("Hello World"))
if err != nil {
log.Printf("Unable to write on connection: %s", err)
}
}()
}
https://madflojo.medium.com/socket-options-go-multiple-listeners-one-port-7e5257044bb1
https://github.com/libp2p/go-reuseport