手撸golanggo与微服务chatserver之1

ioly      2022-02-13     476

关键词:

缘起

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

(未完待续)

手撸golanggo与微服务saga模式之1

缘起最近阅读<<Go微服务实战>>(刘金亮,2021.1)本系列笔记拟采用golang练习之Saga模式saga模式将分布式长事务切分为一系列独立短事务每个短事务是可通过补偿动作进行撤销的事务动作和补偿动作都是幂等的,允许重复执行而... 查看详情

手撸golanggo与微服务saga模式之7

缘起最近阅读<<Go微服务实战>>(刘金亮,2021.1)本系列笔记拟采用golang练习之Saga模式saga模式将分布式长事务切分为一系列独立短事务每个短事务是可通过补偿动作进行撤销的事务动作和补动作偿都是幂等的,允许重复执行而... 查看详情

手撸golangspringioc/aop之2

手撸golangspringioc/aop之2缘起最近阅读[Offer来了:Java面试核心知识点精讲(框架篇)](王磊,2020.6)本系列笔记拟采用golang练习之Talkischeap,showmethecode.SpringSpring基于J2EE技术实现了一套轻量级的JavaWebService系统应用框架。它有很多优秀的... 查看详情

手撸golangetcdraft协议之11

手撸golangetcdraft协议之11缘起最近阅读[云原生分布式存储基石:etcd深入解析](杜军,2019.1)本系列笔记拟采用golang练习之raft分布式一致性算法分布式存储系统通常会通过维护多个副本来进行容错,以提高系统的可用性。这就引出了... 查看详情

手撸golang仿springioc/aop之4蓝图

手撸golang仿springioc/aop之4蓝图缘起最近阅读[SpringBoot技术内幕:架构设计与实现原理](朱智胜,2020.6)本系列笔记拟采用golang练习之Talkischeap,showmethecode.SpringSpring的主要特性:1.控制反转(InversionofControl,IoC)2.面向容器3.面向切面(Aspect... 查看详情

手撸golang仿springioc/aop之12增强3

手撸golang仿springioc/aop之12增强3缘起最近阅读[SpringBoot技术内幕:架构设计与实现原理](朱智胜,2020.6)本系列笔记拟采用golang练习之Talkischeap,showmethecode.SpringSpring的主要特性:1.控制反转(InversionofControl,IoC)2.面向容器3.面向切面(Aspe... 查看详情

手撸golang仿springioc/aop之5如何扫描

手撸golang仿springioc/aop之5如何扫描缘起最近阅读[SpringBoot技术内幕:架构设计与实现原理](朱智胜,2020.6)本系列笔记拟采用golang练习之Talkischeap,showmethecode.SpringSpring的主要特性:1.控制反转(InversionofControl,IoC)2.面向容器3.面向切面(... 查看详情

手撸golangspringioc/aop之2

参考技术A手撸golangspringioc/aop之2最近阅读[Offer来了:Java面试核心知识点精讲(框架篇)](王磊,2020.6)本系列笔记拟采用golang练习之Talkischeap,showmethecode.配置接口指令接口指令构建器接口指令执行上下文接口保存配置另存配置添加... 查看详情

手撸golang学etcd手写raft协议之12单元测试

手撸golang学etcd手写raft协议之12单元测试缘起最近阅读[云原生分布式存储基石:etcd深入解析](杜军,2019.1)本系列笔记拟采用golang练习之raft分布式一致性算法分布式存储系统通常会通过维护多个副本来进行容错,以提高系统的可用... 查看详情

手撸系列之——orm(对象关系映射)(代码片段)

ORM:对象关系映射类》》》数据库的一张表对象》》》表的一条记录对象点属性》》》记录某一个字段对应的值废话不多少,先上代码:#orm.pyfrommysql_singletionimportMysql#设置表字段类,通常需要的属性为字段名,字段类型,是否为... 查看详情

golanggo中的基本web服务器(代码片段)

查看详情

golanggo中的简单静态文件服务器(代码片段)

查看详情

golanggo中的简单http服务器使用mongodb和通道(代码片段)

查看详情

goroutine并发调度模型深度解析之手撸一个协程池(代码片段)

golanggoroutine协程池GroutinePool高并发并发(并行),一直以来都是一个编程语言里的核心主题之一,也是被开发者关注最多的话题;Go语言作为一个出道以来就自带『高并发』光环的富二代编程语言,它的并发(并行)编程肯定是... 查看详情

使用javasocket手撸一个http服务器(代码片段)

原文连接:使用JavaSocket手撸一个http服务器作为一个java后端,提供http服务可以说是基本技能之一了,但是你真的了解http协议么?你知道知道如何手撸一个http服务器么?tomcat的底层是怎么支持http服务的呢?大名鼎鼎的Servlet又是... 查看详情

手撸orm

本文目录ORM简介Python中常用ORM框架 原生操作数据库模块pymysqlORM框架之SQLAlchemy手把手带你写一个自己的ORM框架回到目录ORM简介ORM即ObjectRelationalMapping,全称对象关系映射。当我们需要对数据库进行操作时,势必需要通过连接... 查看详情

使用node.js手撸一个建静态web服务器,内部cv指南(代码片段)

...如上图文章结束话说这个键盘真漂亮~~文章目录使用Node.js手撸一个建静态Web服务器一、动静态服务器的概念1.1静态Web服务器概念1.2静态Web服务器的优点1.3快速搭建的途径二、手撸指南2.1框架搭建2.2Ctrl+C/V2.3启动服务器2.4部署服... 查看详情

微服务与微服务架构

 一、微服务与微服务架构业界大牛马丁.福勒(MartinFowler)这样描述微服务:论文网址:           https://martinfowler.com/articles/microservices.html1、微服务强调的是服务的大小,它关注的是某... 查看详情