前文
网络协议概要
版本
go1.18
CentOs 7
1.Listen函数
demo
func main() {
l, _ := net.Listen("tcp", ":8080")
fmt.Println(l)
}
dlv进入打断点
dlv debug main.go
b net.Listen
c
进入ListenConfig.Listen
b net.ListenConfig.Listen
c
print lc //*net.ListenConfig {Control: nil, KeepAlive: 0}
ListenConfig.Listen函数
func (lc *ListenConfig) Listen(ctx context.Context, network, address string) (Listener, error) {
//解析地址存入addrs数组addrList-[ TCPAddr{} ]
addrs, err := DefaultResolver.resolveAddrList(ctx, "listen", network, address, nil)
if err != nil {
return nil, &OpError{Op: "listen", Net: network, Source: nil, Addr: nil, Err: err}
}
sl := &sysListener{
ListenConfig: *lc,
network: network,
address: address,
}
var l Listener
//isIPV4是一个返回布尔类型的函数
//first轮询数组中的成员判断是否为IPV4地址,并返回满足条件的第一个成员(实际上只有一个成员)
// la net.Addr / *TCPAddr
la := addrs.first(isIPv4)
//根据不同的地址族生成对应的socket句柄
//且该socket已经准备好被连接
switch la := la.(type) {
case *TCPAddr:
l, err = sl.listenTCP(ctx, la)
case *UnixAddr:
l, err = sl.listenUnix(ctx, la)
default:
return nil, &OpError{Op: "listen", Net: sl.network, Source: nil, Addr: la, Err: &AddrError{Err: "unexpected address type", Addr: address}}
}
if err != nil {
return nil, &OpError{Op: "listen", Net: sl.network, Source: nil, Addr: la, Err: err} // l is non-nil interface containing nil pointer
}
return l, nil
}
1.1关于DefaultResolver
print DefaultResolver //实际是一个net.Resolver这是1.18特有的结构体
给net.Resolver.resolveAddrList打断点进入函数,可以看到这个函数实际上是使用Resolver.interAddrList解析addr(host:ip),存入指定结构体(TCP就是TCPAddr{IP,Port,Zone}结构体)并保存于addrList类型中
type addrList []Addr
net.addrList len: 1, cap: 1, [
*net.TCPAddr {
IP: net.IP len: 0, cap: 0, nil,
Port: 8080,
Zone: "",},
]
1.2 listenTCP
函数很简单,调用ipsock_posix.go的internetSocket函数,返回socket的fd
func (sl *sysListener) listenTCP(ctx context.Context, laddr *TCPAddr) (*TCPListener, error) {
fd, err := internetSocket(ctx, sl.network, laddr, nil, syscall.SOCK_STREAM, 0, "listen", sl.ListenConfig.Control)
if err != nil {
return nil, err
}
return &TCPListener{fd: fd, lc: sl.ListenConfig}, nil
}
internetSocket函数十分复杂,里面分别调用了favoriteAddrFamily确定地址族,net.socket调用syscallsocket返回一个绑定好host:ip且可以准备监听的socket的文件句柄
小结
2.Dial函数
demo
func main() {
l, _ := net.Dial("tcp", ":8080")
fmt.Println(l)
}
实际上是调用Dialer结构体的DialContext方法
DialContext首先是设置超时时间
resolver函数返回一个net.Resolver结构体,并调用resloveAddrList函数,这个函数上面Listen有分析过,是解析host:port生成 []AddrList{} 数组,实际上输入的参数是tcp,成员是TCPAddr(AddrList接口的实现结构体之一)
partition函数与上面的addrList.first函数很相似,执行传入的函数,轮询返回满足条件的数组
通过dialSerial函数调用dialSingle函数,根据地址族调用不同的生成连接函数(dialTCP,dialUDP,dialIP和dialUnix),这里我们调用dialTCP,他内部是对doDialTCP的再包装,接着进入该函数
当看到internetSocket我们就不陌生了,他底层会调用syscall.socket来创建套接字,但此时我们不需要“Listen”状态的套接字,需要“Dial”的套接字,即可连接的
至此连接connect套接字已经创建
小结
3.Accept函数
与多路复用有关,先放着,之后回头来看