缘起
最近阅读<
本系列笔记拟采用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 stringWords 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) errorSend(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.ConncloseFlag int32closeChan chan boolsendChan chan IMsgname stringsendLogs []IMsgrecvLogs []IMsgrecvHandler 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 = cgo 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 = 2returncase 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.recvHandlerif fn != nil {fn(msg)}}}}func (me *tChatClient) closeConn() {if atomic.CompareAndSwapInt32(&me.closeFlag, 0, 1) {me.closeChan <- true}}
(未完待续)
