Go-net源码解析

学习一门语言,那么我们必然要涉及到网络通信,而谈到网络通信却又离不开tcp,这里我们利用go标准库net来模拟一个服务端、客户端的流程,从而深入学习其中的代码流程(深入其中解析本质)

func main() {
	server, err := net.Listen("tcp", "127.0.0.1:8080") // 开启服务端
	if err != nil {
		log.Fatal(err)
	}
	defer server.Close()

	go func() { // 模拟客户端往服务端发送数据
		coon, err := net.Dial("tcp", "127.0.0.1:8080")
		if err != nil {
			log.Fatal(err)
		}
		defer coon.Close()
		coon.Write([]byte("hello world")) // 客户端发送数据
	}()

	client, _ := server.Accept() // 接受客户端
	defer client.Close()

	fmt.Println("client addr: ", client.LocalAddr().String()) // 打印服务端地址
	var recv []byte = make([]byte, 100)
	client.Read(recv)
	//recv, _ := io.ReadAll(client)
	fmt.Println("server recv from client: ", string(recv)) // 打印客户端发送的数据
}

以上代码完成了一个简单的网络通信过程,那么我们就先从服务端入手吧,这里显而易见的是net.Listen("tcp", "127.0.0.1:8080")这个函数,我们可以猜想这个函数的处理流程应该是极其重要的,跟进看看:

func Listen(network, address string) (Listener, error) {
	var lc ListenConfig
	return lc.Listen(context.Background(), network, address) // 返回一个Listener接口
}

Listener接口必然映射这一个底层的结构体,那么我们就找到这个结构体分析一下:(跟入lc.Listen(context.Background(), network, address)函数

func (lc *ListenConfig) Listen(ctx context.Context, network, address string) (Listener, error) {
	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{ // 实例化一个sysListener结构体
		ListenConfig: *lc,
		network:      network,
		address:      address,
	}
	var l Listener
	la := addrs.first(isIPv4)
	switch la := la.(type) { // 检查监听的协议
	case *TCPAddr:
		l, err = sl.listenTCP(ctx, la) // 监听tcp,并设置l为TCPListener结构体
	case *UnixAddr:
		l, err = sl.listenUnix(ctx, la) // 设置l为UnixListener结构体
	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
}

而最终返回的结构体则是从sl.listenTCP(ctx, la)或者sl.listenUnix(ctx, la)得到的,那我们就由sl.listenTCP(ctx, la)来分析底层的结构体:

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) // 相当于C语言之中的socket函数
	if err != nil {
		return nil, err
	}
	return &TCPListener{fd: fd, lc: sl.ListenConfig}, nil // 返回的是TCPListener结构体
}

其实际流程为初始化一个socket套接字,并封装于TCPListener结构体之中

type TCPListener struct {
	fd *netFD       // 套接字
	lc ListenConfig // TCP配置
}
type ListenConfig struct {
	Control func(network, address string, c syscall.RawConn) error
	KeepAlive time.Duration
}

那么上层使用的Accpet函数实际为:

func (l *TCPListener) Accept() (Conn, error) {
	if !l.ok() {
		return nil, syscall.EINVAL
	}
	c, err := l.accept() // 开始监听,调用内部函数
	if err != nil {
		return nil, &OpError{Op: "accept", Net: l.fd.net, Source: nil, Addr: l.fd.laddr, Err: err}
	}
	return c, nil
}

func (ln *TCPListener) accept() (*TCPConn, error) {
	fd, err := ln.fd.accept() // 开始监听,对于netFD结构体的一层封装
	if err != nil {
		return nil, err
	}
	tc := newTCPConn(fd)      // 返回tcp客户端,设置tc为TCPConn结构体
	if ln.lc.KeepAlive >= 0 { // 如果持续存活
		setKeepAlive(fd, true)
		ka := ln.lc.KeepAlive
		if ln.lc.KeepAlive == 0 {
			ka = defaultTCPKeepAlive
		}
		setKeepAlivePeriod(fd, ka)
	}
	return tc, nil
}

而返回结果则是tcp对于的一个客户端会话:

type TCPConn struct { // 实际上就是一个套接字,经过了层层(二层)封装
	conn
}
// TCPConn 继承于 conn,而conn是套接字的封装
type conn struct { // 对于套接字的一层封装,具有有读写等功能
	fd *netFD
}

同理,客户端对于服务端的连接流程大致一样,同样从net.Dial("tcp", "127.0.0.1:8080")开始分析:

func Dial(network, address string) (Conn, error) {
	var d Dialer                    // 拨号器
	return d.Dial(network, address) // connect封装
}

func (d *Dialer) Dial(network, address string) (Conn, error) {
	return d.DialContext(context.Background(), network, address)
}
// 关键函数如下
func (d *Dialer) DialContext(ctx context.Context, network, address string) (Conn, error) {
        ...
        c, err := sd.dialParallel(ctx, primaries, fallbacks) // 解析 `整个连接`
        ...
}

关键函数sd.dialParallel(ctx, primaries, fallbacks),如下:

func (sd *sysDialer) dialParallel(ctx context.Context, primaries, fallbacks addrList) (Conn, error) {
	if len(fallbacks) == 0 {
		return sd.dialSerial(ctx, primaries)
	}

	returned := make(chan struct{})
	defer close(returned)

	type dialResult struct {
		Conn
		error
		primary bool
		done    bool
	}
	results := make(chan dialResult) // unbuffered

	startRacer := func(ctx context.Context, primary bool) {
		ras := primaries
		if !primary {
			ras = fallbacks
		}
		c, err := sd.dialSerial(ctx, ras) // 开始尝试连接
		select {
		case results <- dialResult{Conn: c, error: err, primary: primary, done: true}: // 传输连接成功返回的结果
		case <-returned: //如果该函数应该返回,则释放资源
			if c != nil {
				c.Close()
			}
		}
	}

	var primary, fallback dialResult

	// Start the main racer.
	primaryCtx, primaryCancel := context.WithCancel(ctx)
	defer primaryCancel()
	go startRacer(primaryCtx, true)

	// Start the timer for the fallback racer.
	fallbackTimer := time.NewTimer(sd.fallbackDelay())
	defer fallbackTimer.Stop()

	for { // 循环等待
		select {
		case <-fallbackTimer.C:
			fallbackCtx, fallbackCancel := context.WithCancel(ctx)
			defer fallbackCancel()
			go startRacer(fallbackCtx, false)

		case res := <-results: // 等待结果返回,实际为dialResult结构体
			if res.error == nil {
				return res.Conn, nil
			}
			if res.primary {
				primary = res
			} else {
				fallback = res
			}
			if primary.done && fallback.done {
				return nil, primary.error
			}
			if res.primary && fallbackTimer.Stop() {
				// If we were able to stop the timer, that means it
				// was running (hadn't yet started the fallback), but
				// we just got an error on the primary path, so start
				// the fallback immediately (in 0 nanoseconds).
				fallbackTimer.Reset(0)
			}
		}
	}
}

好了,现在我们可以看出来,net实际上就是对于socket的层层封装罢了

posted @ 2023-05-01 17:29  望权栈  阅读(150)  评论(0编辑  收藏  举报