【go语言 socket编程系列】LookupHost与LookupPort函数

func LookupHost(name string) (cname string, addrs []string, err os.Error)
func LookupPort(network, service string) (port int, err os.Error)

两个函数都在 src/net/lookup.go中定义。

【LookupHost】

// LookupHost looks up the given host using the local resolver.
// It returns an array of that host's addresses.
func LookupHost(host string) (addrs []string, err error) {
        return lookupHost(host)
}

实际运营中,通常主机服务器有多块网卡,LookupHost返回多个IP地址。以string数组形式返回。

在源文件中调用了 lookupHost(host) 私有函数。

lookuphost(host)定义在 lookup_unix.go文件中,源码如下

func lookupHost(host string) (addrs []string, err error) {
        addrs, err, ok := cgoLookupHost(host)
        if !ok {
                addrs, err = goLookupHost(host)
        }   
        return
}

函数主体是个Comma-ok,调用了cgoLookupHost(host)函数,其定义在cgo_unix.go中

源码如下:

func cgoLookupHost(name string) (addrs []string, err error, completed bool) {
        ip, err, completed := cgoLookupIP(name)
        for _, p := range ip {
                addrs = append(addrs, p.String())
        }   
        return
}

cgoLookupHost 直接调用cgoLookupIP(name) 函数,其同样定义在cgo_unix.go中

源码如下

func cgoLookupIP(name string) (addrs []IP, err error, completed bool) {
        addrs, _, err, completed = cgoLookupIPCNAME(name)
        return
}

同样cgoLookupIP直接调用cgoLookupIPCNAME(name),该部分主要通过调用系统函数 getaddrinfo将一个主机名和一个服务名映射到一个地址。源码如下

func cgoLookupIPCNAME(name string) (addrs []IP, cname string, err error, completed bool) {
        acquireThread()
        defer releaseThread()

        var res *C.struct_addrinfo
        var hints C.struct_addrinfo

        hints.ai_flags = cgoAddrInfoFlags()
        hints.ai_socktype = C.SOCK_STREAM

        h := C.CString(name)
        defer C.free(unsafe.Pointer(h))
        gerrno, err := C.getaddrinfo(h, nil, &hints, &res)
        if gerrno != 0 { 
                var str string
                if gerrno == C.EAI_NONAME {
                        str = noSuchHost
                } else if gerrno == C.EAI_SYSTEM {
                        if err == nil {
                                // err should not be nil, but sometimes getaddrinfo returns
                                // gerrno == C.EAI_SYSTEM with err == nil on Linux.
                                // The report claims that it happens when we have too many
                                // open files, so use syscall.EMFILE (too many open files in system).
                                // Most system calls would return ENFILE (too many open files),
                                // so at the least EMFILE should be easy to recognize if this
                                // comes up again. golang.org/issue/6232.
                               err = syscall.EMFILE
                        }
                        str = err.Error()
                } else {
                        str = C.GoString(C.gai_strerror(gerrno))
                }
                return nil, "", &DNSError{Err: str, Name: name}, true
        }
        defer C.freeaddrinfo(res)
        if res != nil {
                cname = C.GoString(res.ai_canonname)
                if cname == "" {
                        cname = name
                }
                if len(cname) > 0 && cname[len(cname)-1] != '.' {
                        cname += "."
                }
        }
        for r := res; r != nil; r = r.ai_next {
                // We only asked for SOCK_STREAM, but check anyhow.
                if r.ai_socktype != C.SOCK_STREAM {
                        continue
                }
                switch r.ai_family {
                default:
                        continue
                case C.AF_INET:
                        sa := (*syscall.RawSockaddrInet4)(unsafe.Pointer(r.ai_addr))
                        addrs = append(addrs, copyIP(sa.Addr[:]))
                case C.AF_INET6:
                        sa := (*syscall.RawSockaddrInet6)(unsafe.Pointer(r.ai_addr))
                        addrs = append(addrs, copyIP(sa.Addr[:]))
                }
        }
        return addrs, cname, nil, true
}

####################

【LookupPort】

函数原型如下,同样定义在lookup.go文件中

// LookupPort looks up the port for the given network and service.
func LookupPort(network, service string) (port int, err error) {
        return lookupPort(network, service)
}

直接返回 lookupPort私有函数,其定义在lookuo_unix.go中

源码如下

func lookupPort(network, service string) (port int, err error) {
        port, err, ok := cgoLookupPort(network, service)
        if !ok {
                port, err = goLookupPort(network, service)
        }
        return
}

函数主体直接调用cgoLookupPort 函数,其定义在cgo_unix.go文件中,同样,cgoLookupPort函数会调用系统接口函数getaddrinfo。其实现源码如下:

func cgoLookupPort(net, service string) (port int, err error, completed bool) {
        acquireThread()
        defer releaseThread()

        var res *C.struct_addrinfo
        var hints C.struct_addrinfo

        switch net {
        case "": 
                // no hints
        case "tcp", "tcp4", "tcp6":
                hints.ai_socktype = C.SOCK_STREAM
                hints.ai_protocol = C.IPPROTO_TCP
        case "udp", "udp4", "udp6":
                hints.ai_socktype = C.SOCK_DGRAM
                hints.ai_protocol = C.IPPROTO_UDP
        default:
                return 0, UnknownNetworkError(net), true
        }   
        if len(net) >= 4 { 
                switch net[3] {
                case '4':
                        hints.ai_family = C.AF_INET
                case '6':
                        hints.ai_family = C.AF_INET6
                }
        }

        s := C.CString(service)
        defer C.free(unsafe.Pointer(s))
        if C.getaddrinfo(nil, s, &hints, &res) == 0 {
                defer C.freeaddrinfo(res)
                for r := res; r != nil; r = r.ai_next {
                        switch r.ai_family {
                        default:
                                continue
                        case C.AF_INET:
                                sa := (*syscall.RawSockaddrInet4)(unsafe.Pointer(r.ai_addr))
                                p := (*[2]byte)(unsafe.Pointer(&sa.Port))
                                return int(p[0])<<8 | int(p[1]), nil, true
                        case C.AF_INET6:
                                sa := (*syscall.RawSockaddrInet6)(unsafe.Pointer(r.ai_addr))
                                p := (*[2]byte)(unsafe.Pointer(&sa.Port))
                                return int(p[0])<<8 | int(p[1]), nil, true
                        }
                }
 }
        return 0, &AddrError{"unknown port", net + "/" + service}, true
}

################

【小结】

LookupHost的调用链如下:

func LookupHost(name string) (cname string, addrs []string, err os.Error)
func lookupHost(host string) (addrs []string, err error)
func cgoLookupHost(name string) (addrs []string, err error, completed bool)
func cgoLookupIP(name string) (addrs []IP, err error, completed bool)
func cgoLookupIPCNAME(name string) (addrs []IP, cname string, err error, completed bool)  
getaddrinfo(h, nil, &hints, &res)

LookupPort 的调用链如下:

func LookupPort(network, service string) (port int, err error)
func lookupPort(network, service string) (port int, err error)
func cgoLookupPort(net, service string) (port int, err error, completed bool)
getaddrinfo(nil, s, &hints, &res)

即最后都回归到调用系统借口getaddrinfo。其函数原型如下,定义在netdb.h中。

extern int getaddrinfo (__const char *__restrict __name,
                        __const char *__restrict __service,
                        __const struct addrinfo *__restrict __req,
                        struct addrinfo **__restrict __pai);

将一个主机名和一个服务名映射到一个地址,返回一个addrinfo的链表结构。

其定义如下:

/* Structure to contain information about address of a service provider.  */
struct addrinfo
{
  int ai_flags;                 /* Input flags.  */
  int ai_family;                /* Protocol family for socket.  */
  int ai_socktype;              /* Socket type.  */
  int ai_protocol;              /* Protocol for socket.  */
  socklen_t ai_addrlen;         /* Length of socket address.  */
  struct sockaddr *ai_addr;     /* Socket address for socket.  */
  char *ai_canonname;           /* Canonical name for service location.  */
  struct addrinfo *ai_next;     /* Pointer to next in list.  */
};

猜你喜欢

转载自blog.csdn.net/natpan/article/details/82837442
今日推荐