go语言性能如此之高程序员
无数程序员折断了腰web
云端时代独领风骚服务器
惟独JAVA欲比高websocket
书归正传,为何go在服务器构建时性能如此牛逼呢?markdown
请接下回网络
go在构建网络服务时性能牛逼主要源自于net包内部采用的多路复用技术,再结合go独特的goroutine构成了一种特点模式goroutine per connection。app
贴一段简单的TCP服务端代码,依次分析socket
func main() {
//监听8080端口
ln, err := net.Listen("tcp", ":8080")
if err != nil {
log.Fatal(err)
}
for {
conn, err := ln.Accept()
if err != nil {
continue
}
go read(conn)
}
}
func read(conn net.Conn) {
io.Copy(io.Discard, conn)
}
复制代码
从net的Listen方法进入,一层层往下找就能够找到下面这么一段代码async
// ------------------ net/sock_posix.go ------------------
// socket returns a network file descriptor that is ready for
// asynchronous I/O using the network poller.
func socket(ctx context.Context, net string, family, sotype, proto int, ipv6only bool, laddr, raddr sockaddr, ctrlFn func(string, string, syscall.RawConn) error) (fd *netFD, err error) {
//底层调用系统的socket并返回socket文件描述符fd
s, err := sysSocket(family, sotype, proto)
if err != nil {
return nil, err
}
//设置socket参数
if err = setDefaultSockopts(s, family, sotype, ipv6only); err != nil {
poll.CloseFunc(s)
return nil, err
}
//建立net包下的netFD,其中netFD包含了一个poll.FD
if fd, err = newFD(s, family, sotype, net); err != nil {
poll.CloseFunc(s)
return nil, err
}
if laddr != nil && raddr == nil {
switch sotype {
case syscall.SOCK_STREAM, syscall.SOCK_SEQPACKET:
//设置监听,由于在listenTCP函数中设置的sotype为syscall.SOCK_STREAM
if err := fd.listenStream(laddr, listenerBacklog(), ctrlFn); err != nil {
fd.Close()
return nil, err
}
return fd, nil
case syscall.SOCK_DGRAM:
if err := fd.listenDatagram(laddr, ctrlFn); err != nil {
fd.Close()
return nil, err
}
return fd, nil
}
}
//客户端模式的时候,会进入到这里来
if err := fd.dial(ctx, laddr, raddr, ctrlFn); err != nil {
fd.Close()
return nil, err
}
return fd, nil
}
func (fd *netFD) listenStream(laddr sockaddr, backlog int, ctrlFn func(string, string, syscall.RawConn) error) error {
....
//进行socket绑定
if err = syscall.Bind(fd.pfd.Sysfd, lsa); err != nil {
return os.NewSyscallError("bind", err)
}
//调用操做系统的监听
if err = listenFunc(fd.pfd.Sysfd, backlog); err != nil {
return os.NewSyscallError("listen", err)
}
//fd初始化,里面会有poll.FD初始化,快要进入关键位置了
if err = fd.init(); err != nil {
return err
}
lsa, _ = syscall.Getsockname(fd.pfd.Sysfd)
fd.setAddr(fd.addrFunc()(lsa), nil)
return nil
}
// ------------------ net/fd_unix.go ------------------
func (fd *netFD) init() error {
//netFD的初始化竟然仅仅调用了pfd的初始化,说明pfd至关重要啊。火烧眉毛的为她宽衣解带了
return fd.pfd.Init(fd.net, true)
}
// 文件 Internal/poll/fd_unix.go
func (fd *FD) Init(net string, pollable bool) error {
// We don't actually care about the various network types.
if net == "file" {
fd.isFile = true
}
if !pollable {
fd.isBlocking = 1
return nil
}
//继续初始化fd.pd是pollDesc的对象,这个是对poll文件描述符的一个包装,咱们看看里面是如何初始化的。
err := fd.pd.init(fd)
if err != nil {
// If we could not initialize the runtime poller,
// assume we are using blocking mode.
fd.isBlocking = 1
}
return err
}
//------------------ Internal/poll/fd_poll_runtime.go ------------------
func runtime_pollServerInit()
func runtime_pollOpen(fd uintptr) (uintptr, int)
func runtime_pollClose(ctx uintptr)
func runtime_pollWait(ctx uintptr, mode int) int
func runtime_pollWaitCanceled(ctx uintptr, mode int) int
func runtime_pollReset(ctx uintptr, mode int) int
func runtime_pollSetDeadline(ctx uintptr, d int64, mode int)
func runtime_pollUnblock(ctx uintptr)
func runtime_isPollServerDescriptor(fd uintptr) bool
type pollDesc struct {
runtimeCtx uintptr
}
var serverInit sync.Once
func (pd *pollDesc) init(fd *FD) error {
//服务初始化一次,经过sync.Onece,保障poll在全局只初始化一次
serverInit.Do(runtime_pollServerInit)
//
ctx, errno := runtime_pollOpen(uintptr(fd.Sysfd))
....
}
// ------------------ runtime/netpoll.go ------------------
//此处poll_runtime_pollOpen绑定了上面的runtime_pollOpen
//go:linkname poll_runtime_pollOpen internal/poll.runtime_pollOpen
func poll_runtime_pollOpen(fd uintptr) (*pollDesc, int) {
....
var errno int32
//此处就区别于各个不一样操做系统的定义了。kqueue/epoll
errno = netpollopen(fd, pd)
return pd, int(errno)
}
//------------------ runtime/netpoll_epoll.go ------------------
//这里拿epoll的实现来看,其中调用了epollctl,至此就是真正的与操做系统交互了。这里将当前服务端的链接自身加入到epoll中进行监听,当Accept时,就是等待Epoll事件的回调
func netpollopen(fd uintptr, pd *pollDesc) int32 {
var ev epollevent
ev.events = _EPOLLIN | _EPOLLOUT | _EPOLLRDHUP | _EPOLLET
*(**pollDesc)(unsafe.Pointer(&ev.data)) = pd
return -epollctl(epfd, _EPOLL_CTL_ADD, int32(fd), &ev)
}
复制代码
至此一个TCP服务端的端口绑定及Epoll的监听算是建立好了,整个路径整理后就是tcp
net.Listen -> sysListener.listenTCP -> sysSocket -> netFD.listenStream -> syscall.Bind -> poll.pollDesc.init -> runtime_pollServerInit -> runtime_pollOpen
复制代码
经过这一整套链路,建立socks描述符,绑定地址与端口,接着建立全局惟一的Poller,以后将socket加入到poller中。
至此初始化工做已经作完了,接着就是须要等待客户端的链接了
// ------------------ net/tcpsock.go ------------------
func (ln *TCPListener) accept() (*TCPConn, error) {
//关键代码仍是调用的fd的accept
fd, err := ln.fd.accept()
....
}
// ------------------ net/fd_unix.go ------------------
func (fd *netFD) accept() (netfd *netFD, err error) {
//net的fd最终调用的是pfd.Accept函数,也就是poll.FD的Accept继续
d, rsa, errcall, err := fd.pfd.Accept()
if err != nil {
if errcall != "" {
err = wrapSyscallError(errcall, err)
}
return nil, err
}
// 接受到客户端链接的fd以后,建立一个netFD对象,并初始化。实际上流程又和上面同样,初始化poller,将fd加入到poller监听,这样就造成了一个客户端链接和服务端监听都在一个poller里面管理。
if netfd, err = newFD(d, fd.family, fd.sotype, fd.net); err != nil {
poll.CloseFunc(d)
return nil, err
}
if err = netfd.init(); err != nil {
netfd.Close()
return nil, err
}
lsa, _ := syscall.Getsockname(netfd.pfd.Sysfd)
netfd.setAddr(netfd.addrFunc()(lsa), netfd.addrFunc()(rsa))
return netfd, nil
}
// ------------------ poll/fd_unix.go ------------------
// Accept wraps the accept network call.
func (fd *FD) Accept() (int, syscall.Sockaddr, string, error) {
....
for {
// 这里是无阻塞的获取客户端的连接,若是返回为syscall.EAGAIN,将会进入poll_runtime_pollWait进行等待poller的状态变化。若是poller发生状态变化就再次调用accept进行获取客户端连接并返回给调用方.
s, rsa, errcall, err := accept(fd.Sysfd)
if err == nil {
return s, rsa, "", err
}
switch err {
case syscall.EINTR:
continue
case syscall.EAGAIN:
//syscall.EAGAIN 前面的accept函数调用系统的accept,因为是非阻塞的fd,因此就返回了一个再次尝试的错误。
// 而咱们的pd是可pollable的,因此咱们不会继续重试,而是等待系统poll的事件通知
if fd.pd.pollable() {
if err = fd.pd.waitRead(fd.isFile); err == nil {
continue
}
}
case syscall.ECONNABORTED:
// This means that a socket on the listen
// queue was closed before we Accept()ed it;
// it's a silly error, so try again.
continue
}
return -1, nil, errcall, err
}
}
// ------------------ runtime/netpoll.go ------------------
// 检查poll fd 是否已就绪 。这里调用的都是和操做系统的交互层级了。有兴趣的能够继续深刻
func poll_runtime_pollWait(pd *pollDesc, mode int) int {
errcode := netpollcheckerr(pd, int32(mode))
if errcode != pollNoError {
return errcode
}
// As for now only Solaris, illumos, and AIX use level-triggered IO.
if GOOS == "solaris" || GOOS == "illumos" || GOOS == "aix" {
netpollarm(pd, mode)
}
for !netpollblock(pd, int32(mode), false) {
errcode = netpollcheckerr(pd, int32(mode))
if errcode != pollNoError {
return errcode
}
// Can happen if timeout has fired and unblocked us,
// but before we had a chance to run, timeout has been reset.
// Pretend it has not happened and retry.
}
return pollNoError
}
复制代码
经过什么的建立服务端、建立pollDesc、加入链接到poll监听等一系列操做,从而实现了go底层网络包高性能。
而咱们本身要实现一个高性能web服务、websocket,或者是tcp服务器都会变的很是简单。固然这个高性能只是相对的,若是想继续压榨服务器性能得采用直接和epoll等技术直接交互,这样能够省去goroutine来单独监听链接的读操做。
条理性有待增强,继续努力