golang socket实现

一 Golang TCP架构

在这里插入图片描述

type Conn interface {        
        Read(b []byte) (n int, err error)      
        Write(b []byte) (n int, err error)      
        Close() error      
        LocalAddr() Addr       
        RemoteAddr() Addr
        SetDeadline(t time.Time) error     
        SetReadDeadline(t time.Time) error
        SetWriteDeadline(t time.Time) error
}

而Conn底层:

type conn struct {
        fd *netFD
}

// netFD结构体:是网络文件描述符
type netFD struct {
	pfd poll.FD

	// immutable until Close
	family      int
	sotype      int
	isConnected bool // handshake completed or use of association with peer
	net         string
	laddr       Addr
	raddr       Addr
}

1 TCP 方法

Listen()监听
/*
 * 作用:获取监听器
 * 参数:
 * 	* network:以何种协议监听给定的地址,必须是面向流的协议
 *  * address: 比如127.0.0.1:8080
 * 返回值:
 * 	* Listener:表示监听器
 *  * error:错误信息
*/
func Listen(network, address string) (Listener, error)
Accept() 等待客户端连接
// 当调用监听器的Accept()方法时,流程会被阻塞,直到某个客户端程序与应用程序建立TCP连接。
/*
* 返回值: conn,表示当前TCP连接的net.Conn类型值
*/
conn, err := listen.Accept() // 创建用户数据通信的socket
Dial() 拨号连接
/*
 * 作用:向指定的网络地址发送连接建立申请
 * 参数:
 * 	* network:类似第一个API,只是取值更加广泛(因为在发送数据之前不一定要先建立连接)
 *  * address:网络地址,类似 192.168.1.11:8888
 * 返回值:
 * 	* Conn
 *  * err: 
*/
func Dial(network, address string) (Conn, error)

/*
 * 作用:向指定的网络地址发送连接建立申请
 * 参数:
 * 	* network:类似第一个API,只是取值更加广泛(因为在发送数据之前不一定要先建立连接)
 *  * address:网络地址,类似 192.168.1.11:8888
 * 	* timeout超时时间:单位是纳秒
 * 返回值:
 * 	* Conn
 *  * err: 
*/
func DialTimeout(network, address string, timeout time.Duration) (Conn, error)
Read() 读-收
/*
 * * 作用:用于从socket的接收缓冲区中读取数据
 * * 参数:
 * 		* b:用于存放从连接上接收数据的容器,长度由应用程序决定。
 * * 返回值:
 * 		* n:本次操作实际读取到的数据
*/
Read(b []byte) (n int, err error)   
Write() 写-发
/*
 * * 作用:用于从socket的接收缓冲区中写数据
 * * 参数:
 * 		* b:用于存放从连接上接收数据的容器,长度由应用程序决定。
 * * 返回值:
 * 		* n:本次操作实际读取到的数据
*/
Write(b []byte) (n int, err error)   

2 简单实现

server
package main

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

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.Println("read from client failed, err:", err)
			break
		}
		recvStr := string(buf[:n])
		fmt.Println("收到client端发来的数据:", recvStr)
		conn.Write([]byte("message recrived "+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处理连接
	}
}
client
package main

import(
	"fmt"
	"net"
	"bufio"
	"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 {
		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 := [1024]byte{}
		n, err := conn.Read(buf[:])
		if err != nil {
			fmt.Println("recv failed, err:", err)
			return
		}
		fmt.Println(string(buf[:n]))
	}
}
粘包与解决方案

https的length思想

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

我们可以自己定义一个协议,比如数据包的前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
}

二 UDP

1 不需要建立连接
2 弱化消息传输的可靠性

server

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

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

from:liwenzhou

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值