首页 > 编程语言 >Go-net源码解析

Go-net源码解析

时间:2023-05-01 17:33:14浏览次数:40  
标签:return nil err ctx 源码 address Go net network

学习一门语言,那么我们必然要涉及到网络通信,而谈到网络通信却又离不开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的层层封装罢了

标签:return,nil,err,ctx,源码,address,Go,net,network
From: https://www.cnblogs.com/Dav-ove3/p/17297453.html

相关文章

  • Handling Information Loss of Graph Neural Networks for Session-based Recommendat
    目录概符号说明存在的问题LossysessionencodingproblemIneffectivelong-rangedependencycapturingproblemLESSRS2MGS2SG模型EOPA(Edge-OrderPreservingAggregation)SGAT(ShortcutGraphAttention)叠加代码ChenT.andWongR.C.Handlinginformationlossofgrap......
  • 对视图的对角线切割DiagonalView
    提供对视图的对角线切割,具有很好的用户定制基本用法:<com.intrusoft.squint.DiagonalViewandroid:id="@+id/diagonal"android:layout_width="match_parent"android:layout_height="240dp"......
  • MongoDB【常用命令】
    目录 1:基本常用命令1.1:演示案例1.2:数据库操作1.2.1:选择和创建数据库,查看当前正在使用的数据库命令1.2.2:数据库的删除1.3:集合操作1.3.1:集合的显式创建(了解)1.3.2:集合的隐式创建1.3.3:集合的删除1.4:文档基本CRUD1.4.1:文档的插入1.4.2:文档的基本查询1.4.3:文档的更新1.4.4:删除文档1.5:文......
  • [ABC146F] Sugoroku
    2023-03-03题目题目传送门翻译翻译难度&重要性(1~10):5题目来源AtCoder题目算法贪心解题思路对于第ii个点,只要到达\(s_{i+1}\cdotss_{i+m}\)中最后一个\(0\)的位置。但是这种方法求出的字典序肯定是最大的,但题目要求的是字典序最小。那么就可以倒序枚举,使第\(......
  • 使用 Docker Compose 安装 MongoDB
    最近学习Docker,试着在Docker里安装MongoDB,按照镜像mongo文档一顿操作猛如虎。快速开始写个docker-compose.yml文件:version:'3.8'services:db:image:mongocontainer_name:mongodb-containerports:-'27017:27017'......
  • google map初级尝试
    新建的是googlemap工程packagecom.wt.app;importandroid.os.Bundle;importcom.google.android.maps.GeoPoint;importcom.google.android.maps.MapActivity;importcom.google.android.maps.MapController;importcom.google.android.maps.MapVi......
  • android DragLayer源码
    Android_launcher的源码详细分析/**Copyright(C)2008TheAndroidOpenSourceProject**LicensedundertheApacheLicense,Version2.0(the"License");*youmaynotusethisfileexceptincompliancewiththeLicense.*......
  • Kubernetes从入门到精通 资源管理
    一资源管理介绍在kubernetes中,所有的内容都抽象为资源,用户需要通过操作资源来管理kubernetes。kubernetes的本质上就是一个集群系统,用户可以在集群中部署各种服务,所谓的部署服务,其实就是在kubernetes集群中运行一个个的容器,并将指定的程序跑在容器中。kubernetes的最小管理单......
  • go 数据类型
    序号类型和描述1布尔型布尔型的值只可以是常量true或者false。一个简单的例子:varbbool=true。2数字类型整型int和浮点型float32、float64,Go语言支持整型和浮点型数字,并且支持复数,其中位的运算采用补码。3字符串类型:字符串就是一串固定长度的字符连接......
  • Spring源码:bean的生命周期(一)
    前言本节将正式介绍Spring源码细节,将讲解Bean生命周期。请注意,虽然我们不希望过于繁琐地理解Spring源码,但也不要认为Spring源码很简单。在本节中,我们将主要讲解Spring5.3.10版本的源代码。如果您看到的代码与我讲解的不同,也没有关系,因为其中的原理和业务逻辑基本相同。为了更好......