node
procxy
-
客户端请求服务器
客户端 <-建立连接-> 服务器 客户端 -> 发送HTTP请求 -> 服务器 客户端 <- 接收HTTP响应 <- 服务器 客户端 <- 关闭连接 -> 服务器 -
socket5
Socket5(通常写作SOCKS5)是一种网络协议,用于客户端与代理服务器之间的通信,以及通过代理服务器转发网络请求
客户端 <-建立连接-> SOCKS5代理服务器 客户端 -> 发送认证方法 -> SOCKS5代理服务器 客户端 <- 接收认证响应 <- SOCKS5代理服务器 客户端 -> 发送连接请求 -> SOCKS5代理服务器 客户端 <- 接收连接响应 <- SOCKS5代理服务器 客户端 -> 发送数据 -> SOCKS5代理服务器 -> 目标服务器 客户端 <- 接收数据 <- SOCKS5代理服务器 <- 目标服务器 -
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)等机制进行同步 线程之间共享内存是常见的,这可能导致复杂的竞态条件和死锁问题
-
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,表示不需要认证 */ -
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 是绑定的端口号 */ -
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():阻塞等待直到上下文被取消 */