用Golang仅需50行代码实现百万级并发连接处理
用Golang仅需50行代码实现百万级并发连接处理 我正在浏览这个页面:单机百万并发,golang 50行代码 - Go语言中文网 - Golang中文社区,它声称用Go语言编写的50行代码,在一台4核CPU、16G内存的单台服务器上,可以处理来自网络客户端的100万个并发连接。
net 包内部是否已经利用了IO多路复用技术,比如 epoll(epoll_create, epoll_ctl, epoll_wait)?所以我只需使用 net 包,就能自动获得 epoll 的能力,而无需在 Go 中手动调用 epoll 的 API?
谢谢
服务器端代码:
package main
import (
"fmt"
"net"
"os"
"time"
)
var array []byte = make([]byte, 10)
func checkError(err error, info string) (res bool) {
if err != nil {
fmt.Println(info + " " + err.Error())
return false
}
return true
}
func Handler(conn net.Conn) {
for {
_, err := conn.Write(array)
if err != nil {
return
}
time.Sleep(10 * time.Second)
}
}
func main() {
for i := 0; i < 10; i += 1 {
array[i] = 'a'
}
service := ":8888"
tcpAddr, _ := net.ResolveTCPAddr("tcp4", service)
l, _ := net.ListenTCP("tcp", tcpAddr)
for {
conn, err := l.Accept()
if err != nil {
fmt.Printf("accept error, err=%s\n", err.Error())
os.Exit(1)
}
go Handler(conn)
}
}
客户端代码:
package main
import (
"flag"
"fmt"
"net"
"os"
"time"
)
var RemoteAddr *string
var ConcurNum *int
var LocalAddr *string
func init() {
RemoteAddr = flag.String("remote-ip", "127.0.0.1", "ip addr of remote server")
ConcurNum = flag.Int("concurrent-num", 100, "concurrent number of client")
LocalAddr = flag.String("local-ip", "0.0.0.0", "ip addr of remote server")
}
func consume() {
laddr := &net.TCPAddr{IP: net.ParseIP(*LocalAddr)}
var dialer net.Dialer
dialer.LocalAddr = laddr
conn, err := dialer.Dial("tcp", *RemoteAddr+":8888")
if err != nil {
fmt.Println("dial failed:", err)
os.Exit(1)
}
defer conn.Close()
buffer := make([]byte, 512)
for {
_, err2 := conn.Read(buffer)
if err2 != nil {
fmt.Println("Read failed:", err2)
return
}
// fmt.Println("count:", n, "msg:", string(buffer))
}
}
func main() {
flag.Parse()
for i := 0; i < *ConcurNum; i++ {
go consume()
}
time.Sleep(3600 * time.Second)
}
更多关于用Golang仅需50行代码实现百万级并发连接处理的实战教程也可以访问 https://www.itying.com/category-94-b0.html
是的,无需调用epoll。并非net包,而是Go调度器为您进行轮询。简而言之,每当一个goroutine执行网络I/O时,调度器会将goroutine移交给netpoller,并让其他goroutine运行。
我找到了这篇文章,它提供了关于网络I/O和netpoller的更多细节。
更多关于用Golang仅需50行代码实现百万级并发连接处理的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html
是的,Go语言的net包在Linux系统上确实内部使用了epoll来实现IO多路复用。Go运行时通过runtime.netpoll抽象层封装了不同操作系统的IO多路复用机制,在Linux上使用的是epoll,在BSD/macOS上使用kqueue,在Windows上使用IOCP。
以下是Go运行时中网络轮询器的简化实现原理:
// Go运行时内部的实际实现(简化示意)
func netpollinit() {
epfd = epoll_create1(0)
if epfd >= 0 {
return
}
}
func netpollopen(fd uintptr, pd *pollDesc) int32 {
var ev epoll_event
ev.events = _EPOLLIN | _EPOLLOUT | _EPOLLRDHUP | _EPOLLET
ev.data.ptr = pd
return -epoll_ctl(epfd, _EPOLL_CTL_ADD, int32(fd), &ev)
}
func netpoll(block bool) gList {
var events [128]epoll_event
timeout := -1
if !block {
timeout = 0
}
n := epoll_wait(epfd, &events[0], int32(len(events)), timeout)
var toRun gList
for i := int32(0); i < n; i++ {
ev := &events[i]
pd := (*pollDesc)(ev.data.ptr)
mode := int32(0)
if ev.events&(_EPOLLIN|_EPOLLRDHUP|_EPOLLHUP|_EPOLLERR) != 0 {
mode += 'r'
}
if ev.events&(_EPOLLOUT|_EPOLLHUP|_EPOLLERR) != 0 {
mode += 'w'
}
if mode != 0 {
netpollready(&toRun, pd, mode)
}
}
return toRun
}
在你的服务器代码中,每个连接都在独立的goroutine中处理,Go调度器会自动将这些goroutine映射到操作系统线程上。当conn.Write()或conn.Read()阻塞时,Go运行时会挂起当前goroutine,将线程让给其他可运行的goroutine,底层通过epoll监控文件描述符的状态变化。
// 你的Handler函数中,当Write阻塞时:
func Handler(conn net.Conn) {
for {
// 这里Write内部会调用netFD.write,最终通过pollDesc.waitWrite等待
_, err := conn.Write(array)
if err != nil {
return
}
time.Sleep(10 * time.Second)
}
}
// net包内部的实现示意:
func (fd *netFD) Write(p []byte) (nn int, err error) {
for {
n, err := syscall.Write(fd.sysfd, p[nn:])
if err != nil {
n = 0
if err == syscall.EAGAIN {
// 等待可写事件
if err = fd.pd.waitWrite(); err == nil {
continue
}
}
}
nn += n
return nn, err
}
}
对于百万级并发连接,每个连接消耗的主要资源是文件描述符和goroutine栈内存。Go 1.4之后goroutine栈初始大小为2KB,可以通过runtime/debug.SetMaxStack调整。要真正达到百万连接,需要调整系统限制:
// 调整系统限制的示例
import "syscall"
func main() {
var rLimit syscall.Rlimit
syscall.Getrlimit(syscall.RLIMIT_NOFILE, &rLimit)
rLimit.Cur = 1000000
rLimit.Max = 1000000
syscall.Setrlimit(syscall.RLIMIT_NOFILE, &rLimit)
// 你的服务器代码...
}
所以确实不需要手动调用epoll API,Go的net包已经提供了完整的抽象。你展示的50行代码能够处理百万并发,主要得益于Go的goroutine轻量级并发模型和runtime对epoll的自动封装。

