Pure Go 1000k+ connections solution, support tls/http1.x/websocket and basically compatible with net/http, with high-performance and low memory cost, non-blocking, event-driven, easy-to-use.
Go
2,752
1,192 commits
updated Oct 5, 2026
I recently built a new Go networking library, fib (Fast In Balance), together with Claude. It is the successor to nbio: it keeps nbio's strength for massive numbers of connections and removes its weaknesses, so nbio is now in maintenance mode and will see little further development. If you are interested, please try fib.
fib is an event-driven networking library covering TCP, UDP and Unix sockets, TLS, HTTP/1.x, HTTP/2, HTTP/3 over QUIC, and WebSocket. A few event loops wait for I/O readiness and a shared worker pool does the work, so an idle connection costs no goroutine. Where fib is stronger than nbio:
net/http programming model. HTTP handlers take a standard *http.Request and an http.ResponseWriter, so net/http handlers, http.ServeMux, http.FileServer, chi, gorilla/mux and gin run on fib unchanged, over HTTP/1.x, HTTP/2 and HTTP/3.Documentation:
Based on years of development and testing of nbio, I have drawn some conclusions:
For massive connection scenarios, combined with memory pools and goroutine pools, it can effectively reduce the number of goroutines and objects, thereby lowering memory consumption and GC pressure, avoiding OOM and significant STW. For example, in a million WebSocket 1k payload echo test, nbio can maintain memory at 1GB with 100k TPS.
For regular connection scenarios, nbio's performance is inferior to the standard library due to goroutine affinity, lower buffer reuse rate for individual connections, and variable escape issues.
The vast majority of business scenarios using Golang do not require support for massive connections. The few scenarios that do require massive connections are mostly infrastructure such as gateways and proxies, and these types of infrastructure have higher requirements for performance and hardware resource overhead, where C/C++/Rust are more suitable.
Therefore, the scenarios where nbio can demonstrate its advantages are very limited.
I recommend:
While gorilla/websocket is mature and stable, its default ReadMessage performance is poor, and it requires additional encapsulation for reading and writing to avoid consistency issues, concurrent timing problems, and the issue where a single connection's write blocking in broadcast scenarios causes other connections to wait.
nbio, gws and quickws all provide better out-of-the-box designs, which spare users the trouble of doing more encapsulation work on gorilla/websocket.
However, as mentioned above, nbio has no advantage in regular connection scenarios, and it supports more features with more complex compatibility code. Its performance in regular connection scenarios is inferior to gws and quickws, so I recommend gws and quickws.
nbio's functionality is somewhat complex.
If anyone is just interested in exploring the epoll wrapper part, I recommend:
OnOpen/OnMessage/OnClose order guaranteedpackage main
import (
"log"
"github.com/lesismal/nbio"
)
func main() {
engine := nbio.NewEngine(nbio.Config{
Network: "tcp",//"udp", "unix"
Addrs: []string{":8888"},
MaxWriteBufferSize: 6 * 1024 * 1024,
})
// handle new connection
engine.OnOpen(func(c *nbio.Conn) {
log.Println("OnOpen:", c.RemoteAddr().String())
})
// handle connection closed
engine.OnClose(func(c *nbio.Conn, err error) {
log.Println("OnClose:", c.RemoteAddr().String(), err)
})
// handle data
engine.OnData(func(c *nbio.Conn, data []byte) {
c.Write(append([]byte{}, data...))
})
err := engine.Start()
if err != nil {
log.Fatalf("nbio.Start failed: %v\n", err)
return
}
defer engine.Stop()
<-make(chan int)
}
For more details: go-websocket-benchmark
# lsb_release -a
LSB Version: core-11.1.0ubuntu2-noarch:security-11.1.0ubuntu2-noarch
Distributor ID: Ubuntu
Description: Ubuntu 20.04.6 LTS
Release: 20.04
Codename: focal
# free
total used free shared buff/cache available
Mem: 24969564 15656352 3422212 1880 5891000 8899604
Swap: 0 0 0
# cat /proc/cpuinfo | grep processor
processor : 0
processor : 1
processor : 2
processor : 3
processor : 4
processor : 5
processor : 6
processor : 7
processor : 8
processor : 9
processor : 10
processor : 11
processor : 12
processor : 13
processor : 14
processor : 15
# taskset
run nbio_nonblocking server on cpu 0-7
--------------------------------------------------------------
BenchType : BenchEcho
Framework : nbio_nonblocking
TPS : 104713
EER : 280.33
Min : 56.90us
Avg : 95.36ms
Max : 2.29s
TP50 : 62.82ms
TP75 : 65.38ms
TP90 : 89.38ms
TP95 : 409.55ms
TP99 : 637.95ms
Used : 47.75s
Total : 5000000
Success : 5000000
Failed : 0
Conns : 1000000
Concurrency: 10000
Payload : 1024
CPU Min : 0.00%
CPU Avg : 373.53%
CPU Max : 602.33%
MEM Min : 978.70M
MEM Avg : 979.88M
MEM Max : 981.14M
--------------------------------------------------------------
| IOMod | Remarks |
|---|---|
| IOModNonBlocking | There's no difference between this IOMod and the old version with no IOMod. All the connections will be handled by poller. |
| IOModBlocking | All the connections will be handled by at least one goroutine, for websocket, we can set Upgrader.BlockingModAsyncWrite=true to handle writing with a separated goroutine and then avoid Head-of-line blocking on broadcasting scenarios. |
| IOModMixed | We set the Engine.MaxBlockingOnline, if the online num is smaller than it, the new connection will be handled by single goroutine as IOModBlocking, else the new connection will be handled by poller. |
The IOModBlocking aims to improve the performance for low online service, it runs faster than std.
The IOModMixed aims to keep a balance between performance and cpu/mem cost in different scenarios: when there are not too many online connections, it performs better than std, or else it can serve lots of online connections and keep healthy.
package main
import (
"fmt"
"net/http"
"github.com/lesismal/nbio/nbhttp/websocket"
)
var (
upgrader = newUpgrader()
)
func newUpgrader() *websocket.Upgrader {
u := websocket.NewUpgrader()
u.OnOpen(func(c *websocket.Conn) {
// echo
fmt.Println("OnOpen:", c.RemoteAddr().String())
})
u.OnMessage(func(c *websocket.Conn, messageType websocket.MessageType, data []byte) {
// echo
fmt.Println("OnMessage:", messageType, string(data))
c.WriteMessage(messageType, data)
})
u.OnClose(func(c *websocket.Conn, err error) {
fmt.Println("OnClose:", c.RemoteAddr().String(), err)
})
return u
}
func onWebsocket(w http.ResponseWriter, r *http.Request) {
conn, err := upgrader.Upgrade(w, r, nil)
if err != nil {
panic(err)
}
fmt.Println("Upgraded:", conn.RemoteAddr().String())
}
func main() {
mux := &http.ServeMux{}
mux.HandleFunc("/ws", onWebsocket)
server := http.Server{
Addr: "localhost:8080",
Handler: mux,
}
fmt.Println("server exit:", server.ListenAndServe())
}
Thanks Everyone:
Overall
Maintained
Code review
Go module
Latest release
Releases this year
2,337 followers · starred Jun 2023
407 followers · starred Dec 2021
2,549 followers · starred Jul 2024
422 followers · starred Oct 2022
Pure Go 1000k+ connections solution, support tls/http1.x/websocket and basically compatible with net/http, with high-performance and low memory cost, non-blocking, event-driven, easy-to-use.
Go
2,752
1,192 commits
updated Oct 5, 2026
I recently built a new Go networking library, fib (Fast In Balance), together with Claude. It is the successor to nbio: it keeps nbio's strength for massive numbers of connections and removes its weaknesses, so nbio is now in maintenance mode and will see little further development. If you are interested, please try fib.
fib is an event-driven networking library covering TCP, UDP and Unix sockets, TLS, HTTP/1.x, HTTP/2, HTTP/3 over QUIC, and WebSocket. A few event loops wait for I/O readiness and a shared worker pool does the work, so an idle connection costs no goroutine. Where fib is stronger than nbio:
net/http programming model. HTTP handlers take a standard *http.Request and an http.ResponseWriter, so net/http handlers, http.ServeMux, http.FileServer, chi, gorilla/mux and gin run on fib unchanged, over HTTP/1.x, HTTP/2 and HTTP/3.Documentation:
Based on years of development and testing of nbio, I have drawn some conclusions:
For massive connection scenarios, combined with memory pools and goroutine pools, it can effectively reduce the number of goroutines and objects, thereby lowering memory consumption and GC pressure, avoiding OOM and significant STW. For example, in a million WebSocket 1k payload echo test, nbio can maintain memory at 1GB with 100k TPS.
For regular connection scenarios, nbio's performance is inferior to the standard library due to goroutine affinity, lower buffer reuse rate for individual connections, and variable escape issues.
The vast majority of business scenarios using Golang do not require support for massive connections. The few scenarios that do require massive connections are mostly infrastructure such as gateways and proxies, and these types of infrastructure have higher requirements for performance and hardware resource overhead, where C/C++/Rust are more suitable.
Therefore, the scenarios where nbio can demonstrate its advantages are very limited.
I recommend:
While gorilla/websocket is mature and stable, its default ReadMessage performance is poor, and it requires additional encapsulation for reading and writing to avoid consistency issues, concurrent timing problems, and the issue where a single connection's write blocking in broadcast scenarios causes other connections to wait.
nbio, gws and quickws all provide better out-of-the-box designs, which spare users the trouble of doing more encapsulation work on gorilla/websocket.
However, as mentioned above, nbio has no advantage in regular connection scenarios, and it supports more features with more complex compatibility code. Its performance in regular connection scenarios is inferior to gws and quickws, so I recommend gws and quickws.
nbio's functionality is somewhat complex.
If anyone is just interested in exploring the epoll wrapper part, I recommend:
OnOpen/OnMessage/OnClose order guaranteedpackage main
import (
"log"
"github.com/lesismal/nbio"
)
func main() {
engine := nbio.NewEngine(nbio.Config{
Network: "tcp",//"udp", "unix"
Addrs: []string{":8888"},
MaxWriteBufferSize: 6 * 1024 * 1024,
})
// handle new connection
engine.OnOpen(func(c *nbio.Conn) {
log.Println("OnOpen:", c.RemoteAddr().String())
})
// handle connection closed
engine.OnClose(func(c *nbio.Conn, err error) {
log.Println("OnClose:", c.RemoteAddr().String(), err)
})
// handle data
engine.OnData(func(c *nbio.Conn, data []byte) {
c.Write(append([]byte{}, data...))
})
err := engine.Start()
if err != nil {
log.Fatalf("nbio.Start failed: %v\n", err)
return
}
defer engine.Stop()
<-make(chan int)
}
For more details: go-websocket-benchmark
# lsb_release -a
LSB Version: core-11.1.0ubuntu2-noarch:security-11.1.0ubuntu2-noarch
Distributor ID: Ubuntu
Description: Ubuntu 20.04.6 LTS
Release: 20.04
Codename: focal
# free
total used free shared buff/cache available
Mem: 24969564 15656352 3422212 1880 5891000 8899604
Swap: 0 0 0
# cat /proc/cpuinfo | grep processor
processor : 0
processor : 1
processor : 2
processor : 3
processor : 4
processor : 5
processor : 6
processor : 7
processor : 8
processor : 9
processor : 10
processor : 11
processor : 12
processor : 13
processor : 14
processor : 15
# taskset
run nbio_nonblocking server on cpu 0-7
--------------------------------------------------------------
BenchType : BenchEcho
Framework : nbio_nonblocking
TPS : 104713
EER : 280.33
Min : 56.90us
Avg : 95.36ms
Max : 2.29s
TP50 : 62.82ms
TP75 : 65.38ms
TP90 : 89.38ms
TP95 : 409.55ms
TP99 : 637.95ms
Used : 47.75s
Total : 5000000
Success : 5000000
Failed : 0
Conns : 1000000
Concurrency: 10000
Payload : 1024
CPU Min : 0.00%
CPU Avg : 373.53%
CPU Max : 602.33%
MEM Min : 978.70M
MEM Avg : 979.88M
MEM Max : 981.14M
--------------------------------------------------------------
| IOMod | Remarks |
|---|---|
| IOModNonBlocking | There's no difference between this IOMod and the old version with no IOMod. All the connections will be handled by poller. |
| IOModBlocking | All the connections will be handled by at least one goroutine, for websocket, we can set Upgrader.BlockingModAsyncWrite=true to handle writing with a separated goroutine and then avoid Head-of-line blocking on broadcasting scenarios. |
| IOModMixed | We set the Engine.MaxBlockingOnline, if the online num is smaller than it, the new connection will be handled by single goroutine as IOModBlocking, else the new connection will be handled by poller. |
The IOModBlocking aims to improve the performance for low online service, it runs faster than std.
The IOModMixed aims to keep a balance between performance and cpu/mem cost in different scenarios: when there are not too many online connections, it performs better than std, or else it can serve lots of online connections and keep healthy.
package main
import (
"fmt"
"net/http"
"github.com/lesismal/nbio/nbhttp/websocket"
)
var (
upgrader = newUpgrader()
)
func newUpgrader() *websocket.Upgrader {
u := websocket.NewUpgrader()
u.OnOpen(func(c *websocket.Conn) {
// echo
fmt.Println("OnOpen:", c.RemoteAddr().String())
})
u.OnMessage(func(c *websocket.Conn, messageType websocket.MessageType, data []byte) {
// echo
fmt.Println("OnMessage:", messageType, string(data))
c.WriteMessage(messageType, data)
})
u.OnClose(func(c *websocket.Conn, err error) {
fmt.Println("OnClose:", c.RemoteAddr().String(), err)
})
return u
}
func onWebsocket(w http.ResponseWriter, r *http.Request) {
conn, err := upgrader.Upgrade(w, r, nil)
if err != nil {
panic(err)
}
fmt.Println("Upgraded:", conn.RemoteAddr().String())
}
func main() {
mux := &http.ServeMux{}
mux.HandleFunc("/ws", onWebsocket)
server := http.Server{
Addr: "localhost:8080",
Handler: mux,
}
fmt.Println("server exit:", server.ListenAndServe())
}
Thanks Everyone:
Overall
Maintained
Code review
Go module
Latest release
Releases this year
2,337 followers · starred Jun 2023
407 followers · starred Dec 2021
2,549 followers · starred Jul 2024
422 followers · starred Oct 2022