缘起
最近浏览 <<Go 微服务实战 >> (刘金亮, 2021.1)
本系列笔记拟采纳 golang 练习之
案例需要 (聊天服务器)
- 用户能够连贯到服务器。
- 用户能够设定本人的用户名。
- 用户能够向服务器发送音讯,同时服务器也会向其余用户播送该音讯。
指标
- 定义通信协议, 包含信令定义, 编解码实现
- 实现聊天客户端 (工夫无限, 后续实现聊天服务端并测试)
设计
- IMsg: 定义音讯接口, 以及相干音讯的实现. 为不便任意音讯内容的解码, 音讯传输时, 采纳 base64 转码
- IMsgDecoder: 定义音讯解码器及其实现
- IChatClient: 定义聊天客户端接口
- tChatClient: 聊天客户端, 实现 IChatClient 接口
- IChatServer: 尚未实现
- tChatServer: 尚未实现
IMsg.go
定义音讯接口, 以及相干音讯的实现. 为不便任意音讯内容的解码, 音讯传输时, 采纳 base64 转码
package chat_server
import (
"encoding/base64"
"fmt"
)
type IMsg interface {Encode() string
}
type NameMsg struct {Name string}
func (me *NameMsg) Encode() string {return fmt.Sprintf("NAME %s", base64.StdEncoding.EncodeToString([]byte(me.Name)))
}
type ChatMsg struct {
Name string
Words string
}
func (me *ChatMsg) Encode() string {
return fmt.Sprintf("CHAT %s %s",
base64.StdEncoding.EncodeToString([]byte(me.Name)),
base64.StdEncoding.EncodeToString([]byte(me.Words)),
)
}
IMsgDecoder.go
定义音讯解码器及其实现
package chat_server
import (
"encoding/base64"
"strings"
)
type IMsgDecoder interface {Decode(line string) (bool, IMsg)
}
type tMsgDecoder struct {
}
func (me *tMsgDecoder) Decode(line string) (bool, IMsg) {items := strings.Split(line, " ")
size := len(items)
if items[0] == "NAME" && size == 2 {name, err := base64.StdEncoding.DecodeString(items[1])
if err != nil {return false, nil}
return true, &NameMsg{Name: string(name),
}
}
if items[0] == "CHAT" && size == 3 {name, err := base64.StdEncoding.DecodeString(items[1])
if err != nil {return false, nil}
words, err := base64.StdEncoding.DecodeString(items[2])
if err != nil {return false, nil}
return true, &ChatMsg{Name: string(name),
Words: string(words),
}
}
return false, nil
}
var MsgDecoder = &tMsgDecoder{}
IChatClient.go
定义聊天客户端接口
package chat_server
type IChatClient interface {Dial(address string) error
Send(msg IMsg)
RecvHandler(handler RecvFunc)
Close()}
type RecvFunc func(msg IMsg)
tChatClient.go
聊天客户端, 实现 IChatClient 接口
package chat_server
import (
"bufio"
"net"
"sync/atomic"
)
type tChatClient struct {
conn net.Conn
closeFlag int32
closeChan chan bool
sendChan chan IMsg
name string
sendLogs []IMsg
recvLogs []IMsg
recvHandler RecvFunc
}
func DialChatClient(address string) (error, IChatClient) {
it := &tChatClient{
conn: nil,
closeFlag: 0,
closeChan: make(chan bool),
sendChan: make(chan IMsg),
name: "anonymous",
sendLogs: []IMsg{},
recvLogs: []IMsg{},
}
e := it.Dial(address)
if e != nil {return e, nil}
return nil, it
}
func (me *tChatClient) Dial(address string) error {c, e := net.Dial("tcp", address)
if e != nil {return e}
me.conn = c
go me.beginWrite()
go me.beginRead()
return nil
}
func (me *tChatClient) isClosed() bool {return me.closeFlag != 0}
func (me *tChatClient) isNotClosed() bool {return !me.isClosed()
}
func (me *tChatClient) Send(msg IMsg) {if me.isNotClosed() {me.sendChan <- msg}
}
func (me *tChatClient) RecvHandler(handler RecvFunc) {if me.isNotClosed() {me.recvHandler = handler}
}
func (me *tChatClient) Close() {if me.isNotClosed() {me.closeConn()
}
}
func (me *tChatClient) beginWrite() {writer := bufio.NewWriter(me.conn)
newline := '\n'
for {
select {
case <- me.closeChan:
_ = me.conn.Close()
me.closeFlag = 2
return
case msg := <- me.sendChan:
_,e := writer.WriteString(msg.Encode())
if e != nil {me.closeConn()
} else {_,e = writer.WriteRune(newline)
if e != nil {me.closeConn()
}
}
}
}
}
func (me *tChatClient) beginRead() {reader := bufio.NewReader(me.conn)
for me.isNotClosed() {line, _, err := reader.ReadLine()
if err != nil {break}
ok, msg := MsgBuilder.Build(string(line))
if ok {
fn := me.recvHandler
if fn != nil {fn(msg)
}
}
}
}
func (me *tChatClient) closeConn() {if atomic.CompareAndSwapInt32(&me.closeFlag, 0, 1) {me.closeChan <- true}
}
(未完待续)