tinode开发环境搭建

tinode

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

下载代码

项目在github上的地址

Go Modules

  • 设置proxy
    在这里插入图片描述

运行

goland配置

在这里插入图片描述

注意

  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消息,这是为啥呢?
        • stackoverflow的解释
        • 就如上面的stackoverflow的解释一样,底层协议会自动回复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表的结构
          unameuseridschemeauthlvlsecretexpires
          asic:alice803470600923254000basic20$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"}} 
以下是对vx6中mkfs.c代码的注释: ``` /* * mkfs.c - file system initialization program */ #include "param.h" #include "buf.h" #include "inode.h" #include "filsys.h" #define NINOBLK (BSIZE/sizeof(struct dinode)) #define NDIRECT (BSIZE/sizeof(struct direct)) struct filsys sblock; /* the file system super block */ int fmod = 0; /* is the file system already modified? */ int fso, fsi; /* file system device file descriptors */ unsigned int fmax; /* number of blocks in file system */ unsigned char *fbuf; /* buffer for file system super block */ /* * main - file system initialization program */ main(argc, argv) int argc; char *argv[]; { register struct dinode *ip; struct direct dp; int n, i, j; daddr_t bn; char filename[50]; /* Open file system device file */ if(argc < 2) { printf("Usage: mkfs filsys\n"); exit(1); } fso = open(argv[1], 1); if(fso < 0) { printf("Can't open %s\n", argv[1]); exit(1); } /* Compute the number of blocks in the file system */ fsi = open(argv[1], 0); fmax = 0; while(read(fsi, fbuf, BSIZE) == BSIZE) fmax++; close(fsi); /* Compute the number of inodes in the file system */ sblock.s_isize = roundup(fmax/16, BSIZE) + 2; sblock.s_fsize = fmax; /* Initialize the super block */ sblock.s_flock = 0; sblock.s_ilock = 0; sblock.s_fmod = 0; sblock.s_ronly = 0; sblock.s_time = 0; sblock.s_tfree = fmax; sblock.s_tinode = sblock.s_isize * NINOBLK; sblock.s_dinfo[0] = 0; sblock.s_dinfo[1] = 0; sblock.s_dinfo[2] = 0; sblock.s_dinfo[3] = 0; sblock.s_dinfo[4] = 0; sblock.s_dinfo[5] = 0; sblock.s_dinfo[6] = 0; sblock.s_dinfo[7] = 0; /* Write the super block to disk */ lseek(fso, SUPERBOFF, 0); write(fso, &sblock, sizeof(sblock)); /* Initialize the inode blocks */ n = sblock.s_isize; ip = (struct dinode *) fbuf; for(i = 2; i < n; i++) { bzero(fbuf, BSIZE); for(j = 0; j < NINOBLK; j++, ip++) { ip->di_mode = 0; ip->di_nlink = 0; ip->di_uid = 0; ip->di_gid = 0; ip->di_size = 0; bn = itod(i) * BSIZE + j * sizeof(struct dinode); lseek(fso, bn, 0); write(fso, ip, sizeof(struct dinode)); } } /* Initialize the root directory */ bzero(fbuf, BSIZE); ip = (struct dinode *) fbuf; ip->di_mode = IFDIR | 0777; ip->di_nlink = 2; ip->di_uid = 0; ip->di_gid = 0; ip->di_size = sizeof(struct direct); ip->di_addr[0] = itod(2); lseek(fso, itod(2) * BSIZE, 0); write(fso, fbuf, BSIZE); /* Add "." and ".." entries to the root directory */ dp.d_ino = 1; strncpy(dp.d_name, ".", DIRSIZ); lseek(fso, itod(2) * BSIZE, 0); write(fso, &dp, sizeof(dp)); dp.d_ino = 1; strncpy(dp.d_name, "..", DIRSIZ); write(fso, &dp, sizeof(dp)); /* Initialize the free block list */ bzero(fbuf, BSIZE); fbuf[0] |= BUSY; for(i = 2; i < fmax; i++) write(fso, fbuf, BSIZE); /* All done */ close(fso); exit(0); } ``` 上面代码的主要作用是: 1. 定义了文件系统超级块结构体 `struct filsys` 和磁盘上的 `inode` 结构体 `struct dinode`。 2. 定义了文件系统初始化工具 `mkfs` 的全局变量,包括文件系统设备文件描述符、文件系统设备文件的块数量、文件系统超级块等。 3. 通过读取命令行参数,打开文件系统设备文件。 4. 计算文件系统中的块数量和inode数量,并初始化文件系统的超级块结构体。 5. 将文件系统的超级块写入磁盘,并初始化inode块和根目录。 6. 最后,初始化空闲块链表,写入磁盘,完成文件系统的初始化工作。 总体而言,mkfs.c的作用是初始化vx6文件系统,并在磁盘上创建一个文件系统,以便操作系统可以使用这个文件系统来存储和管理文件和目录等数据。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值