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'...

剩余50%的内容订阅专栏后可查看

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

0 条评论

请先 登录 后评论