成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

深入Go語言網絡庫的基礎實現

開發 前端 開發工具
目前很多高性能的基礎網絡服務器都是采用的C語言開發的,比如:Nginx、Redis、memcached等,它們都是基于”事件驅動 + 事件回掉函數”的方式實現,也就是采用epoll等作為網絡收發數據包的核心驅動。

Go語言的出現,讓我見到了一門語言把網絡編程這件事情給做“正確”了,當然,除了Go語言以外,還有很多語言也把這件事情做”正確”了。我一直堅持著這樣的理念——要做"正確"的事情,而不是"高性能"的事情;很多時候,我們在做系統設計、技術選型的時候,都被“高性能”這三個字給綁架了,當然不是說性能不重要,你懂的。

目前很多高性能的基礎網絡服務器都是采用的C語言開發的,比如:Nginx、Redis、memcached等,它們都是基于”事件驅動 + 事件回掉函數”的方式實現,也就是采用epoll等作為網絡收發數據包的核心驅動。不少人(包括我自己)都認為“事件驅動 + 事件回掉函數”的編程方法是“反人類”的;因為大多數人都更習慣線性的處理一件事情,做完***件事情再做第二件事情,并不習慣在N件事情之間頻繁的切換干 活。為了解決程序員在開發服務器時需要自己的大腦不斷的“上下文切換”的問題,Go語言引入了一種用戶態線程goroutine來取代編寫異步的事件回掉 函數,從而重新回歸到多線程并發模型的線性、同步的編程方式上。

用Go語言寫一個最簡單的echo服務器:

  1. package main 
  2.  
  3. import ( 
  4.     "log" 
  5.     "net" 
  6.  
  7. func main() { 
  8.     ln, err := net.Listen("tcp"":8080"
  9.     if err != nil { 
  10.             log.Println(err) 
  11.             return 
  12.     } 
  13.     for { 
  14.             conn, err := ln.Accept() 
  15.             if err != nil { 
  16.                 log.Println(err) 
  17.                 continue 
  18.             } 
  19.  
  20.             go echoFunc(conn) 
  21.     } 
  22.  
  23. func echoFunc(c net.Conn) { 
  24.     buf := make([]byte1024
  25.  
  26.     for { 
  27.             n, err := c.Read(buf) 
  28.             if err != nil { 
  29.                 log.Println(err) 
  30.                 return 
  31.             } 
  32.  
  33.             c.Write(buf[:n]) 
  34.     } 

main函數的過程就是首先創建一個監聽套接字,然后用一個for循環不斷的從監聽套接字上Accept新的連接,***調用echoFunc函數在建立的連接上干活。關鍵代碼是:

  1. go echoFunc(conn) 

每收到一個新的連接,就創建一個“線程”去服務這個連接,因此所有的業務邏輯都可以同步、順序的編寫到echoFunc函數中,再也不用去關心網絡 IO是否會阻塞的問題。不管業務多復雜,Go語言的并發服務器的編程模型都是長這個樣子。可以肯定的是,在linux上Go語言寫的網絡服務器也是采用的 epoll作為***層的數據收發驅動,Go語言網絡的底層實現中同樣存在“上下文切換”的工作,只是這個切換工作由runtime的調度器來做了,減少了 程序員的負擔。

弄明白網絡庫的底層實現,貌似只要弄清楚echo服務器中的Listen、Accept、Read、Write四個函數的底層實現關系就可以了。本文將采用自底向上的方式來介紹,也就是從***層到上層的方式,這也是我閱讀源碼的方式。底層實現涉及到的核心源碼文件主要有:
net/fd_unix.go
net/fd_poll_runtime.go
runtime/netpoll.goc
runtime/netpoll_epoll.c
runtime/proc.c (調度器)

netpoll_epoll.c文件是Linux平臺使用epoll作為網絡IO多路復用的實現代碼,這份代碼可以了解到epoll相關的操作(比 如:添加fd到epoll、從epoll刪除fd等),只有4個函數,分別是runtime·netpollinit、 runtime·netpollopen、runtime·netpollclose和runtime·netpoll。init函數就是創建epoll 對象,open函數就是添加一個fd到epoll中,close函數就是從epoll刪除一個fd,netpoll函數就是從epoll wait得到所有發生事件的fd,并將每個fd對應的goroutine(用戶態線程)通過鏈表返回。用epoll寫過程序的人應該都能理解這份代碼,沒 什么特別之處。

  1. void 
  2. runtime·netpollinit(void
  3.     epfd = runtime·epollcreate1(EPOLL_CLOEXEC); 
  4.     if(epfd >= 0
  5.         return
  6.     epfd = runtime·epollcreate(1024); 
  7.     if(epfd >= 0) { 
  8.         runtime·closeonexec(epfd); 
  9.         return
  10.     } 
  11.     runtime·printf("netpollinit: failed to create descriptor (%d)\n", -epfd); 
  12.     runtime·throw("netpollinit: failed to create descriptor"); 

runtime·netpollinit函數首先使用runtime·epollcreate1創建epoll實例,如果沒有創建成功,就換用 runtime·epollcreate再創建一次。這兩個create函數分別等價于glibc的epoll_create1和 epoll_create函數。只是因為Go語言并沒有直接使用glibc,而是自己封裝的系統調用,但功能是等價于glibc的。可以通過man手冊查 看這兩個create的詳細信息。

  1. int32 
  2. runtime·netpollopen(uintptr fd, PollDesc *pd) 
  3.     EpollEvent ev; 
  4.     int32 res; 
  5.      
  6.     ev.events = EPOLLIN|EPOLLOUT|EPOLLRDHUP|EPOLLET; 
  7.     ev.data = (uint64)pd; 
  8.     res = runtime·epollctl(epfd, EPOLL_CTL_ADD, (int32)fd, &ev); 
  9.     return -res; 

添加fd到epoll中的runtime·netpollopen函數可以看到每個fd一開始都關注了讀寫事件,并且采用的是邊緣觸發,除此之外還關注了一個不常見的新事件EPOLLRDHUP,這個事件是在較新的內核版本添加的,目的是解決對端socket關閉,epoll本身并不能直接感知到這個關閉動作的問題。注意任何一個fd在添加到epoll中的時候就關注了EPOLLOUT事件的話,就立馬產生一次寫事件,這次事件可能是多余浪費的。

#p#

epoll操作的相關函數都會在事件驅動的抽象層中去調用,為什么需要這個抽象層呢?原因很簡單,因為Go語言需要跑在不同的平臺上,有Linux、Unix、Mac OS X和Windows等,所以需要靠事件驅動的抽象層來為網絡庫提供一致的接口,從而屏蔽事件驅動的具體平臺依賴實現。runtime/netpoll.goc源文件就是整個事件驅動抽象層的實現,抽象層的核心數據結構是:

  1. struct PollDesc 
  2.     PollDesc* link; // in pollcache, protected by pollcache.Lock 
  3.     Lock;       // protectes the following fields 
  4.     uintptr fd; 
  5.     bool    closing; 
  6.     uintptr seq;    // protects from stale timers and ready notifications 
  7.     G*  rg; // G waiting for read or READY (binary semaphore) 
  8.     Timer   rt; // read deadline timer (set if rt.fv != nil) 
  9.     int64   rd; // read deadline 
  10.     G*  wg; // the same for writes 
  11.     Timer   wt; 
  12.     int64   wd; 
  13. }; 

每個添加到epoll中的fd都對應了一個PollDesc結構實例,PollDesc維護了讀寫此fd的goroutine這一非常重要的信息。可以大膽的推測一下,網絡IO讀寫操作的實現應該是:當在一個fd上讀寫遇到EAGAIN錯誤的時候,就將當前goroutine存儲到這個fd對應的PollDesc中,同時將goroutine給park住,直到這個fd上再此發生了讀寫事件后,再將此goroutine給ready激活重新運行。事實上的實現大概也是這個樣子的。

事件驅動抽象層主要干的事情就是將具體的事件驅動實現(比如: epoll)通過統一的接口封裝成Go接口供net庫使用,主要的接口也是:創建事件驅動實例、添加fd、刪除fd、等待事件以及設置DeadLine。runtime_pollServerInit負責創建事件驅動實例,runtime_pollOpen將分配一個PollDesc實例和fd綁定起來,然后將fd添加到epoll中,runtime_pollClose就是將fd從epoll中刪除,同時將刪除的fd綁定的PollDesc實例刪除,runtime_pollWait接口是至關重要的,這個接口一般是在非阻塞讀寫發生EAGAIN錯誤的時候調用,作用就是park當前讀寫的goroutine。

runtime中的epoll事件驅動抽象層其實在進入net庫后,又被封裝了一次,這一次封裝從代碼上看主要是為了方便在純Go語言環境進行操作,net庫中的這次封裝實現在net/fd_poll_runtime.go文件中,主要是通過pollDesc對象來實現的:

  1. type pollDesc struct { 
  2.     runtimeCtx uintptr 

 注意:此處的pollDesc對象不是上文提到的runtime中的PollDesc,相反此處pollDesc對象的runtimeCtx成員才是指向的runtime的PollDesc實例。pollDesc對象主要就是將runtime的事件驅動抽象層給再封裝了一次,供網絡fd對象使用。

  1. var serverInit sync.Once 
  2.  
  3. func (pd *pollDesc) Init(fd *netFD) error { 
  4.     serverInit.Do(runtime_pollServerInit) 
  5.     ctx, errno := runtime_pollOpen(uintptr(fd.sysfd)) 
  6.     if errno != 0 { 
  7.         return syscall.Errno(errno) 
  8.     } 
  9.     pd.runtimeCtx = ctx 
  10.     return nil 

pollDesc對象最需要關注的就是其Init方法,這個方法通過一個sync.Once變量來調用了runtime_pollServerInit函數,也就是創建epoll實例的函數。意思就是runtime_pollServerInit函數在整個進程生命周期內只會被調用一次,也就是只會創建一次epoll實例。epoll實例被創建后,會調用runtime_pollOpen函數將fd添加到epoll中。

網絡編程中的所有socket fd都是通過netFD對象實現的,netFD是對網絡IO操作的抽象,linux的實現在文件net/fd_unix.go中。netFD對象實現有自己的init方法,還有完成基本IO操作的Read和Write方法,當然除了這三個方法以外,還有很多非常有用的方法供用戶使用。

  1. // Network file descriptor. 
  2. type netFD struct { 
  3.     // locking/lifetime of sysfd + serialize access to Read and Write methods 
  4.     fdmu fdMutex 
  5.  
  6.     // immutable until Close 
  7.     sysfd       int 
  8.     family      int 
  9.     sotype      int 
  10.     isConnected bool 
  11.     net         string 
  12.     laddr       Addr 
  13.     raddr       Addr 
  14.  
  15.     // wait server 
  16.     pd pollDesc 

通過netFD對象的定義可以看到每個fd都關聯了一個pollDesc實例,通過上文我們知道pollDesc對象最終是對epoll的封裝。

  1. func (fd *netFD) init() error { 
  2.     if err := fd.pd.Init(fd); err != nil { 
  3.         return err 
  4.     } 
  5.     return nil 

netFD對象的init函數僅僅是調用了pollDesc實例的Init函數,作用就是將fd添加到epoll中,如果這個fd是***個網絡socket fd的話,這一次init還會擔任創建epoll實例的任務。要知道在Go進程里,只會有一個epoll實例來管理所有的網絡socket fd,這個epoll實例也就是在***個網絡socket fd被創建的時候所創建。

  1. for { 
  2.     n, err = syscall.Read(int(fd.sysfd), p) 
  3.     if err != nil { 
  4.         n = 0 
  5.         if err == syscall.EAGAIN { 
  6.             if err = fd.pd.WaitRead(); err == nil { 
  7.                 continue 
  8.             } 
  9.         } 
  10.     } 
  11.     err = chkReadErr(n, err, fd) 
  12.     break 

上面代碼段是從netFD的Read方法中摘取,重點關注這個for循環中的syscall.Read調用的錯誤處理。當有錯誤發生的時候,會檢查這個錯誤是否是syscall.EAGAIN,如果是,則調用WaitRead將當前讀這個fd的goroutine給park住,直到這個fd上的讀事件再次發生為止。當這個socket上有新數據到來的時候,WaitRead調用返回,繼續for循環的執行。這樣的實現,就讓調用netFD的Read的地方變成了同步“阻塞”方式編程,不再是異步非阻塞的編程方式了。netFD的Write方法和Read的實現原理是一樣的,都是在碰到EAGAIN錯誤的時候將當前goroutine給park住直到socket再次可寫為止。

本文只是將網絡庫的底層實現給大體上引導了一遍,知道底層代碼大概實現在什么地方,方便結合源碼深入理解。Go語言中的高并發、同步阻塞方式編程的關鍵其實是”goroutine和調度器”,針對網絡IO的時候,我們需要知道EAGAIN這個非常關鍵的調度點,掌握了這個調度點,即使沒有調度器,自己也可以在epoll的基礎上配合協程等用戶態線程實現網絡IO操作的調度,達到同步阻塞編程的目的。

***,為什么需要同步阻塞的方式編程?只有看多、寫多了異步非阻塞代碼的時候才能夠深切體會到這個問題。真正的高大上絕對不是——“別人不會,我會;別人寫不出來,我寫得出來。”

原文鏈接:http://skoo.me/go/2014/04/21/go-net-core/

責任編輯:林師授 來源: skoo's notes
相關推薦

2023-12-30 10:22:57

Go語言函數開發

2023-11-30 08:09:02

Go語言

2022-10-24 00:48:58

Go語言errgroup

2021-10-16 17:53:35

Go函數編程

2023-11-01 08:08:50

Go語言傳遞請求

2023-11-06 13:32:38

Go編程

2024-03-25 07:22:50

GolangMySQL數據庫

2024-01-07 19:54:51

2021-01-23 12:47:19

MySQL數據庫Go語言

2017-08-31 11:28:47

Slice底層實現

2022-05-09 10:36:05

PythonPyScript開發者

2023-01-30 08:16:39

Go語言Map

2024-05-29 08:05:15

Go協程通信

2024-04-07 00:04:00

Go語言Map

2021-06-09 09:06:52

Go語言算法

2021-02-06 18:19:54

TimeGo語言

2024-03-26 00:17:51

Go語言IO

2024-03-29 09:12:43

Go語言工具

2024-02-26 19:38:20

GitHubGo庫Golang

2023-04-02 23:13:07

Go語言bufio
點贊
收藏

51CTO技術棧公眾號

主站蜘蛛池模板: 就操在线 | 国产精品综合色区在线观看 | 中文字幕日韩欧美 | 欧美欧美欧美 | 久久中文字幕av | 久久久精 | 永久精品| 天天久 | 免费久久久 | 六月成人网 | 国产线视频精品免费观看视频 | 国产精品一区二区三区在线 | 天天夜夜人人 | 国产日韩欧美在线观看 | h片免费在线观看 | 色就干 | 日本久久www成人免 成人久久久久 | 久久亚洲精品国产精品紫薇 | 九九精品影院 | 亚洲成色777777在线观看影院 | 国产成人一区二区三区电影 | 99精品欧美一区二区三区 | 国产成人在线视频 | 欧美视频偷拍 | 日韩在线一区二区 | h视频免费看| 成人免费视频观看视频 | 日日操操 | 久久精品一区二区视频 | 久久国产综合 | 日本高清不卡视频 | 亚洲三区在线播放 | 国产激情一区二区三区 | 人人做人人澡人人爽欧美 | 久久久久久av | 精品视频久久久 | 高清视频一区二区三区 | 国产欧美精品在线观看 | 国产成人av在线播放 | 欧美精品一区二区免费视频 | 国产高清在线精品 |