Windows常用软件

我最早接触到的系统就是DOS系统(暴露年龄哈),现在已经发展到Windows 10了,Windows陪伴我太久了,从以前的游戏,之后的工作,它都是我的主力系统。它给我们带来欢乐与乐趣,已经融入我的生活。即便现在我的主力系统已经不是它,但工作、生活还是离不开。下面我来介绍两款我常用的小工具。

Launchy

Launchy 是一个免费跨平台软件,从它的名字就可以看出它是个启动器,它可以快速帮我们启动我们想要打开的软件。
按下启动快捷键(我的是alt+space)显示Launchy主界面。

启动界面
主界面要包括设置、搜索名称输入框与搜索结果预览框。我们输入需要启动的名称如:word,按回车即可启动。

点击设置按钮进行软件配置界面,主要配置如下:

  • GeneralHotKey设置激活热键,勾选Hide Launchy when it loses focus,这样失焦时软件自动会隐藏。

  • Skins根据喜好调整皮肤

  • Catalog是搜索文件路径,系统会默认指定一些,我们也可以增加自定义软件路径及文件扩展名,如选择一些绿色软件的目录。

Listary

Listary是一个文件管理工具(free for personal use only),最主要的功能是在任何地方快速的帮我们查找想要的文件。以前我用的是Everything,因为与系统深度集成与颜值让我选择了Listary。

Listary不用配置开箱即用。在安装后它会有使用引导,很实用,大家按照引导即可快速上手,下面我介绍下我常用的功能:

  • 在任何地方按两次ctrl激活全局搜索,输入想要的文件名称即可。

  • 在其他软件打开文件时弹出的选择文件框也可搜索快速定位,此时切换到File Explorer后,再切回来,选择文件框的路径会自动切换到File Explorer对应的路径。

这两个软件有些功能是重合的,但我觉得应该专业的事交给专业的人(软件)去做,所以我启动文件会用Launchy,查找文件就用Listary。希望能帮助到大家。

2018下半年计划

时光非快,转眼18年已过半,回想上半年,想也半天也想不出个所以然,不禁感慨自己并没有任何计划,每天的时光都是在瞎忙。

正好看到有人正在分享做计划,我也照猫画虎一回。

学习与成长

活到老学到老,首先关注一下学习与成长。

  • 外语

外语我喜欢的是英语,技术开发少不了与它接触,再加上平时喜欢看美剧,所以期待会有些提升。从单词开始,每天都进行背诵,主要以anki为主;其次就是关于阅读,争取做到至少阅读三本英文书,每天都要读一篇英文文章。

  • 阅读

除了刚提到的英文书,还需要从多个角度来扩展自己的阅读面。下半年至少阅读60本书。专业书是老本行,不能忘,占50%;另外经济管理心理学各占10%;加上20%平时喜欢关注的效率&成长
另外也需要关注一些新闻资讯,那就每天定期刷下RSS吧。

  • 输出

之前和一群优秀的小伙伴们一起坚持博文一个月一篇,每次都是最后2天压力巨大,拖到最后才憋出来,造成这样的原因主要是因为没有规划。现在我想把一个月一篇升级为一个月两篇,争取在月初做计划时就规定好题目。

日记,继续坚持写日记(电子),每周一次钢笔字练习。

健康

  • 每日一次晨练,放在早上,不要时间长,贵在坚持。
  • 每周一次禁食日
  • 每周一次素食日
  • 每周二次公共通勤日
  • 作息早5:20~晚11点
  • 每周一次冥想

兴趣娱乐

  • 游戏,通关《怪物猎人XX》与《塞尔达传说》,就是喜欢,没的说。
  • 学习围棋,争取多花些时间和孩子一起玩。
  • 每周一次画画。让自己静下来。

家庭生活

  • 每周一次游戏机日,从孩子抓起。
  • 至少二次爬山,感受户外的乐趣。
  • 至少二次游泳。
  • 多去看望父母。
  • 每月一次家庭会议。
  • 计划一次旅行
  • 整理、备份照片
  • 每周一次烘焙
  • 每周一次家庭整理日

社交

  • 每月联系一位老朋友
  • 每月约见一位老朋友
  • 每月认识一会新朋友(有意思的人)
  • 每周与家人通话一次

财务

加强对理财的理解与应用,加强互联网理财方面了解,在指数基金与数字货币方向下些功夫。另外一方面是要创收,做几个自己的项目,先让自己用的舒服,再尝试分享出来。

职业

  • 优化团队工具流。
  • 加强管理。
  • 尽全力帮助公司盈利。

基本就是这些了,先下个小决心,等年底时再说。

办公室整理小技巧

这些年流行许多关于整理的书,如:《断舍离》,这些书都是本着少即是多的原则介绍关于整理的理念。人们拥有的东西越来越多,这样的情况造成最大的困扰是人们花太多的时间查找我们需要的物品,本文简要的介绍一些实用的办公室整理小技巧。

学会扔东西吧

扔是整理种最重要的方法。有的人花了许多时间进行系统整理,结果并不理想,原因是东西太多了,人们太怕扔错东西而留着许多跟本不会再用的东西。

我们首先要确认这个物品是否该扔,想想以后是否还会用到它,如果不确定那可暂时把它归档放入一个柜子里或箱子里,按照顺序摆放,过些个月后就可以把最下面没有用到的东西扔掉了。

保持桌面整洁,把常用的东西放在手边

无论是办公桌还是电脑桌面一定要整洁,并把最长用的东西放在上面。桌面上放一些笔、便利贴、订书器,为了整洁可放在笔筒内,放在手很容易就拿到的地方;电脑内桌面可以放些常用的文件或正在进行的项目,电脑的文件也一定要整理,最好的方式对文件名进行编码,如:日期、项目名、文件名、关键词等,并放按项目分类放入文件夹内,这样很方便快速查找并定位。

纸质文件整理

纸质的文件是办公室最常见的物件,我们该怎么对待它们呢?
– 尽量减少打印,环保。
– 统一文件大小,大的缩印,小的用订书器订在正常纸上。
– 归档,不常用的放起来

归位

还有个小技巧就是整理完了,要坚持用完归位,这样下次再用的时候就不用担心找不到了。

好啦,办公室的一些小技巧就先介绍到这,去看世界杯了:)

如何写出让人一看就懂的邮件—剧情化

前文提到我们通过区分及物动词不及物动词、使用“串联”和“并联”梳理时间轴与使用”governing“制作思维抽屉等方法使得我们的邮件更有条理。今天我们再来介绍三种方法让我们的邮件具有剧情化。

剧情化让我们写的内容生动形象,能够吸引读者眼球。

制作勾起对方兴趣的诱饵,学会使用”引导文“吸引对方

开头表达什么内容需要根据读者的心理及所处环境进行判断。下面列举一些常见的技巧。
– 如果对方期待的是”回答“,以开头引导文就问题进行回答形式,更能让对方对你的答案产生兴趣,如:面试官询问你是否有过XX经验,你觉得机会来了,大讲特讲你在XX经验相关内容,面试官其实只想知道你的答案”Yes”或”No”,脾气好的面试官可能会对谈话内容进行引导,脾气不好的可能就会被出现粗暴打断的尴尬场景。
– 迎合对方的”期待“。此时就需要讲对方爱听的了。
– ”结论第一“不是万能的。之前讨论过,我们需要把我们描述的内容进行总结概括,但也有些例外场景:
– 如果对方不了解事情的来龙去脉,那么我们就不应该把结论放在前面,需要让其了解事情的背景。
– 如果你处理”说话人主场“(不会被不耐烦的打断)情况,也可进行解释说明。

通过”拆分MECE”(Mutually Exclusive Collectively Exhaustive)来制造高潮

通过”拆分MECE”使得我们说明的内容不重复、不遗漏,但也不必执着于MECE,应该着手想要表达重要的信息,确认是否信息是否有必要传达。另外尽量使用”相对MECE“,如:统计一周写代码时间,正常是周一至周五上班,那么周未即可不必列举并统计。

使用专有名词引起共鸣

  • 我们尽量使用专有名词能让案例更生动。
  • 使用专有名词将听重变成”主人公“。
  • 写出”对症下药“的内容。

如何写出让人一看就懂的邮件

我们每天要处理大量的电子邮件,它是每个人工作不可或缺的工具。在快节奏的工作里,大家都不愿意浪费时间与精力,所以我们的目的是为了让对方立刻理解我们表达的内容。我们经常遇见无法抓住重点、难以理解的邮件。反之当我们发送通知、汇报工作、沟通项目时,有没有想过你表达的信息是否被阅读人有效吸收?
我们只需要在我们的邮件里适当使用项目符号(•Bullet)来描述想要表达的内容,既可化繁为简,又可增强想表达的内容。

逻辑性

使用项目符号需要注意的是我们不应只是罗列内容,而是让每个排列都有意义,将想要传递的内容作为主干,补充内容做为分支。下面我们就来看一下如何通过三个方法来让我们的内容有层次感。

分组

我们要做的将相近的内容整理在一起,可以是主次分组,将主干(主要)内容和补充(次要)内容总结到一个大分组里;也可分开不及物动词与及物动词,将描述对象的状态或现象(静)的内容与行为(动)内容分离开来。因为两者接收的印象不一样。让人轻松理解你想表达的内容。

主次分组很简单,不必赘述。为了排除语意上的歧义,我们在陈述时应该区分开不及物动词与及物动词:
– 如果想表达某个瞬间静止画面,“那个时刻状态”那么每个句子都应该使用不及物动词。使用不及物动词来表达状态也能够让动作发生的因果关系变得模糊,从而模糊责任。
– 杯子坏了。
– 一只笔在这里。
– 如果想表达某个瞬间动态画面,“某人对某事产生了影响的行为”那么每个句子都应该使用及物动词来表达。如果想表达行为及背后的原因就用及物动词。
– 我摔坏了杯子。
– 我放了一只笔在这里。

用“串联”与“并联”来梳理时间轴

大家都学过电路的串联与并联,文字的串并联有什么区别呢?其实主要是在时间轴运用不同,即有无时间流动。
– “串联”,有时间流动。
– “并联”,无时间流动。

使用总述点明主旨

大家在增强逻辑的同时不妨在开头就将问题的数量或对策数量写出来,这样可以在读者头脑中创造存放下文表达内容的内容抽屉,理解重点。最经典的就是乔布斯的“Today I want to tell you three stories from my life”与”One more thing”。

iOS 11 electra越狱安装Clutch

需要工具

越狱

连接手机,使用Cydia Impactor 安装electra,此时需要输入Apple ID 帐户用户名与密码,在手机上启动electra点击越狱。

安装Clutch

下载Clutch,配置环境

killall Xcode
cp /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS.sdk/SDKSettings.plist ~/
/usr/libexec/PlistBuddy -c "Set :DefaultProperties:CODE_SIGNING_REQUIRED NO" /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS.sdk/SDKSettings.plist
/usr/libexec/PlistBuddy -c "Set :DefaultProperties:AD_HOC_CODE_SIGNING_ALLOWED YES" /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS.sdk/SDKSettings.plist

编译,不带签名

xcodebuild -arch arm64 clean build CODE_SIGN_IDENTITY=""

传到设备

scp build/Clutch root@192.168.1.142:

增加Clutch.entitlements文件

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
    <key>platform-application</key>
    <true/>
    <key>get-task-allow</key>
    <true/>
    <key>task_for_pid-allow</key>
    <true/>
    <key>com.apple.backboardd.debugapplications</key>
    <true/>
    <key>com.apple.springboard.debugapplications</key>
    <true/>
    <key>run-unsigned-code</key>
    <true/>
    <key>com.apple.private.librarian.can-get-application-info</key>
    <true/>
    <key>com.apple.private.mobileinstall.allowedSPI</key>
    <array>
        <string>Lookup</string>
        <string>CopyInstalledAppsForLaunchServices</string>
    </array>
</dict>
</plist>

签名

jtool --sign Clutch --ent Clutch.entitlements --inplace

确认,放入bin目录

jtool --ent Clutch
jtool --sig Clutch
mv Clutch /bootstrap/usr/local/bin/Clutch

centos openvpn实现共享上网

问题描述

在同一局域网内有三个机器,只有一台能上网,没有双网卡,试过使用socks5进行代理,可是没能成功,最后尝试openvpn方案,下面介绍下openvpn配置,记录备忘。

##安装服务端

直接使用yum install openvpn -y 进行安装。

生成证书

openvpn需要使用证书进行校验,客户端与服务端都需要生成对应的证书,在网上找了个脚本Easy-RSA 2.2.2(没有使用最新的版本)。

解压后使用source ./vars导入相关环境变量。

生成ca证书:

./build-ca

生成服务端证书:

# server 为name
./build-key-server server 

生成客户端证书:

# client7 为name
./build-key client7 

生成生成迪菲·赫尔曼交换密钥,openvpn有用到:

./build-dh

服务端配置

使用vim server.conf修改配置文件:

# 端口
port 1194 
# 证书正常名称即可

# 设置服务器为代理路由器,否则客户端流量不走服务器,这点至关重要
push "redirect-gateway def1 bypass-dhcp" 
#设置客户端DNS,否则客户端Ping不通域名
;push "route 192.168.10.0 255.255.255.0" 
;push "route 192.168.20.0 255.255.255.0"

使用vi /etc/sysctl.conf开启路由转发:

net.ipv4.ip_forward = 1

重启内核使其生效:sysctl -p

启动

openvpn server.conf

客户端安装

由于客户端不能上网,所以下载好安装包,传过去:

scp rpm/*.rpm root@192.168.1.7:

进行安装:

rpm -ivh pkcs11-helper-1.11-3.el7.x86_64.rpm;
rpm -ivh lz4-r131-1.el7.x86_64.rpm;
rpm -ivh openvpn-2.4.4-1.el7.x86_64.rpm;

使用vim client.conf修改客户端配置文件 :

# 配置服务端ip与port
remote 192.168.1.8 1194 
# 这句注释掉,不需要tls
tls-auth ta.key 1 
# 修改成自己的
cert client7.crt 
key client7.key 

把证书传过去:

scp ca.crt client7.crt client7.key root@192.168.1.7:/etc/openvpn/client/

启动:

openvpn client.conf

软件开发原则-控制代码行

原则

编写代码单元应该少于15行,方便阅读、理解、测试、重构,超过15行应提取拆分,直至少于15行。

应用

下面我们通过例子来应用此原则。原代码如下:

func initEngine() *gin.Engine {
    router := gin.Default()
    m := melody.New()
    m.Config.MaxMessageSize = 10024
    m.Config.MessageBufferSize = 5120
    Rooms = make(map[string]RoomInfo)
    router.GET("/v", func(c *gin.Context) {
        m.HandleRequest(c.Writer, c.Request)
    })
    m.HandleConnect(func(session *melody.Session) {
        log.Info("[ws] connect:", session)
    })

    m.HandleDisconnect(func(session *melody.Session) {
        log.Info("[ws] disconnect", session)
    })

    m.HandleMessage(func(session *melody.Session, msg []byte) {
        reg, exists := session.Get("reg")
        log.Info("[ws] message:", string(msg), "reg:", reg)
        if string(msg) == WebSocketClientBye {
            if exists {
                sp := (reg).(*WSMessage)
                if removeRoom(sp.RoomID, sp.ClientID) != "" {
                    log.Warnf("Socket removeRoom error:%v", reg)
                }
            }
            log.Info("Socket bye", reg)
            return
        }
        message := &WSMessage{}
        err := json.Unmarshal(msg, message)
        if err != nil {
            log.Error("Json error :", err)
            return
        }
        switch message.CMD {
        case "register":
            log.Debug("register")
            session.Set("reg", message)
            return
        default:
            log.Debug("socket default json:", message)
            // check
            p := (reg).(*WSMessage)
            roomID := p.RoomID
            my := p.ClientID
            m.BroadcastFilter(msg, func(s *melody.Session) bool {
                if reg, exists = s.Get("reg"); exists {
                    sp := (reg).(*WSMessage)
                    if r, ok := Rooms[roomID]; ok {
                        for _, c := range r.Clients {
                            if (sp.ClientID == c.ClientID) && (c.ClientID != my) {
                                log.Info("[ws] <----sent---->", c, my, string(msg))
                                return true
                            }
                        }
                    } else {
                        log.Warnf("[ws] only myself???? roomID:%d,my:%v,msg:%v", roomID, my, string(msg))
                    }
                }
                return false
            })
        }

    })
    return router
}

这是一个初始化并实现WebSocketWeb功能的服务器,省略部分代码,并删除了一些调试信息。注释少的可怜,如果初次到这样的代码一定会觉得头大。在重构过程中发现使用此原则会让代码结构清晰,这也是我们重构的目的。下面简单介绍使用的方法:

  • 提取函数

当函数内容太多的时候一定是把所有功能塞到一起了,应该想办法把相同的功能抽离出新的函数。

原代码:

m.BroadcastFilter(msg, func(s *melody.Session) bool {
                if reg, exists = s.Get("reg"); exists {
                    sp := (reg).(*WSMessage)
                    if r, ok := Rooms[roomID]; ok {
                        for _, c := range r.Clients {
                            if (sp.ClientID == c.ClientID) && (c.ClientID != my) {
                                log.Info("[ws] <----sent---->", c, my, string(msg))
                                return true
                            }
                        }
                    } else {
                        log.Warnf("[ws] only myself???? roomID:%d,my:%v,msg:%v", roomID, my, string(msg))
                    }
                }
                return false
            })

重构代码:

// SendRoomBroadcast 发送房间广播
func (wss *WebSocketServer) SendRoomBroadcast(roomID, my string, msg []byte) {
    wss.Server.BroadcastFilter(msg, func(s *melody.Session) bool {
        if reg, exists := s.Get("reg"); exists {
            sp := (reg).(*WSMessage)
            if r, ok := Rooms[roomID]; ok {
                for _, c := range r.Clients {
                    if (sp.ClientID == c.ClientID) && (c.ClientID != my) {
                        return true
                    }
                }
            } else {
                log.Warnf("[ws] only myself???? roomID:%d,my:%v,msg:%v", roomID, my, string(msg))
            }
        }
        return false
    })
}
  • 封装类

当代码整理差不多后,会发现有很多相同的功能,当相同的功能太多的时候可以考虑封装成类。

// WebSocketServer 结构体
type WebSocketServer struct {
    Server *melody.Melody // websocket
}

// NewWebSocketServer 初始化WebSocketServer
func NewWebSocketServer(router *gin.Engine) *WebSocketServer {
    wss := &WebSocketServer{
        Server: melody.New(),
    }
    // 配置buffer size
    wss.Server.Config.MaxMessageSize = 10024
    wss.Server.Config.MessageBufferSize = 5120
    // 升级为websocket
    router.GET("/v", func(c *gin.Context) {
        wss.Server.HandleRequest(c.Writer, c.Request)
    })
    // 处理连接
    wss.Server.HandleConnect(wss.handleConnect)
    // 处理断开连接
    wss.Server.HandleDisconnect(wss.handleDisconnect)
    // 处理消息
    wss.Server.HandleMessage(wss.handleMessage)
    return wss
}

func (wss *WebSocketServer) handleExit(reg interface{}, exists bool, msg []byte) bool {
    if string(msg) == WebSocketClientBye {
        if exists {
            sp := (reg).(*WSMessage)
            if removeRoom(sp.RoomID, sp.ClientID) != "" {
                log.Warnf("Socket removeRoom error:%v", reg)
            }
        }
        log.Info("Socket bye", reg)
        return true
    }
    return false
}

// SendRoomBroadcast 发送房间广播
func (wss *WebSocketServer) SendRoomBroadcast(roomID, my string, msg []byte) {
    wss.Server.BroadcastFilter(msg, func(s *melody.Session) bool {
        if reg, exists := s.Get("reg"); exists {
            sp := (reg).(*WSMessage)
            if r, ok := Rooms[roomID]; ok {
                for _, c := range r.Clients {
                    if (sp.ClientID == c.ClientID) && (c.ClientID != my) {
                        return true
                    }
                }
            } else {
                log.Warnf("[ws] only myself???? roomID:%d,my:%v,msg:%v", roomID, my, string(msg))
            }
        }
        return false
    })
}

func (wss *WebSocketServer) handleMessage(session *melody.Session, msg []byte) {
    reg, exists := session.Get("reg")
    log.Info("[ws] message:", string(msg), "reg:", reg)
    if wss.handleExit(reg, exists, msg) {
        return
    }
    message := &WSMessage{}
    err := json.Unmarshal(msg, message)
    if err != nil {
        log.Error("Json error :", err)
        return
    }
    switch message.CMD {
    case "register":
        log.Debug("register")
        session.Set("reg", message)
        return
    default:
        log.Debug("socket default json:", message)
        wss.SendRoomBroadcast((reg).(*WSMessage).RoomID, (reg).(*WSMessage).ClientID, msg)
    }
}

func (wss *WebSocketServer) handleConnect(session *melody.Session) {
    log.Info("[ws] connect:", session)
}
func (wss *WebSocketServer) handleDisconnect(session *melody.Session) {
    log.Info("[ws] disconnect", session)
}

总结

这个原则足够简单,只要有耐心就能办到。还是建议大家拿起自己手中的代码体验一下,在重构过程中慢慢体会,不过一定要写测试哦,要不就变成裸奔啦。欢迎拍砖:)

Thrift与golang应用

Apache Thrift是一种流行的远程服务调用框架,它采用接口描述定义并创建服务,它的特定是支持多种语言,可以指定多种传输协议、传输层与服务类型快速创建高效服务。

Thrift

数据类型

基本类型:
bool:布尔值,true 或 false,对应 Java 的 boolean
byte:8 位有符号整数,对应 Java 的 byte
i16:16 位有符号整数,对应 Java 的 short
i32:32 位有符号整数,对应 Java 的 int
i64:64 位有符号整数,对应 Java 的 long
double:64 位浮点数,对应 Java 的 double
string:未知编码文本或二进制字符串,对应 Java 的 String
结构体类型:
struct:定义公共的对象,类似于 C 语言中的结构体定义,在 Java 中是一个 JavaBean
容器类型:
list:对应 Java 的 ArrayList
set:对应 Java 的 HashSet
map:对应 Java 的 HashMap
异常类型:
exception:对应 Java 的 Exception
服务类型:
service:对应服务的类

协议

  • TBinaryProtocol — 二进制编码格式进行数据传输
  • TCompactProtocol — 高效率的、密集的二进制编码格式进行数据传输
  • TJSONProtocol — 使用 JSON 的数据编码协议进行数据传输
  • TSimpleJSONProtocol — 只提供 JSON 只写的协议,适用于通过脚本语言解析

传输层

  • TSocket — 使用阻塞式 I/O 进行传输,是最常见的模式
  • TFramedTransport — 使用非阻塞方式,按块的大小进行传输,类似于 Java 中的 NIO
  • TNonblockingTransport — 使用非阻塞方式,用于构建异步客户端

服务类型

  • TSimpleServer — 单线程服务器端使用标准的阻塞式 I/O
  • TThreadPoolServer — 多线程服务器端使用标准的阻塞式 I/O
  • TNonblockingServer — 多线程服务器端使用非阻塞式 I/O

架构图

定义Thrift文件

Base.thrift文件为API基类描述:

namespace java com.qiknow.proto

/**
 * 请求头信息
 */
struct ReqHeader{
    /**
     * Token
     */
    1: string token
}

/**
 * 用户信息
 */
struct UserInfo{
    /**
     * 头像
     */
    1: string headImgPath
    /**
     * 用户姓名
     */
    2: string name
    /**
     * 昵称
     */
    3: string nickname
    /**
     * 性别(1男,2女,0未知)
     */
    4: i32 gender
    /**
     * 电话
     */
    5: string phoneNo
    /**
     * 用户的标签
     */
    6: list<string> tags
    /**
     * 用户ID
     */
    7: i64 userId
    /**
     * 用户关系。1:其他关系,2:我的关注者,3:关注我的人,4:互相关注,5:好友关系,
     */
    8: i32 relationship
}

Req.thrift为请求描述:

include "Base.thrift"
namespace java com.qiknow.proto

/**
 * 加好友请求参数
 */
struct AddFriendReq{
    1: Base.ReqHeader header
    /**
     * 申请者用户ID
     */
    2: i64 requesterId
    /**
     * 被申请者用户ID
     */
    3: i64 approverId
    /**
     * 加好友备注信息
     */
    4: string comments
}

Resp.thrift为返回描述:

include "Base.thrift"
namespace java com.qiknow.proto

struct AddFriendResp {
    /**
     * 状态码
     */
    1: i32 status
    /**
     * 返回结果描述
     */
    2: string msg
}

UserService.thrift为服务接口描述:

include "Req.thrift"
include "Resp.thrift"

namespace java com.qiknow.proto

/**<pre>
描述:账号服务,包括登录的、登出、获取验证码、修改密码、用户信息等相关功能接口
</pre> */   
service UserService{

    /**<pre>
    描述:申请加好友
    参数:申请加好友参数
    结果:返回申请结果
    认证方式:Token
    </pre> */
    Resp.AddFriendResp AddFriend(1: Req.AddFriendReq req)

}

生成代码

使用thrift生成代码

thrift --gen go -out proto/ idl/UserService.thrift
thrift --gen go -out proto/ idl/Resp.thrift
thrift --gen go -out proto/ idl/Req.thrift
thrift --gen go -out proto/ idl/Base.thrift

编写代码

获取git.apache.org/thrift.git/lib/go/thrift依赖

go get git.apache.org/thrift.git/lib/go/thrift

创建对应的Handler

package handler

import (
    "thrift/proto/req"
    "thrift/proto/resp"
)
type UserHandler struct {
}

func NewUserHandler() *UserHandler {
    return &UserHandler{}
}


// <pre>
// 描述:申请加好友
// 参数:申请加好友参数
// 结果:返回申请结果
// 认证方式:Token
// </pre>
//
// Parameters:
//  - Req
func (p *UserHandler) AddFriend(req *req.AddFriendReq) (r *resp.AddFriendResp, err error) {
    return &resp.AddFriendResp{},nil
}

测试

package handler

import (
    "testing"
    "thrift/proto/req"
    "thrift/proto/resp"
    "thrift/proto/userservice"
    "time"

    "git.apache.org/thrift.git/lib/go/thrift"
    "github.com/stretchr/testify/assert"
)

const SERVER_ADDRESS = "127.0.0.1:56783"

func init() {
    testing
    transportFactory := thrift.NewTTransportFactory()
    protocolFactory := thrift.NewTBinaryProtocolFactoryDefault()
    go runServer(transportFactory, protocolFactory, SERVER_ADDRESS)
    time.Sleep(time.Second)
}

// runServer 启动服务器
func runServer(transportFactory thrift.TTransportFactory, protocolFactory thrift.TProtocolFactory, addr string) error {
    var transport thrift.TServerTransport
    var err error
    transport, err = thrift.NewTServerSocket(addr)
    if err != nil {
        return err
    }
    handler := NewUserHandler()
    processor := userservice.NewUserServiceProcessor(handler)
    server := thrift.NewTSimpleServer4(processor, transport, transportFactory, protocolFactory)
    return server.Serve()
}

func TestServer(t *testing.T) {
    var (
        friendResp *resp.AddFriendResp
    )
    // 建立连接
    var transport, err = thrift.NewTSocket(SERVER_ADDRESS)
    assert.NoError(t, err)
    assert.NotNil(t, transport)
    client := userservice.NewUserServiceClientFactory(transport, thrift.NewTBinaryProtocolFactoryDefault())
    assert.NotNil(t, client)
    err = transport.Open()
    assert.NoError(t, err)
    friendResp, err = client.AddFriend(&req.AddFriendReq{
        RequesterId: 100001,
        ApproverId:  100002,
    })
    assert.NotNil(t, friendResp)
    assert.NoError(t, err)
}

Apache Thrift – 可伸缩的跨语言服务开发框架

WebRTC实践(Android、golang实现视频通信)

项目需要翻了好多关于WebRTC的资料,现在做一个简单的总结。

目标

通过研究WebRTC实现一个基于Android的Demo。

学习

之前没有接触过WebRTC,所以是时候需要恶补了一下,第一想到的是在网上找找相关的书籍,大概翻了下《WebRTC Cookbook》与《WebRTC权威指南》,简单了解了本地媒体、信令、STUN、TURN等知识,找了几个js的例子试了下。

Android编译

通过webrtc.org官网的介绍方法,使用gclient获取了webrtc源代码,用gn生成对应cpu项目,用ninja编译,最后生成并在手机上进行测试,完全没问题。

改造

官方的例子好使,验证了方向没问题就开始吧。使用turnserver搭建STUN与TURN服务器。

首先把代码集成到我们的项目中,只需把例子相关类、资源拷贝到项目,并把org.webrtc:google-webrtc包引入到项目即可。

compile 'org.webrtc:google-webrtc:1.0.+'

由于我们的服务是使用go写的,信令服务器这应该是用不了的,当时也没仔细找这部分代码,直接读代码仿写的。

总结

很快代码全部完成,测试也都通过,皆大欢喜,没想到如此顺利,后来打算拿别人手机试一下,出了问题,原来一直在同一wifi下测试,根本不用考虑穿墙的事。首先考虑到是STUN、TURN服务问题,一个个排查,查看ice发现外网ip可以取到,那么可能就是TURN服务的问题,反复排查不得其解,最后发现TURN要求使用long-term credential认证,也就是说必须得有用户名、密码,最后分享一个测试地址,可以测试STUN与TURN服务是否正常。

WebRTC下的网络连接: STUN, TURN, ICE, TCP