golang socket編程

osi參考模型將計算機網絡結構分爲7個層次,可是在實際的開發應用中,咱們更加承認TCP/IP族協議的五層結構,即應用層(http、ftp、dns),傳輸層(udp、tcp),網絡層(ip),鏈路層(以太網),物理層。golang

socket編程做爲一種基於網絡層和傳輸層的數據io模式主要分爲兩種,TCP Socket和UDP Socket,也即面向鏈接的流式Socket和麪向無鏈接的數據報式Socket。編程

今天主要和你們講講golang的TCP Socket編程。先來看個簡單的例子吧服務器

server.go網絡

//模擬server端
func main() {
        tcpServer, _ := net.ResolveTCPAddr("tcp4", ":8080")
        listener, _ := net.ListenTCP("tcp", tcpServer)

        for {
                //當有新的客戶端請求來的時候,拿到與客戶端的鏈接
                conn, err := listener.Accept()
                if err != nil {
                        fmt.Println(err)
                        continue
                }   
 
                //處理邏輯
                go handle(conn)
        }   
}

func handle(conn net.Conn) {
        defer conn.Close()                                                                                                                                                                                                                                                    

        //讀取客戶端傳送的消息
        go func() {
                response, _ := ioutil.ReadAll(conn)
                fmt.Println(string(response))
        }() 

        //向客戶端發送消息
        time.Sleep(1 * time.Second)
        now := time.Now().String()
        conn.Write([]byte(now))
}

client.go數據結構

//模擬客戶端
func main() {
        if len(os.Args) < 2 { 
                fmt.Fprintf(os.Stderr, "Usage: %s host:port", os.Args[0])
        }   
        //獲取命令行參數 socket地址
        server := os.Args[1]
        addr, err := net.ResolveTCPAddr("tcp4", server)
        checkError(err)

        //創建tcp鏈接
        conn, err := net.DialTCP("tcp4", nil, addr)
        checkError(err)

        //向服務端發送數據
        _, err = conn.Write([]byte("HEAD / HTTP/1.0\r\n\r\n"))
        checkError(err)
        //接收響應
        response, _ := ioutil.ReadAll(conn)
        fmt.Println(string(response))
        os.Exit(0)                                                                                                                                                                                                                                                            
}

func checkError(err error) {
        if err != nil {
                fmt.Println(err)
                os.Exit(1)
        }   
}

總體大概的流程是:socket

    server端先經過tcp協議監聽端口8080,以後當有客戶端來訪問時可以讀取信息而且寫入響應信息,注意此處 conn, err := listener.Accept() 語句是用來獲取下一個調用鏈接的,若是一直沒有鏈接,則會處於阻塞狀態。tcp

    客戶端經過DialTCP嘗試與服務端創建鏈接,鏈接成功後向服務器端發送數據並接收響應,而且以上代碼中,只有等待服務器端關閉鏈接以後,該鏈接纔會失效,不然鏈接會一直處於ESTABLISHED狀態。以下圖是筆者將defer conn.Close()註釋掉以後,查看的8080端口的鏈接,會發現鏈接一直存在:計算機網絡

若是使用細心一點的話,在客戶端代碼執行結束以後,此時的服務器端鏈接並無釋放(注意下圖local address是8080端口),而是進入了TIME_WAIT狀態:命令行

一開始筆者覺得是鏈接沒有正確釋放的緣由,後來查看了下TCP四次揮手有關的內容才弄明白這塊的緣由,由於是服務器端主動斷開鏈接,因此在服務器端發送完最後一個ACK以後,會進入TIME_WAIT狀態等待一段時間,防止ACK沒有發送成功,等待時間一過,系統會自動釋放鏈接。以下是一個簡單的狀態轉變圖(不是特別精準,只看斷開鏈接部分),但願能對讀者有所幫助:code

詳細的四次揮手的各個狀態輪轉的變化,能夠去谷歌搜索一下。

 

PS:以上是一個簡單的socket編程的示例,只包含了一對client和server,那若是一個server有不少個client,此時有一個client但願可以將消息發送給除它本身以外的全部client fd怎麼辦?

        其實這個問題能夠經過server端來作,首先server端定義一種數據結構,不管是map仍是slice,而後將全部的有效鏈接都放入其中,若是有client fd發送消息過來,遍歷該結構,將消息發送給每個非當前client的fd,由此解決該問題。

相關文章
相關標籤/搜索