先上代碼
server.go
package main
import (
"wingbow.com.cn/server/controller"
)
func main() {
controller.ServerRun()
}
controller.go
package controller
import (
"fmt"
"io"
"net"
"wingbow.com.cn/server/network"
)
//ServerRun 服務(wù)
func ServerRun() {
lister, err := net.Listen("tcp", "localhost:8888")
fmt.Println("服務(wù)啟動(dòng)成功:localhost:8888")
CheckErr(err)
defer lister.Close()
for {
conn, err := lister.Accept()
CheckErr(err)
fmt.Println("用戶接入")
client := network.NewTCPSocket(conn)
go func() {
defer client.Close()
for {
data, err := client.Read()
if err == io.EOF {
fmt.Println("斷開鏈接")
return
}
if err != nil {
continue
}
switchController(data, client)
}
}()
}
}
//CheckErr 異常檢查
func CheckErr(err error) {
if err != nil {
panic(err)
}
}
func switchController(data []byte, c *network.TCPSocket) {
fmt.Println("讀到的數(shù)據(jù): " + string(data))
switch string(data) {
case "ping":
c.Write([]byte("pong"))
fmt.Println("發(fā)出的數(shù)據(jù): pong")
break
}
}
network.go
package network
import (
"bufio"
"bytes"
"encoding/binary"
"net"
)
//TCPSocket 連接
type TCPSocket struct {
tag string
conn net.Conn
r *bufio.Reader
}
//NewTCPSocket 創(chuàng)建一個(gè)TCP客戶端
func NewTCPSocket(conn net.Conn) *TCPSocket {
return &TCPSocket{conn: conn, r: bufio.NewReader(conn)}
}
//LocalAddr 本地地址
func (c *TCPSocket) LocalAddr() net.Addr {
return c.conn.LocalAddr()
}
//RemoteAddr 遠(yuǎn)程地址
func (c *TCPSocket) RemoteAddr() net.Addr {
return c.conn.RemoteAddr()
}
//Close 關(guān)閉
func (c *TCPSocket) Close() error {
return c.conn.Close()
}
//Write 寫消息
func (c *TCPSocket) Write(message []byte) (int, error) {
// 讀取消息的長(zhǎng)度
var length = int32(len(message))
var pkg = new(bytes.Buffer)
//寫入消息頭
err := binary.Write(pkg, binary.BigEndian, length)
if err != nil {
return 0, err
}
//寫入消息體
err = binary.Write(pkg, binary.BigEndian, message)
if err != nil {
return 0, err
}
nn, err := c.conn.Write(pkg.Bytes())
if err != nil {
return 0, err
}
return nn, nil
}
//Read 讀消息
func (c *TCPSocket) Read() ([]byte, error) {
// Peek 返回緩存的一個(gè)切片,該切片引用緩存中前 n 個(gè)字節(jié)的數(shù)據(jù)嘉蕾,
// 該操作不會(huì)將數(shù)據(jù)讀出双戳,只是引用你踩,引用的數(shù)據(jù)在下一次讀取操作之
// 前是有效的。如果切片長(zhǎng)度小于 n,則返回一個(gè)錯(cuò)誤信息說明原因汁政。
// 如果 n 大于緩存的總大小,則返回 ErrBufferFull缀旁。
lengthByte, err := c.r.Peek(4)
if err != nil {
return nil, err
}
//創(chuàng)建 Buffer緩沖器
lengthBuff := bytes.NewBuffer(lengthByte)
var length int32
// 通過Read接口可以將buf中得內(nèi)容填充到data參數(shù)表示的數(shù)據(jù)結(jié)構(gòu)中
err = binary.Read(lengthBuff, binary.BigEndian, &length)
if err != nil {
return nil, err
}
// Buffered 返回緩存中未讀取的數(shù)據(jù)的長(zhǎng)度
if int32(c.r.Buffered()) < length+4 {
return nil, err
}
// 讀取消息真正的內(nèi)容
pack := make([]byte, int(4+length))
// Read 從 b 中讀出數(shù)據(jù)到 p 中记劈,返回讀出的字節(jié)數(shù)和遇到的錯(cuò)誤。
// 如果緩存不為空并巍,則只能讀出緩存中的數(shù)據(jù)目木,不會(huì)從底層 io.Reader
// 中提取數(shù)據(jù),如果緩存為空懊渡,則:
// 1刽射、len(p) >= 緩存大小,則跳過緩存剃执,直接從底層 io.Reader 中讀
// 出到 p 中誓禁。
// 2、len(p) < 緩存大小忠蝗,則先將數(shù)據(jù)從底層 io.Reader 中讀取到緩存
// 中现横,再?gòu)木彺孀x取到 p 中。
_, err = c.r.Read(pack)
if err != nil {
return nil, err
}
return pack[4:], nil
}
客戶端
client.go
package main
import (
"bytes"
"encoding/binary"
"fmt"
"log"
"net"
"time"
)
func main() {
conn, err := net.Dial("tcp", "localhost:8888")
if err != nil {
log.Println("dial error:", err)
return
}
defer conn.Close()
for {
data, _ := Encode(time.Now().Format("2006-01-02 15:04:05"))
time.Sleep(time.Second)
_, err := conn.Write(data)
fmt.Println(err)
}
}
func Encode(message string) ([]byte, error) {
// 讀取消息的長(zhǎng)度
var length = int32(len(message))
var pkg = new(bytes.Buffer)
// 寫入消息頭
err := binary.Write(pkg, binary.BigEndian, length)
if err != nil {
return nil, err
}
// 寫入消息實(shí)體
err = binary.Write(pkg, binary.BigEndian, []byte(message))
if err != nil {
return nil, err
}
return pkg.Bytes(), nil
}
上面處理粘包的方法是用了自定義的非定長(zhǎng)的數(shù)據(jù)包阁最,這也是一種常用的粘包處理方式戒祠。
另外還有自定義分隔符、定長(zhǎng)分割等方法速种。