在golang中,網(wǎng)絡(luò)協(xié)議已經(jīng)被封裝的非常完好了,想要寫一個(gè)Socket的Server,我們并不用像其他語言那樣需要為socket、bind、listen、receive等一系列操作頭疼,只要使用Golang中自帶的net包即可很方便的完成連接等操作~
在這里,給出一個(gè)最最基礎(chǔ)的基于Socket的Server的寫法:
package main
import (
"fmt"
"net"
"log"
"os"
)
func main() {
//建立socket,監(jiān)聽端口
netListen, err := net.Listen("tcp", "localhost:1024")
CheckError(err)
defer netListen.Close()
Log("Waiting for clients")
for {
conn, err := netListen.Accept()
if err != nil {
continue
}
Log(conn.RemoteAddr().String(), " tcp connect success")
handleConnection(conn)
}
}
//處理連接
func handleConnection(conn net.Conn) {
buffer := make([]byte, 2048)
for {
n, err := conn.Read(buffer)
if err != nil {
Log(conn.RemoteAddr().String(), " connection error: ", err)
return
}
Log(conn.RemoteAddr().String(), "receive data string:/n", string(buffer[:n]))
}
}
func Log(v ...interface{}) {
log.Println(v...)
}
func CheckError(err error) {
if err != nil {
fmt.Fprintf(os.Stderr, "Fatal error: %s", err.Error())
os.Exit(1)
}
}
唔,拋除Go語言里面10行代碼有5行error的蛋疼之處,你可以看到,Server想要建立并接受一個(gè)Socket,其核心流程就是
netListen, err := net.Listen("tcp", "localhost:1024")
conn, err := netListen.Accept()
n, err := conn.Read(buffer)
這三步,通過Listen、Accept 和Read,我們就成功的綁定了一個(gè)端口,并能夠讀取從該端口傳來的內(nèi)容~
Server寫好之后,接下來就是Client方面啦,我手寫一個(gè)HelloWorld給大家:
package main
import (
"fmt"
"net"
"os"
)
func sender(conn net.Conn) {
words := "hello world!"
conn.Write([]byte(words))
fmt.Println("send over")
}
func main() {
server := "127.0.0.1:1024"
tcpAddr, err := net.ResolveTCPAddr("tcp4", server)
if err != nil {
fmt.Fprintf(os.Stderr, "Fatal error: %s", err.Error())
os.Exit(1)
}
conn, err := net.DialTCP("tcp", nil, tcpAddr)
if err != nil {
fmt.Fprintf(os.Stderr, "Fatal error: %s", err.Error())
os.Exit(1)
}
fmt.Println("connect success")
sender(conn)
}
可以看到,Client這里的關(guān)鍵在于
tcpAddr, err := net.ResolveTCPAddr("tcp4", server)
conn, err := net.DialTCP("tcp", nil, tcpAddr)
這兩步,主要是負(fù)責(zé)解析端口和連接~
寫好Server和Client之后,讓我們運(yùn)行一下看看:~~
成功運(yùn)行,Console出現(xiàn)Server等待連接的提示:
Server端成功的收到了我們的Hello-World啦,至于后面的那行紅字,則是斷開連接的提示~
到這里,一個(gè)最基礎(chǔ)的使用Socket的Server-Client框架就出來啦~
如果想要讓Server能夠響應(yīng)來自不同Client的請求,我們只要在Server端的代碼的main入口中,
在 handleConnection(conn net.Conn) 這句代碼的前面加上一個(gè) go,就可以讓服務(wù)器并發(fā)處理不同的Client發(fā)來的請求啦
自定義通訊協(xié)議
在上面我們做出來一個(gè)最基礎(chǔ)的demo后,已經(jīng)可以初步實(shí)現(xiàn)Server和Client之間的信息交流了~ 這一章我會(huì)介紹一下怎么在Server和Client之間實(shí)現(xiàn)一個(gè)簡單的通訊協(xié)議,從而增強(qiáng)整個(gè)信息交流過程的穩(wěn)定性。
在Server和client的交互過程中,有時(shí)候很難避免出現(xiàn)網(wǎng)絡(luò)波動(dòng),而在通訊質(zhì)量較差的時(shí)候,Client有可能無法將信息流一次性完整發(fā)送,最終傳到Server上的信息很可能變?yōu)楹芏喽巍?br /> 如下圖所示,本來應(yīng)該是分條傳輸?shù)膉son,結(jié)果因?yàn)橐恍┰蜻B接在了一起,這時(shí)候就會(huì)出現(xiàn)問題啦,Server端要怎么判斷收到的消息是否完整呢?~
唔,答案就是這篇文章的主題啦:在Server和Client交互的時(shí)候,加入一個(gè)通訊協(xié)議(protocol),讓二者的交互通過這個(gè)協(xié)議進(jìn)行封裝,從而使Server能夠判斷收到的信息是否為完整的一段。(也就是解決分包的問題)
因?yàn)橹饕康氖菫榱俗孲erver能判斷客戶端發(fā)來的信息是否完整,因此整個(gè)協(xié)議的核心思路并不是很復(fù)雜:
協(xié)議的核心就是設(shè)計(jì)一個(gè)頭部(headers),在Client每次發(fā)送信息的時(shí)候?qū)eader封裝進(jìn)去,再讓Server在每次收到信息的時(shí)候按照預(yù)定格式將消息進(jìn)行解析,這樣根據(jù)Client傳來的數(shù)據(jù)中是否包含headers,就可以很輕松的判斷收到的信息是否完整了~
如果信息完整,那么就將該信息發(fā)送給下一個(gè)邏輯進(jìn)行處理,如果信息不完整(缺少headers),那么Server就會(huì)把這條信息與前一條信息合并繼續(xù)處理。
下面是協(xié)議部分的代碼,主要分為數(shù)據(jù)的封裝(Enpack)和解析(Depack)兩個(gè)部分,其中Enpack用于Client端將傳給服務(wù)器的數(shù)據(jù)封裝,而Depack是Server用來解析數(shù)據(jù),其中Const部分用于定義Headers,HeaderLength則是Headers的長度,用于后面Server端的解析。這里要說一下ConstMLength,這里代表Client傳入信息的長度,因?yàn)樵趃olang中,int轉(zhuǎn)為byte后會(huì)占4長度的空間,因此設(shè)定為4。每次Client向Server發(fā)送信息的時(shí)候,除了將Headers封裝進(jìn)去意以外,還會(huì)將傳入信息的長度也封裝進(jìn)去,這樣可以方便Server進(jìn)行解析和校驗(yàn)。
//通訊協(xié)議處理
package protocol
import (
"bytes"
"encoding/binary"
)
const (
ConstHeader = "Headers"
ConstHeaderLength = 7
ConstMLength = 4
)
//封包
func Enpack(message []byte) []byte {
return append(append([]byte(ConstHeader), IntToBytes(len(message))...), message...)
}
//解包
func Depack(buffer []byte, readerChannel chan []byte) []byte {
length := len(buffer)
var i int
for i = 0; i < length; i = i + 1 {
if length < i+ConstHeaderLength+ConstMLength {
break
}
if string(buffer[i:i+ConstHeaderLength]) == ConstHeader {
messageLength := BytesToInt(buffer[i+ConstHeaderLength : i+ConstHeaderLength+ConstMLength])
if length < i+ConstHeaderLength+ConstLength+messageLength {
break
}
data := buffer[i+ConstHeaderLength+ConstMLength : i+ConstHeaderLength+ConstMLength+messageLength]
readerChannel <- data
}
}
if i == length {
return make([]byte, 0)
}
return buffer[i:]
}
//整形轉(zhuǎn)換成字節(jié)
func IntToBytes(n int) []byte {
x := int32(n)
bytesBuffer := bytes.NewBuffer([]byte{})
binary.Write(bytesBuffer, binary.BigEndian, x)
return bytesBuffer.Bytes()
}
//字節(jié)轉(zhuǎn)換成整形
func BytesToInt(b []byte) int {
bytesBuffer := bytes.NewBuffer(b)
var x int32
binary.Read(bytesBuffer, binary.BigEndian, &x)
return int(x)
}
協(xié)議寫好之后,接下來就是在Server和Client的代碼中應(yīng)用協(xié)議啦,下面是Server端的代碼,主要負(fù)責(zé)解析Client通過協(xié)議發(fā)來的信息流:
package main
import (
"protocol"
"fmt"
"net"
"os"
)
func main() {
netListen, err := net.Listen("tcp", "localhost:6060")
CheckError(err)
defer netListen.Close()
Log("Waiting for clients")
for {
conn, err := netListen.Accept()
if err != nil {
continue
}
//timeouSec :=10
//conn.
Log(conn.RemoteAddr().String(), " tcp connect success")
go handleConnection(conn)
}
}
func handleConnection(conn net.Conn) {
// 緩沖區(qū),存儲(chǔ)被截?cái)嗟臄?shù)據(jù)
tmpBuffer := make([]byte, 0)
//接收解包
readerChannel := make(chan []byte, 16)
go reader(readerChannel)
buffer := make([]byte, 1024)
for {
n, err := conn.Read(buffer)
if err != nil {
Log(conn.RemoteAddr().String(), " connection error: ", err)
return
}
tmpBuffer = protocol.Depack(append(tmpBuffer, buffer[:n]...), readerChannel)
}
defer conn.Close()
}
func reader(readerChannel chan []byte) {
for {
select {
case data := <-readerChannel:
Log(string(data))
}
}
}
func Log(v ...interface{}) {
fmt.Println(v...)
}
func CheckError(err error) {
if err != nil {
fmt.Fprintf(os.Stderr, "Fatal error: %s", err.Error())
os.Exit(1)
}
}
然后是Client端的代碼,這個(gè)簡單多了,只要給信息封裝一下就可以了~:
package main
import (
"protocol"
"fmt"
"net"
"os"
"time"
"strconv"
)
func send(conn net.Conn) {
for i := 0; i < 100; i++ {
session:=GetSession()
words := "{/"ID/":"+ strconv.Itoa(i) +"/",/"Session/":"+session +"2015073109532345/",/"Meta/":/"golang/",/"Content/":/"message/"}"
conn.Write(protocol.Enpacket([]byte(words)))
}
fmt.Println("send over")
defer conn.Close()
}
func GetSession() string{
gs1:=time.Now().Unix()
gs2:=strconv.FormatInt(gs1,10)
return gs2
}
func main() {
server := "localhost:6060"
tcpAddr, err := net.ResolveTCPAddr("tcp4", server)
if err != nil {
fmt.Fprintf(os.Stderr, "Fatal error: %s", err.Error())
os.Exit(1)
}
conn, err := net.DialTCP("tcp", nil, tcpAddr)
if err != nil {
fmt.Fprintf(os.Stderr, "Fatal error: %s", err.Error())
os.Exit(1)
}
fmt.Println("connect success")
send(conn)
}
這樣我們就成功實(shí)現(xiàn)在Server和Client之間建立一套自定義的基礎(chǔ)通訊協(xié)議啦,讓我們運(yùn)行一下看下效果:
成功識別每一條Client發(fā)來的信息啦~~
























