socket 应该是各种语言中网络编程的基础,它介于应用层与传输层之间,只要学会使用它的接口即可。
TCP
以下建立两台机器互相通信。
Server
以下是Go 语言中通过socket 和goroutine 编写的一个非常简单的服务端。
流程如下:
建立与服务端的链接
进行数据收发
关闭链接
// D:\GoLeran\src\yunya.com\TCPServer>
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, "\r\n") // 去除空行等,防止阻塞
conn.Write([]byte(inputMsg))
}
}
Client
以下是客户端的代码。
建立与服务端的链接
进行数据收发
关闭链接
D:\GoLeran\src\yunya.com\TCPClient>
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, "\r\n") // 去除空行等,防止阻塞
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\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)
}
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
}
|
请发表评论