Golang-网络编程

TCP

服务端

package main

import (
	"bufio"
	"fmt"
	"net"
)

// TCP server端

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:1226")
	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
		}
		// 启动一个goroutine处理连接
		go process(conn)
	}
}

客户端:

package main

import (
	"bufio"
	"fmt"
	"net"
	"os"
	"strings"
)

// TCP client端

func main() {
	conn, err := net.Dial("tcp", "127.0.0.1:1226")
	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" {
			return
		}
		// 把字符串转换成字节流切片[]byte
		// 因为TCP是基于字节流的传输
		_, err = conn.Write([]byte(inputInfo)) // 发送数据
		if err != nil {
			return
		}
		// var buf [512]byte 等价
		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端
package main

import (
	"fmt"
	"net"
)

func main() {
	// 绑定IP地址
	listen, err := net.ListenUDP("udp", &net.UDPAddr{
		IP:   net.IPv4(0, 0, 0, 0),
		Port: 1227,
	})
	if err != nil {
		fmt.Println("listen failed, err:", err)
		return
	}
	defer listen.Close()
	for {
		var data [1024]byte
		// 接收数据,用字节数组接收,转换成string输出
		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
		}
	}
}

客户端

// 客户端
package main

import (
	"fmt"
	"net"
)

func main() {
	// 连接
	socket, err := net.DialUDP("udp", nil, &net.UDPAddr{
		IP:   net.IPv4(0, 0, 0, 0),
		Port: 1227,
	})
	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
	}
	// 构建一个最大容量为4096的切片
	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 粘包

粘包的代码:
服务端

// 服务端
package main

import (
	"bufio"
	"fmt"
	"io"
	"net"
)

func process(conn net.Conn) {
	defer conn.Close()
	// buf在循环外面,共用一个buf
	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:1226")
	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)
		fmt.Println("for")
	}
}

客户端:

// 客户端
package main

import (
	"fmt"
	"net"
)

func main() {
	conn, err := net.Dial("tcp", "127.0.0.1:1226")
	if err != nil {
		fmt.Println("dial failed,err:", err)
		return
	}
	defer conn.Close()
	for i := 0; i < 20; i++ {
		msg := `Hello, how are you?`
		conn.Write([]byte(msg))
	}
}

1.由Nagle算法造成的发送端的粘包:Nagle算法是一种改善网络传输效率的算法。简单来说就是当我们提交一段数据给TCP发送时,TCP并不立刻发送此段数据,而是等待一小段时间看看在等待期间是否还有要发送的数据,若有则会一次把这两段数据发送出去。
2.接收端接收不及时造成的接收端粘包:TCP会把接收到的数据存在自己的缓冲区中,然后通知应用层取数据。当应用层由于某些原因不能及时的把TCP的数据取出来,就会造成TCP缓冲区中存放了几段数据。

协议:

package proto

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

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

	// 写入消息体,要把string转换成字节切片
	err = binary.Write(pkg, binary.LittleEndian, []byte(message))
	if err != nil {
		return nil, err
	}
	// 返回字节切片和err
	return pkg.Bytes(), nil
}

// Decode 解码消息
func Decode(reader *bufio.Reader) (string, error) {
	// 读取消息的长度,但是不从流中取出来
	lengthByte, _ := reader.Peek(4) // 读取前4个字节的数据
	// 创建一个新的缓冲区,内容是lengthByte
	lengthBuff := bytes.NewBuffer(lengthByte)
	var length int32
	// 把byte切片转换为int32这么费劲?
	// binary二进制操作类似内存拷贝,把缓冲区的内容拷贝的length的地址空间去
	err := binary.Read(lengthBuff, binary.LittleEndian, &length)
	if err != nil {
		return "", err
	}

	// Buffered返回缓冲中现有的可读取的字节数 总字节数=length+4
	if int32(reader.Buffered()) < length+4 {
		return "", err
	}

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

	// 最后返回将字节切片转换成字符串
	// 从第四个字节开始取,说明peek确实没有把前四个字节拿走
	return string(pack[4:]), nil
}

服务端:

package main

import (
	"bufio"
	"fmt"
	"go-test/11-tcp/proto"
	"io"
	"net"
)

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:3000")
	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"
	"go-test/11-tcp/proto"
	"net"
)

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

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

服务端:

package main

import (
	"fmt"
	"net/http"
)

func main() {
	// http://127.0.0.1:8000/go
	// 单独写回调函数,有连接过来就调用这个函数?
	http.HandleFunc("/go", myHandler)
	// addr:监听的地址
	// handler:回调函数
	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"))
}

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

橙子砰砰枪

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值