缘起
最近浏览<<Go微服务实战>> (刘金亮, 2021.1)
本系列笔记拟采纳golang练习之
案例需要(聊天服务器)
- 用户能够连贯到服务器。
- 用户能够设定本人的用户名。
- 用户能够向服务器发送音讯,同时服务器也会向其余用户播送该音讯。
指标
- 定义通信协议, 包含信令定义, 编解码实现
- 实现聊天客户端(工夫无限, 后续实现聊天服务端并测试)
设计
- IMsg: 定义音讯接口, 以及相干音讯的实现. 为不便任意音讯内容的解码, 音讯传输时, 采纳base64转码
- IMsgDecoder: 定义音讯解码器及其实现
- IChatClient: 定义聊天客户端接口
- tChatClient: 聊天客户端, 实现IChatClient接口
- IChatServer: 尚未实现
- tChatServer: 尚未实现
IMsg.go
定义音讯接口, 以及相干音讯的实现. 为不便任意音讯内容的解码, 音讯传输时, 采纳base64转码
package chat_serverimport ( "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_serverimport ( "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_servertype IChatClient interface { Dial(address string) error Send(msg IMsg) RecvHandler(handler RecvFunc) Close()}type RecvFunc func(msg IMsg)
tChatClient.go
聊天客户端, 实现IChatClient接口
package chat_serverimport ( "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 }}
(未完待续)