tinode开发环境搭建

tinode

  1. 支持websocket, long polling(长轮询), grpc
  2. 支持3种数据库:MySQL, Mongodb, rethinkdb
  3. 完整的客户端: web,ios,andorid
  4. 后端编程语言:Golang

下载代码

项目在github上的地址

Go Modules

  • 设置proxy
    tinode开发环境搭建

运行

goland配置

tinode开发环境搭建

注意

  1. 要配置好tinode.conf, 根据你设置的-tags 来设置数据库相关连接
  2. static_data指向的目录,可以从github上的release直接下载

服务端编译

在goland的配置里已经展现了

一些琐碎的点

  1. 配置文件里的api_key_salt不是字符串,在代码里是[]byte类型
  2. MySQL,Mongodb,RethinkDb3种数据库被称为store, 抽象为adapter.
  3. 数据库类型的选在在go build -tags 指定的, 因为只编译对应的代码,就只有对应的数据库被注册到adapter里了
  4. 用websocket的时候,如果不勾选保持登录一刷新页面就退出了
  5. 用websocket的时候心跳包机制没搞懂
    • 客户端可能发送1,服务端响应0
      func (s *Session) dispatchRaw(raw []byte) {
      var msg ClientComMessage
      
      if len(raw) == 1 && raw[0] == 0x31 {
      	// 0x31 == '1'. This is a network probe message. Respond with a '0':
      	s.queueOutBytes([]byte{0x30})
      	return
      }
      // ....
      
    • 在每个ws连接的writeLoop里是有如下代码在发送心跳
      case <-ticker.C:
         	if err := wsWrite(sess.ws, websocket.PingMessage, nil); err != nil {
         		if websocket.IsUnexpectedCloseError(err, websocket.CloseGoingAway, websocket.CloseAbnormalClosure,
         			websocket.CloseNormalClosure) {
         			log.Println("ws: writeLoop ping", sess.sid, err)
         		}
         		return
         	}
      
      • 对应的日志 :
        2020/03/04 14:44:31 out: mt = `9`, ``
      • 但是web页面的console里没有看到对应的pong消息,这是为啥呢?
        • *的解释
        • 就如上面的*的解释一样,底层协议会自动回复pong,这是ws协议,web端不需要任何显示代码
        • 关闭前端网页的时候,是能看到相关退出的日志的
              2020/03/04 16:36:38 readLoop, websocket: close 1001 (going away)
              2020/03/04 16:36:38 readLoop, defer ,zbz6L4jbbcI
          
  6. 每一个连接都是一个session,不论这个连接是http长轮询,ws,grpc
  7. 每一个session都有一个sid,所有的session都保存在一个map里,sid为key
  8. 每一个websocket的连接都有2个goroutine: 一个负责读,一个负责写
    • ws的写(负责向客户端发送消息)使用了一个带缓存的channel, 负责读的goroutine把消息写入这个channel
    • 读的groutine通过读取ws, 通过switch判断然后给不同的处理函数
      • 读有一个plugin的处理过程,默认的只有一个python_chat_bot且被disable掉的
  9. 时间都是用的UTC时间,server/store/types/types.go
    	func TimeNow() time.Time {
    		return time.Now().UTC().Round(time.Millisecond)
    	}
    
    
  10. 发起ws连接的时候apikey非常重要,是连上服务器的凭据
    	ws://127.0.0.1:6060/v0/channels?apikey=AQEAAAABAAD_rAp4DJh05a1HAwFT3A6K
    
    只要有个apikey就能连上服务器,而且一直能连上,感觉像是个bug
  11. websocket是没有压缩的
  12. 所有的消息都有在服务器上存储: messges这个collections里,带有seqid,而且还挺复杂
  13. token不是每次和服务器的交流都带着的,好像只是在修改某些东西的时候带着,如果token过期了呢?
    • 在登录后返回的ctrl消息里带有token和token的过期时间

基本概念

session

会话是客户端应用程序与服务器之间的网络连接	
  1. 建立会话后,用户可以开始通过主题(topic)与其他用户进行交互

user

代表通过会话(session)连接到服务器的人
  1. 用户ID是唯一的,带有’usr’前缀的字符串,后跟base64-URL编码的伪随机64位数字,例如usr2il9suCbuko
  2. 同一用户可以同时建立多个会话

client

诸如移动或Web应用程序之类的客户端
  1. 客户端需要身份验证才能执行大多数操作

topic

会话(session)之间路由内容的命名通信渠道
  1. topic id

流程

登录

  1. hi

    • 客户端发出hi
      {
      	"hi": {
      		"id": "69718",
      		"ver": "0.16.8-beta1",
      		"ua": "TinodeWeb/0.16.8 (Firefox/83.0; Win32); tinodejs/0.16.8-beta1",
      		"lang": "zh-CN"
      	}
      }
      
    • 服务端响应
      {
      	"ctrl": {
      		"id": "69718",
      		"params": {
      			"build": "mysql:undef",
      			"maxFileUploadSize": 8388608,
      			"maxMessageSize": 262144,
      			"maxSubscriberCount": 128,
      			"maxTagCount": 16,
      			"maxTagLength": 96,
      			"minTagLength": 2,
      			"ver": "0.16"
      		},
      		"code": 201,
      		"text": "created",
      		"ts": "2020-11-25T08:26:14.072Z"
      	}
      }
      
    • 客户端发出login
      {
      	"login": {
      		"id": "69719",
      		"scheme": "basic",
      		"secret": "YWxpY2U6YWxpY2UxMjM="
      	}
      }
      
      • schema为basic,表示用户名密码登录,其他的方式参见配置文件tinode.conf里的auth_config
      • secret类似http里的HTTP基本认证,是用户和密码的组和的base64编码
    • 服务端返回
      {
      	"ctrl": {
      		"id": "69719",
      		"params": {
      			"authlvl": "auth",
      			"expires": "2020-12-09T08:26:14.17Z",
      			"token": "VvZ8n1ZhmwAmitBfFAABAAEA7bOB+w3aXZ1J/59lZj44Ilm8yqEao912fbrCyMwinGc=",
      			"user": "usrVvZ8n1ZhmwA"
      		},
      		"code": 200,
      		"text": "ok",
      		"ts": "2020-11-25T08:26:14.089Z"
      	}
      }
      
      • server/session.go

        这里根据客户端发出的login.schema来选择验证方法.

        func (s *Session) login(msg *ClientComMessage) {
        	// 
        	handler := store.GetLogicalAuthHandler(msg.Login.Scheme)
        	//...
        }
        
      • basic模式为例:server/auth/basic/auth_basic.go的
        func (a *authenticator) Authenticate(secret []byte) (*auth.Rec, []byte, error) {
        	// 从数据库拿到相关的记录,传入的参数是: a.name是模式,比如basic, uname是用户名 
        	// 从`auth`表里根据basic:uname获取记录
        	uid, authLvl, passhash, expires, err := store.Users.GetAuthUniqueRecord(a.name, uname)
        	// 校验uid
        	if uid.IsZero() {
        		// Invalid login.
        		return nil, nil, types.ErrFailed
        	}
        	// 校验过期时间
        	if !expires.IsZero() && expires.Before(time.Now()) {
        		// The record has expired
        		return nil, nil, types.ErrExpired
        	}
        	// 校验密码,把明文密码加密后和数据库取出的secret字段对比
        	err = bcrypt.CompareHashAndPassword(passhash, []byte(password))
        	if err != nil {
        		// Invalid password
        		return nil, nil, types.ErrFailed
        	}
        }
        
        • auth表的结构
          uname userid scheme authlvl secret expires
          asic:alice 803470600923254000 basic 20 $2a 10 10 10QuAbMRkzzB/M7wGTTnxRj.DYa34BcXrfMIwh3jylpgot5BeN8KEVS
  2. 交互消息,out是客户端发出,in是客户端接收

out: {"hi":{"id":"83985","ver":"0.16.3","ua":"TinodeWeb/0.16.3 (Chrome/80.0; Win32); tinodejs/0.16.3","lang":"zh-CN"}} 
in: {"ctrl":{"id":"83985","params":{"build":"mongodb:undef","maxFileUploadSize":8388608,"maxMessageSize":262144,"maxSubscriberCount":128,"maxTagCount":16,"ver":"0.16"},"code":201,"text":"created","ts":"2020-03-23T07:55:58.439Z"}} 
out: {"login":{"id":"83986","scheme":"basic","secret":"bWExMjM0Ok1hQDEyMzQ="}} 
in:{"ctrl":{"id":"83986","params":{"authlvl":"auth","expires":"2020-04-06T07:55:58.504Z","token":"Q31lre9hlo6O4IpeFAABAAEAUo6b0ItShDLjnW15vXYZzvVjg2zE++bUZ6swdvVbcro=","user":"usrQ31lre9hlo4"},"code":200,"text":"ok","ts":"2020-03-23T07:55:58.441Z"}} 
out: {"sub":{"id":"83987","topic":"me","get":{"what":"sub desc tags cred"}}} 
in: {"ctrl":{"id":"83987","topic":"me","code":200,"text":"ok","ts":"2020-03-23T07:55:58.512Z"}} 
in: {"meta":{"id":"83987","topic":"me","ts":"2020-03-23T07:55:58.513Z","desc":{"updated":"2020-02-17T03:23:38.392Z","touched":"2020-02-17T03:23:38.392Z","defacs":{"auth":"JRWPAS","anon":"N"},"public":{"fn":"马兵"}}}} 
in: {"meta":{"id":"83987","topic":"me","ts":"2020-03-23T07:55:58.513Z","sub":[{"updated":"2020-02-17T03:24:32.548Z","acs":{"mode":"JRWPA","given":"JRWPAS","want":"JRWPA"},"read":11,"recv":11,"public":{"fn":"Alice Johnson","photo":{"data":"<8908, bytes: /9j/4AAQSkZJ...sUaqGs//2Q==>","type":"jpg"}},"topic":"usrE7cMWMo0oAU","touched":"2020-03-23T03:38:50.685Z","seq":11},{"updated":"2020-03-05T07:40:23.407Z","acs":{"mode":"JRWPA","given":"JRWPAS","want":"JRWPA"},"public":{"fn":"Bob Smith","photo":{"data":"<7236, bytes: /9j/4AAQSkZJ...30e375//2Q==>","type":"jpg"}},"topic":"usrurYOjmqq3fo","touched":"2020-03-05T07:40:23.407Z"}]}} 
in: {"meta":{"id":"83987","topic":"me","ts":"2020-03-23T07:55:58.516Z","tags":["basic:ma1234","email:cumt_ttr@163.com"]}} 
in: {"meta":{"id":"83987","topic":"me","ts":"2020-03-23T07:55:58.516Z","cred":[{"meth":"email","val":"cumt_ttr@163.com","done":true},{"meth":"email","val":"rangerforce007@gmail.com"}]}} 
in: {"pres":{"topic":"me","src":"usrE7cMWMo0oAU","what":"on"}}

聊天

  1. 切换聊天对象: 比如在和A聊天,然后点击了B,进入和B聊天的界面
    • “topic”:“usrE7cMWMo0oAU”, E7cMWMo0oAU是用户的id
    • "leave"消息表示离开用户的E7cMWMo0oAU聊天界面
    • "sub"消息表示进入用户urYOjmqq3fo的聊天界面
tinode.prod.js:1 [03:47:20:828] out: {"leave":{"id":"88960","topic":"usrE7cMWMo0oAU"}} 
tinode.prod.js:1 [03:47:20:829] out: {"sub":{"id":"88961","topic":"usrurYOjmqq3fo","get":{"data":{"limit":24},"sub":{"ims":"2020-03-23T03:44:44.118Z"},"desc":{"ims":"2020-03-23T03:44:44.118Z"},"what":"data sub desc"}}} 
tinode.prod.js:1 [03:47:20:844] in: {"ctrl":{"id":"88960","topic":"usrE7cMWMo0oAU","code":200,"text":"ok","ts":"2020-03-23T03:47:20.829Z"}} 
tinode.prod.js:1 [03:47:20:847] in: {"ctrl":{"id":"88961","topic":"usrurYOjmqq3fo","code":200,"text":"ok","ts":"2020-03-23T03:47:20.838Z"}} 
tinode.prod.js:1 [03:47:20:848] in: {"meta":{"id":"88961","topic":"usrurYOjmqq3fo","ts":"2020-03-23T03:47:20.838Z","desc":{"created":"2020-03-05T07:40:23.407Z","updated":"2020-03-05T07:40:23.407Z","touched":"2020-03-05T07:40:23.407Z","acs":{"mode":"JRWPA","given":"JRWPAS","want":"JRWPA"}}}} 
tinode.prod.js:1 [03:47:20:848] in: {"meta":{"id":"88961","topic":"usrurYOjmqq3fo","ts":"2020-03-23T03:47:20.838Z","sub":[{"updated":"2020-03-05T07:40:23.407Z","acs":{"mode":"JRWPA","given":"JRWPAS","want":"JRWPA"},"user":"usrQ31lre9hlo4"},{"updated":"2020-03-05T07:40:23.408Z","acs":{"mode":"JRWPA","given":"JRWPA","want":"JRWPA"},"user":"usrurYOjmqq3fo"}]}} 
tinode.prod.js:1 [03:47:20:848] in: {"ctrl":{"id":"88961","topic":"usrurYOjmqq3fo","params":{"count":0,"what":"data"},"code":200,"text":"ok","ts":"2020-03-23T03:47:20.840Z"}} 
上一篇:剑指47.求1+2+3+...+n


下一篇:高手2020揭秘《幸 运飞艇定位5码冠军大小单双滚雪球公式规律》玩家盈利走势技巧