青训营X豆包MarsCode 技术训练营第一课 | go实践(一)

69 阅读6分钟

node

procxy

  1. 客户端请求服务器

    客户端 <-建立连接-> 服务器
    客户端 -> 发送HTTP请求 -> 服务器
    客户端 <- 接收HTTP响应 <- 服务器
    客户端 <- 关闭连接 -> 服务器
    
  2. socket5

    Socket5(通常写作SOCKS5)是一种网络协议,用于客户端与代理服务器之间的通信,以及通过代理服务器转发网络请求

    客户端 <-建立连接-> SOCKS5代理服务器
    客户端 -> 发送认证方法 -> SOCKS5代理服务器
    客户端 <- 接收认证响应 <- SOCKS5代理服务器
    客户端 -> 发送连接请求 -> SOCKS5代理服务器
    客户端 <- 接收连接响应 <- SOCKS5代理服务器
    客户端 -> 发送数据 -> SOCKS5代理服务器 -> 目标服务器
    客户端 <- 接收数据 <- SOCKS5代理服务器 <- 目标服务器
    
  3. code_1

    func main() {
    	server, err := net.Listen("tcp", "127.0.0.1:1080")
    	if err != nil {
    		panic(err)
    	}
    	for {
    		client, err := server.Accept()
    		if err != nil {
    			log.Printf("Accept failed %v", err)
    			continue
    		}
    		go process(client)
    	}
    }
    
    // net.Listen("tcp", "127.0.0.1:1080") 返回一个监听对象,该对象表示这个新的网络连接
    /*server.Accept():这是一个方法调用,用于接受来自客户端的连接请求
    Accept 方法会阻塞当前goroutine,直到有新的客户端连接到来
    */
    
    
    

    go协程

    轻量级:

    goroutine由go的运行时管理,而不是操作系统
    它们占用的内存更少,初始栈大小通常为2kb,可以动态伸缩
    
    c/c++中线程由操作系统管理的,每个线程都有自己的堆栈和寄存器状态
    线程的开销比较大
    

    调度:

    goroutine的调度是协作的,而不是抢占式的
    gi运行时有一个自己的调度器,负责多个操作系统上调度数以千计的goroutine
    创建和销毁goroutine非常快速和廉价, 它们主要是在 用户态 进行
    goroutine的栈是服用的,这减少了内存分配和回收的开销
    
    c/c++中线程,操作系统负责在多个CPU核心上分配线程,这涉及到用户态和内核态之间的切换,开销大
    创建和销毁线程成本较高,因为它们需要操作系统资源的分配和回收
    

    同步机制:

    goroutine 使用通道(channel)进行goroutine之间的通信,这是一种更安全、更简单的同步机制
    Goroutine之间通常不共享内存,这减少了并发编程中的竞态条件
    
    c/c++线程通常通过互斥锁(mutexes)、条件变量(condition variables)、信号量(semaphores)等机制进行同步
    线程之间共享内存是常见的,这可能导致复杂的竞态条件和死锁问题
    

  4. code_2

    func auth(reader *bufio.Reader, conn net.Conn) (err error) {
    	// +----+----------+----------+
    	// |VER | NMETHODS | METHODS  |
    	// +----+----------+----------+
    	// | 1  |    1     | 1 to 255 |
    	// +----+----------+----------+
    	// VER: 协议版本,socks5为0x05
    	// NMETHODS: 支持认证的方法数量
    	// METHODS: 对应NMETHODS,NMETHODS的值为多少,METHODS就有多少个字节。RFC预定义了一些值的含义,内容如下:
    	// X’00’ NO AUTHENTICATION REQUIRED
    	// X’02’ USERNAME/PASSWORD
    
    	ver, err := reader.ReadByte()
    	if err != nil {
    		return fmt.Errorf("read ver failed:%w", err)
    	}
    	if ver != socks5Ver {
    		return fmt.Errorf("not supported ver:%v", ver)
    	}
    	methodSize, err := reader.ReadByte()
    	if err != nil {
    		return fmt.Errorf("read methodSize failed:%w", err)
    	}
    	method := make([]byte, methodSize)
    	_, err = io.ReadFull(reader, method)
    	if err != nil {
    		return fmt.Errorf("read method failed:%w", err)
    	}
    	log.Println("ver", ver, "method", method)
    	// +----+--------+
    	// |VER | METHOD |
    	// +----+--------+
    	// | 1  |   1    |
    	// +----+--------+
    	_, err = conn.Write([]byte{socks5Ver, 0x00})
    	if err != nil {
    		return fmt.Errorf("write failed:%w", err)
    	}
    	return nil
    }
    
    /*
    	1.注释为socket5的请求
    	2.reader.ReadByte()读取客户端的请求的一个字节,VER:版本号
    	3.再读取一个字节,将其赋值给methodSize
    	4.METHODS: 对应NMETHODS,NMETHODS的值为多少,METHODS就有多少个字节
    	5.创建methodSize大小的切片来接收METHODS
    	6.打印和读取版本号,方法methods
    	7.给客户端conn.Write([]byte{socks5Ver, 0x00}),写入0x00,表示不需要认证
    */
    
  5. code_3

    func connect(reader *bufio.Reader, conn net.Conn) (err error) {
    	// +----+-----+-------+------+----------+----------+
    	// |VER | CMD |  RSV  | ATYP | DST.ADDR | DST.PORT |
    	// +----+-----+-------+------+----------+----------+
    	// | 1  |  1  | X'00' |  1   | Variable |    2     |
    	// +----+-----+-------+------+----------+----------+
    	// VER 版本号,socks5的值为0x05
    	// CMD 0x01表示CONNECT请求
    	// RSV 保留字段,值为0x00
    	// ATYP 目标地址类型,DST.ADDR的数据对应这个字段的类型。
    	//   0x01表示IPv4地址,DST.ADDR为4个字节
    	//   0x03表示域名,DST.ADDR是一个可变长度的域名
    	// DST.ADDR 一个可变长度的值
    	// DST.PORT 目标端口,固定2个字节
    
    	buf := make([]byte, 4)
    	_, err = io.ReadFull(reader, buf)
    	if err != nil {
    		return fmt.Errorf("read header failed:%w", err)
    	}
    	ver, cmd, atyp := buf[0], buf[1], buf[3]
    	if ver != socks5Ver {
    		return fmt.Errorf("not supported ver:%v", ver)
    	}
    	if cmd != cmdBind {
    		return fmt.Errorf("not supported cmd:%v", cmd)
    	}
    	addr := ""
    	switch atyp {
    	case atypeIPV4:
    		_, err = io.ReadFull(reader, buf)
    		if err != nil {
    			return fmt.Errorf("read atyp failed:%w", err)
    		}
    		addr = fmt.Sprintf("%d.%d.%d.%d", buf[0], buf[1], buf[2], buf[3])
    	case atypeHOST:
    		hostSize, err := reader.ReadByte()
    		if err != nil {
    			return fmt.Errorf("read hostSize failed:%w", err)
    		}
    		host := make([]byte, hostSize)
    		_, err = io.ReadFull(reader, host)
    		if err != nil {
    			return fmt.Errorf("read host failed:%w", err)
    		}
    		addr = string(host)
    	case atypeIPV6:
    		return errors.New("IPv6: no supported yet")
    	default:
    		return errors.New("invalid atyp")
    	}
    	_, err = io.ReadFull(reader, buf[:2])
    	if err != nil {
    		return fmt.Errorf("read port failed:%w", err)
    	}
    	port := binary.BigEndian.Uint16(buf[:2])
    
    	log.Println("dial", addr, port)
    
    	// +----+-----+-------+------+----------+----------+
    	// |VER | REP |  RSV  | ATYP | BND.ADDR | BND.PORT |
    	// +----+-----+-------+------+----------+----------+
    	// | 1  |  1  | X'00' |  1   | Variable |    2     |
    	// +----+-----+-------+------+----------+----------+
    	// VER socks版本,这里为0x05
    	// REP Relay field,内容取值如下 X’00’ succeeded
    	// RSV 保留字段
    	// ATYPE 地址类型
    	// BND.ADDR 服务绑定的地址
    	// BND.PORT 服务绑定的端口DST.PORT
    	_, err = conn.Write([]byte{0x05, 0x00, 0x00, 0x01, 0, 0, 0, 0, 0, 0})
    	if err != nil {
    		return fmt.Errorf("write failed: %w", err)
    	}
    	return nil
    }
    /*
    1.读取前4个字节,判断VER | CMD |  RSV  | ATYP
    2.通过switch判断ATYP目标地址是什么类型
    3.如果是ipv4,读取4个字节,表示目标设备的ipv4地址
    4.如果是域名,读取域名长度,将其转换为字符串
    5.发送回客户端{0x05, 0x00, 0x00, 0x01, 0, 0, 0, 0, 0, 0}
    	对照注释,0x05 表示 SOCKS5 协议版;0x00 表示响应状态,0x00 表示成功;0x00 是保留字段,值为 0x00;0x01 表示地址类型,0x01 表示 IPv4 地址;0, 0, 0, 0 是绑定的 IPv4 地址,这里全为 0 表示服务器将使用自己的地址进行绑定;最后两个字节 0, 0 是绑定的端口号
    */
    
  6. code_4

    ctx, cancel := context.WithCancel(context.Background())
    	defer cancel()
    
    	go func() {
    		_, _ = io.Copy(dest, reader)
    		cancel()
    	}()
    	go func() {
    		_, _ = io.Copy(conn, dest)
    		cancel()
    	}()
    
    	<-ctx.Done()
    	return nil
    
    /*
    1.ctx, cancel := context.WithCancel(context.Background()):创建一个可取消的上下文 ctx 和一个取消函数 cancel
    2.defer cancel():在函数结束时调用 cancel 函数,以确保在函数退出时取消上下go func() {... }() 和 go func() {... }():启动两个 goroutine,分别用于从客户端读取数据并写入到目标地址,以及从目标地址读取数据并写入到客户端文
    3.cancel():在数据拷贝完成时调用 cancel 函数,以取消另一个 goroutine 的执行
    4.<-ctx.Done():阻塞等待直到上下文被取消
    */