Go语言网络编程精讲

目录TCP编程UDP编程TCP黏包http编程webSocket编程Go实现web服务器Go实现web客户端Go实现DNS查询TCP编程//TCP服务端//tcp/server/main.go//处理函数funcprocess(connnet.Conn){

目录


TCP编程

// TCP服务端
// tcp/server/main.go
// 处理函数
func process(conn net.Conn) {
    defer conn.Close() // 关闭连接
    for {
        reader := bufio.NewReader(conn)
        var buf [128]byte
        n, err := reader.Read(buf[:]) // 读取数据
        if err != nil {
            fmt.Println("read from client failed, err:", err)
            break
        }
        recvStr := string(buf[:n])
        fmt.Println("收到client端发来的数据:", recvStr)
        conn.Write([]byte(recvStr)) // 发送数据
    }
}

func main() {
    listen, err := net.Listen("tcp", "127.0.0.1:20000")
    if err != nil {
        fmt.Println("listen failed, err:", err)
        return
    }
    for {
        conn, err := listen.Accept() // 建立连接
        if err != nil {
            fmt.Println("accept failed, err:", err)
            continue
        }
        go process(conn) // 启动一个goroutine处理连接
    }
}   

// TCP客户端
// tcp/client/main.go
func main() {
    conn, err := net.Dial("tcp", "127.0.0.1:20000")
    if err != nil {
        fmt.Println("err :", err)
        return
    }
    defer conn.Close() // 关闭连接
    inputReader := bufio.NewReader(os.Stdin)
    for {
        input, _ := inputReader.ReadString('\n') // 读取用户输入
        inputInfo := strings.Trim(input, "\r\n")
        if strings.ToUpper(inputInfo) == "Q" { // 如果输入q就退出
            return
        }
        _, err = conn.Write([]byte(inputInfo)) // 发送数据
        if err != nil {
            return
        }
        buf := [512]byte{}
        n, err := conn.Read(buf[:])
        if err != nil {
            fmt.Println("recv failed, err:", err)
            return
        }
        fmt.Println(string(buf[:n]))
    }
}   

UDP编程

// UDP/server/main.go
// UDP server端
func main() {
    listen, err := net.ListenUDP("udp", &net.UDPAddr{
        IP:   net.IPv4(0, 0, 0, 0),
        Port: 30000,
    })
    if err != nil {
        fmt.Println("listen failed, err:", err)
        return
    }
    defer listen.Close()
    for {
        var data [1024]byte
        n, addr, err := listen.ReadFromUDP(data[:]) // 接收数据
        if err != nil {
            fmt.Println("read udp failed, err:", err)
            continue
        }
        fmt.Printf("data:%v addr:%v count:%v\n", string(data[:n]), addr, n)
        _, err = listen.WriteToUDP(data[:n], addr) // 发送数据
        if err != nil {
            fmt.Println("write to udp failed, err:", err)
            continue
        }
    }
}

// UDP 客户端
func main() {
    socket, err := net.DialUDP("udp", nil, &net.UDPAddr{
        IP:   net.IPv4(0, 0, 0, 0),
        Port: 30000,
    })
    if err != nil {
        fmt.Println("连接服务端失败,err:", err)
        return
    }
    defer socket.Close()
    sendData := []byte("Hello server")
    _, err = socket.Write(sendData) // 发送数据
    if err != nil {
        fmt.Println("发送数据失败,err:", err)
        return
    }
    data := make([]byte, 4096)
    n, remoteAddr, err := socket.ReadFromUDP(data) // 接收数据
    if err != nil {
        fmt.Println("接收数据失败,err:", err)
        return
    }
    fmt.Printf("recv:%v addr:%v count:%v\n", string(data[:n]), remoteAddr, n)
}  

TCP黏包

出现粘包主要原因就是tcp数据传递模式是流模式,在保持长连接的时候可以进行多次的收和发。

“粘包”可发生在发送端也可发生在接收端:

  1. 由Nagle算法造成的发送端的粘包:Nagle算法是一种改善网络传输效率的算法。简单来说就是当我们提交一段数据给TCP发送时,TCP并不立刻发送此段数据,而是等待一小段时间看看在等待期间是否还有要发送的数据,若有则会一次把这两段数据发送出去。

  2. 接收端接收不及时造成的接收端粘包:TCP会把接收到的数据存在自己的缓冲区中,然后通知应用层取数据。当应用层由于某些原因不能及时的把TCP的数据取

解决办法

出现”粘包”的关键在于接收方不确定将要传输的数据包的大小,因此我们可以对数据包进行封包和拆包的操作。

封包:封包就是给一段数据加上包头,这样一来数据包就分为包头和包体两部分内容了(过滤非法包时封包会加入”包尾”内容)。包头部分的长度是固定的,并且它存储了包体的长度,根据包头长度固定以及包头中含有包体长度的变量就能正确的拆分出一个完整的数据包。

我们可以自己定义一个协议,比如数据包的前4个字节为包头,里面存储的是发送的数据的长度。

// socket_stick/proto/proto.go
package proto

import (
    "bufio"
    "bytes"
    "encoding/binary"
)

// Encode 将消息编码
func Encode(message string) ([]byte, error) {
  // 读取消息的长度,转换成int32类型(占4个字节)
  var length = int32(len(message))
  var pkg = new(bytes.Buffer)
  // 写入消息头
  err := binary.Write(pkg, binary.LittleEndian, length)
  if err != nil {
      return nil, err
  }
  // 写入消息实体
  err = binary.Write(pkg, binary.LittleEndian, []byte(message))
  if err != nil {
      return nil, err
  }
  return pkg.Bytes(), nil
}

// Decode 解码消息
func Decode(reader *bufio.Reader) (string, error) {
  // 读取消息的长度
  lengthByte, _ := reader.Peek(4) // 读取前4个字节的数据
  lengthBuff := bytes.NewBuffer(lengthByte)
  var length int32
  err := binary.Read(lengthBuff, binary.LittleEndian, &length)
  if err != nil {
      return "", err
  }
  // Buffered返回缓冲中现有的可读取的字节数。
  if int32(reader.Buffered()) < length+4 {
      return "", err
  }

  // 读取真正的消息数据
  pack := make([]byte, int(4+length))
  _, err = reader.Read(pack)
  if err != nil {
      return "", err
  }
  return string(pack[4:]), nil
}  

接下来在服务端和客户端分别使用上面定义的proto包的Decode和Encode函数处理数据。 服务端代码如下:

// socket_stick/server2/main.go
func process(conn net.Conn) {
    defer conn.Close()
    reader := bufio.NewReader(conn)
    for {
        msg, err := proto.Decode(reader)
        if err == io.EOF {
            return
        }
        if err != nil {
            fmt.Println("decode msg failed, err:", err)
            return
        }
        fmt.Println("收到client发来的数据:", msg)
    }
}

func main() {

    listen, err := net.Listen("tcp", "127.0.0.1:30000")
    if err != nil {
        fmt.Println("listen failed, err:", err)
        return
    }
    defer listen.Close()
    for {
        conn, err := listen.Accept()
        if err != nil {
            fmt.Println("accept failed, err:", err)
            continue
        }
        go process(conn)
    }
}

客户端代码如下:

// socket_stick/client2/main.go
func main() {
  conn, err := net.Dial("tcp", "127.0.0.1:30000")
  if err != nil {
    fmt.Println("dial failed, err", err)
    return
  }
  defer conn.Close()
  for i := 0; i < 20; i++ {
    msg := `Hello, Hello. How are you?`
    data, err := proto.Encode(msg)
    if err != nil {
        fmt.Println("encode msg failed, err:", err)
        return
    }
    conn.Write(data)
  }
} 

http编程

// HTTP服务端
package main

import (
    "fmt"
    "net/http"
)

func main() {
    //http://127.0.0.1:8000/go
    // 单独写回调函数
    http.HandleFunc("/go", myHandler)
    //http.HandleFunc("/ungo",myHandler2 )
    // addr:监听的地址
    // handler:回调函数
    http.ListenAndServe("127.0.0.1:8000", nil)
}

// handler函数
func myHandler(w http.ResponseWriter, r *http.Request) {
    fmt.Println(r.RemoteAddr, "连接成功")
    // 请求方式:GET POST DELETE PUT UPDATE
    fmt.Println("method:", r.Method)
    // /go
    fmt.Println("url:", r.URL.Path)
    fmt.Println("header:", r.Header)
    fmt.Println("body:", r.Body)
    // 回复
    w.Write([]byte("www.5lmh.com"))
}

// HTTP客户端
package main

import (
    "fmt"
    "io"
    "net/http"
)

func main() {
    //resp, _ := http.Get("http://www.baidu.com")
    //fmt.Println(resp)
    resp, _ := http.Get("http://127.0.0.1:8000/go")
    defer resp.Body.Close()
    // 200 OK
    fmt.Println(resp.Status)
    fmt.Println(resp.Header)

    buf := make([]byte, 1024)
    for {
      // 接收服务端信息
      n, err := resp.Body.Read(buf)
      if err != nil && err != io.EOF {
          fmt.Println(err)
          return
      } else {
          fmt.Println("读取完毕")
          res := string(buf[:n])
          fmt.Println(res)
          break
      }
    }
}

webSocket编程

  • WebSocket是一种在单个TCP连接上进行全双工通信的协议
  • WebSocket使得客户端和服务器之间的数据交换变得更加简单,允许服务端主动向客户端推送数据
  • 在WebSocket API中,浏览器和服务器只需要完成一次握手,两者之间就直接可以创建持久性的连接,并- 进行双向数据传输

需要安装第三方包:

  • cmd中:go get -u -v github.com/gorilla/websocket

聊天室实例

在同一级目录下新建四个go文件connection.go|data.go|hub.go|server.go

运行 go run server.go hub.go data.go connection.go 运行之后执行local.html文件

// server.go
package main
import (
  "fmt"
  "net/http"

  "github.com/gorilla/mux"
)
func main() {
  router := mux.NewRouter()
  go h.run()
  router.HandleFunc("/ws", myws)
  if err := http.ListenAndServe("127.0.0.1:8080", router); err != nil {
      fmt.Println("err:", err)
  }
}

// hub.go
package main
import "encoding/json"
var h = hub{
  c: make(map[*connection]bool),
  u: make(chan *connection),
  b: make(chan []byte),
  r: make(chan *connection),
}
type hub struct {
  c map[*connection]bool
  b chan []byte
  r chan *connection
  u chan *connection
}

func (h *hub) run() {
  for {
    select {
    case c := <-h.r:
      h.c[c] = true
      c.data.Ip = c.ws.RemoteAddr().String()
      c.data.Type = "handshake"
      c.data.UserList = user_list
      data_b, _ := json.Marshal(c.data)
      c.sc <- data_b
    case c := <-h.u:
      if _, ok := h.c[c]; ok {
        delete(h.c, c)
        close(c.sc)
      }
    case data := <-h.b:
      for c := range h.c {
        select {
        case c.sc <- data:
        default:
          delete(h.c, c)
          close(c.sc)
        }
      }
    }
  }
}

// data.go
package main
type Data struct {
  Ip       string   `json:"ip"`
  User     string   `json:"user"`
  From     string   `json:"from"`
  Type     string   `json:"type"`
  Content  string   `json:"content"`
  UserList []string `json:"user_list"`
}

// connection.go
package main
import (
  "encoding/json"
  "fmt"
  "net/http"

  "github.com/gorilla/websocket"
)

type connection struct {
  ws   *websocket.Conn
  sc   chan []byte
  data *Data
}

var wu = &websocket.Upgrader{ReadBufferSize: 512,
  WriteBufferSize: 512, CheckOrigin: func(r *http.Request) bool { return true }}

func myws(w http.ResponseWriter, r *http.Request) {
  ws, err := wu.Upgrade(w, r, nil)
  if err != nil {
      return
  }
  c := &connection{sc: make(chan []byte, 256), ws: ws, data: &Data{}}
  h.r <- c
  go c.writer()
  c.reader()
  defer func() {
      c.data.Type = "logout"
      user_list = del(user_list, c.data.User)
      c.data.UserList = user_list
      c.data.Content = c.data.User
      data_b, _ := json.Marshal(c.data)
      h.b <- data_b
      h.r <- c
  }()
}

func (c *connection) writer() {
  for message := range c.sc {
      c.ws.WriteMessage(websocket.TextMessage, message)
  }
  c.ws.Close()
}

var user_list = []string{}

func (c *connection) reader() {
  for {
    _, message, err := c.ws.ReadMessage()
    if err != nil {
        h.r <- c
        break
    }
    json.Unmarshal(message, &c.data)
    switch c.data.Type {
    case "login":
        c.data.User = c.data.Content
        c.data.From = c.data.User
        user_list = append(user_list, c.data.User)
        c.data.UserList = user_list
        data_b, _ := json.Marshal(c.data)
        h.b <- data_b
    case "user":
        c.data.Type = "user"
        data_b, _ := json.Marshal(c.data)
        h.b <- data_b
    case "logout":
        c.data.Type = "logout"
        user_list = del(user_list, c.data.User)
        data_b, _ := json.Marshal(c.data)
        h.b <- data_b
        h.r <- c
    default:
        fmt.Print("========default================")
    }
  }
}

func del(slice []string, user string) []string {
  count := len(slice)
  if count == 0 {
      return slice
  }
  if count == 1 && slice[0] == user {
      return []string{}
  }
  var n_slice = []string{}
  for i := range slice {
      if slice[i] == user && i == count {
          return slice[:count]
      } else if slice[i] == user {
          n_slice = append(slice[:i], slice[i+1:]...)
          break
      }
  }
  fmt.Println(n_slice)
  return n_slice
}

执行文件 local.html

<!DOCTYPE html>
<html>
<head>
    <title></title>
    <meta http-equiv="content-type" content="text/html;charset=utf-8">
    <style>
        p {
            text-align: left;
            padding-left: 20px;
        }
    </style>
</head>
<body>
<div style="width: 800px;height: 600px;margin: 30px auto;text-align: center">
    <h1>www.5lmh.comy演示聊天室</h1>
    <div style="width: 800px;border: 1px solid gray;height: 300px;">
        <div style="width: 200px;height: 300px;float: left;text-align: left;">
            <p><span>当前在线:</span><span id="user_num">0</span></p>
            <div id="user_list" style="overflow: auto;">
            </div>
        </div>
        <div id="msg_list" style="width: 598px;border:  1px solid gray; height: 300px;overflow: scroll;float: left;">
        </div>
    </div>
    <br>
    <textarea id="msg_box" rows="6" cols="50" onkeydown="confirm(event)"></textarea><br>
    <input type="button" value="发送" onclick="send()">
</div>
</body>
</html>
<script type="text/javascript">
    var uname = prompt('请输入用户名', 'user' + uuid(8, 16));
    var ws = new WebSocket("ws://127.0.0.1:8080/ws");
    ws.onopen = function () {
        var data = "系统消息:建立连接成功";
        listMsg(data);
    };
    ws.onmessage = function (e) {
        var msg = JSON.parse(e.data);
        var sender, user_name, name_list, change_type;
        switch (msg.type) {
            case 'system':
                sender = '系统消息: ';
                break;
            case 'user':
                sender = msg.from + ': ';
                break;
            case 'handshake':
                var user_info = {'type': 'login', 'content': uname};
                sendMsg(user_info);
                return;
            case 'login':
            case 'logout':
                user_name = msg.content;
                name_list = msg.user_list;
                change_type = msg.type;
                dealUser(user_name, change_type, name_list);
                return;
        }
        var data = sender + msg.content;
        listMsg(data);
    };
    ws.onerror = function () {
        var data = "系统消息 : 出错了,请退出重试.";
        listMsg(data);
    };
    function confirm(event) {
        var key_num = event.keyCode;
        if (13 == key_num) {
            send();
        } else {
            return false;
        }
    }
    function send() {
        var msg_box = document.getElementById("msg_box");
        var content = msg_box.value;
        var reg = new RegExp("\r\n", "g");
        content = content.replace(reg, "");
        var msg = {'content': content.trim(), 'type': 'user'};
        sendMsg(msg);
        msg_box.value = '';
    }
    function listMsg(data) {
        var msg_list = document.getElementById("msg_list");
        var msg = document.createElement("p");
        msg.innerHTML = data;
        msg_list.appendChild(msg);
        msg_list.scrollTop = msg_list.scrollHeight;
    }
    function dealUser(user_name, type, name_list) {
        var user_list = document.getElementById("user_list");
        var user_num = document.getElementById("user_num");
        while(user_list.hasChildNodes()) {
            user_list.removeChild(user_list.firstChild);
        }
        for (var index in name_list) {
            var user = document.createElement("p");
            user.innerHTML = name_list[index];
            user_list.appendChild(user);
        }
        user_num.innerHTML = name_list.length;
        user_list.scrollTop = user_list.scrollHeight;
        var change = type == 'login' ? '上线' : '下线';
        var data = '系统消息: ' + user_name + ' 已' + change;
        listMsg(data);
    }
    function sendMsg(msg) {
        var data = JSON.stringify(msg);
        ws.send(data);
    }
    function uuid(len, radix) {
        var chars = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'.split('');
        var uuid = [], i;
        radix = radix || chars.length;
        if (len) {
            for (i = 0; i < len; i++) uuid[i] = chars[0 | Math.random() * radix];
        } else {
            var r;
            uuid[8] = uuid[13] = uuid[18] = uuid[23] = '-';
            uuid[14] = '4';
            for (i = 0; i < 36; i++) {
                if (!uuid[i]) {
                    r = 0 | Math.random() * 16;
                    uuid[i] = chars[(i == 19) ? (r & 0x3) | 0x8 : r];
                }
            }
        }
        return uuid.join('');
    }
</script>

Go实现web服务器

创建第一个Web服务器

Hello World

package main

import (
    "fmt"
    "net/http"
)

func helloWorld(w http.ResponseWriter, r *http.Request) {
    fmt.Fprintf(w, "Hello, World!")
}

func main() {
    http.HandleFunc("/", helloWorld)
    http.ListenAndServe(":8080", nil)
}

运行该程序后,在浏览器访问http://localhost:8080即可看到“Hello, World!”。

处理静态文件

package main

import (
    "log"
    "net/http"
)

func main() {
    http.Handle("/", http.FileServer(http.Dir("./static")))
    log.Println("Listening on :8080")
    err := http.ListenAndServe(":8080", nil)
    if err != nil {
        log.Fatal(err)
    }
}

将静态文件放在./static目录下,服务器会自动处理这些请求。

路由与中间件

路由设计 使用第三方库如gorilla/mux来简化路由管理。

package main

import (
    "net/http"

    "github.com/gorilla/mux"
)

func homePage(w http.ResponseWriter, r *http.Request) {
    fmt.Fprint(w, "Welcome to the HomePage!")
}

func aboutPage(w http.ResponseWriter, r *http.Request) {
    fmt.Fprint(w, "About Page")
}

func main() {
    r := mux.NewRouter()

    r.HandleFunc("/", homePage)
    r.HandleFunc("/about", aboutPage)

    log.Fatal(http.ListenAndServe(":8080", r))
}

中间件 中间件用于在处理请求前或后执行某些操作。

func loggingMiddleware(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        log.Printf("%s - %s\n", r.Method, r.URL)
        next.ServeHTTP(w, r)
    })
}

模板渲染

HTML模板 使用html/template包来渲染HTML页面。

package main

import (
    "html/template"
    "net/http"
)

type Page struct {
    Title string
    Body  []byte
}

func (p *Page) save() error {
    // Save page content to file.
    return nil
}

func homePage(w http.ResponseWriter, r *http.Request) {
    tmpl := template.Must(template.ParseFiles("home.html"))
    tmpl.Execute(w, nil)
}

func main() {
    http.HandleFunc("/", homePage)
    http.ListenAndServe(":8080", nil)
}

创建home.html文件作为模板。

数据库交互

连接数据库 使用database/sql和相应的数据库驱动。

db, err := sql.Open("mysql", "user:password@tcp(127.0.0.1:3306)/dbname")
if err != nil {
    log.Fatal(err)
}
defer db.Close()

更复杂的路由与参数处理

复杂路由 使用gorilla/mux来处理带有参数的路由。

package main

import (
    "fmt"
    "log"
    "net/http"

    "github.com/gorilla/mux"
)

func homePage(w http.ResponseWriter, r *http.Request) {
    fmt.Fprint(w, "Welcome to the HomePage!")
}

func aboutPage(w http.ResponseWriter, r *http.Request) {
    fmt.Fprint(w, "About Page")
}

func userPage(w http.ResponseWriter, r *http.Request) {
    vars := mux.Vars(r)
    userId := vars["id"]
    fmt.Fprintf(w, "User ID: %s", userId)
}

func main() {
    r := mux.NewRouter()

    r.HandleFunc("/", homePage)
    r.HandleFunc("/about", aboutPage)
    r.HandleFunc("/user/{id}", userPage)

    log.Fatal(http.ListenAndServe(":8080", r))
}

访问http://localhost:8080/user/123会输出“User ID: 123”。

表单处理与POST请求

package main

import (
    "fmt"
    "log"
    "net/http"

    "github.com/gorilla/mux"
)

func homePage(w http.ResponseWriter, r *http.Request) {
    fmt.Fprint(w, "Welcome to the HomePage!")
}

func submitForm(w http.ResponseWriter, r *http.Request) {
    err := r.ParseForm()
    if err != nil {
        http.Error(w, err.Error(), http.StatusBadRequest)
        return
    }

    name := r.FormValue("name")
    email := r.FormValue("email")

    fmt.Fprintf(w, "Name: %s, Email: %s", name, email)
}

func main() {
    r := mux.NewRouter()

    r.HandleFunc("/", homePage)
    r.HandleFunc("/submit", submitForm).Methods("POST")

    log.Fatal(http.ListenAndServe(":8080", r))
}

创建一个简单的HTML表单:

<form action="/submit" method="post">
    <label>Name:</label>
    <input type="text" name="name"><br>
    <label>Email:</label>
    <input type="text" name="email"><br>
    <button type="submit">Submit</button>
</form>

提交表单后,可以看到输出的信息。

连接MySQL数据库

package main

import (
    "database/sql"
    "fmt"
    "log"
    "net/http"

    _ "github.com/go-sql-driver/mysql"
    "github.com/gorilla/mux"
)

type User struct {
    ID   int    `json:"id"`
    Name string `json:"name"`
}

func homePage(w http.ResponseWriter, r *http.Request) {
    db, err := sql.Open("mysql", "user:password@tcp(127.0.0.1:3306)/dbname")
    if err != nil {
        log.Fatal(err)
    }
    defer db.Close()

    var users []User
    rows, err := db.Query("SELECT id, name FROM users")
    if err != nil {
        http.Error(w, err.Error(), http.StatusInternalServerError)
        return
    }
    defer rows.Close()

    for rows.Next() {
        var user User
        if err := rows.Scan(&user.ID, &user.Name); err != nil {
            http.Error(w, err.Error(), http.StatusInternalServerError)
            return
        }
        users = append(users, user)
    }

    fmt.Println(users)
}

func main() {
    r := mux.NewRouter()

    r.HandleFunc("/", homePage)

    log.Fatal(http.ListenAndServe(":8080", r))
}

数据库查询结果会被打印出来。

Go实现web客户端

环境搭建与基础知识

安装Go

  • 访问Go官网下载对应操作系统的安装包。
  • 安装完成后,配置环境变量。

基本概念

  • Go标准库中的net/http包提供了HTTP客户端的功能。
  • http.Client用于发送HTTP请求。
  • http.Request表示一个HTTP请求。
  • http.Response表示一个HTTP响应。

创建第一个Web客户端

GET请求

package main

import (
    "fmt"
    "log"
    "net/http"
)

func main() {
    resp, err := http.Get("https://www.example.com")
    if err != nil {
        log.Fatal(err)
    }
    defer resp.Body.Close()

    fmt.Println("Response Status:", resp.Status)
    fmt.Println("Response Headers:", resp.Header)
}

运行程序后,会打印出响应的状态码和头部信息。

处理响应体

读取响应体

package main

import (
    "bufio"
    "fmt"
    "log"
    "net/http"
)

func main() {
    resp, err := http.Get("https://www.example.com")
    if err != nil {
        log.Fatal(err)
    }
    defer resp.Body.Close()

    scanner := bufio.NewScanner(resp.Body)
    for scanner.Scan() {
        fmt.Println(scanner.Text())
    }

    if err := scanner.Err(); err != nil {
        log.Fatal(err)
    }
}

逐行读取并打印响应体内容。

发送POST请求

package main

import (
    "bytes"
    "fmt"
    "log"
    "net/http"
)

func main() {
    data := url.Values{}
    data.Set("name", "Alice")
    data.Set("age", "30")

    resp, err := http.PostForm("https://www.example.com/submit", data)
    if err != nil {
        log.Fatal(err)
    }
    defer resp.Body.Close()

    fmt.Println("Response Status:", resp.Status)
}

发送带有表单数据的POST请求。

自定义请求头

设置请求头

package main

import (
    "fmt"
    "log"
    "net/http"
)

func main() {
    req, err := http.NewRequest("GET", "https://www.example.com", nil)
    if err != nil {
        log.Fatal(err)
    }

    req.Header.Set("User-Agent", "MyCustomClient")
    req.Header.Set("X-Custom-Header", "CustomHeaderValue")

    client := &http.Client{}
    resp, err := client.Do(req)
    if err != nil {
        log.Fatal(err)
    }
    defer resp.Body.Close()

    fmt.Println("Response Status:", resp.Status)
}

自定义请求头。

处理JSON请求与响应

发送JSON请求

package main

import (
    "encoding/json"
    "fmt"
    "log"
    "net/http"
)

type User struct {
    Name string `json:"name"`
    Age  int    `json:"age"`
}

func main() {
    user := User{Name: "Alice", Age: 30}

    jsonData, err := json.Marshal(user)
    if err != nil {
        log.Fatal(err)
    }

    req, err := http.NewRequest("POST", "https://www.example.com/users", bytes.NewBuffer(jsonData))
    if err != nil {
        log.Fatal(err)
    }

    req.Header.Set("Content-Type", "application/json")

    client := &http.Client{}
    resp, err := client.Do(req)
    if err != nil {
        log.Fatal(err)
    }
    defer resp.Body.Close()

    fmt.Println("Response Status:", resp.Status)
}

发送JSON格式的数据。

解析JSON响应

package main

import (
    "encoding/json"
    "fmt"
    "log"
    "net/http"
)

type User struct {
    ID   int    `json:"id"`
    Name string `json:"name"`
    Age  int    `json:"age"`
}

func main() {
    resp, err := http.Get("https://www.example.com/users/1")
    if err != nil {
        log.Fatal(err)
    }
    defer resp.Body.Close()

    var user User
    err = json.NewDecoder(resp.Body).Decode(&user)
    if err != nil {
        log.Fatal(err)
    }

    fmt.Println("User:", user)
}

解析JSON格式的响应数据。

使用Cookie

发送带Cookie的请求

package main

import (
    "fmt"
    "log"
    "net/http"
)

func main() {
    cookie := &http.Cookie{
        Name:  "session",
        Value: "abc123",
    }

    req, err := http.NewRequest("GET", "https://www.example.com", nil)
    if err != nil {
        log.Fatal(err)
    }

    req.AddCookie(cookie)

    client := &http.Client{}
    resp, err := client.Do(req)
    if err != nil {
        log.Fatal(err)
    }
    defer resp.Body.Close()

    fmt.Println("Response Status:", resp.Status)
}

发送带有Cookie的请求。

处理响应中的Cookie

package main

import (
    "fmt"
    "log"
    "net/http"
)

func main() {
    resp, err := http.Get("https://www.example.com")
    if err != nil {
        log.Fatal(err)
    }
    defer resp.Body.Close()

    for _, cookie := range resp.Cookies() {
        fmt.Println("Cookie:", cookie.Name, cookie.Value)
    }
}

处理响应中的Cookie。

使用代理

通过代理发送请求

package main

import (
    "fmt"
    "log"
    "net/http"
)

func main() {
    proxyURL, _ := url.Parse("http://proxy.example.com:8080")

    transport := &http.Transport{
        Proxy: http.ProxyURL(proxyURL),
    }

    client := &http.Client{Transport: transport}
    resp, err := client.Get("https://www.example.com")
    if err != nil {
        log.Fatal(err)
    }
    defer resp.Body.Close()

    fmt.Println("Response Status:", resp.Status)
}

通过代理服务器发送请求。

超时与重试

设置超时

package main

import (
    "context"
    "fmt"
    "log"
    "net/http"
    "time"
)

func main() {
    ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
    defer cancel()

    client := &http.Client{}
    resp, err := client.Do(&http.Request{
        URL:    url.URL{Scheme: "https", Host: "www.example.com"},
        Method: "GET",
        Context: ctx,
    })
    if err != nil {
        log.Fatal(err)
    }
    defer resp.Body.Close()

    fmt.Println("Response Status:", resp.Status)
}

设置请求超时时间为5秒。

重试机制

package main

import (
    "context"
    "fmt"
    "log"
    "net/http"
    "time"
)

func retryRequest(url string, maxRetries int) (*http.Response, error) {
    for i := 0; i <= maxRetries; i++ {
        ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
        defer cancel()

        client := &http.Client{}
        resp, err := client.Do(&http.Request{
            URL:    url.URL{Scheme: "https", Host: "www.example.com"},
            Method: "GET",
            Context: ctx,
        })
        if err == nil {
            return resp, nil
        }

        time.Sleep(time.Duration(i+1) * time.Second)
    }

    return nil, fmt.Errorf("max retries exceeded")
}

func main() {
    resp, err := retryRequest("https://www.example.com", 3)
    if err != nil {
        log.Fatal(err)
    }
    defer resp.Body.Close()

    fmt.Println("Response Status:", resp.Status)
}

实现简单的重试机制。

并发请求

package main

import (
    "fmt"
    "log"
    "net/http"
    "sync"
)

func fetch(url string, ch chan<- string) {
    resp, err := http.Get(url)
    if err != nil {
        ch <- fmt.Sprintf("Error fetching %s: %v", url, err)
        return
    }
    defer resp.Body.Close()

    ch <- fmt.Sprintf("Fetched %s: %s", url, resp.Status)
}

func main() {
    urls := []string{
        "https://www.example.com",
        "https://www.google.com",
        "https://www.github.com",
    }

    var wg sync.WaitGroup
    results := make(chan string, len(urls))

    for _, url := range urls {
        wg.Add(1)
        go func(url string) {
            defer wg.Done()
            fetch(url, results)
        }(url)
    }

    go func() {
        wg.Wait()
        close(results)
    }()

    for result := range results {
        fmt.Println(result)
    }
}

并发地从多个URL获取数据,并收集所有结果。

Go实现DNS查询

基础DNS查询

首先,我们可以通过net.LookupHost来执行一个基础的DNS查询,这将返回一个IP地址列表。

package main

import (
    "fmt"
    "log"
    "net"
)

func main() {
    // 查询 example.com 的 A 记录
    ips, err := net.LookupHost("example.com")
    if err != nil {
        log.Fatal(err)
    }
    fmt.Println("IP Addresses:", ips)
}

更详细的DNS查询

如果你需要更详细的控制DNS查询,比如查询特定类型的DNS记录(如MX记录或TXT记录),或者指定DNS服务器,你可以使用net.Dial来连接到DNS服务器,并手动构建DNS请求。

下面是一个查询MX记录的例子:

package main

import (
    "fmt"
    "log"
    "net"
    "time"

    "github.com/miekg/dns"
)

func main() {
    // 创建一个新的DNS客户端和请求
    client := new(dns.Client)
    msg := new(dns.Msg)
    msg.SetQuestion("example.com.", dns.TypeMX)

    // 设置超时时间
    client.Timeout = 3 * time.Second

    // 指定DNS服务器地址
    response, _, err := client.Exchange(msg, "8.8.8.8:53")
    if err != nil {
        log.Fatal(err)
    }

    // 解析响应
    for _, ans := range response.Answer {
        mx, ok := ans.(*dns.MX)
        if !ok {
            continue
        }
        fmt.Printf("MX Record: %s %d\n", mx.Host, mx.Preference)
    }
}

在这个例子中,我们使用了miekg/dns库,这是一个非常强大且流行的DNS库,它提供了更多高级功能和支持多种DNS记录类型的能力。

异步DNS查询

虽然net包提供的DNS查询是同步的,但你可以通过使用Go的并发特性来实现异步查询。例如,可以启动多个goroutine来同时查询不同的DNS服务器。

package main

import (
    "fmt"
    "log"
    "net"
    "sync"
)

func queryDNS(server string, ch chan<- string) {
    ips, err := net.LookupHost("example.com", server)
    if err != nil {
        ch <- fmt.Sprintf("Error querying %s: %v", server, err)
        return
    }
    ch <- fmt.Sprintf("IP Addresses from %s: %v", server, ips)
}

func main() {
    servers := []string{"8.8.8.8:53", "8.8.4.4:53"}
    results := make(chan string, len(servers))
    var wg sync.WaitGroup

    for _, server := range servers {
        wg.Add(1)
        go func(server string) {
            defer wg.Done()
            queryDNS(server, results)
        }(server)
    }

    go func() {
        wg.Wait()
        close(results)
    }()

    for result := range results {
        fmt.Println(result)
    }
}

在这个示例中,我们为每个DNS服务器启动了一个goroutine,并通过通道接收查询结果。

点赞 0
收藏 0
分享
本文参与登链社区写作激励计划 ,好文好收益,欢迎正在阅读的你也加入。

0 条评论

请先 登录 后评论
天涯学馆
天涯学馆
0x9d6d...50d5
资深大厂程序员,12年开发经验,致力于探索前沿技术!