Go socket编程

socket

   socket应该是各种语言中网络编程的基础,它介于应用层与传输层之间,只要学会使用它的接口即可。

TCP

   以下建立两台机器互相通信。

Server

   以下是Go语言中通过socketgoroutine编写的一个非常简单的服务端。

   流程如下:

   建立与服务端的链接

   进行数据收发

   关闭链接

// D:GoLeransrcyunya.comTCPServer>

package main

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

func main(){
	listen, err := net.Listen("tcp", "127.0.0.1:9999")
	if err != nil {
		fmt.Println("listent failed, err:", err)
		return
	}
	for {
		conn, err := listen.Accept() // 建立链接
		if err != nil {
			fmt.Println("accept failed, err:", err) // 三次握手失败
			continue
		}
		go process(conn) // 启动多个goroutine来处理回复
	}
}

// 处理请求
func process(conn net.Conn) {
	defer conn.Close() // 关闭链接通道
	for {
		reader := bufio.NewReader(conn)
		var buf [1024]byte
		n, err := reader.Read(buf[:]) // 读取数据 读取的字节数,错误信息
		if err != nil {
			fmt.Print("read form client failed, err:", err)
			break
		}
		recvStr := string(buf[:n])
		fmt.Println("client message:", recvStr)
		var inputMsg string
		fmt.Println("请输入你要发送的信息:")
		fmt.Scanln(&inputMsg)
		inputMsg = strings.Trim(inputMsg, "
") // 去除空行等,防止阻塞
		conn.Write([]byte(inputMsg))
	}
}

Client

   以下是客户端的代码。

   建立与服务端的链接

   进行数据收发

   关闭链接

D:GoLeransrcyunya.comTCPClient>

package main

import (
	"fmt"
	"net"
	"strings"
)

func main() {
	conn, err := net.Dial("tcp", "127.0.0.1:9999") // 绑定服务端地址
	if err != nil {
		fmt.Println("err:", err)
		return
	}
	defer conn.Close() // 关闭双向链接
	for {
		var inputMsg string
		fmt.Println("请输入你要发送的信息:")
		fmt.Scanln(&inputMsg)
		inputMsg = strings.Trim(inputMsg, "
") // 去除空行等,防止阻塞
		if strings.ToUpper(inputMsg) == "quit" {
			return
		}
		_, err = conn.Write([]byte(inputMsg)) // 发送数据
		if err != nil {
			return
		}
		buf := [512]byte{}
		serverMsg, err := conn.Read(buf[:]) // 服务端返回的信息
		if err != nil {
			fmt.Println("recv failed err:", err)
			return
		}
		fmt.Println("server message:", string(buf[:serverMsg]))
	}
}

UDP

Server

   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
", string(data[:n]), addr, n)
		_, err = listen.WriteToUDP(data[:n], addr) // 发送数据
		if err != nil {
			fmt.Println("write to udp failed, err:", err)
			continue
		}
	}
}

Client

   客户端代码如下:

// 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
", string(data[:n]), remoteAddr, n)
}

TCP粘包

解决方案

   由于TCP是流式传输协议。所以可能会产生粘包现象,我们需要划分每次数据的大小边界,所以可以自定制一个收发消息的协议。如下:

// 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
}
原文地址:https://www.cnblogs.com/Yunya-Cnblogs/p/13815864.html