<ruby id="bdb3f"></ruby>

    <p id="bdb3f"><cite id="bdb3f"></cite></p>

      <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
        <p id="bdb3f"><cite id="bdb3f"></cite></p>

          <pre id="bdb3f"></pre>
          <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

          <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
          <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

          <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                <ruby id="bdb3f"></ruby>

                ??一站式輕松地調用各大LLM模型接口,支持GPT4、智譜、豆包、星火、月之暗面及文生圖、文生視頻 廣告
                在很多底層網絡應用開發者的眼里一切編程都是Socket,話雖然有點夸張,但卻也幾乎如此了,現在的網絡編程幾乎都是用Socket來編程。你想過這些情景么?我們每天打開瀏覽器瀏覽網頁時,瀏覽器進程怎么和Web服務器進行通信的呢?當你用QQ聊天時,QQ進程怎么和服務器或者是你的好友所在的QQ進程進行通信的呢?當你打開PPstream觀看視頻時,PPstream進程如何與視頻服務器進行通信的呢? 如此種種,都是靠Socket來進行通信的,以一斑窺全豹,可見Socket編程在現代編程中占據了多么重要的地位,這一節我們將介紹Go語言中如何進行Socket編程。 ## [](https://github.com/astaxie/build-web-application-with-golang/blob/master/zh/08.1.md#什么是socket)什么是Socket? Socket起源于Unix,而Unix基本哲學之一就是“一切皆文件”,都可以用“打開open –> 讀寫write/read –> 關閉close”模式來操作。Socket就是該模式的一個實現,網絡的Socket數據傳輸是一種特殊的I/O,Socket也是一種文件描述符。Socket也具有一個類似于打開文件的函數調用:Socket(),該函數返回一個整型的Socket描述符,隨后的連接建立、數據傳輸等操作都是通過該Socket實現的。 常用的Socket類型有兩種:流式Socket(SOCK_STREAM)和數據報式Socket(SOCK_DGRAM)。流式是一種面向連接的Socket,針對于面向連接的TCP服務應用;數據報式Socket是一種無連接的Socket,對應于無連接的UDP服務應用。 ## [](https://github.com/astaxie/build-web-application-with-golang/blob/master/zh/08.1.md#socket如何通信)Socket如何通信 網絡中的進程之間如何通過Socket通信呢?首要解決的問題是如何唯一標識一個進程,否則通信無從談起!在本地可以通過進程PID來唯一標識一個進程,但是在網絡中這是行不通的。其實TCP/IP協議族已經幫我們解決了這個問題,網絡層的“ip地址”可以唯一標識網絡中的主機,而傳輸層的“協議+端口”可以唯一標識主機中的應用程序(進程)。這樣利用三元組(ip地址,協議,端口)就可以標識網絡的進程了,網絡中需要互相通信的進程,就可以利用這個標志在他們之間進行交互。請看下面這個TCP/IP協議結構圖 [![](https://github.com/astaxie/build-web-application-with-golang/raw/master/zh/images/8.1.socket.png?raw=true)](https://github.com/astaxie/build-web-application-with-golang/blob/master/zh/images/8.1.socket.png?raw=true) 圖8.1 七層網絡協議圖 使用TCP/IP協議的應用程序通常采用應用編程接口:UNIX BSD的套接字(socket)和UNIX System V的TLI(已經被淘汰),來實現網絡進程之間的通信。就目前而言,幾乎所有的應用程序都是采用socket,而現在又是網絡時代,網絡中進程通信是無處不在,這就是為什么說“一切皆Socket”。 ## [](https://github.com/astaxie/build-web-application-with-golang/blob/master/zh/08.1.md#socket基礎知識)Socket基礎知識 通過上面的介紹我們知道Socket有兩種:TCP Socket和UDP Socket,TCP和UDP是協議,而要確定一個進程的需要三元組,需要IP地址和端口。 ### [](https://github.com/astaxie/build-web-application-with-golang/blob/master/zh/08.1.md#ipv4地址)IPv4地址 目前的全球因特網所采用的協議族是TCP/IP協議。IP是TCP/IP協議中網絡層的協議,是TCP/IP協議族的核心協議。目前主要采用的IP協議的版本號是4(簡稱為IPv4),發展至今已經使用了30多年。 IPv4的地址位數為32位,也就是最多有2的32次方的網絡設備可以聯到Internet上。近十年來由于互聯網的蓬勃發展,IP位址的需求量愈來愈大,使得IP位址的發放愈趨緊張,前一段時間,據報道IPV4的地址已經發放完畢,我們公司目前很多服務器的IP都是一個寶貴的資源。 地址格式類似這樣:127.0.0.1 172.122.121.111 ### [](https://github.com/astaxie/build-web-application-with-golang/blob/master/zh/08.1.md#ipv6地址)IPv6地址 IPv6是下一版本的互聯網協議,也可以說是下一代互聯網的協議,它是為了解決IPv4在實施過程中遇到的各種問題而被提出的,IPv6采用128位地址長度,幾乎可以不受限制地提供地址。按保守方法估算IPv6實際可分配的地址,整個地球的每平方米面積上仍可分配1000多個地址。在IPv6的設計過程中除了一勞永逸地解決了地址短缺問題以外,還考慮了在IPv4中解決不好的其它問題,主要有端到端IP連接、服務質量(QoS)、安全性、多播、移動性、即插即用等。 地址格式類似這樣:2002:c0e8:82e7:0:0:0:c0e8:82e7 ### [](https://github.com/astaxie/build-web-application-with-golang/blob/master/zh/08.1.md#go支持的ip類型)Go支持的IP類型 在Go的`net`包中定義了很多類型、函數和方法用來網絡編程,其中IP的定義如下: ~~~ type IP []byte ~~~ 在`net`包中有很多函數來操作IP,但是其中比較有用的也就幾個,其中`ParseIP(s string) IP`函數會把一個IPv4或者IPv6的地址轉化成IP類型,請看下面的例子: ~~~ package main import ( "net" "os" "fmt" ) func main() { if len(os.Args) != 2 { fmt.Fprintf(os.Stderr, "Usage: %s ip-addr\n", os.Args[0]) os.Exit(1) } name := os.Args[1] addr := net.ParseIP(name) if addr == nil { fmt.Println("Invalid address") } else { fmt.Println("The address is ", addr.String()) } os.Exit(0) } ~~~ 執行之后你就會發現只要你輸入一個IP地址就會給出相應的IP格式 ## [](https://github.com/astaxie/build-web-application-with-golang/blob/master/zh/08.1.md#tcp-socket)TCP Socket 當我們知道如何通過網絡端口訪問一個服務時,那么我們能夠做什么呢?作為客戶端來說,我們可以通過向遠端某臺機器的的某個網絡端口發送一個請求,然后得到在機器的此端口上監聽的服務反饋的信息。作為服務端,我們需要把服務綁定到某個指定端口,并且在此端口上監聽,當有客戶端來訪問時能夠讀取信息并且寫入反饋信息。 在Go語言的`net`包中有一個類型`TCPConn`,這個類型可以用來作為客戶端和服務器端交互的通道,他有兩個主要的函數: ~~~ func (c *TCPConn) Write(b []byte) (n int, err os.Error) func (c *TCPConn) Read(b []byte) (n int, err os.Error) ~~~ `TCPConn`可以用在客戶端和服務器端來讀寫數據。 還有我們需要知道一個`TCPAddr`類型,他表示一個TCP的地址信息,他的定義如下: ~~~ type TCPAddr struct { IP IP Port int } ~~~ 在Go語言中通過`ResolveTCPAddr`獲取一個`TCPAddr` ~~~ func ResolveTCPAddr(net, addr string) (*TCPAddr, os.Error) ~~~ * net參數是"tcp4"、"tcp6"、"tcp"中的任意一個,分別表示TCP(IPv4-only),TCP(IPv6-only)或者TCP(IPv4,IPv6的任意一個). * addr表示域名或者IP地址,例如"[www.google.com:80](http://www.google.com/)" 或者"127.0.0.1:22". ### [](https://github.com/astaxie/build-web-application-with-golang/blob/master/zh/08.1.md#tcp-client)TCP client Go語言中通過net包中的`DialTCP`函數來建立一個TCP連接,并返回一個`TCPConn`類型的對象,當連接建立時服務器端也創建一個同類型的對象,此時客戶端和服務器段通過各自擁有的`TCPConn`對象來進行數據交換。一般而言,客戶端通過`TCPConn`對象將請求信息發送到服務器端,讀取服務器端響應的信息。服務器端讀取并解析來自客戶端的請求,并返回應答信息,這個連接只有當任一端關閉了連接之后才失效,不然這連接可以一直在使用。建立連接的函數定義如下: ~~~ func DialTCP(net string, laddr, raddr *TCPAddr) (c *TCPConn, err os.Error) ~~~ * net參數是"tcp4"、"tcp6"、"tcp"中的任意一個,分別表示TCP(IPv4-only)、TCP(IPv6-only)或者TCP(IPv4,IPv6的任意一個) * laddr表示本機地址,一般設置為nil * raddr表示遠程的服務地址 接下來我們寫一個簡單的例子,模擬一個基于HTTP協議的客戶端請求去連接一個Web服務端。我們要寫一個簡單的http請求頭,格式類似如下: ~~~ "HEAD / HTTP/1.0\r\n\r\n" ~~~ 從服務端接收到的響應信息格式可能如下: ~~~ HTTP/1.0 200 OK ETag: "-9985996" Last-Modified: Thu, 25 Mar 2010 17:51:10 GMT Content-Length: 18074 Connection: close Date: Sat, 28 Aug 2010 00:43:48 GMT Server: lighttpd/1.4.23 ~~~ 我們的客戶端代碼如下所示: ~~~ package main import ( "fmt" "io/ioutil" "net" "os" ) func main() { if len(os.Args) != 2 { fmt.Fprintf(os.Stderr, "Usage: %s host:port ", os.Args[0]) os.Exit(1) } service := os.Args[1] tcpAddr, err := net.ResolveTCPAddr("tcp4", service) checkError(err) conn, err := net.DialTCP("tcp", nil, tcpAddr) checkError(err) _, err = conn.Write([]byte("HEAD / HTTP/1.0\r\n\r\n")) checkError(err) result, err := ioutil.ReadAll(conn) checkError(err) fmt.Println(string(result)) os.Exit(0) } func checkError(err error) { if err != nil { fmt.Fprintf(os.Stderr, "Fatal error: %s", err.Error()) os.Exit(1) } } ~~~ 通過上面的代碼我們可以看出:首先程序將用戶的輸入作為參數`service`傳入`net.ResolveTCPAddr`獲取一個tcpAddr,然后把tcpAddr傳入DialTCP后創建了一個TCP連接`conn`,通過`conn`來發送請求信息,最后通過`ioutil.ReadAll`從`conn`中讀取全部的文本,也就是服務端響應反饋的信息。 ### [](https://github.com/astaxie/build-web-application-with-golang/blob/master/zh/08.1.md#tcp-server)TCP server 上面我們編寫了一個TCP的客戶端程序,也可以通過net包來創建一個服務器端程序,在服務器端我們需要綁定服務到指定的非激活端口,并監聽此端口,當有客戶端請求到達的時候可以接收到來自客戶端連接的請求。net包中有相應功能的函數,函數定義如下: ~~~ func ListenTCP(net string, laddr *TCPAddr) (l *TCPListener, err os.Error) func (l *TCPListener) Accept() (c Conn, err os.Error) ~~~ 參數說明同DialTCP的參數一樣。下面我們實現一個簡單的時間同步服務,監聽7777端口 ~~~ package main import ( "fmt" "net" "os" "time" ) func main() { service := ":7777" tcpAddr, err := net.ResolveTCPAddr("tcp4", service) checkError(err) listener, err := net.ListenTCP("tcp", tcpAddr) checkError(err) for { conn, err := listener.Accept() if err != nil { continue } daytime := time.Now().String() conn.Write([]byte(daytime)) // don't care about return value conn.Close() // we're finished with this client } } func checkError(err error) { if err != nil { fmt.Fprintf(os.Stderr, "Fatal error: %s", err.Error()) os.Exit(1) } } ~~~ 上面的服務跑起來之后,它將會一直在那里等待,直到有新的客戶端請求到達。當有新的客戶端請求到達并同意接受`Accept`該請求的時候他會反饋當前的時間信息。值得注意的是,在代碼中`for`循環里,當有錯誤發生時,直接continue而不是退出,是因為在服務器端跑代碼的時候,當有錯誤發生的情況下最好是由服務端記錄錯誤,然后當前連接的客戶端直接報錯而退出,從而不會影響到當前服務端運行的整個服務。 上面的代碼有個缺點,執行的時候是單任務的,不能同時接收多個請求,那么該如何改造以使它支持多并發呢?Go里面有一個goroutine機制,請看下面改造后的代碼 ~~~ package main import ( "fmt" "net" "os" "time" ) func main() { service := ":1200" tcpAddr, err := net.ResolveTCPAddr("tcp4", service) checkError(err) listener, err := net.ListenTCP("tcp", tcpAddr) checkError(err) for { conn, err := listener.Accept() if err != nil { continue } go handleClient(conn) } } func handleClient(conn net.Conn) { defer conn.Close() daytime := time.Now().String() conn.Write([]byte(daytime)) // don't care about return value // we're finished with this client } func checkError(err error) { if err != nil { fmt.Fprintf(os.Stderr, "Fatal error: %s", err.Error()) os.Exit(1) } } ~~~ 通過把業務處理分離到函數`handleClient`,我們就可以進一步地實現多并發執行了。看上去是不是很帥,增加`go`關鍵詞就實現了服務端的多并發,從這個小例子也可以看出goroutine的強大之處。 有的朋友可能要問:這個服務端沒有處理客戶端實際請求的內容。如果我們需要通過從客戶端發送不同的請求來獲取不同的時間格式,而且需要一個長連接,該怎么做呢?請看: ~~~ package main import ( "fmt" "net" "os" "time" "strconv" ) func main() { service := ":1200" tcpAddr, err := net.ResolveTCPAddr("tcp4", service) checkError(err) listener, err := net.ListenTCP("tcp", tcpAddr) checkError(err) for { conn, err := listener.Accept() if err != nil { continue } go handleClient(conn) } } func handleClient(conn net.Conn) { conn.SetReadDeadline(time.Now().Add(2 * time.Minute)) // set 2 minutes timeout request := make([]byte, 128) // set maxium request length to 128KB to prevent flood attack defer conn.Close() // close connection before exit for { read_len, err := conn.Read(request) if err != nil { fmt.Println(err) break } if read_len == 0 { break // connection already closed by client } else if string(request) == "timestamp" { daytime := strconv.FormatInt(time.Now().Unix(), 10) conn.Write([]byte(daytime)) } else { daytime := time.Now().String() conn.Write([]byte(daytime)) } request = make([]byte, 128) // clear last read content } } func checkError(err error) { if err != nil { fmt.Fprintf(os.Stderr, "Fatal error: %s", err.Error()) os.Exit(1) } } ~~~ 在上面這個例子中,我們使用`conn.Read()`不斷讀取客戶端發來的請求。由于我們需要保持與客戶端的長連接,所以不能在讀取完一次請求后就關閉連接。由于`conn.SetReadDeadline()`設置了超時,當一定時間內客戶端無請求發送,`conn`便會自動關閉,下面的for循環即會因為連接已關閉而跳出。需要注意的是,`request`在創建時需要指定一個最大長度以防止flood attack;每次讀取到請求處理完畢后,需要清理request,因為`conn.Read()`會將新讀取到的內容append到原內容之后。 ### [](https://github.com/astaxie/build-web-application-with-golang/blob/master/zh/08.1.md#控制tcp連接)控制TCP連接 TCP有很多連接控制函數,我們平常用到比較多的有如下幾個函數: ~~~ func DialTimeout(net, addr string, timeout time.Duration) (Conn, error) ~~~ 設置建立連接的超時時間,客戶端和服務器端都適用,當超過設置時間時,連接自動關閉。 ~~~ func (c *TCPConn) SetReadDeadline(t time.Time) error func (c *TCPConn) SetWriteDeadline(t time.Time) error ~~~ 用來設置寫入/讀取一個連接的超時時間。當超過設置時間時,連接自動關閉。 ~~~ func (c *TCPConn) SetKeepAlive(keepalive bool) os.Error ~~~ 設置客戶端是否和服務器端保持長連接,可以降低建立TCP連接時的握手開銷,對于一些需要頻繁交換數據的應用場景比較適用。 更多的內容請查看`net`包的文檔。 ## [](https://github.com/astaxie/build-web-application-with-golang/blob/master/zh/08.1.md#udp-socket)UDP Socket Go語言包中處理UDP Socket和TCP Socket不同的地方就是在服務器端處理多個客戶端請求數據包的方式不同,UDP缺少了對客戶端連接請求的Accept函數。其他基本幾乎一模一樣,只有TCP換成了UDP而已。UDP的幾個主要函數如下所示: ~~~ func ResolveUDPAddr(net, addr string) (*UDPAddr, os.Error) func DialUDP(net string, laddr, raddr *UDPAddr) (c *UDPConn, err os.Error) func ListenUDP(net string, laddr *UDPAddr) (c *UDPConn, err os.Error) func (c *UDPConn) ReadFromUDP(b []byte) (n int, addr *UDPAddr, err os.Error func (c *UDPConn) WriteToUDP(b []byte, addr *UDPAddr) (n int, err os.Error) ~~~ 一個UDP的客戶端代碼如下所示,我們可以看到不同的就是TCP換成了UDP而已: ~~~ package main import ( "fmt" "net" "os" ) func main() { if len(os.Args) != 2 { fmt.Fprintf(os.Stderr, "Usage: %s host:port", os.Args[0]) os.Exit(1) } service := os.Args[1] udpAddr, err := net.ResolveUDPAddr("udp4", service) checkError(err) conn, err := net.DialUDP("udp", nil, udpAddr) checkError(err) _, err = conn.Write([]byte("anything")) checkError(err) var buf [512]byte n, err := conn.Read(buf[0:]) checkError(err) fmt.Println(string(buf[0:n])) os.Exit(0) } func checkError(err error) { if err != nil { fmt.Fprintf(os.Stderr, "Fatal error ", err.Error()) os.Exit(1) } } ~~~ 我們來看一下UDP服務器端如何來處理: ~~~ package main import ( "fmt" "net" "os" "time" ) func main() { service := ":1200" udpAddr, err := net.ResolveUDPAddr("udp4", service) checkError(err) conn, err := net.ListenUDP("udp", udpAddr) checkError(err) for { handleClient(conn) } } func handleClient(conn *net.UDPConn) { var buf [512]byte _, addr, err := conn.ReadFromUDP(buf[0:]) if err != nil { return } daytime := time.Now().String() conn.WriteToUDP([]byte(daytime), addr) } func checkError(err error) { if err != nil { fmt.Fprintf(os.Stderr, "Fatal error ", err.Error()) os.Exit(1) } } ~~~ ## [](https://github.com/astaxie/build-web-application-with-golang/blob/master/zh/08.1.md#總結)總結 通過對TCP和UDP Socket編程的描述和實現,可見Go已經完備地支持了Socket編程,而且使用起來相當的方便,Go提供了很多函數,通過這些函數可以很容易就編寫出高性能的Socket應用。
                  <ruby id="bdb3f"></ruby>

                  <p id="bdb3f"><cite id="bdb3f"></cite></p>

                    <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
                      <p id="bdb3f"><cite id="bdb3f"></cite></p>

                        <pre id="bdb3f"></pre>
                        <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

                        <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
                        <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

                        <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                              <ruby id="bdb3f"></ruby>

                              哎呀哎呀视频在线观看