一、环境准备与技术选择

抓包库使用:

GIT 地址:https://github.com/google/gopacket
文档地址:https://godoc.org/github.com/google/gopacket

        gopacket 是 google 实现的一个基于 libpcap 的包,它包含许多子包,提供了丰富的函数库,通过导入 gopacket 就可以使用 libpcap 提供的大多数 API。

开发语言选择:

        该服务使用 Go 语言开发,主要基于 Go 的高性能,下面根据本服务所用到的 Go 的特点进行简单介绍:
        a.性能高

        采取多核编程模式

        b.goroutine

        Go 语言中有个概念叫做 goroutine, 类似线程,但是更轻量级。

        c.信道

        goroutine 之间互相通讯的东西,类似 Unix 上的消息队列(可以在进程间传递消息), 用来 goroutine 之间发消息和接收消息。其实,就是在做 goroutine 之间的内存共享。你可以把它看成一个管道,通过它并发核心单元就可以发送或者接收数据进行通讯 (communication)。Channel 可以作为一个先入先出 (FIFO) 的队列,接收的数据和发送的数据的顺序是一致的并且可以在多个 goroutine 从/往 一个 channel 中 receive/send 数据, 不必考虑额外的同步措施。

数据库:mongo

二、代码框架设计

技术原理请查看文章:https://testerhome.com/topics/13298

附功能测试地址:http://opentest.360.cn/phonerenter.html

简单回忆下抓包基本流程:

        抓包服务主要功能是实时存储手机的网络包数据,web 端服务去数据库取网络包数据进行显示,并提供下载支持。

        根据抓包所应该支持的功能划分为 3 个线程分别去实现不同的功能:

                一线程:打开监听抓包服务 ——> 根据 ip 过滤手机包(源 ip 和目的 ip 来区分是否是目标包)——> 存储下载网络包信息 ——> 是否需要存储显示包数据
                

                二线程:监听手机抓包开始和结束的请求
                三线程:删除数据库(每个手机下载包数据最多存储 3000 条,显示包数据最多存储 500 条)

        注:一线程的功能是抓包服务的核心功能,下面通过测试中遇到的问题以及解决的方法来深入说明这一线程的职责。

三、性能优化

使用过程中出现以下两个问题:

        (1)mongo 服务器有慢日志:数据库服务存在几百条慢日志,最长时间达到一千多毫秒
        (2)出现丢包现象(可通过 tcpdump 与该服务进行对比得到)
        (3)CPU 消耗低(对于一个高频服务来说,这是不正常的)


下面主要针对以上两个问题做了以下优化:

版本一优化:

        解决问题:mongo 服务器有慢日志和丢包现象
        前提:对于多个手机,在数据库中的存储完全是根据它们的 phoneip 信息进行唯一标识的,当对该手机的包信息进行插入和删除时都是根据 phoneip 字段去查找并执行相应操作。
        优化:创建 phoneip 为 mongo 数据库的索引
        原理:根据索引查询比较高效

版本二优化:

        解决问题:CPU 消耗低
        前提:单线程去执行所有手机的存储可下载包和显示信息的操作
        优化:将单个线程内操作所有手机的整体包信息与显示包信息存储修改改为给每个手机分配一个线程执行存储操作。
        原理:主线程与该手机的存储线程的通信使用 chan,这样主函数只需要将对应需存的信息传送至某个手机的 chan 进程即可,很大程度上减少了主协程的工作量。

        

版本三优化:

        解决问题:提高服务性能
        前提:解析操作负责解析该包的源 IP、目的 IP、源端口、目的端口等信息,但主协程只在存储显示网络包的时候用到了这些信息。
        优化:将解析包操作分割,在主线程保留解析源 IP、目的 IP 操作,而将源端口、目的端口等显示信息的解析操作放在该手机的存储线程去执行。
        原理:将解析数据操作分割在不同的程序运行点解析,减少主协程的工作量

       

版本四优化:

        解决问题:提高服务性能
        前提:每个手机的存储线程都是从 chan 中读取一次信息,便执行一次 mongo 存储,网络 IO(mongo 在远端服务器,若在本地则是磁盘 IO)很频繁,拉低了整个服务性能。

        优化:批量存储整体包信息,设置存储的合理阈值(当拿到 10 条数据再去存储,进行一次网络 IO)。
        原理:减少网络 IO 次数

       

四、拓展知识

1、破解 wireshake 的解析包数据

       

        在最初的一版 web 端显示时,因为直接把获取到的包的信息字段显示会出现大量乱码。于是就去研究追查了 wireshake 在包解析的时候的实现。

        从图上可以看出:
                (1)包的数据显示 wireshake 以 16 进制显示,即左下角的数据,右侧为对应位置 16 进制数的 ASCII 显示
                (2)ASCII 包含可打印字符和不可打印字符,wireshake 将可打印字符原样显示,将不可打印字符显示为 “.”。(不可打印字符包括:1-31 控制字符、0 字符串结束符、-1(255) EOF 文件结束符。)

2、若抓取的网络包又被多次封装,可使用 gopacket.NewPacket() 进行重新封装原始包的段数据成新包,然后进行包字段解析。

五、总结

尽可能的优化代码性能,对于一些要求高负载的服务来说,性能的提升会减少对资源的浪费并提高用户体验。

技术经验:

        (1)对于频繁运行的代码块,应尽量减轻该处代码的压力,减少不必要操作,浪费资源
        (2)在确保功能正常下,尽可能的减少 IO 操作
        (3)代码的设计上秉持高内聚、低耦合的思想,易扩展,方便日后维护


↙↙↙阅读原文可查看相关链接,并与作者交流