Go语言中net标准库
net提供了用于网络通信的基本功能。net包支持TCP、UDP、Unix域套接字和HTTP等协议,使得开发网络应用程序变得简单和高效。下面是net包中一些常用的功能:
1. TCP和UDP网络通信:
net.Dial: 用于建立TCP或UDP连接。net.Listen: 用于在指定网络地址上监听连接。net.DialTimeout: 带有超时的连接建立。net.PacketConn: 用于使用UDP协议进行数据包通信。
2. HTTP网络通信:
http.Get: 用于发送HTTP GET请求并获取响应。http.Post: 用于发送HTTP POST请求并获取响应。http.ListenAndServe: 用于在指定端口上监听HTTP请求。
3. 基本网络工具:
net.IP: 代表一个IPv4或IPv6地址。net.TCPAddr和net.UDPAddr: 分别代表TCP和UDP地址。net.ResolveTCPAddr和net.ResolveUDPAddr: 解析TCP和UDP地址字符串。net.LookupIP: 执行DNS查找并返回与主机名关联的IP地址。
4. Socket操作:
net.Conn: 代表通用的网络连接。net.Listener: 代表通用的网络监听器。net.Dialer: 定义用于拨号的参数。net.ListenConfig: 定义用于监听的参数。
简单的HTTP Web服务器编程案例
1. 常见的HTTP请求方法:
- GET:用于从服务器获取资源。GET请求是一种幂等的请求方法,意味着多次发送相同的GET请求,服务器的状态不会改变。
- POST:用于向服务器提交数据,通常用于创建新的资源。POST请求是非幂等的,多次发送相同的POST请求,服务器可能会创建多个相同的资源。
- PUT:用于向服务器更新资源或替换资源。PUT请求是幂等的,多次发送相同的PUT请求,服务器的资源状态始终相同。
- DELETE:用于请求服务器删除指定的资源。DELETE请求也是幂等的,多次发送相同的DELETE请求,服务器的状态仍然保持一致。
- PATCH:用于部分更新服务器上的资源。与PUT请求不同,PATCH请求只会更新资源的部分内容,而不是整个资源。
- HEAD:类似于GET请求,但服务器不会返回实际的数据主体,只返回响应头,通常用于检查资源的元数据或验证资源是否存在。
- OPTIONS:用于请求服务器告知支持的请求方法和其他功能,比如CORS(跨域资源共享)时预检请求会使用OPTIONS方法。
- CONNECT:用于建立与代理服务器的隧道通信,通常用于SSL加密连接。
- TRACE:用于追踪HTTP请求-响应的传输路径,主要用于调试和测试。
2. HTTP服务器编程案例(GET、POST、PUT、DELETE):
package main
import (
"encoding/json"
"fmt"
"net/http"
"strconv"
)
// ResponseData 是用于响应的数据结构
type ResponseData struct {
Method string `json:"method"` // 请求方法
Message string `json:"message"` // 响应消息
Name string `json:"name"` // 参数 name 的值
Age int `json:"age"` // 参数 age 的值
}
// requestHandler 处理所有类型的HTTP请求
func requestHandler(w http.ResponseWriter, r *http.Request) {
switch r.Method {
case http.MethodGet:
// 处理GET请求,从URL的查询参数中读取 name 和 age 参数的值
name := r.URL.Query().Get("name")
age := r.URL.Query().Get("age")
// 构造响应数据
response := ResponseData{
Method: "GET",
Message: "This is a GET request",
Name: name,
}
// 将 age 转换为整数
var err error
response.Age, err = strconv.Atoi(age)
if err != nil {
http.Error(w, "Invalid age value", http.StatusBadRequest)
return
}
sendResponse(w, response)
case http.MethodPost:
// 处理POST请求,解析JSON数据
var requestData map[string]string
err := json.NewDecoder(r.Body).Decode(&requestData)
if err != nil {
http.Error(w, "Invalid request data", http.StatusBadRequest)
return
}
// 构造响应数据
response := ResponseData{
Method: "POST",
Message: fmt.Sprintf("This is a POST request with data: %v", requestData),
Name: requestData["name"],
}
// 将 age 转换为整数
response.Age, err = strconv.Atoi(requestData["age"])
if err != nil {
http.Error(w, "Invalid age value", http.StatusBadRequest)
return
}
sendResponse(w, response)
case http.MethodPut:
// 处理PUT请求,解析JSON数据
var requestData map[string]string
err := json.NewDecoder(r.Body).Decode(&requestData)
if err != nil {
http.Error(w, "Invalid request data", http.StatusBadRequest)
return
}
// 构造响应数据
response := ResponseData{
Method: "PUT",
Message: "This is a PUT request",
Name: requestData["name"],
}
// 将 age 转换为整数
response.Age, err = strconv.Atoi(requestData["age"])
if err != nil {
http.Error(w, "Invalid age value", http.StatusBadRequest)
return
}
sendResponse(w, response)
case http.MethodDelete:
// 处理DELETE请求,从URL的查询参数中读取 name 和 age 参数的值
name := r.URL.Query().Get("name")
age := r.URL.Query().Get("age")
// 构造响应数据
response := ResponseData{
Method: "DELETE",
Message: "This is a DELETE request",
Name: name,
}
// 将 age 转换为整数
var err error
response.Age, err = strconv.Atoi(age)
if err != nil {
http.Error(w, "Invalid age value", http.StatusBadRequest)
return
}
sendResponse(w, response)
default:
// 对于不支持的请求方法,返回"Method not allowed"错误
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
}
}
// sendResponse 发送JSON响应
func sendResponse(w http.ResponseWriter, response ResponseData) {
// Content-Type是HTTP响应头的一个字段,用于指示服务器返回的数据的类型。通过设置Content-Type为`application/json`,服务器通知客户端响应数据将会是JSON格式的数据
w.Header().Set("Content-Type", "application/json")
// 设置HTTP响应的状态码,状态码用于表示服务器对请求的处理结果
// 常见的HTTP状态码包括200(OK)、404(Not Found)、500(Internal Server Error)等。其中,`http.StatusOK`是一个常量,表示状态码200,它表示服务器成功处理了请求。
w.WriteHeader(http.StatusOK)
// 调用`Encode(response)`方法,编码器将`response`结构体(或数据类型)转换为JSON格式的数据,并将其写入到`http.ResponseWriter`中。这样,服务器就能够将JSON数据作为HTTP响应返回给客户端。
json.NewEncoder(w).Encode(response)
}
func main() {
// 用于将请求路径 `/` (根路径) 与特定的处理函数 `requestHandler` 绑定在一起
// 当用户访问根路径时(例如 `http://localhost:8080/`),服务器将会调用 `requestHandler` 函数来处理该请求
http.HandleFunc("/", requestHandler)
fmt.Println("Server started at http://localhost:8080")
http.ListenAndServe(":8080", nil)
}
在上述代码中,requestHandler函数处理所有类型的HTTP请求,并根据请求方法类型返回不同的响应。sendResponse函数用于发送JSON响应,以减少重复代码。
3. HTTP服务器案例使用(GET、POST、PUT、DELETE):
- 首先,运行上述代码,启动HTTP服务器,如下图表示服务器启动成功
- 打开一个HTTP请求工具
- GET:
输入HTTP请求,查询参数
name和age指定了需要获取的资源的条件,这些条件被编码在URL中,以便将数据传递给服务器。
- POST: 在POST请求中,通常使用JSON格式来更新服务器上的资源。为了发送JSON数据,客户端需要将JSON数据作为请求体的内容,设置请求头的"Content-Type"为"application/json"。
- PUT: 在PUT请求中,通常使用JSON格式来更新服务器上的资源。为了发送JSON数据,客户端需要将JSON数据作为请求体的内容,通常设置请求头的"Content-Type"为"application/json"。
- DELETE:
在DELETE请求中,指定参数
name和age的条件,这些条件被编码在URL中,以便将数据传递给服务器。
简单的SOCKS5代理服务器编程案例
以下代码来自于青训营课程资源,加入了我自己的理解。该代码实现了一个简单的SOCKS5代理服务器。SOCKS5是一种网络协议,允许客户端通过代理服务器与远程服务器建立连接,并通过该连接进行数据传输。代理服务器在这里运行于本地,监听在127.0.0.1:1080上,当客户端连接到该代理服务器时,它将建立到目标服务器的连接,实现了一个简单的端口转发功能。
package main
import (
"bufio" // 用于带缓冲的读写操作
"context" // 用于传递请求的上下文信息,并在多个 goroutine 之间进行通信和协调
"encoding/binary" // 用于进行二进制数据编码和解码
"errors" // 用于处理错误
"fmt" // 用于格式化输出
"io" // 用于 I/O 操作
"log" // 用于日志输出
"net" // 用于网络相关的操作,包括创建监听器、连接等
)
const socks5Ver = 0x05
const cmdBind = 0x01
const atypeIPV4 = 0x01
const atypeHOST = 0x03
const atypeIPV6 = 0x04
func main() {
// 创建一个 TCP 监听器,监听本地 127.0.0.1 地址的 1080 端口
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) // 在新的 goroutine 中调用 process() 函数处理客户端连接,并继续等待其他客户端连接
}
}
// process 函数用于处理客户端连接
func process(conn net.Conn) {
defer conn.Close() // 延迟关闭连接,在处理完成后始终关闭连接,避免资源泄露
reader := bufio.NewReader(conn) // 创建一个带缓冲的读取器,用于从客户端连接中读取数据
err := auth(reader, conn) // 调用 auth() 函数进行认证处理
if err != nil {
log.Printf("client %v auth failed:%v", conn.RemoteAddr(), err)
return // 如果认证失败,记录错误信息,并终止该连接的处理
}
err = connect(reader, conn) // 调用 connect() 函数处理客户端的 CONNECT 请求
if err != nil {
log.Printf("client %v auth failed:%v", conn.RemoteAddr(), err)
return // 如果处理 CONNECT 请求失败,记录错误信息,并终止该连接的处理
}
}
// auth 函数用于处理客户端的认证请求
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预定义了一些方法值的含义,其中 0x00 表示不需要认证,0x02 表示用户名/密码认证。
// 读取并验证协议版本
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 |
// +----+--------+
// 返回认证响应,这里使用无需认证的方法(0x00)
_, err = conn.Write([]byte{socks5Ver, 0x00})
if err != nil {
return fmt.Errorf("write failed:%w", err)
}
return nil
}
// connect 函数用于处理客户端的 CONNECT 请求
func connect(reader *bufio.Reader, conn net.Conn) (err error) {
// CONNECT 请求格式如下:
// +----+-----+-------+------+----------+----------+
// |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个字节
// 读取 CONNECT 请求头部信息
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 := ""
// 根据目标地址类型 atyp 进行不同的处理
switch atyp {
case atypeIPV4:
// 当目标地址类型为 IPv4 地址时,读取 4 个字节数据,解析为一个 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 地址时,返回不支持 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])
// 根据解析的目标地址和端口,创建与目标服务器的连接
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)
// 返回 CONNECT 响应,这里固定为成功(0x00)
_, err = conn.Write([]byte{0x05, 0x00, 0x00, 0x01, 0, 0, 0, 0, 0, 0})
if err != nil {
return fmt.Errorf("write failed: %w", err)
}
// 创建一个可取消的Context对象ctx,和一个用于主动取消ctx的cancel函数。
ctx, cancel := context.WithCancel(context.Background())
// 使用defer确保在函数返回时调用cancel函数,及时释放资源。
defer cancel()
// 在两个goroutine中进行数据传递。
// 第一个goroutine从reader中复制数据到dest,同时在传输完成后主动调用cancel函数取消ctx。
go func() {
_, _ = io.Copy(dest, reader)
cancel()
}()
// 第二个goroutine从dest中复制数据到conn,同时在传输完成后主动调用cancel函数取消ctx。
go func() {
_, _ = io.Copy(conn, dest)
cancel()
}()
// <-ctx.Done()是一个阻塞操作,等待ctx.Done()通道关闭。
// 当其中一个goroutine完成数据传递并调用了cancel函数后,会导致ctx.Done()的通道关闭。
// 这时<-ctx.Done()将不再阻塞,程序会继续往下执行。
// 通过这种方式,可以等待所有goroutine完成数据传递操作后再继续执行后续的代码,确保所有goroutine都已完成。
<-ctx.Done()
return nil
}
- 主函数
main:在127.0.0.1:1080上创建一个TCP监听器server,接受来自客户端的连接请求。每当有客户端连接时,启动一个process协程来处理该连接 process函数:处理客户端连接的主要逻辑。
- 进行认证阶段:根据SOCKS5协议规定,客户端与代理服务器在连接建立后,首先要进行认证阶段。客户端发送其支持的认证方法给代理服务器,然后代理服务器从中选择一种认证方法,告知客户端。这里的代理服务器仅支持无需认证和用户名/密码认证两种方式。首先读取客户端发送的认证方法,然后向客户端回复选择的认证方法。
- 进行连接阶段:认证成功后,客户端请求与目标服务器建立连接。客户端发送一个特定的请求给代理服务器,请求连接到目标服务器的指定地址和端口。代理服务器解析该请求,然后建立与目标服务器的连接,并返回连接状态给客户端。目标服务器的地址可以是IPv4地址、IPv6地址或者域名。
- 进行数据传输:当连接阶段成功后,代理服务器与目标服务器建立了连接。接下来,代理服务器使用两个协程来处理数据传输。一个协程从客户端读取数据并将其写入到目标服务器,另一个协程从目标服务器读取数据并将其写入到客户端。这样就实现了数据在代理服务器中的转发。
- 结束连接:一旦数据传输完成或发生错误,代理服务器关闭与客户端和目标服务器的连接。