Go 语言的实战案例 SOCKS5 代理 | 青训营

Powered by:NEFU AB-IN

文章目录

  • Go 语言的实战案例 SOCKS5 代理 | 青训营
    • 引入
    • TCP echo server
    • auth 认证
    • 请求阶段
    • relay阶段

Go 语言的实战案例 SOCKS5 代理 | 青训营

  • GO语言工程实践课后作业:实现思路、代码以及路径记录

引入

代理是指在计算机网络中,代理服务器充当客户端和目标服务器之间的中介。它接收来自客户端的请求,然后将请求转发给目标服务器,再将目标服务器的响应返回给客户端。

用途

  • 匿名浏览:Socks5代理可以隐藏客户端的真实IP地址,使得客户端在访问目标服务器时更加匿名。可以代表客户端向其他服务器发送请求

    • 在网络爬虫中,使用代理服务器可以解决IP访问频率限制问题,因为它们可以隐藏真实的客户端IP地址并提供新的IP地址来进行请求,从而减轻对单个IP的访问频率限制。这样,爬虫就可以通过多个代理IP来轮流发送请求,避免被目标服务器封禁。
  • 数据加密:Socks5代理支持对数据流进行加密传输,提供更高的数据安全性。

  • 跨协议代理:Socks5代理不仅支持HTTP协议,还支持包括FTP、SMTP等在内的多种协议,实现了更广泛的应用场景。

  • 穿越防火墙:由于Socks5代理更通用,不受网络防火墙的限制,可以更容易地穿越网络防火墙,访问被封锁或限制的资源。

Socks5代理是一种代理协议,用于在网络上进行数据传输。Socks5代理服务器可以支持多种协议的代理,包括HTTP、FTP、SMTP等。与HTTP代理相比,Socks5代理更加灵活和通用。

主要区别如下:

  • HTTP代理:主要用于HTTP请求,支持浏览器的代理设置,透明代理
  • Socks5代理:支持HTTP以外的其他协议的代理,不支持浏览器的代理设置,需要在应用程序中设置代理,Socks5代理的主要特点是它可以在客户端和代理服务器之间建立一个 TCP 或 UDP 连接,并直接将数据转发给目标服务器,而不需要解析或修改数据包。这种特性使得SOCKS5代理适用于很多不同类型的网络应用,如 P2P 文件共享、实时视频传输等。

代理过程通常如下:

  1. 客户端向代理服务器发送连接请求,并指定目标服务器的地址。
  2. 代理服务器接收到请求后,解析目标服务器地址,并建立与目标服务器的连接。
  3. 代理服务器将客户端的请求转发给目标服务器。
  4. 目标服务器接收到请求后,处理请求并生成响应。
  5. 代理服务器将目标服务器的响应转发给客户端。
  6. 客户端接收到代理服务器返回的响应,并进行处理。

image.png
总的来说,代理服务器充当了客户端和目标服务器之间的中转站,使得客户端能间接与目标服务器进行通信,提供了更多的隐私保护和功能扩展。

TCP echo server

/*
 * @Author: NEFU AB-IN
 * @Date: 2023-08-23 22:36:26
 * @FilePath: \GoTest\6\6.go
 * @LastEditTime: 2023-08-23 22:43:57
 */
package main

import (
	"bufio"
	"log"
	"net"
)

// TCP echo server

func main() {
	server, err := net.Listen("tcp", "127.0.0.1:1080") // 监听端口,返回一个server
	if err != nil {
		panic(err)
	}
	for {
		client, err := server.Accept() // 在死循环里,用这个方法接收一个请求
		if err != nil {
			log.Printf("Accept failed %v", err)
			continue
		}
		go process(client) // 处理这个链接 go关键字 启动子线程去处理
		// 如果接受连接时出现错误,服务器会记录错误信息,然后继续等待下一个客户端连接
		// 否则为每个客户端连接启动一个独立的process协程来处理客户端请求,这样可以并发处理多个客户端连接。
	}
}

func process(conn net.Conn) {
	defer conn.Close()              // 最后将连接关掉
	reader := bufio.NewReader(conn) // 基于这个链接,创造一个只读的带缓冲的流
	for {
		b, err := reader.ReadByte() // 每次读一个字节
		if err != nil {
			break
		}
		_, err = conn.Write([]byte{b}) // 将读取的字节回显给客户端,直接写回到连接中
		if err != nil {
			break
		}
	}
}

image.png

auth 认证

认证阶段:实现auth函数,更改process函数,里面用到auth函数

/*
 * @Author: NEFU AB-IN
 * @Date: 2023-08-23 22:50:05
 * @FilePath: \GoTest\7\7.go
 * @LastEditTime: 2023-08-23 22:50:11
 */
package main

import (
	"bufio"
	"fmt"
	"io"
	"log"
	"net"
)
// 一些常量定义,用于表示Socks5协议中的一些数值,例如协议版本、命令类型和地址类型等。
const socks5Ver = 0x05
const cmdBind = 0x01
const atypeIPV4 = 0x01
const atypeHOST = 0x03
const atypeIPV6 = 0x04

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)
	}
}

func process(conn net.Conn) {
	defer conn.Close()
	reader := bufio.NewReader(conn)
	err := auth(reader, conn)
	if err != nil {
		log.Printf("client %v auth failed:%v", conn.RemoteAddr(), err)
		return
	}
	log.Println("auth success")
}

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) // 它会从r中连续读取数据,直到buf被填满,或者遇到了一个错误。如果读取完整的buf之前发生错误,函数会返回读取的字节数n和错误信息err;如果成功读取了整个buf,函数会返回buf的长度和nil。
// 这个函数在需要确保读取完整数据包时很有用,比如读取固定长度的消息、解码二进制数据等。
	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
}

请求阶段

请求阶段:实现connect函数,签名一致,在process中调用

package main

import (
	"bufio"
	"encoding/binary"
	"errors"
	"fmt"
	"io"
	"log"
	"net"
)

const socks5Ver = 0x05
const cmdBind = 0x01
const atypeIPV4 = 0x01
const atypeHOST = 0x03
const atypeIPV6 = 0x04

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)
	}
}

func process(conn net.Conn) {
	defer conn.Close()
	reader := bufio.NewReader(conn)
	err := auth(reader, conn)
	if err != nil {
		log.Printf("client %v auth failed:%v", conn.RemoteAddr(), err)
		return
	}
	err = connect(reader, conn)
	if err != nil {
		log.Printf("client %v auth failed:%v", conn.RemoteAddr(), err)
		return
	}
}

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)
	}

	// +----+--------+
	// |VER | METHOD |
	// +----+--------+
	// | 1  |   1    |
	// +----+--------+
	_, err = conn.Write([]byte{socks5Ver, 0x00})
	if err != nil {
		return fmt.Errorf("write failed:%w", err)
	}
	return nil
}

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个字节


// !!!! 可以看到前四个字段都是单字节,可以采用之前的方法一个个读出来,这里直接构造一个4字节的缓冲区,用readfull全读出来
	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: // 若是IPV4 读四个字节
		_, 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: // 先不实现IPV6
		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])  // 用于将 2 字节的大端序字节序列解码为一个无符号 16 位整数(uint16)。

	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}) // 拼成byte数组
	if err != nil {
		return fmt.Errorf("write failed: %w", err)
	}
	return nil
}

relay阶段

relay阶段:最后一步,真正和服务器建立TCP连接,在 connect 函数中,增加功能

package main

import (
	"bufio"
	"context"
	"encoding/binary"
	"errors"
	"fmt"
	"io"
	"log"
	"net"
)

const socks5Ver = 0x05
const cmdBind = 0x01
const atypeIPV4 = 0x01
const atypeHOST = 0x03
const atypeIPV6 = 0x04

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)
	}
}

func process(conn net.Conn) {
	defer conn.Close()
	reader := bufio.NewReader(conn)
	err := auth(reader, conn)
	if err != nil {
		log.Printf("client %v auth failed:%v", conn.RemoteAddr(), err)
		return
	}
	err = connect(reader, conn)
	if err != nil {
		log.Printf("client %v auth failed:%v", conn.RemoteAddr(), err)
		return
	}
}

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)
	}

	// +----+--------+
	// |VER | METHOD |
	// +----+--------+
	// | 1  |   1    |
	// +----+--------+
	_, err = conn.Write([]byte{socks5Ver, 0x00})
	if err != nil {
		return fmt.Errorf("write failed:%w", err)
	}
	return nil
}

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])

	// 建立浏览器和服务器的双向数据转换,使用 `net.Dial` 函数连接到目标服务器 `dest`,即客户端所请求的远程服务器。这样就建立了代理服务器与目标服务器之间的连接。
	dest, err := net.Dial("tcp", fmt.Sprintf("%v:%v", addr, port))
	if err != nil {
		return fmt.Errorf("dial dst failed:%w", err)
	}
	defer dest.Close()
	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)
	}
        
        
// 这段代码的主要功能是实现两个goroutine并行执行io.Copy操作,并在其中一个goroutine完成后取消另一个goroutine的执行。
//- 第一行创建一个新的上下文对象ctx,并使用context.Background()作为根上下文对象。这个上下文对象用于在不同goroutine之间传递取消信号。
//- 第二行定义了一个cancel函数,用于取消上下文对象ctx。defer关键字用于延迟执行cancel函数,在函数返回时自动调用。
//- 第四行开启了一个goroutine,该goroutine负责将读取到的数据从reader复制到dest,并在复制完成后调用cancel函数取消上下文对象的执行。这样做是为了在复制完成后,能正确地通知另一个goroutine停止执行。
//- 第六行开启了第二个goroutine,该goroutine负责将dest的数据复制到conn,并在复制完成后也调用cancel函数取消上下文对象的执行。
//- 第九行使用<-ctx.Done()语法,表示等待上下文对象ctx被取消。一旦ctx被取消,即ctx.Done()返回,程序会继续往下执行。

	ctx, cancel := context.WithCancel(context.Background())
	defer cancel()
 
	go func() {
		_, _ = io.Copy(dest, reader)
		cancel()
	}()
	go func() {
		_, _ = io.Copy(conn, dest)
		cancel()
	}()

	<-ctx.Done() // 阻塞等待,直到两个数据转发的Go协程都完成后,函数返回。执行时机,也就是cancel的时间。
	return nil
}

ctx, cancel := context.WithCancel(context.Background())解释

  • 上下文对象是 Go 语言中用于跨 goroutine 传递请求的值、截止时间以及取消信号的一种机制。它可以用来解决在并发环境下协调多个 goroutine 之间的操作和通信的问题。

  • 上下文对象通常用于以下几种情况:

    1. 传递请求的相关值:例如请求ID、用户信息等,在多个 goroutine 之间共享。
    2. 设置截止时间:对某些操作设置一个超时时间,用于控制操作的执行时间。
    3. 取消信号:当一个操作出现问题或者不再需要执行时,可以通过取消上下文对象来通知相关的 goroutine 停止执行。
  • context.WithCancel(context.Background()) 这句代码的意思是创建一个具有取消功能的上下文对象(WithCancel()函数接受一个 Context 并返回其子Context和取消函数cancel)。context.Background() 创建一个根上下文对象,而 context.WithCancel 则基于根上下文对象创建一个新的上下文对象,该上下文对象具有取消功能。返回的 ctx 和 cancel 是用于取消该上下文对象的函数。

  • 所以这句代码的作用就是创建一个新的上下文对象 ctx,并使用 context.WithCancel 函数将其绑定到一个取消函数 cancel 上。这样可以通过调用 cancel 函数来取消该上下文对象的执行。该上下文对象可以被传递给其他需要依赖上下文的 goroutine 来实现协作和取消的功能。

image.png

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/89201.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Stable Diffusion web UI 部署详细教程

前言 本文使用 AutoDL 平台进行 Stable Diffusion web UI 云端部署 AutoDL 官网&#xff1a;AutoDL算力云 | 弹性、好用、省钱。租GPU就上AutoDL Stable Diffusion web UI 官网&#xff1a;AUTOMATIC1111/stable-diffusion-webui: Stable Diffusion web UI (github.com) 步…

stm32之15.超声波与灯光功能一起实现(进阶)

主函数代码修改 --------------------- 源码 int main(void) {uint32_t t0;uint32_t distance;NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4);led_init();key_init();/* 初始化串口1波特率为115200bps&#xff0c;若发送/接收数据有乱码&#xff0c;请检查PLL */usart1_ini…

https 的ssl证书过期处理解决方案(lighthttpd)

更换证书&#xff1a;lighthttpd 配置文件位置&#xff1a;/opt/vmware/etc/lighttpd/lighttpd.conf &#xff08;配置文件的最底部 G快速来到底部&#xff09; 方案一&#xff1a;阿里云申请免费的证书 这里公司内网环境没有配置域名&#xff0c;可以创建一个临时域名&…

让大数据平台数据安全可见-行云管家

数字化经济在快速发展&#xff0c;大数据时代已经到来&#xff0c;大数据已经成为企业和政府决策的重要依据。然而大数据行业快速发展所带来的一系列安全问题也继续解决&#xff0c;例如数据安全更难保障&#xff0c;例如认证体系不完善等等。为此行云管家推出了大数据平台数据…

文心一言 VS 讯飞星火 VS chatgpt (81)-- 算法导论7.4 6题

六、如果用go语言&#xff0c;考虑对 PARTITION 过程做这样的修改:从数组 A 中随机选出三个元素&#xff0c;并用这三个元素的中位数(即这三个元素按大小排在中间的值)对数组进行划分。求以a 的函数形式表示的、最坏划分比例为 a:(1-a)的近似概率&#xff0c;其中 0<a<1。…

Qt --- 显示相关设置 窗口属性等

主界面&#xff0c;窗口 最小化 最大化 关闭按钮、显示状态自定义&#xff1a; setWindowFlags(Qt::CustomizeWindowHint); setWindowFlags(Qt::WindowCloseButtonHint); //只要关闭按钮 setWindowFlags(Qt::WindowFlags type) Qt::FrameWindowHint:没有边框的窗口 Qt::Window…

Jenkins的流水线详解

来源&#xff1a;u.kubeinfo.cn/ozoxBB 什么是流水线 声明式流水线 Jenkinsfile 的使用 什么是流水线 jenkins 有 2 种流水线分为声明式流水线与脚本化流水线&#xff0c;脚本化流水线是 jenkins 旧版本使用的流水线脚本&#xff0c;新版本 Jenkins 推荐使用声明式流水线。…

如何理解原假设和备择假设?

原假设H0&#xff1a;一般是想要推翻的结论&#xff0c;如指标没有变化&#xff0c;实验组和对照组的该结果指标没有差异等。 备择假设H1&#xff1a;一般是想要证明的结论&#xff0c;如实验组的指标是显著提升的&#xff0c;指标提升10%等。 反证法的思想&#xff1a;因为假…

YOLOV1

YOU ONLY LOOK ONCE

KubeSphere 社区双周报 | Java functions framework 支持 SkyWalking | 2023.8.4-8.17

KubeSphere 社区双周报主要整理展示新增的贡献者名单和证书、新增的讲师证书以及两周内提交过 commit 的贡献者&#xff0c;并对近期重要的 PR 进行解析&#xff0c;同时还包含了线上/线下活动和布道推广等一系列社区动态。 本次双周报涵盖时间为&#xff1a;2023.08.04-2023.…

已解决Gradle错误:“Unable to load class ‘org.gradle.api.plugins.MavenPlugin‘”

&#x1f337;&#x1f341; 博主猫头虎 带您 Go to New World.✨&#x1f341; &#x1f984; 博客首页——猫头虎的博客&#x1f390; &#x1f433;《面试题大全专栏》 文章图文并茂&#x1f995;生动形象&#x1f996;简单易学&#xff01;欢迎大家来踩踩~&#x1f33a; &a…

SpringBoot Cache

一、基本概念 Spring Cache 是一个框架&#xff0c;实现了基于注解的缓存功能&#xff0c;只需要简单地加一个注解&#xff0c;就能实现缓存功能。 Spring Cache 提供了一层抽象&#xff0c;底层可以切换不同的缓存实现&#xff0c;例如&#xff1a; • EHCache • Caffeine …

Android11.0 Launcher3单层app列表页排序功能实现

1.概述 在11.0的定制化开发中,对于Launcher3的功能定制也是好多的,而对于单层app列表页来说排序功能的开发,也是常有的功能这就需要了解加载app数据的流程,然后根据需要进行排序就可以了, 如图: 2. Launcher3 单层app列表页排序功能实现 packages\apps\Launcher3\src\…

java八股文面试[数据结构]——List和Set的区别

List和Set是用来存放集合的接口&#xff0c;并且二者都继承自接接口Collection List 中的元素存放是有序的&#xff0c;可以存放重复的元素&#xff0c;检索效率较高&#xff0c;插入删除效率较低。 Set 没有存放顺序不能存放重复元素检索效率较低&#xff0c;插入删除效率较…

Elasticsearch(十二)搜索---搜索匹配功能③--布尔查询及filter查询原理

一、前言 本节主要学习ES匹配查询中的布尔查询以及布尔查询中比较特殊的filter查询及其原理。 复合搜索&#xff0c;顾名思义是一种在一个搜索语句中包含一种或多种搜索子句的搜索。 布尔查询是常用的复合查询&#xff0c;它把多个子查询组合成一个布尔表达式&#xff0c;这些…

2.文章复现《热电联产系统在区域综合能源系统中的定容选址研究》(附matlab程序)

0.代码链接 1.简述 光热发电是大规模利用太阳能的新兴方式&#xff0c;其储热系 统能够调节光热电站的出力特性&#xff0c;进而缓解光热电站并网带来的火电机组调峰问题。合理配置光热电站储热容量&#xff0c;能够 有效降低火电机组调峰成本。该文提出一种光热电站储热容 量配…

linux安装部署gitlab全教程,包含配置中文

linux安装部署gitlab全教程&#xff0c;包含配置中文 大家好&#xff0c;我是酷酷的韩~ 1.前期准备 安装包下载地址 https://mirrors.tuna.tsinghua.edu.cn/gitlab-ce/yum/el7/ 我这里选择的这个gitlab-ce-15.7.3-ce.0.el7.x86_64.rpm 还有一些相关依赖包(地址等审核过我放到…

IDEA远程开发

IDEA远程开发 前期准备 IDEA的远程开发是在本地去操昨远程服务器上的代码&#xff0c;所以我们先需要准备一台服务器,在此我使用vmware虚拟出ubuntu-20.04.6的Server版本,以便后面演示。 Ubuntu的Java环境配置 JDK8 sudo apt install openjdk-8-jdkmaven sudo apt instal…

三维模拟推演电子沙盘虚拟数字沙盘开发教程第13课

三维模拟推演电子沙盘虚拟数字沙盘开发教程第13课 该数据库中只提供 成都市火车南站附近的数据请注意&#xff0c;104.0648,30.61658 在SDK中为了方便三方数据的接入&#xff0c;引入了一个用户层接口。主要是完成三方数据的接入&#xff0c;含动态数据&#xff08;如GPS&…

字节一面:post为什么会发送两次请求?

前言 最近博主在字节面试中遇到这样一个面试题&#xff0c;这个问题也是前端面试的高频问题&#xff0c;因为在前端开发的日常开发中我们总是会与post请求打交道&#xff0c;一个小小的post请求也是牵扯到很多知识点的&#xff0c;博主在这给大家细细道来。 &#x1f680; 作者…