真实的国产乱ⅩXXX66竹夫人,五月香六月婷婷激情综合,亚洲日本VA一区二区三区,亚洲精品一区二区三区麻豆

成都創(chuàng)新互聯(lián)網(wǎng)站制作重慶分公司

Go36-46-訪問網(wǎng)絡(luò)服務(wù)(socket)

訪問網(wǎng)絡(luò)服務(wù)

這篇開始講網(wǎng)絡(luò)編程。不過網(wǎng)絡(luò)編程的內(nèi)容過于龐大,這里主要講socket。而socket可以講的東西也太多了,因此,這里只圍繞Go語言介紹一些它的基礎(chǔ)知識(shí)。

為下城等地區(qū)用戶提供了全套網(wǎng)頁設(shè)計(jì)制作服務(wù),及下城網(wǎng)站建設(shè)行業(yè)解決方案。主營業(yè)務(wù)為網(wǎng)站設(shè)計(jì)制作、成都網(wǎng)站制作、下城網(wǎng)站設(shè)計(jì),以傳統(tǒng)方式定制建設(shè)網(wǎng)站,并提供域名空間備案等一條龍服務(wù),秉承以專業(yè)、用心的態(tài)度為用戶提供真誠的服務(wù)。我們深信只要達(dá)到每一位用戶的要求,就會(huì)得到認(rèn)可,從而選擇與我們長(zhǎng)期合作。這樣,我們也可以走得更遠(yuǎn)!

IPC方法

所謂socket,是一種IPC(Inter-Process Communication)方法,可以被翻譯為進(jìn)程間通信。顧名思義,IPC這個(gè)概念(或者說規(guī)范)主要定義的是多個(gè)進(jìn)程之間,相互通信的方法。這些方法主要包括:

  • 系統(tǒng)信號(hào)(signal),os包和os/signal包有針對(duì)系統(tǒng)信號(hào)的API
  • 管道(pipe),os.Pipe函數(shù)可以創(chuàng)建命名管道,os/exec包支持另一類管道:匿名管道
  • 套接字(socket),net包中提供支持
  • 文件鎖(file lock)
  • 消息隊(duì)列(message queue)
  • 信號(hào)燈(semaphore),也稱為信號(hào)量

現(xiàn)存的主要操作系統(tǒng)大都對(duì)IPC提供了強(qiáng)有力的支持,尤其是socket。

socket

socket,常被稱作套接字,它是網(wǎng)絡(luò)編程世界中最為核心的知識(shí)之一。
毫不夸張的說,在眾多IPC方法中,socket是最為通用和靈活的一種。與其他的IPC方法不同,利用socket進(jìn)行通信的進(jìn)程,可以不局限在同一臺(tái)計(jì)算機(jī)當(dāng)中。通信雙方只要能夠通過網(wǎng)絡(luò)進(jìn)行互聯(lián),就可以使用socket。
支持socket的操作系統(tǒng)一般都會(huì)對(duì)外提供一套API。跑在它們之上的應(yīng)用程序,利用這套API就可以與互聯(lián)網(wǎng)上的另一臺(tái)計(jì)算機(jī)中的程序、同一臺(tái)計(jì)算機(jī)中的其他程序,甚至同一個(gè)程序中的其他線程進(jìn)行通信。例如,在Linux操作系統(tǒng)中,用于創(chuàng)建socket實(shí)例的API,就是由一個(gè)名為socket的系統(tǒng)調(diào)用代表的。這個(gè)系統(tǒng)調(diào)用是Linux內(nèi)核的一部分。所謂的系統(tǒng)調(diào)用,你可以理解為特殊的C語言函數(shù)。它們是連接應(yīng)用程序和操作系統(tǒng)內(nèi)核的橋梁,也是應(yīng)用程序使用操作系統(tǒng)功能的唯一渠道。

syscall包

在Go語言標(biāo)準(zhǔn)庫的syscall包中,有一個(gè)與這個(gè)socket系統(tǒng)調(diào)用相對(duì)應(yīng)的函數(shù)。這兩者的函數(shù)簽名是基本一致的,它們都會(huì)接受三個(gè)int類型的參數(shù),并會(huì)返回一個(gè)可以代表文件描述符的結(jié)果。但不同的是,syscall包中的Socket函數(shù)本身是平臺(tái)不相關(guān)的。在其底層,Go語言為它支持的每個(gè)操作系統(tǒng)都做了適配,這樣這個(gè)函數(shù)無論在哪個(gè)平臺(tái)上,總是有效的。
在syscall.Socket函數(shù)中的三個(gè)參數(shù)分別是:

  • socket實(shí)例的通信域
  • socket實(shí)例的類型
  • socket實(shí)例的使用協(xié)議

下面,通過這3個(gè)參數(shù)來了解一下socket的基礎(chǔ)知識(shí)。

通信域

Socket的通信域主要有3種,分別對(duì)應(yīng)syscall包中的一個(gè)常量:

  1. AF_INET : IPv4域
  2. AF_INET6 : IPv6域
  3. AF_UNIX : Unix域

關(guān)于IPv4和IPv6就不講了,Unix域簡(jiǎn)單提一下。
Unix域,指的是一種類Unix操作系統(tǒng)中特有的通信域。在裝有此類操作系統(tǒng)的同一臺(tái)計(jì)算機(jī)中,應(yīng)用程序可以基于此域建立socket連接。

類型

Socket的類型一個(gè)有4種,在syscall包中有同名的常量對(duì)應(yīng):

  1. SOCK_DGRAM
  2. SOCK_STREAM
  3. SOCK_SEQPACKET
  4. SOCK_RAW

上面的4種類型,前兩個(gè)更加常用。

UDP
SOCK_DGRA中的DGRAM就是datagram,即數(shù)據(jù)報(bào)文。它是一種有消息邊界但沒有邏輯連接的非可靠socket類型,UDP協(xié)議的網(wǎng)絡(luò)通信就是這類。
有消息邊界的意思是,與socket相關(guān)的操作系統(tǒng)內(nèi)核中的程序,即內(nèi)核程序,在發(fā)送或接收數(shù)據(jù)的時(shí)候是以消息為單位的。這里可以把消息理解為帶有固定邊界的一段數(shù)據(jù)。內(nèi)核程序可以自動(dòng)的識(shí)別和維護(hù)這種邊界。在必要的時(shí)候,把數(shù)據(jù)切割成一個(gè)一個(gè)的消息,或者把多個(gè)消息串接成連續(xù)的數(shù)據(jù)。這樣,應(yīng)用程序值需要面向消息進(jìn)行處理就可以了。
只要應(yīng)用程序指定好對(duì)方的網(wǎng)絡(luò)地址,內(nèi)核程序就可以立即把數(shù)據(jù)報(bào)文發(fā)送出去。這有優(yōu)勢(shì)也有劣勢(shì)。優(yōu)勢(shì)是,發(fā)送速度快,不長(zhǎng)期占用網(wǎng)絡(luò)資源,并且每次發(fā)送都可以指定不同的網(wǎng)絡(luò)地址。最后一條既是優(yōu)勢(shì)也是劣勢(shì),因?yàn)檫@會(huì)使數(shù)據(jù)報(bào)文更長(zhǎng)。其他劣勢(shì)還有,無法保證傳輸?shù)目煽啃?,不能?shí)現(xiàn)數(shù)據(jù)的有序性,以及數(shù)據(jù)只能單向進(jìn)行傳輸。

TCP
SOCK_STREAM類型,是沒有消息邊界但有邏輯連接,能夠保證傳輸?shù)目煽啃院蛿?shù)據(jù)的有序性,同時(shí)還可以實(shí)現(xiàn)數(shù)據(jù)的雙向傳輸。TCP協(xié)議的網(wǎng)絡(luò)通信就是這類。
有邏輯連接是指,通信雙方在收發(fā)數(shù)據(jù)之前必須先建立網(wǎng)絡(luò)連接。等連接建立好之后,雙方就可以一對(duì)一的進(jìn)行數(shù)據(jù)傳輸了。
這樣的網(wǎng)絡(luò)通信傳輸數(shù)據(jù)的形式是字節(jié)流,而不是數(shù)據(jù)報(bào)文。字節(jié)流是以字節(jié)為單位的。內(nèi)核程序無法感知一段字節(jié)流中包含了多少個(gè)消息,以及這些消息是否完整,這完全需要應(yīng)用程序自己來把控。不過,此類網(wǎng)絡(luò)通信中的一段,總會(huì)忠實(shí)的按照另一端發(fā)送數(shù)據(jù)是的字節(jié)排列順序,接收和緩存它們。所以,應(yīng)用程序需要根據(jù)雙方的約定去數(shù)據(jù)中查找消息邊界,并按照邊界切割數(shù)據(jù)。

使用協(xié)議

通常只要明確指定了前兩個(gè)參數(shù)值,就無需在去確定這里的使用協(xié)議了,一般把它置為0就可以了。這時(shí),內(nèi)核程序會(huì)自行選擇最合適的協(xié)議。

不完整的示例

package main

import (
    "fmt"
    "os"
    "syscall"
)

func main() {
    fd, err := syscall.Socket(syscall.AF_INET, syscall.SOCK_STREAM, syscall.IPPROTO_TCP)
    if err != nil {
        fmt.Fprintf(os.Stderr, "ERROR: %v\n", err)
        return
    }
    defer syscall.Close(fd)
    fmt.Println("socket的文件描述符:", fd)
    // 之后就省略了,要使用syscall包來建立網(wǎng)絡(luò)連接,過程太繁瑣
}

這個(gè)代碼包的使用太底層,通常也不需要我們直接使用。Go語言的net包中的很多程序?qū)嶓w,都會(huì)直接或間接的使用到syscall.Socket函數(shù),并且無需給定細(xì)致的參數(shù)。但是,在使用這些API的時(shí)候,現(xiàn)在我們就應(yīng)該知道上面這些基礎(chǔ)知識(shí)了。

net.Dial函數(shù)

net.Dial函數(shù)會(huì)接受兩個(gè)參數(shù),network和address,具體看下面:

func Dial(network, address string) (Conn, error) {
    var d Dialer
    return d.Dial(network, address)
}

network參數(shù)

參數(shù)network常用的可選值一共有9個(gè),這些值分別代表了程序底層創(chuàng)建的socket實(shí)例可使用的不同通信協(xié)議:

  1. "tcp" : 代表TCP協(xié)議,其基于的IP協(xié)議的版本根據(jù)參數(shù)address的值自適應(yīng)
  2. "tcp4" : 代表基于IPv4協(xié)議的TCP協(xié)議
  3. "tcp6" : 代表基于IPv6協(xié)議的TCP協(xié)議
  4. "udp" : 代表UDP協(xié)議,其基于的IP協(xié)議的版本根據(jù)address的值自適應(yīng)
  5. "udp4" : 代表基于IPv4協(xié)議的UDP協(xié)議
  6. "udp6" : 代表基于IPv6協(xié)議的UDP協(xié)議
  7. "unix" : 代表Unix通信域下的一種內(nèi)部socket協(xié)議,以SOCK_STREAM為socket類型
  8. "unixgram" : 代表Unix通信域下的一種內(nèi)部socket協(xié)議,以SOCK_DGRAM為socket類型
  9. "unixpacket" : 代表Unix通信域下的一種內(nèi)部socket協(xié)議,以SOCK_SEQPACKET為socket類型

net包發(fā)送http請(qǐng)求

對(duì)于http請(qǐng)求,在標(biāo)準(zhǔn)庫里還有更高級(jí)的封裝,不過http本質(zhì)上也是socket,這里展示用net包發(fā)送請(qǐng)求的示例:

package main

import (
    "fmt"
    "io"
    "net"
    "os"
)

func main() {
    conn, err := net.Dial("tcp", "baidu.com:80")
    if err != nil {
        fmt.Fprintf(os.Stderr, "ERROR: %v\n", err)
        return
    }
    defer conn.Close()

    reqStr := "HEAD / HTTP/1.1\r\n" + // HEAD請(qǐng)求,只返回請(qǐng)求頭
        "Host: baidu.com\r\n" + 
        "Connection: close\r\n" + // 返回后,服務(wù)器會(huì)斷開連接,默認(rèn)是keep-alive
        "\r\n"  // 請(qǐng)求頭結(jié)束
    _, err = io.WriteString(conn, reqStr)
    if err != nil {
        fmt.Fprintf(os.Stderr, "ERROR: %v\n", err)
        return
    }

    buf := make([]byte, 1024)
    for {
        n, err := conn.Read(buf)
        fmt.Println(string(buf[:n]))
        if err != nil {
            if err == io.EOF {
                fmt.Println("END")
                break
            } else {
                fmt.Fprintf(os.Stderr, "ERROR: %v\n", err)
            }
        }
    }
}

如果是https的請(qǐng)求,還需要借助crypto/tls包,而調(diào)用起來基本是一樣的:

package main

import (
    "crypto/tls"
    "fmt"
    "io"
    "os"
)

func main() {
    tlsConf := &tls.Config{
        InsecureSkipVerify: true,
        MinVersion:         tls.VersionTLS10,
    }

    conn, err := tls.Dial("tcp", "gitee.com:443", tlsConf)
    if err != nil {
        fmt.Fprintf(os.Stderr, "ERROR: %v\n", err)
        return
    }
    defer conn.Close()

    reqStr := "HEAD / HTTP/1.1\r\n" + // HEAD請(qǐng)求,只返回請(qǐng)求頭
        "Host: gitee.com\r\n" +
        "Connection: close\r\n" + // 返回后,服務(wù)器會(huì)斷開連接,默認(rèn)是keep-alive
        "\r\n" // 請(qǐng)求頭結(jié)束
    _, err = io.WriteString(conn, reqStr)
    if err != nil {
        fmt.Fprintf(os.Stderr, "ERROR: %v\n", err)
        return
    }

    buf := make([]byte, 1024)
    for {
        n, err := conn.Read(buf)
        fmt.Println(string(buf[:n]))
        if err != nil {
            if err == io.EOF {
                fmt.Println("END")
                break
            } else {
                fmt.Fprintf(os.Stderr, "ERROR: %v\n", err)
            }
        }
    }
}

net.DialTimeout函數(shù)

net.DialTimeout函數(shù)和net.Dial函數(shù)相比,多接受了一個(gè)參數(shù)timeout。而底層實(shí)現(xiàn)可以看到是一樣的,只是對(duì)Dialer結(jié)構(gòu)體的Timeout字段進(jìn)行了設(shè)置,而在net.Dial函數(shù)里結(jié)構(gòu)體都是默認(rèn)值:

func DialTimeout(network, address string, timeout time.Duration) (Conn, error) {
    d := Dialer{Timeout: timeout}
    return d.Dial(network, address)
}

超時(shí)時(shí)間

這里的超時(shí)時(shí)間,出函數(shù)為網(wǎng)絡(luò)連接建立完成而等待的最長(zhǎng)時(shí)間。
開始的時(shí)間點(diǎn)幾乎是調(diào)用net.DialTimeout函數(shù)的那一刻。在這之后,時(shí)間會(huì)主要花費(fèi)在解析參數(shù)的network值和address值,以及創(chuàng)建socket實(shí)例并建立網(wǎng)絡(luò)連接這兩件事情上。如果超時(shí)了而網(wǎng)絡(luò)連接還沒有建立完成,該函數(shù)就會(huì)返回一個(gè)I/O操作超時(shí)的錯(cuò)誤值。
在解析address的值的時(shí)候,函數(shù)會(huì)確定網(wǎng)絡(luò)服務(wù)的IP地址、端口號(hào)等必要信息,并在需要的時(shí)候訪問DNS服務(wù)。另外,如果解析出的IP地址有多個(gè),函數(shù)會(huì)串行或并行的嘗試建立連接。無論用什么方式嘗試,函數(shù)總會(huì)以最先建立成功的那個(gè)連接為準(zhǔn)。同時(shí)還會(huì)根據(jù)超時(shí)時(shí)間的剩余時(shí)間去設(shè)定對(duì)每次連接嘗試的超時(shí)時(shí)間。
找一個(gè)國外的網(wǎng)站,或者干脆找一個(gè)連不上的地址,看下超時(shí)時(shí)間的作用:

package main

import (
    "fmt"
    "net"
    "os"
    "time"
)

func main() {
    tStart := time.Now()
    conn, err := net.DialTimeout("tcp", "godoc.org:80", time.Second * 10)
    tEnd := time.Now()
    fmt.Println("連接持續(xù)時(shí)間:", time.Duration(tEnd.Sub(tStart)))
    if err != nil {
        fmt.Fprintf(os.Stderr, "ERROR: %v\n", err)
        return
    }
    defer conn.Close()
    fmt.Println("本地連接地址:", conn.LocalAddr())
    fmt.Println("對(duì)端連接地址:", conn.RemoteAddr())
}

本文名稱:Go36-46-訪問網(wǎng)絡(luò)服務(wù)(socket)
標(biāo)題網(wǎng)址:http://weahome.cn/article/ihigid.html

其他資訊

在線咨詢

微信咨詢

電話咨詢

028-86922220(工作日)

18980820575(7×24)

提交需求

返回頂部