1. 程式人生 > >【go語言 socket程式設計系列】TCPConn型別與ne.tDialTCP方法

【go語言 socket程式設計系列】TCPConn型別與ne.tDialTCP方法

【TCPConn】 netTCPConn是允許服務端與客戶端之間的全雙工通訊的Go型別。其定義在tcpsock_posix.go檔案。

其定義如下

type TCPConn struct {
        conn
}

注意到 conn 是小寫的c,其定義在net.go檔案中,原始碼如下

type conn struct {
        fd *netFD
}

即conn是一個struct型別,只有一個指向netFD型別的  fd指標。 netFD定義在fd_unix.go中,其定義如下

即 netFD是一個網路檔案描述符。

type netFD struct {
        // locking/lifetime of sysfd + serialize access to Read and Write methods
        fdmu fdMutex

        // immutable until Close
        sysfd       int 
        family      int 
        sotype      int 
        isConnected bool
        net         string
        laddr       Addr
        raddr       Addr

        // wait server
        pd pollDesc
}

【常用方法】

func (c *conn) Read(b []byte) (int, error) 

其在net.go中定義如下

// Read implements the Conn Read method.
func (c *conn) Read(b []byte) (int, error) {
        if !c.ok() {
                return 0, syscall.EINVAL
        }   
        return c.fd.Read(b)
}

接受者c *conn 的Read方法 呼叫了fd成員的Read方法。其定義在fd_unix.go 中,原始碼如下:

func (fd *netFD) Read(p []byte) (n int, err error) {
        if err := fd.readLock(); err != nil {
                return 0, err
        }
        defer fd.readUnlock()
        if err := fd.pd.PrepareRead(); err != nil {
                return 0, &OpError{"read", fd.net, fd.raddr, err}
        }   
        for {
                n, err = syscall.Read(int(fd.sysfd), p)
                if err != nil {
                        n = 0 
                        if err == syscall.EAGAIN {
                                if err = fd.pd.WaitRead(); err == nil {
                                        continue
                                }   
                        }   
                }   
                err = chkReadErr(n, err, fd) 
                break
        }   
        if err != nil && err != io.EOF {
                err = &OpError{"read", fd.net, fd.raddr, err}
        }   
        return
}

func (c *conn) Write(b []byte) (int, error) 函式同Read一樣,略。

【net.DialTCP方法】

func DialTCP(net string, laddr, raddr *TCPAddr) (*TCPConn, error) 主要用於建立一個TCPConn

net 引數為 tcp  tcp4  tcp6

laddr 為本地地址,通常為nil

raddr 為目的地址, 為TCPAddr型別的指標

函式返回一個 *TCPConn,可通過 Read 和Write 方法傳遞資料。

net,DialTCP定義在tcpsock_posix.go檔案中,其原始碼如下

func DialTCP(net string, laddr, raddr *TCPAddr) (*TCPConn, error) {
        switch net {
        case "tcp", "tcp4", "tcp6":
        default:
                return nil, &OpError{Op: "dial", Net: net, Addr: raddr, Err: UnknownNetworkError(net)}
        }   
        if raddr == nil {
                return nil, &OpError{Op: "dial", Net: net, Addr: nil, Err: errMissingAddress}
        }   
        return dialTCP(net, laddr, raddr, noDeadline)
}

主要通過呼叫私有函式dialTCP實現TCPConn的建立,dialTCP又會通過newTCPConn 來實現控制代碼的建立,最終會呼叫unix系統介面完成。

【程式碼實現】

package main

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

func main() {
	service := "www.baidu.com:80"
	tcpAddr, err := net.ResolveTCPAddr("tcp", service)
	checkError(err)
	fmt.Println("tcpAddr :")
	typeof(tcpAddr)

	myConn, err1 := net.DialTCP("tcp", nil, tcpAddr)
	checkError(err1)
	fmt.Println("myConn :")
	typeof(myConn)

	_, err = myConn.Write([]byte("HEAD / HTTP/1.1\r\n\r\n"))
	checkError(err)

	result, err := ioutil.ReadAll(myConn)
	checkError(err)
	fmt.Println(string(result))
	os.Exit(0)
}

func typeof(v interface{}) {
	fmt.Printf("type is:%T\n", v)
}

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

編譯執行如下

$./l_DialTCP  tcpAddr : type is:*net.TCPAddr myConn : type is:*net.TCPConn HTTP/1.1 400 Bad Request Server: nginx/1.13.5 Date: Wed, 26 Sep 2018 07:17:05 GMT Content-Type: text/html Content-Length: 173 Connection: close

$