socket
socket 应该是各种语言中网络编程的基础,它介于应用层与传输层之间,只要学会使用它的接口即可。
TCP
以下建立两台机器互相通信。
Server
以下是Go 语言中通过socket 和goroutine 编写的一个非常简单的服务端。
流程如下:
建立与服务端的链接
进行数据收发
关闭链接
// 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) // 三次握手失败
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)
break
}
recvStr := string(buf[:n])
fmt.Println("client message:",recvStr)
var inputMsg string
fmt.Println("请输入你要发送的信息:")
fmt.Scanln(&inputMsg)
inputMsg = strings.Trim(inputMsg,"rn") // 去除空行等,防止阻塞
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,"rn") // 去除空行等,防止阻塞
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),Port: 30000,})
if err != nil {
fmt.Println("listen failed,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)
continue
}
fmt.Printf("data:%v addr:%v count:%vn",string(data[:n]),n)
_,err = listen.WriteToUDP(data[:n],addr) // 发送数据
if err != nil {
fmt.Println("write to udp failed,err)
continue
}
}
}
Client
客户端代码如下:
// UDP 客户端
func main() {
socket,err := net.DialUDP("udp",nil,})
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:%vn",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,[]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,&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
}
(编辑:李大同)
【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容!
|