什么是三次握手呢葵硕?這是服務(wù)器和客戶端之間溝通的過程。
首先贯吓,客戶端對服務(wù)器發(fā)送了條信息懈凹。
然后,服務(wù)端對客戶端說悄谐,我收到了介评。
最后,客戶端對服務(wù)端說爬舰,好的们陆,我知道你收到了。
怎么樣情屹?這就是三次握手棒掠。哈哈!
服務(wù)端屁商,我們先聲明本地要監(jiān)聽的地址和端口烟很。
netListent, err := net.Listen("tcp", "localhost:7373")
有開始就有結(jié)束颈墅,當主函數(shù)運行結(jié)束時,需要釋放資源雾袱。
defer netListent.Close()
在服務(wù)端做標記恤筛,表明現(xiàn)在開始等待客戶端訪問了。
Log(time.Now().Format("2006-01-02 15:04:05.0000000"),"Waiting for client ...")
監(jiān)聽是一個不停息的循環(huán)運行芹橡。所以使用 for{}
for{
conn, err := netListent.Accept()
if err != nil{
continue
}
//標記發(fā)生了一次連接
Log(conn.RemoteAddr().String(), "tcp connect success")
go handleConnection(conn)
}
每個從客戶端請求的連接毒坛,都會在服務(wù)端生成一個 goroutine 協(xié)程去處理。
go handleConnection(conn)
服務(wù)端處理的過程是這樣的林说。建立一個緩存煎殷,接收客戶端信息。如果接收正確腿箩,就反饋給客戶端說“我收到了”豪直。如果客戶端沒有反應(yīng),就證明客戶端“沒有收到回執(zhí)”珠移。如果客戶端有反饋弓乙,就說明“客戶端收到回執(zhí)”。
最后钧惧,溝通結(jié)束暇韧,釋放連接資源。defer conn.Close()
//客戶端連接處理
func handleConnection(conn net.Conn) {
buffer := make([]byte, 1024)
for{
//接收客戶端信息
msg, err := conn.Read(buffer)
if err != nil{
//接收錯誤浓瞪,日志打印
Log(conn.RemoteAddr().String(), "connection error: ", err)
return
}
//接收正確懈玻,日志打印
Log(conn.RemoteAddr().String(), "receive data: ", string(buffer[:msg]))
//反饋給客戶端
bufferReturn := "我收到了"
msgR, err2 := conn.Write([]byte(bufferReturn))
//確認客戶端未收到回執(zhí)
if err2 != nil{
Log(conn.RemoteAddr().String(), "沒有收到回執(zhí)")
return
}
//確認客戶端收到回執(zhí)
msg, err = conn.Read(buffer)
Log(conn.RemoteAddr().String(), "客戶端收到回執(zhí)", string(buffer[:msg]), "客戶收到了", msgR, ";實際發(fā)送了", len(bufferReturn))
}
defer conn.Close()
}
其中應(yīng)用到的日志記錄和錯誤處理乾颁。
//日志記錄
func Log(i ...interface{}) {
fmt.Println(i...)
return
}
//錯誤處理
func CheckErr(err error) {
if err != nil{
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
}
完整的服務(wù)端代碼示例
/**
* MySocketProtocolServer
* @Author: Jian Junbo
* @Email: junbojian@qq.com
* @Create: 2017/9/15 9:07
* Copyright (c) 2017 Jian Junbo All rights reserved.
*
* Description: 服務(wù)端 Socket 信息接收
*/
package main
import (
"fmt"
"net"
"os"
"time"
)
func main() {
//監(jiān)聽服務(wù)聲明實例化
netListent, err := net.Listen("tcp", "localhost:7373")
CheckErr(err)
defer netListent.Close()
//標記開始服務(wù)
Log(time.Now().Format("2006-01-02 15:04:05.0000000"),"Waiting for client ...")
//服務(wù)監(jiān)聽
for{
conn, err := netListent.Accept()
if err != nil{
continue
}
//標記發(fā)生了一次連接
Log(conn.RemoteAddr().String(), "tcp connect success")
go handleConnection(conn)
}
}
//客戶端連接處理
func handleConnection(conn net.Conn) {
buffer := make([]byte, 1024)
for{
//接收客戶端信息
msg, err := conn.Read(buffer)
if err != nil{
//接收錯誤酪刀,日志打印
Log(conn.RemoteAddr().String(), "connection error: ", err)
return
}
//接收正確,日志打印
Log(conn.RemoteAddr().String(), "receive data: ", string(buffer[:msg]))
//反饋給客戶端
bufferReturn := "我收到了"
msgR, err2 := conn.Write([]byte(bufferReturn))
//確認客戶端未收到回執(zhí)
if err2 != nil{
Log(conn.RemoteAddr().String(), "沒有收到回執(zhí)")
return
}
//確認客戶端收到回執(zhí)
msg, err = conn.Read(buffer)
Log(conn.RemoteAddr().String(), "客戶端收到回執(zhí)", string(buffer[:msg]), "客戶收到了", msgR, "钮孵;實際發(fā)送了", len(bufferReturn))
}
defer conn.Close()
}
//日志記錄
func Log(i ...interface{}) {
fmt.Println(i...)
return
}
//錯誤處理
func CheckErr(err error) {
if err != nil{
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
}
客戶端的任務(wù)是發(fā)送信息給服務(wù)端骂倘,等待服務(wù)端的反饋。收到服務(wù)端的反饋后巴席,再通知服務(wù)端說“ok”历涝,表示已經(jīng)知道服務(wù)端收到信息了。
先要確定獲取服務(wù)端的地址和通訊端口漾唉。
server := "localhost:7373"
tcpAddr, err := net.ResolveTCPAddr("tcp4", server)
建立服務(wù)器連接
conn, err := net.DialTCP("tcp", nil, tcpAddr)
連接成功后荧库,日志打印表示一下
Log("connection success")
發(fā)送信息到服務(wù)器,是一個函數(shù) sender(conn) 最終完成的赵刑。
func sender(conn *net.TCPConn) {
myage := int(time.Now().Year())
myage -= 1973
words := "{\"ID\":\"i\",\"Name\":\"Joel\",\"Age\":\""+strconv.Itoa(myage)+"\",\"Programming Language\":\"go\"}"
//msgBack, err := conn.Write(protocol.Enpack([]byte(words)))
msgBack, err := conn.Write([]byte(words))
if err != nil{
Log(conn.RemoteAddr().String(), "Fatal error: ", err)
os.Exit(1)
}
buffer := make([]byte, 1024)
msg, err := conn.Read(buffer)
Log(conn.RemoteAddr().String(), "服務(wù)器反饋: ", string(buffer[:msg]), msgBack,"分衫;實際發(fā)送了", len(words))
conn.Write([]byte("ok"))
}
這個函數(shù)中,我們準備好了要發(fā)送的信息 words般此。
寫入信息到連接中
msgBack, err := conn.Write([]byte(words))
接收服務(wù)器對信息的反饋
msg, err := conn.Read(buffer)
在告訴服務(wù)器蚪战,它的反饋收到了牵现。
conn.Write([]byte("ok"))
完整的客戶端代碼示例
/**
* MySocketProtocolClient
* @Author: Jian Junbo
* @Email: junbojian@qq.com
* @Create: 2017/9/15 10:44
* Copyright (c) 2017 Jian Junbo All rights reserved.
*
* Description: 客戶端 Socket
*/
package main
import (
"net"
"fmt"
"os"
"time"
)
func main() {
//獲取服務(wù)器地址和端口
server := "localhost:7373"
tcpAddr, err := net.ResolveTCPAddr("tcp4", server)
if err != nil{
Log(os.Stderr, "Fatal error: ", err)
os.Exit(1)
}
//建立服務(wù)器連接
conn, err := net.DialTCP("tcp", nil, tcpAddr)
if err != nil{
Log(conn.RemoteAddr().String(), os.Stderr, "Fatal error: ", err)
os.Exit(1)
}
Log("connection success")
sender(conn)
fmt.Println("send over")
}
func sender(conn *net.TCPConn) {
myage := int(time.Now().Year())
myage -= 1973
words := "{\"ID\":\"i\",\"Name\":\"Joel\",\"Age\":\""+strconv.Itoa(myage)+"\",\"Programming Language\":\"go\"}"
msgBack, err := conn.Write([]byte(words))
if err != nil{
Log(conn.RemoteAddr().String(), "Fatal error: ", err)
os.Exit(1)
}
buffer := make([]byte, 1024)
msg, err := conn.Read(buffer)
Log(conn.RemoteAddr().String(), "服務(wù)器反饋: ", string(buffer[:msg]), msgBack,";實際發(fā)送了", len(words))
conn.Write([]byte("ok"))
}
//日志打印
func Log(v ...interface{}) {
fmt.Println(v...)
}
服務(wù)端運行結(jié)果
127.0.0.1:50756 tcp connect success
127.0.0.1:50756 receive data: {"ID":"i","Name":"Joel","Age":"44","Programming Language":"go"}
127.0.0.1:50756 客戶端收到回執(zhí) ok 客戶收到了 12 邀桑;實際發(fā)送了 12
127.0.0.1:50756 connection error: read tcp 127.0.0.1:7373->127.0.0.1:50756: wsarecv: An existing connection was forcibly closed by the remote host.
客戶端運行結(jié)果
connection success
127.0.0.1:7373 服務(wù)器反饋: 我收到了 63 瞎疼;實際發(fā)送了 63
send over
現(xiàn)在通過網(wǎng)絡(luò)傳遞的信息,沒有特別的加工壁畸,沒有自定義的通訊協(xié)議贼急。如果要增加這個,建立自己的通訊協(xié)議后(封裝 Enpack捏萍、解析 Depack)太抓,在客戶端進行封裝,在服務(wù)端進行解析令杈。
在當前的代碼中做修改走敌。
服務(wù)端
Log(conn.RemoteAddr().String(), "receive data: ", string(buffer[:msg]))
改成 ↓
tmpBuffer := make([]byte, 1024)
tmpBuffer = protocol.Depack(append(tmpBuffer,buffer[:msg]...))
Log(conn.RemoteAddr().String(), "receive data: ", string(tmpBuffer))
客戶端
msgBack, err := conn.Write([]byte(words))
...
Log(conn.RemoteAddr().String(), "服務(wù)器反饋: ", string(buffer[:msg]), msgBack,";實際發(fā)送了", len(words))
改成 ↓
msgBack, err := conn.Write(protocol.Enpack([]byte(words)))
...
Log(conn.RemoteAddr().String(), "服務(wù)器反饋: ", string(buffer[:msg]), msgBack,"这揣;實際發(fā)送了", len(protocol.Enpack([]byte(words))))
由于添加了協(xié)議后,發(fā)送內(nèi)容增加了頭部的一些信息影斑,所以實際發(fā)送信息的長度也變化了给赞。
協(xié)議protocol的代碼示例
/**
* protocol
* @Author: Jian Junbo
* @Email: junbojian@qq.com
* @Create: 2017/9/14 11:49
* Copyright (c) 2017 Jian Junbo All rights reserved.
*
* Description: 通訊協(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) []byte {
length := len(buffer)
var i int
data := make([]byte, 32)
for i = 0; i < length; i++ {
if length < i + ConstHeaderLength + ConstMLength{
break
}
if string(buffer[i:i+ConstHeaderLength]) == ConstHeader {
messageLength := ByteToInt(buffer[i+ConstHeaderLength : i+ConstHeaderLength+ConstMLength])
if length < i+ConstHeaderLength+ConstMLength+messageLength {
break
}
data = buffer[i+ConstHeaderLength+ConstMLength : i+ConstHeaderLength+ConstMLength+messageLength]
}
}
if i == length {
return make([]byte, 0)
}
return data
}
//字節(jié)轉(zhuǎn)換成整形
func ByteToInt(n []byte) int {
bytesbuffer := bytes.NewBuffer(n)
var x int32
binary.Read(bytesbuffer, binary.BigEndian, &x)
return int(x)
}
//整數(shù)轉(zhuǎn)換成字節(jié)
func IntToBytes(n int) []byte {
x := int32(n)
bytesBuffer := bytes.NewBuffer([]byte{})
binary.Write(bytesBuffer, binary.BigEndian, x)
return bytesBuffer.Bytes()
}
增加了解析功能的服務(wù)端代碼示例
/**
* MySocketProtocolServer
* @Author: Jian Junbo
* @Email: junbojian@qq.com
* @Create: 2017/9/15 9:07
* Copyright (c) 2017 Jian Junbo All rights reserved.
*
* Description: 服務(wù)端 Socket 信息接收
*/
package main
import (
"fmt"
"net"
"os"
"time"
"protocol"
)
func main() {
//監(jiān)聽服務(wù)聲明實例化
netListent, err := net.Listen("tcp", "localhost:7373")
CheckErr(err)
defer netListent.Close()
//標記開始服務(wù)
Log(time.Now().Format("2006-01-02 15:04:05.0000000"),"Waiting for client ...")
//服務(wù)監(jiān)聽
for{
conn, err := netListent.Accept()
if err != nil{
continue
}
//標記發(fā)生了一次連接
Log(conn.RemoteAddr().String(), "tcp connect success")
go handleConnection(conn)
}
}
//客戶端連接處理
func handleConnection(conn net.Conn) {
buffer := make([]byte, 2048)
for{
//接收客戶端信息
msg, err := conn.Read(buffer)
if err != nil{
//接收錯誤,日志打印
Log(conn.RemoteAddr().String(), "connection error: ", err)
return
}
//接收正確矫户,日志打印
tmpBuffer := make([]byte, 1024)
tmpBuffer = protocol.Depack(append(tmpBuffer,buffer[:msg]...))
Log(conn.RemoteAddr().String(), "receive data: ", string(tmpBuffer))
//反饋給客戶端
bufferReturn := "我收到了"
msgR, err2 := conn.Write([]byte(bufferReturn))
//確認客戶端未收到回執(zhí)
if err2 != nil{
Log(conn.RemoteAddr().String(), "沒有收到回執(zhí)")
return
}
//確認客戶端收到回執(zhí)
msg, err = conn.Read(buffer)
Log(conn.RemoteAddr().String(), "客戶端收到回執(zhí)", string(buffer[:msg]), "客戶收到了", msgR, "片迅;實際發(fā)送了", len(bufferReturn))
}
defer conn.Close()
}
//日志記錄
func Log(i ...interface{}) {
fmt.Println(i...)
return
}
//錯誤處理
func CheckErr(err error) {
if err != nil{
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
}
增加了封裝的客戶端代碼示例
/**
* MySocketProtocolClient
* @Author: Jian Junbo
* @Email: junbojian@qq.com
* @Create: 2017/9/15 10:44
* Copyright (c) 2017 Jian Junbo All rights reserved.
*
* Description: 客戶端 Socket
*/
package main
import (
"net"
"fmt"
"os"
"time"
"strconv"
"protocol"
)
func main() {
//獲取服務(wù)器地址和端口
server := "localhost:7373"
tcpAddr, err := net.ResolveTCPAddr("tcp4", server)
if err != nil{
Log(os.Stderr, "Fatal error: ", err)
os.Exit(1)
}
//建立服務(wù)器連接
conn, err := net.DialTCP("tcp", nil, tcpAddr)
if err != nil{
Log(conn.RemoteAddr().String(), os.Stderr, "Fatal error: ", err)
os.Exit(1)
}
Log("connection success")
sender(conn)
fmt.Println("send over")
}
func sender(conn *net.TCPConn) {
myage := int(time.Now().Year())
myage -= 1973
words := "{\"ID\":\"i\",\"Name\":\"Joel\",\"Age\":\""+strconv.Itoa(myage)+"\",\"Programming Language\":\"go\"}"
msgBack, err := conn.Write(protocol.Enpack([]byte(words)))
//msgBack, err := conn.Write([]byte(words))
if err != nil{
Log(conn.RemoteAddr().String(), "Fatal error: ", err)
os.Exit(1)
}
buffer := make([]byte, 1024)
msg, err := conn.Read(buffer)
Log(conn.RemoteAddr().String(), "服務(wù)器反饋: ", string(buffer[:msg]), msgBack,";實際發(fā)送了", len(protocol.Enpack([]byte(words))))
conn.Write([]byte("ok"))
}
//日志打印
func Log(v ...interface{}) {
fmt.Println(v...)
}
運行效果上只是客戶端這里顯示增加了幾個字節(jié)
connection success
127.0.0.1:7373 服務(wù)器反饋: 我收到了 74 皆辽;實際發(fā)送了 74
send over