golang网络编程
1.TCP编程
TCP服务端程序的处理流程:
1.监听端口
2.接收客户端请求建立链接
3.创建goroutine处理链接。
使用Go语言的net包实现的TCP服务端代码如下:
服务端
package main
import (
"bufio"
"fmt"
"net"
)
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("从客户端读取消息失败..., err", err)
break
} else {
fmt.Println("收到一条数据:")
}
recvStr := string(buf[:n])
fmt.Println(recvStr)
fmt.Println("向客户端发送确认消息!")
echo := "echo: " + recvStr
conn.Write([]byte(echo))
}
}
func main() {
listen, err := net.Listen("tcp", "127.0.0.1:20000")
if err != nil {
fmt.Println("建立连接失败, err", err)
return
}
fmt.Println("准备接收客户端的连接...")
for {
conn, err := listen.Accept()
if err != nil {
fmt.Println("连接失败, err", err)
continue
} else {
fmt.Println("连接成功!")
}
go process(conn)
}
}
客户端
package main
import (
"bufio"
"fmt"
"net"
"os"
"strings"
)
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 {
fmt.Println("请输入待发送的消息:")
input, _ := inputReader.ReadString('\n')
inputInfo := strings.Trim(input, "\r\n")
if strings.ToUpper(inputInfo) == "Q" {
fmt.Println("停止输入,断开连接!")
return
}
fmt.Println("开始发送数据...")
_, err = conn.Write([]byte(inputInfo))
if err != nil {
return
} else {
fmt.Println("发送成功!")
}
buf := [512]byte{}
n, err := conn.Read(buf[:])
if err != nil {
fmt.Println("接收失败, err: ", err)
return
} else {
fmt.Println("收到了服务器的回复:")
}
fmt.Println(string(buf[:n]))
}
}
2.UDP编程
UDP协议(User Datagram Protocol)中文名称是用户数据报协议,是OSI(Open System Interconnection,开放式系统互联)参考模型中一种无连接的传输层协议,不需要建立连接就能直接进行数据发送和接收,属于不可靠的、没有时序的通信,但是UDP协议的实时性比较好,通常用于视频直播相关领域。
使用Go语言的net包实现的UDP服务端代码如下
服务端
package main
import (
"fmt"
"net"
)
func main() {
listen, err := net.ListenUDP("udp", &net.UDPAddr{
IP: net.IPv4(0, 0, 0, 0),
Port: 30000,
})
if err != nil {
fmt.Println("建立监听失败!, err:", err)
return
}
defer listen.Close()
for {
var data [1024]byte
n, addr, err := listen.ReadFromUDP(data[:])
if err != nil {
fmt.Println("接收数据失败!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("发送数据失败!err:", err)
continue
}
}
}
客户端
package main
import (
"fmt"
"net"
)
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\n", string(data[:n]), remoteAddr, n)
}
3. TCP黏包
黏包的场景
以下代码是用golang实现的TCP服务端和客户端,会产生黏包。
服务端:
package main
import (
"bufio"
"fmt"
"io"
"net"
)
func process(conn net.Conn) {
defer conn.Close()
reader := bufio.NewReader(conn)
var buf [1024]byte
for {
n, err := reader.Read(buf[:])
if err == io.EOF {
break
}
if err != nil {
fmt.Println("read from client failed, err:", err)
break
}
recvStr := string(buf[:n])
fmt.Println("收到client发来的数据:", recvStr)
}
}
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)
}
}
客户端:
package main
import (
"fmt"
"net"
)
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?`
conn.Write([]byte(msg))
}
}
输出结果:
收到client发来的数据: Hello, Hello. How are you?Hello, Hello. How are you?Hello, Hello. How are you?Hello, Hello. How are you?Hello, Hello. How are you?Hello, Hello. How are you?Hello, Hello. How are you?Hello, Hello. How are you?Hello, Hello. How are you?Hello, Hello. How are you?Hello, Hello. How are you?Hello, Hello. How are you?Hello, Hello. How are you?Hello, Hello. How are you?Hello, Hello. How are you?Hello, Hello. How are you?Hello, Hello. How are you?Hello, Hello. How are you?Hello, Hello. How are you?Hello, Hello. How are you?
客户端分10次发送的数据,在服务端并没有成功的输出10次,而是多条数据“粘”到了一起。
黏包的原因
主要原因就是tcp数据传递模式是流模式,在保持长连接的时候可以进行多次的收和发。
“粘包”可发生在发送端也可发生在接收端:
1.由Nagle算法造成的发送端的粘包:Nagle算法是一种改善网络传输效率的算法。简单来说就是当我们提交一段数据给TCP发送时,TCP并不立刻发送此段数据,而是等待一小段时间看看在等待期间是否还有要发送的数据,若有则会一次把这两段数据发送出去。 2.接收端接收不及时造成的接收端粘包:TCP会把接收到的数据存在自己的缓冲区中,然后通知应用层取数据。当应用层由于某些原因不能及时的把TCP的数据取出来,就会造成TCP缓冲区中存放了几段数据。
解决办法
出现”粘包”的关键在于接收方不确定将要传输的数据包的大小,因此我们可以对数据包进行封包和拆包的操作。
封包:封包就是给一段数据加上包头,这样一来数据包就分为包头和包体两部分内容了(过滤非法包时封包会加入”包尾”内容)。包头部分的长度是固定的,并且它存储了包体的长度,根据包头长度固定以及包头中含有包体长度的变量就能正确的拆分出一个完整的数据包。
常用的协议是TLV编码: 下面我们将实现简单的LV编码,T就暂时不管。 数据包的前4个字节为包头,里面存储的是发送的数据的长度。
编码解码
package LVEcode
import (
"bufio"
"bytes"
"encoding/binary"
)
func Encode(message string) ([]byte, error) {
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
}
func Decode(reader *bufio.Reader) (string, error) {
lengthByte, _ := reader.Peek(4)
lengthBuff := bytes.NewReader(lengthByte)
var length int32
err := binary.Read(lengthBuff, binary.LittleEndian, &length)
if err != nil {
return "", err
}
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
}
服务端
package main
import (
"NetWork/TCP3/LVEcode"
"bufio"
"fmt"
"io"
"net"
)
func process(conn net.Conn) {
defer conn.Close()
reader := bufio.NewReader(conn)
for {
msg, err := LVEcode.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)
}
}
客户端
package main
import (
"NetWork/TCP3/LVEcode"
"fmt"
"net"
)
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 := LVEcode.Encode(msg)
if err != nil {
fmt.Println("encode msg failed, err:", err)
return
}
conn.Write(data)
}
}
输出:
收到client发来的数据: Hello, Hello. How are you?
收到client发来的数据: Hello, Hello. How are you?
收到client发来的数据: Hello, Hello. How are you?
收到client发来的数据: Hello, Hello. How are you?
收到client发来的数据: Hello, Hello. How are you?
收到client发来的数据: Hello, Hello. How are you?
收到client发来的数据: Hello, Hello. How are you?
收到client发来的数据: Hello, Hello. How are you?
收到client发来的数据: Hello, Hello. How are you?
收到client发来的数据: Hello, Hello. How are you?
收到client发来的数据: Hello, Hello. How are you?
收到client发来的数据: Hello, Hello. How are you?
收到client发来的数据: Hello, Hello. How are you?
收到client发来的数据: Hello, Hello. How are you?
收到client发来的数据: Hello, Hello. How are you?
收到client发来的数据: Hello, Hello. How are you?
收到client发来的数据: Hello, Hello. How are you?
收到client发来的数据: Hello, Hello. How are you?
收到client发来的数据: Hello, Hello. How are you?
收到client发来的数据: Hello, Hello. How are you?
可以发现,现在已经不黏包了。
4. HTTP编程
web工作流程
Web服务器的工作原理可以简单地归纳为:
- 客户机通过TCP/IP协议建立到服务器的TCP连接
- 客户端向服务器发送HTTP协议请求包,请求服务器里的资源文档
- 服务器向客户机发送HTTP协议应答包,如果请求的资源包含有动态语言的内容,那么服务器会调用动态语言的解释引擎负责处理“动态内容”,并将处理得到的数据返回给客户端
- 客户机与服务器断开。由客户端解释HTML文档,在客户端屏幕上渲染图形结果
HTTP协议
- 超文本传输协议(HTTP,HyperText Transfer
Protocol)是互联网上应用最为广泛的一种网络协议,它详细规定了浏览器和万维网服务器之间互相通信的规则,通过因特网传送万维网文档的数据传送协议 - HTTP协议通常承载于TCP协议之上
HTTP服务端
package main
import (
"fmt"
"net/http"
)
func main() {
http.HandleFunc("/go", myHandler)
http.ListenAndServe("127.0.0.1:8000", nil)
}
func myHandler(w http.ResponseWriter, r *http.Request) {
fmt.Println(r.RemoteAddr, "连接成功")
fmt.Println("method:", r.Method)
fmt.Println("url:", r.URL.Path)
fmt.Println("header:", r.Header)
fmt.Println("body:", r.Body)
w.Write([]byte("www.51mh.com"))
}
HTTP客户端
package main
import (
"fmt"
"io"
"net/http"
)
func main() {
resp, _ := http.Get("http://127.0.0.1:8000/go")
defer resp.Body.Close()
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
}
}
}
服务端运行结果:
127.0.0.1:8187 连接成功
method: GET
url: /go
header: map[Accept-Encoding:[gzip] User-Agent:[Go-http-client/1.1]]
body: {}
客户端运行结果:
200 OK
map[Content-Length:[12] Content-Type:[text/plain; charset=utf-8] Date:[Thu, 28 Oct 2021 14:38:58 GMT]]
读取完毕
www.51mh.com
|