zoukankan      html  css  js  c++  java
  • 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
    }
    
  • 相关阅读:
    改动文件名称
    十五周 项目1 工资数据的输入
    通过YAJL获取json中的值
    图数据库之Pregel
    hdu 1028 Ignatius and the Princess III
    iOS使用ffmpeg播放rstp实时监控视频数据流
    Android的Bitmap和BitmapDrawable类解析-android学习之旅(六十)
    MAC中在eclipse luna上搭建移动平台自己主动化測试框架(UIAutomator/Appium/Robotium/MonkeyRunner)关键点记录
    QCustomPlot使用手冊(三)
    Mac下搭建react native开发环境
  • 原文地址:https://www.cnblogs.com/Yunya-Cnblogs/p/13815864.html
Copyright © 2011-2022 走看看