手撸golang GO与微服务 ChatServer之1

ioly · · 611 次点击 · · 开始浏览    
这是一个创建于 的文章,其中的信息可能已经有所发展或是发生改变。

缘起

最近阅读<<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
    }
}

(未完待续)


有疑问加站长微信联系(非本文作者)

本文来自:Segmentfault

感谢作者:ioly

查看原文:手撸golang GO与微服务 ChatServer之1

入群交流(和以上内容无关):加入Go大咖交流群,或添加微信:liuxiaoyan-s 备注:入群;或加QQ群:692541889

611 次点击  
加入收藏 微博
添加一条新回复 (您需要 登录 后才能回复 没有账号 ?)
  • 请尽量让自己的回复能够对别人有帮助
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`
  • 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
  • 图片支持拖拽、截图粘贴等方式上传