Go Socket操做筆記

概念

首先什麼事Socket,翻譯過來就是孔或者插座。網絡上的兩個程序經過一個雙向的通訊鏈接實現數據的交換,這個鏈接的一端稱爲一個socket。
Socket的本質實際上是編程接口,是一個IPC接口。(IPC:進程間通訊)與其餘IPC方法不一樣的是,它能夠經過網絡讓多個進程創建通訊,是的通訊雙方是否在同一個機器上變得可有可無。編程

Socket如何通訊

socket是經過TCP/IP協議族來提供網絡連接。Socket是應用程序和運輸層之間的抽象層,封裝了TCP/IP協議族,用一組簡單的接口就能就能經過網絡連接通訊。下圖爲網上經典圖,用戶不須要知道TCP/IP的各類複雜功能協議等,直接使用Socket提供的接口就能完成全部工做。
圖片描述服務器

Socket通訊流程

  • 服務端: 首先服務端須要初始化Socket,而後與端口綁定(bind),對端口進行監聽(listen),調用accept阻塞,等待客戶端鏈接。在這時若是有個客戶端初始化一個Socket,而後鏈接服務器(connect),若是鏈接成功,這時客戶端與服務器端的鏈接就創建了。
  • 客戶端:客戶端發送數據請求,服務器端接收請求並處理請求,而後把迴應數據發送給客戶端,客戶端讀取數據,最後關閉鏈接,一次交互結束。

圖片描述

Socket提供的主要接口

  • 初始化:int socket(int domain, int type, int protocol)
  • 綁定:int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
  • 監聽:listen()
  • 接受請求:accept()

具體參數的意義先不展開,咱們主要是看go如何操做socket。網絡

Go 如何操做Socket

上邊簡單的介紹了Socket的概念,在go語言中咱們能夠很方便的使用net包來操做。其實go的net包就是對上面的Socket的接口作了再次封裝,讓咱們能很方便的創建Socket鏈接和使用鏈接通訊。直接上代碼dom

公共函數

//公共函數 用來定義Socket類型 ip 端口。
const(
    Server_NetWorkType = "tcp"
    Server_Address = "127.0.0.1:8085"
    Delimiter = '\t'
)

// 往conn中寫數據,能夠用於客戶端傳輸給服務端, 也能夠服務端返回客戶端
func Write(conn net.Conn, content string)(int, error){
    var buffer bytes.Buffer
    buffer.WriteString(content)
    buffer.WriteByte(Delimiter)

    return conn.Write(buffer.Bytes())
}

// 從conn中讀取字節流,以上面的結束符爲標記
func Read(conn net.Conn)(string, error){
    readBytes := make([]byte,1)
    var buffer bytes.Buffer
    for{
        if _,err := conn.Read(readBytes);err != nil{
            return "", err
        }
        readByte := readBytes[0]
        if readByte == Delimiter{
            break
        }
        buffer.WriteByte(readByte)
    }

    return buffer.String(), nil
}

服務端

func main() {
    // net listen 函數 傳入socket類型和ip端口,返回監聽對象
    listener, err := net.Listen(socket.Server_NetWorkType,socket.Server_Address)
    if err == nil{
        // 循環等待客戶端訪問
        for{
            conn,err := listener.Accept()
            if err == nil{
                // 一旦有外部請求,而且沒有錯誤 直接開啓異步執行
                go handleConn(conn)
            }
        }
    }else{
        fmt.Println("server error", err)
    }
    defer listener.Close()
}

func handleConn(conn net.Conn){
    for {
        // 設置讀取超時時間
        conn.SetReadDeadline(time.Now().Add(time.Second * 2))
        // 調用公用方法read 獲取客戶端傳過來的消息。
        if str, err := socket.Read(conn); err == nil{
            fmt.Println("client:",conn.RemoteAddr(),str)
            // 經過write 方法往客戶端傳遞一個消息
            socket.Write(conn,"server got:"+str)
        }


    }
}

客戶端

func main() {
    // 調用net包中的dial 傳入ip 端口 進行撥號鏈接,經過三次握手以後獲取到conn
    conn,err := net.Dial(socket.Server_NetWorkType, socket.Server_Address)
    if err != nil{
        fmt.Println("Client create conn error err:", err)
    }
    defer conn.Close()
    //往服務端傳遞消息
    socket.Write(conn,"aaaa")
    //讀取服務端返回的消息
    if str, err := socket.Read(conn);err == nil{
        fmt.Println(str)
    }

}

能夠看到,上邊的代碼很簡單。使用net包就能夠很輕鬆的實現Socket通訊。異步

簡單源碼查看

咱們能夠看到最上邊咱們介紹的Socket最少須要有建立(socket函數) 綁定(bind函數)監聽(listen函數)這些最基本的步驟,這些步驟其實都封裝在咱們的net包中,到了咱們代碼中客戶從net.Listen 函數裏查看源代碼。由於代碼調用過多隻貼一些關鍵性代碼段。
首先 listen 會判斷是監聽tcp,仍是unix。以後通過一些列的調用走到sysSocket方法,這個方法會調用系統的socket方法初始化socket對象返回一個socket的標識符。以後就會使用這個標識符進行綁定 監聽。最終返回listener對象。socket

func Listen(network, address string) (Listener, error) {
    addrs, err := DefaultResolver.resolveAddrList(context.Background(), "listen", network, address, nil)
    if err != nil {
        return nil, &OpError{Op: "listen", Net: network, Source: nil, Addr: nil, Err: err}
    }
    var l Listener
    switch la := addrs.first(isIPv4).(type) {
    case *TCPAddr:
        // 監聽TCP 
        l, err = ListenTCP(network, la)
    case *UnixAddr:
        l, err = ListenUnix(network, la)
    default:
        return nil, &OpError{Op: "listen", Net: network, Source: nil, Addr: la, Err: &AddrError{Err: "unexpected address type", Addr: address}}
    }
    if err != nil {
        return nil, err // l is non-nil interface containing nil pointer
    }
    return l, nil
}
// 最終調用的系統方法, 是否是跟socket 的初始化方法很像?
func sysSocket(family, sotype, proto int) (int, error) {
    // See ../syscall/exec_unix.go for description of ForkLock.
    syscall.ForkLock.RLock()
    s, err := socketFunc(family, sotype, proto)
    if err == nil {
        syscall.CloseOnExec(s)
    }
    syscall.ForkLock.RUnlock()
    if err != nil {
        return -1, os.NewSyscallError("socket", err)
    }
    if err = syscall.SetNonblock(s, true); err != nil {
        poll.CloseFunc(s)
        return -1, os.NewSyscallError("setnonblock", err)
    }
    return s, nil
}
相關文章
相關標籤/搜索