Jack Jiang

我的最新工程MobileIMSDK:http://git.oschina.net/jackjiang/MobileIMSDK
posts - 506, comments - 13, trackbacks - 0, articles - 1

本文由得物技术团队Uni分享,本文有内容修订和大量排版优化。

1、引言

关于Java网络编程中的同步IO和异步IO的区别及原理的文章非常的多,具体来说主要还是在讨论Java BIO和Java NIO这两者,而关于Java AIO的文章就少之又少了(即使用也只是介绍了一下概念和代码示例)。

在深入了解AIO之前,我注意到以下几个现象:

  • 1)2011年Java 7发布,它增加了AIO(号称异步IO网络编程模型),但12年过去了,平时使用的开发框架和中间件却还是以NIO为主(例如网络框架Netty、Mina,Web容器Tomcat、Undertow),这是为什么?
  • 2)Java AIO又称为NIO 2.0,难道它也是基于NIO来实现的?
  • 3)Netty为什么会舍去了AIO的支持?(点此查看);
  • 4)AIO看起来貌似只是解决了有无,实际是发布了个寂寞?

Java AIO的这些不合常理的现象难免会令人心存疑惑。所以决定写这篇文章时,我不想只是简单的把AIO的概念再复述一遍,而是要透过现象,深入分析、思考和并理解Java AIO的本质。

 

技术交流:

(本文已同步发布于:http://www.52im.net/thread-4283-1-1.html

2、我们所理解的异步

AIO的A是Asynchronous(即异步)的意思,在了解AIO的原理之前,我们先理清一下“异步”到底是怎样的一个概念。

说起异步编程,在平时的开发还是比较常见的。

例如以下的代码示例:

@Async

publicvoidcreate() {

    //TODO

}

 

publicvoidbuild() {

    executor.execute(() -> build());

}

不管是用@Async注解,还是往线程池里提交任务,他们最终都是同一个结果,就是把要执行的任务,交给另外一个线程来执行。

这个时候,我们可以大致的认为,所谓的“异步”,就是用多线程的方式去并行执行任务。

3、Java BIO和NIO到底是同步还是异步?

Java BIO和NIO到底是同步还是异步,我们先按照异步这个思路,做异步编程。

3.1BIO代码示例

byte[] data = newbyte[1024];

InputStream in = socket.getInputStream();

in.read(data);

// 接收到数据,异步处理

executor.execute(() -> handle(data));

 

publicvoidhandle(byte[] data) {

    // TODO

}

如上:BIO在read()时,虽然线程阻塞了,但在收到数据时,可以异步启动一个线程去处理。

3.2NIO代码示例

selector.select();

Set<SelectionKey> keys = selector.selectedKeys();

Iterator<SelectionKey> iterator = keys.iterator();

while(iterator.hasNext()) {

    SelectionKey key = iterator.next();

    if(key.isReadable()) {

        SocketChannel channel = (SocketChannel) key.channel();

        ByteBuffer byteBuffer = (ByteBuffer) key.attachment();

        executor.execute(() -> {

            try{

                channel.read(byteBuffer);

                handle(byteBuffer);

            } catch(Exception e) {

 

            }

        });

    }

}

 

publicstaticvoidhandle(ByteBuffer buffer) {

    // TODO

}

同理:NIO虽然read()是非阻塞的,通过select()可以阻塞等待数据,在有数据可读的时候,异步启动一个线程,去读取数据和处理数据。

3.3产生的理解偏差

此时我们信誓旦旦地说,Java的BIO和NIO是异步还是同步,取决你的心情,你高兴给它个多线程,它就是异步的。

果真如此么?

在翻阅了大量博客文章之后,基本一致的阐明了——BIO和NIO是同步的。

那问题点出在哪呢,是什么造成了我们理解上的偏差呢?

那就是参考系的问题,以前学物理时,公交车上的乘客是运动还是静止,需要有参考系前提,如果以地面为参考,他是运动的,以公交车为参考,他是静止的。

Java IO也是一样,需要有个参考系,才能定义它是同步还是异步。

既然我们讨论的是关于Java IO是哪一种模式,那就是要针对IO读写操作这件事来理解,而其他的启动另外一个线程去处理数据,已经是脱离IO读写的范围了,不应该把他们扯进来。

3.4尝试定义异步

所以以IO读写操作这事件作为参照,我们先尝试的这样定义,就是:发起IO读写的线程(调用read和write的线程),和实际操作IO读写的线程,如果是同一个线程,就称之为同步,否则是异步。

按上述定义:

  • 1)显然BIO只能是同步,调用in.read()当前线程阻塞,有数据返回的时候,接收到数据的还是原来的线程;
  • 2)而NIO也称之为同步,原因也是如此,调用channel.read()时,线程虽然不会阻塞,但读到数据的还是当前线程。

按照这个思路,AIO应该是发起IO读写的线程,和实际收到数据的线程,可能不是同一个线程。

是不是这样呢?我们将在上一节直接上Java AIO的代码,我们从 实际代码中一窥究竟吧。

4、一个Java AIO的网络编程示例

4.1AIO服务端程序代码

publicclassAioServer {

 

    publicstaticvoidmain(String[] args) throwsIOException {

        System.out.println(Thread.currentThread().getName() + " AioServer start");

        AsynchronousServerSocketChannel serverChannel = AsynchronousServerSocketChannel.open()

                .bind(newInetSocketAddress("127.0.0.1", 8080));

        serverChannel.accept(null, newCompletionHandler<AsynchronousSocketChannel, Void>() {

 

            @Override

            publicvoidcompleted(AsynchronousSocketChannel clientChannel, Void attachment) {

                System.out.println(Thread.currentThread().getName() + " client is connected");

                ByteBuffer buffer = ByteBuffer.allocate(1024);

                clientChannel.read(buffer, buffer, newClientHandler());

            }

 

            @Override

            publicvoidfailed(Throwable exc, Void attachment) {

                System.out.println("accept fail");

            }

        });

        System.in.read();

    }

}

 

publicclassClientHandler implementsCompletionHandler<Integer, ByteBuffer> {

    @Override

    publicvoidcompleted(Integer result, ByteBuffer buffer) {

        buffer.flip();

        byte[] data = newbyte[buffer.remaining()];

        buffer.get(data);

        System.out.println(Thread.currentThread().getName() + " received:"+ newString(data, StandardCharsets.UTF_8));

    }

 

    @Override

    publicvoidfailed(Throwable exc, ByteBuffer buffer) {

 

    }

}

4.2AIO客户端程序

publicclassAioClient {

    publicstaticvoidmain(String[] args) throwsException {

        AsynchronousSocketChannel channel = AsynchronousSocketChannel.open();

        channel.connect(newInetSocketAddress("127.0.0.1", 8080));

        ByteBuffer buffer = ByteBuffer.allocate(1024);

        buffer.put("Java AIO".getBytes(StandardCharsets.UTF_8));

        buffer.flip();

        Thread.sleep(1000L);

        channel.write(buffer);

 }

}

4.3异步的定义猜想结论

分别运行服务端和客户端程序:

在服务端运行结果里:

1)main线程发起serverChannel.accept的调用,添加了一个CompletionHandler监听回调,当有客户端连接过来时,Thread-5线程执行了accep的completed回调方法。

2)紧接着Thread-5又发起了clientChannel.read调用,也添加了个CompletionHandler监听回调,当收到数据时,是Thread-1的执行了read的completed回调方法。

这个结论和上面异步猜想一致:发起IO操作(例如accept、read、write)调用的线程,和最终完成这个操作的线程不是同一个,我们把这种IO模式称之AIO。

当然了,这样定义AIO只是为了方便我们理解,实际中对异步IO的定义可能更抽象一点。

5、 AIO示例引发思考1:“执行completed()方法的线程是谁创建、什么时候创建?”

一般,这样的问题,需要从程序的入口的开始了解,但跟线程相关,其实是可以从线程栈的运行情况来定位线程是怎么运行。

只运行AIO服务端程序,客户端不运行,打印一下线程栈(备注:程序在Linux平台上运行,其他平台略有差异)。如下图所示。

分析线程栈,发现,程序启动了那么几个线程:

  • 1)线程Thread-0阻塞在EPoll.wait()方法上;
  • 2)线程Thread-1、Thread-2~Thread-n(n和CPU核心数量一致)从阻塞队列里take()任务,阻塞等待有任务返回。

此时可以暂定下一个结论:AIO服务端程序启动之后,就开始创建了这些线程,且线程都处于阻塞等待状态。

另外:发现这些线程的运行都跟epoll有关系!

提到epoll,我们印象中,Java NIO在Linux平台底层就是用epoll来实现的,难道Java AIO也是用epoll来实现么?

为了证实这个结论,我们从下一个问题来展开讨论。

6、 AIO示例引发思考2:AIO注册事件监听和执行回调是如何实现的?

带着这个问题,去阅读JDK分析源码时,发现源码特别的长,而源码解析是一项枯燥乏味的过程,很容易把阅读者给逼走劝退掉。

对于长流程和逻辑复杂的代码的理解,我们可以抓住它几个脉络,找出哪几个核心流程。

以注册监听read为例clientChannel.read(...),它主要的核心流程是:注册事件 -> 监听事件 -> 处理事件。

注册事件:

注:注册事件调用EPoll.ctl(...)函数,这个函数在最后的参数用于指定是一次性的,还是永久性。上面代码events | EPOLLONSHOT字面意思看来,是一次性的。

监听事件:

处理事件:

 

核心流程总结:

在分析完上面的代码流程后会发现:每一次IO读写都要经历的这三个事件是一次性的,也就是在处理事件完,本次流程就结束了,如果想继续下一次的IO读写,就得从头开始再来一遍。这样就会存在所谓的死亡回调(回调方法里再添加下一个回调方法),这对于编程的复杂度大大提高了。

7、 AIO示例引发思考3:监听回调的本质是什么?

7.1概述

先说一下结论:所谓监听回调的本质,就是用户态线程调用内核态的函数(准确的说是API,例如read、write、epollWait),该函数还没有返回时,用户线程被阻塞了。当函数返回时,会唤醒阻塞的线程,执行所谓回调函数。

对于这个结论的理解,要先引入几个概念。

7.2系统调用与函数调用

函数调用:找到某个函数,并执行函数里的相关命令。

系统调用:操作系统对用户应用程序提供了编程接口,所谓API。

系统调用执行过程:

  • 1)传递系统调用参数;
  • 2)执行陷入指令,用用户态切换到核心态(这是因为系统调用一般都需要再核心态下执行);
  • 3)执行系统调用程序;
  • 4)返回用户态。

7.3用户态和内核态之间的通信

用户态->内核态:通过系统调用方式即可。

内核态->用户态:内核态根本不知道用户态程序有什么函数,参数是啥,地址在哪里。所以内核是不可能去调用用户态的函数,只能通过发送信号,比如kill 命令关闭程序就是通过发信号让用户程序优雅退出的。

既然内核态是不可能主动去调用用户态的函数,为什么还会有回调呢,只能说这个所谓回调其实就是用户态的自导自演。它既做了监听,又做了执行回调函数。

7.4用实际例子验证结论

为了验证这个结论是否有说服力,举个例子:平时开发写代码用的IntelliJ IDEA,它是如何监听鼠标、键盘事件和处理事件的。

按照惯例,先打印一下线程栈,会发现鼠标、键盘等事件的监听是由“AWT-XAWT”线程负责的,处理事件则是“AWT-EventQueue”线程负责。如下图所示。

定位到具体的代码上:可以看到“AWT-XAWT”正在做while循环,调用waitForEvents函数等待事件返回。如果没有事件,线程就一直阻塞在那边。如下图所示。

8、Java AIO的本质是什么?

8.1Java AIO的本质,就是只在用户态实现了异步

由于内核态无法直接调用用户态函数,Java AIO的本质,就是只在用户态实现异步,并没有达到理想意义上的异步。

1)理想中的异步:

何谓理想意义上的异步?这里举个网购的例子。

两个角色,消费者A、快递员B:

  • 1)A在网上购物时,填好家庭地址付款提交订单,这个相当于注册监听事件;
  • 2)商家发货,B把东西送到A家门口,这个相当于回调。

A在网上下完单,后续的发货流程就不用他来操心了,可以继续做其他事。B送货也不关心A在不在家,反正就把货扔到家门口就行了,两个人互不依赖,互不相干扰。

假设A购物是用户态来做,B送快递是内核态来做,这种程序运行方式过于理想了,实际中实现不了。

2)现实中的异步:

A住的是高档小区,不能随意进去,快递只能送到小区门口。

A买了一件比较重的商品,比如一台电视,因为A要上班不在家里,所以找了一个好友C帮忙把电视搬到他家。

A出门上班前,跟门口的保安D打声招呼,说今天有一台电视送过来,送到小区门口时,请电话联系C,让他过来拿。

具体就是:

  • 1)此时,A下单并跟D打招呼,相当于注册事件。在AIO中就是EPoll.ctl(...)注册事件;
  • 2)保安在门口蹲着相当于监听事件,在AIO中就是Thread-0线程,做EPoll.wait(..);
  • 3)快递员把电视送到门口,相当于有IO事件到达;
  • 4)保安通知C电视到了,C过来搬电视,相当于处理事件(在AIO中就是Thread-0往任务队列提交任务,Thread-1 ~n去取数据,并执行回调方法)。

整个过程中,保安D必须一直蹲着,寸步不能离开,否则电视送到门口,就被人偷了。

好友C也必须在A家待着,受人委托,东西到了,人却不在现场,这有点失信于人。

所以实际的异步和理想中的异步,在互不依赖,互不干扰,这两点相违背了。保安的作用最大,这是他人生的高光时刻。

异步过程中的注册事件、监听事件、处理事件,还有开启多线程,这些过程的发起者全是用户态一手操办。所以说Java AIO本质只是在用户态实现了异步,这个和BIO、NIO先阻塞,阻塞唤醒后开启异步线程处理的本质一致。

8.2Java AIO的其它真相

Java AIO跟NIO一样:在各个平台的底层实现方式也不同,在Linux是用epoll、Windows是IOCP、Mac OS是KQueue。原理是大同小异,都是需要一个用户线程阻塞等待IO事件,一个线程池从队列里处理事件。

Netty之所以移除掉AIO:很大的原因是在性能上AIO并没有比NIO高。Linux虽然也有一套原生的AIO实现(类似Windows上的IOCP),但Java AIO在Linux并没有采用,而是用epoll来实现。

Java AIO不支持UDP。

AIO编程方式略显复杂,比如“死亡回调”。

9、参考资料

[1] 少啰嗦!一分钟带你读懂Java的NIO和经典IO的区别

[2] 史上最强Java NIO入门:担心从入门到放弃的,请读这篇!

[3] Java的BIO和NIO很难懂?用代码实践给你看,再不懂我转行!

[4] Java新一代网络编程模型AIO原理及Linux系统AIO介绍

[5] 从0到1的快速裂变:详解快的打车架构设计及技术实践

[6] 新手入门:目前为止最透彻的的Netty高性能原理和框架架构解析

[7] 史上最通俗Netty框架入门长文:基本介绍、环境搭建、动手实战

[8] 高性能网络编程(五):一文读懂高性能网络编程中的I/O模型

[9] 高性能网络编程(六):一文读懂高性能网络编程中的线程模型

[10] 高性能网络编程(七):到底什么是高并发?一文即懂!

[11] 从根上理解高性能、高并发(二):深入操作系统,理解I/O与零拷贝技术

[12] 从根上理解高性能、高并发(三):深入操作系统,彻底理解I/O多路复用

[13] 从根上理解高性能、高并发(四):深入操作系统,彻底理解同步与异步

[14] 从根上理解高性能、高并发(五):深入操作系统,理解高并发中的协程

(本文已同步发布于:http://www.52im.net/thread-4283-1-1.html

posted @ 2023-06-21 11:19 Jack Jiang 阅读(272) | 评论 (0)编辑 收藏

为了更好地分类阅读 52im.net 总计1000多篇精编文章,我将在每周三推送新的一期技术文集,本次是第17 期。

[- 1 -] 社交软件红包技术解密(一):全面解密QQ红包技术方案——架构、技术实现等

[链接] http://www.52im.net/thread-2202-1-1.html

[摘要] 本文将从架构开始,到手机 QQ 移动端优化,再到个性化红包和 AR 新玩法,为大家全面解密 QQ 红包技术方案。


[- 2 -] 社交软件红包技术解密(二):解密微信摇一摇红包从0到1的技术演进

[链接http://www.52im.net/thread-2519-1-1.html

[摘要今天下午跟大家分享的主题是:微信团队是如何从0到1实现“有把握”的微信春晚摇一摇红包系统的。


[- 3 -] 社交软件红包技术解密(三):微信摇一摇红包雨背后的技术细节

[链接http://www.52im.net/thread-2533-1-1.html

[摘要本文将由微信团队工程师张文瑞分享微信春节摇一摇红包技术背后的方方面面,希望能给同行们带来启发。


[- 4 -]社交软件红包技术解密(四):微信红包系统是如何应对高并发的

[链接] http://www.52im.net/thread-2548-1-1.html

[摘要本文将为读者介绍微信百亿级别红包背后的高并发设计实践,内容包括微信红包系统的技术难点、解决高并发问题通常使用的方案,以及微信红包系统的所采用高并发解决方案。


[- 5 -] 社交软件红包技术解密(五):微信红包系统是如何实现高可用性的

[链接http://www.52im.net/thread-2564-1-1.html

[摘要本次分享介绍了微信红包后台系统的高可用实践经验,主要包括后台的 set 化设计、异步化设计、订单异地存储设计、存储层容灾设计与平行扩缩容等。听众可以了解到微信红包后台架构的设计细节,共同探讨高可用设计实践上遇到的问题与解决方案。


[- 6 -] 社交软件红包技术解密(六):微信红包系统的存储层架构演进实践

[链接http://www.52im.net/thread-2568-1-1.html

[摘要] 微信红包本质是小额资金在用户帐户流转,有发、抢、拆三大步骤。在这个过程中对事务有高要求,所以订单最终要基于传统的RDBMS,这方面是它的强项,最终订单的存储使用互联网行业最通用的MySQL数据库。支持事务、成熟稳定,我们的团队在MySQL上有长期技术积累。但是传统数据库的扩展性有局限,需要通过架构解决。


[- 7 -] 社交软件红包技术解密(七):支付宝红包的海量高并发技术实践

[链接http://www.52im.net/thread-2573-1-1.html

[摘要本文将为读者剖析支付宝红包系统背后的技术细节。


[- 8 -]  社交软件红包技术解密(八):全面解密微博红包技术方案

[链接http://www.52im.net/thread-2576-1-1.html

[摘要在服务器数量一定的情况下,如何构建高并发操作、瞬间峰值高的稳定服务?对于团队和架构师都是一个极大的挑战。这时候系统的架构尤为重要!本文将为你分享这些内容。


[- 9 -] 社交软件红包技术解密(九):谈谈手Q红包的功能逻辑、容灾、运维、架构等

[链接http://www.52im.net/thread-2583-1-1.html

[摘要本文将会详细介绍手Q春节红包项目的功能设计/逻辑、容灾、运维、架构以及实践总结。


[- 10 -] 社交软件红包技术解密(十):手Q客户端针对2020年春节红包的技术实践

[链接] http://www.52im.net/thread-2966-1-1.html

[摘要对于这种大体量的IM社交应用运营活动,技术上除了前端、后台的大力支撑,对于手Q客户端来说,又是从哪些方面来保证整个红包活动的灵活性、稳定性和用户体验的呢?带着这个问题,我们一起来阅读余下的文字。


[- 11 -] 社交软件红包技术解密(十一):解密微信红包随机算法(含代码实现)

[链接] http://www.52im.net/thread-3125-1-1.html

[摘要本文根据有限的资料,分享了微信红包随机算法实现中的一些技术要点,并整理了两种比较靠谱的红包算法实现思路(含可运行的实现代码),希望能给你的红包算法开发带来启发。


[- 12 -] 社交软件红包技术解密(十二):解密抖音春节红包背后的技术设计与实践

[链接http://www.52im.net/thread-3945-1-1.html

[摘要本文将要分享的是春节期间海量红包社交活动为抖音所带来的各种技术挑战,以及抖音技术团队是如何在实践中一一解决这些问题的。


👉52im社区本周新文:《到底什么是Java AIO?为什么Netty会移除AOI?一文搞懂AIO的本质!http://www.52im.net/thread-4283-1-1.html》,欢迎阅读!👈

我是Jack Jiang,我为自已带盐!https://github.com/JackJiang2011/MobileIMSDK/

posted @ 2023-06-19 13:40 Jack Jiang 阅读(99) | 评论 (0)编辑 收藏

► 相关链接:

一、技术准备

您是否已对Web端即时通讯技术有所了解?

您需要对WebSocket技术有所了解:

WebSocket标准文档、API手册:

二、开发工具准备

1)WebStorm:

(JackJiang 使用的版本号如上图所示,建议你也使用此版或较新版本)

2)一站式下载地址:WebStorm官方下载地址点此进入

三、工程文件用途说明

3.1文件概览

纯原生JS实现,无任何重框架依赖:

MobileIMSDK-H5端SDK本身只是JS文件源码的集合,本工程中自带的前端Demo的目的只是为了方便随时测试MobileIMSDK-H5端的SDK代码而已,在此工程中的使用也仅仅只涉及了一个主Demo页面而已。

工程目录说明:

3.2详细说明

SDK 各模块/文件作用说明:

四、主要 API 接口

4.1主要 API 接口概览

如下图所示:所有 SDK 接口均由/mobileimsdk/mobileimsdk-client-sdk.js 提供。,接口设计跟MobileIMSDK 的APP版一样,均为高内聚和低侵入的回调方式传入SDK处理逻辑,无需(也不建议)开发者直接修改sdk级代码。

▲ 图上为浏览器端SDK的对外接口文件位置

▲ 图上为浏览器SDK为开发者提供的回调接口

▲ 图上浏览器端SDK的对外接口文件全图

4.2主要 API 接口用途说明

1)IMSDK.isLogined():

  • 用途:是否已经完成过首次登陆。
  • 说明 :用户一旦从自已的应用中完成登陆IM服务器后,本方法就会一直返回true(直到退出登陆IM)。
  • 返回值:{boolean},true表示已完成首次成功登陆(即已经成功登陆过IM服务端了,后面掉线时不影响此标识),否则表示尚未连接IM服务器。

2)IMSDK.isOnline():

  • 用途:是否在线。
  • 说明 :表示网络连接是否正常。
  • 返回值:{boolean},true表示网络连接正常,否则表示已掉线,本字段只在this._logined=true时有意义(如果都没有登陆到IM服务器,怎么存在在线或掉线的概念呢)。

3)IMSDK.getLoginInfo():

  • 用途:返回登陆时提交的登陆信息(用户名、密码/token等)。
  • 说明 :格式形如:{loginUserId:'',loginToken:''},此返回值的内容由调用登陆函数 loginImpl()时传入的内容决定。字段定义详见:PLoginInfo
  • 返回值:{boolean},true表示网络连接正常,否则表示已掉线,本字段只在this._logined=true时有意义(如果都没有登陆到IM服务器,怎么存在在线或掉线的概念呢)。

4)IMSDK.sendData(p, fnSucess, fnFail, fnComplete):

  • 用途:向某人发送一条消息。
  • 参数p:{Protocal} 要发送的消息协议包对象,Protocal详情请见“/module/mb_constants.js”下的createCommonData函数说明。
  • 返回值:{int} 0表示成功,否则表示错误码,错码详见“/module/mb_constants.js”下的MBErrorCode对象属性说明。

5)IMSDK.disconnectSocket():

  • 用途:客户端主动断开客户端socket连接。
  • 说明 :当开发者登陆IM后,需要退出登陆时,调用本函数就对了,本函数相当于登陆函数 loginImpl()的逆操作。

6)IMSDK.setDebugCoreEnable(enable):

  • 用途:是否开启MobileIMSDK-Uniapp端核心算法层的log输入,方便开发者调试。
  • 参数enable :{boolean} true表示开启log输出,否则不输出,开发者不调用本函数的话系统默认是false(即不输出log)。

7)IMSDK.setDebugSDKEnable(enable):

  • 用途:是否开启MobileIMSDK-Uniapp端框架层的log输入,方便开发者调试。
  • 参数enable :{boolean} true表示开启log输出,否则不输出,开发者不调用本函数的话系统默认是false(即不输出log)。

8)IMSDK.setDebugPingPongEnable(enable):

  • 用途:是否开启MobileIMSDK-Uniapp端框架层的底层网络WebSocket心跳包的log输出,方便开发者调试。
  • 参数enable :{boolean} true表示开启log输出,否则不输出,开发者不调用本函数的话系统默认是false(即不输出log)。
  • 注意:必须 setDebugEnable(true) 且 setDebugPingPongEnable(true) 时,心跳log才会真正输出,方便控制。
  • 返回值:true表示开启log输出,否则不输出,开发者不调用本函数的话系统默认是false(即不输出log)。

9)IMSDK.loginImpl(varloginInfo, wsUrl):

  • 用途:登陆/连接MobileIMSDK服务器时调用的方法。
  • 说明 :登陆/连接MobileIMSDK服务器由本函数发起
  • 参数varloginInfo:{PLoginInfo} 必填项,登陆要提交给Websocket服务器的认证信息,不可为空,对象字段定义见:PLoginInfo
  • 参数wsUrl:{string} 必填项:要连接的Websocket服务器地址,不可为空,形如:wss://yousite.net:3000/websocket。

10)IMSDK.callback_onIMLog(message, toConsole):

  • 用途:由开发者设置的回调方法:用于debug的log输出。
  • 推荐用法 :开发者可在此回调中按照自已的意图打印MobileIMSDK微信小程序端框架中的log,方便调试时使用。
  • 参数1: {String}:必填项,字符串类型,表示log内容。
  • 参数2: {boolean}:选填项,true表示输出到console,否则默认方式(由开发者设置的回调决定)。

11)IMSDK.callback_onIMData(p, options):

  • 用途:由开发者设置的回调方法:用于收到聊天消息时在UI上展现出来(事件通知于收到IM消息时)。
  • 推荐用法:开发者可在此回调中处理收到的各种IM消息。
  • 参数1: {Protocal}:详情请见“/module/mb_constants.js”下的Protocal类定义)。

12)IMSDK.callback_onIMAfterLoginSucess():

  • 用途:由开发者设置的回调方法:客户端的登陆请求被服务端成功认证完成后的回调(事件通知于 登陆/认证 成功后)。
  • 推荐用法 :开发者可在此回调中进行登陆IM服务器成功后的处理。

13)IMSDK.callback_onIMAfterLoginFailed(isReconnect):

  • 用途:由开发者设置的回调方法:客户端的登陆请求被服务端认证失败后的回调(事件通知于 登陆/认证 失败后)。
  • 说明 :补充说明:登陆/认证失败的原因可能是用户名、密码等不正确等,但具体逻辑由服务端的 callBack_checkAuthToken回调函数去处理。
  • 推荐用法:开发者可在此回调中提示用户登陆IM服务器失败。。
  • 参数1: {boolean}:true表示是掉线重连后的认证失败(在登陆其间可能用户的密码信息等发生了变更),否则表示首次登陆时的认证失败。

14)IMSDK.callback_onIMReconnectSucess():

  • 用途:由开发者设置的回调方法:掉线重连成功后的回调(事件通知于掉线重连成功后)。
  • 推荐用法 :开发者可在此回调中处理掉线重连成功后的界面状态更新等,比如设置将界面上的“离线”文字更新成“在线”。

15)IMSDK.callback_onIMDisconnected():

  • 用途:由开发者设置的回调方法:网络连接已断开时的回调(事件通知于与服务器的网络断开后)。
  • 推荐用法 :开发者可在此回调中处理掉线时的界面状态更新等,比如设置将界面上的“在线”文字更新成“离线”。

16)IMSDK.callback_onIMPing():

  • 用途:由开发者设置的回调方法:本地发出心跳包后的回调通知(本回调并非MobileIMSDK-Uniapp端核心逻辑,开发者可以不需要实现!)。
  • 推荐用法 :开发者可在此回调中处理底层网络的活动情况。

17)IMSDK.callback_onIMPong():

  • 用途:由开发者设置的回调方法:收到服务端的心跳包反馈的回调通知(本回调并非MobileIMSDK-Uniapp端核心逻辑,开发者可以不需要实现!)。
  • 推荐用法 :开发者可在此回调中处理底层网络的活动情况。

18)IMSDK.callback_onIMShowAlert(alertContent):

  • 用途:由开发者设置的回调方法:框架层的一些提示信息显示回调(本回调并非MobileIMSDK-Uniapp端核心逻辑,开发者可以不需要实现!)。
  • 说明 :开发者不设置的情况下,框架默认将调用wx.showModal()显示提示信息,否则将使用开发者设置的回调——目的主要是给开发者自定义这种信息的UI显示,提升UI体验,别无它用】。
  • 参数1:{String}:必填项,文本类型,表示提示内容。

19)IMSDK.callback_onIMKickout(kickoutInfo):

  • 用途:由开发者设置的回调方法:收到服务端的“踢出”指令(本回调并非MobileIMSDK-Uniapp端核心逻辑,开发者可以不需要实现!)。
  • 参数1 :{PKickoutInfo}:非空,详见:PKickoutInfo

20)IMSDK.callback_onMessagesLost(lostMessages):

  • 用途:由开发者设置的回调方法:消息未送达的回调事件通知。
  • 发生场景 :比如用户刚发完消息但网络已经断掉了的情况下,表现形式如:就像手机qq或微信一样消息气泡边上会出现红色图标以示没有发送成功)。
  • 建议用途:应用层可通过回调中的指纹特征码找到原消息并可以UI上将其标记为“发送失败”以便即时告之用户。
  • 参数1:{Array<rotocal>}:由框架的QoS算法判定出来的未送达消息列表。

21)IMSDK.callback_onMessagesBeReceived(theFingerPrint):

  • 用途:由开发者设置的回调方法:消息已被对方收到的回调事件通知。
  • 说明 :目前,判定消息被对方收到是有两种可能:
  • 1) 对方确实是在线并且实时收到了;
  • 2) 对方不在线或者服务端转发过程中出错了,由服务端进行离线存储成功后的反馈(此种情况严格来讲不能算是“已被收到”,但对于应用层来说,离线存储了的消息原则上就是已送达了的消息:因为用户下次登陆时肯定能通过HTTP协议取到)。
  • 建议用途:应用层可通过回调中的指纹特征码找到原消息并可以UI上将其标记为“发送成功”以便即时告之用户。
  • 参数1:{String}:已被收到的消息的指纹特征码(唯一ID),应用层可据此ID找到原先已发的消息并可在UI是将其标记为”已送达“或”已读“以便提升用户体验。

五、前端开发指南

5.1如何引入SDK文件到您的前端工程中?

很简单:只需要将第2节中提到的SDK所有JS文件复制到您的Uniapp工程下即可。

SDK内容见下图:

5.2如何在代码中调用SDK?

第一步:在你的网页中引用SDK的js文件(具体例子详见Demo中的index.html文件)

第二步:直接在你的JS文件中编写回调配置代码具体例子详见Demo中的index.js文件

第三步:在你的JS文件中调用IM的登陆方法即可具体例子详见Demo中的index.js文件

注意:上图中登录连接的IP地址请设置为您的MobileIMSDK服务器地址哦。

六、Demo运行方法(在WebStorm中直接预览)

6.1重要说明

特别说明:MobileIMSDK的H5端(包括Demo在内),全部是静态的HTML+JS资源,可以通过WebStorm自带的HTML页面预览功能,直接自动加载到电脑的浏览器中运行和预览。

6.2预览方法

1)在Demo中的index.html文件中,移动鼠标,会在右上角出现如下图所示的浮出菜单:

2)点击右上角浮出菜单上相应的浏览器就可以自动预览了这里以我电脑上已安装的Edge浏览器为例):

七、Demo运行方法(在Web服务器中部署并访问)

7.1重要说明

特别说明:MobileIMSDK的H5端(包括Demo在内),全部是静态的HTML+JS资源,对于服务端是没有任何依赖的,只需要保证浏览器端能加载到即可,可以把它们放置在Tomcat、Apache、IIS、Nginx等等传统Web服务器中即可,无需任何动态运行环境。

7.2安装Tomcat

提示:以下Demo的部署,以Java程序员最常用和Tomcat为例(Apache、IIS、Nginx等依此类推)。

Tomcat的安装就没什么好说的,直接官网下载对应的版本即可:https://tomcat.apache.org/download-90.cgi

7.3配置要连接的MobileIMSDK服务器IP

注意:下图中登陆连接的IP地址请设置为您的MobileIMSDK服务器地址哦。

友情提示: MobileIMSDK的服务端该怎么部署就不是本手册要讨论的内容了,你可以参见《即时通讯框架MobileIMSDK的Demo使用帮助:Server端》。

▲ 配置要连接的服务器IP以上代码详见demo/index.js 文件

7.4部署Demo

说“部署”有点扯蛋,因为Demo(包括SDK)在内,全是HTML静态内容,只需要直接复制到任何一种Web服务器即可。

以下是复制到Tomcat服务器网页目录后的截图:

7.5启动Tomcat

提示:本手册中仅以启Tomcat为例,Apache、IIS、Nginx等Web服务器的启动请自动百度。

运行startup.bat启动Tomcat:

7.6Demo的运行效果预览

八、Demo功能预览和说明

九、Demo运行效果实拍图

1)Demo在手机端浏览器中的真机实拍图:

2)Demo在电脑端浏览器中的真机实拍图:

十、更多Demo运行效果截图

1)Demo在PC端浏览器运行效果:

2)Demo在手机端浏览器运行效果:

 

3)Demo在PC端各主流浏览器的运行效果:

十一、常见问题(FAQ)

11.1为什么浏览控制台下有些log不显示?

原因是浏览器控制台下的日志级别默认进行了过滤,勾选所有日志级别,就能看到SDK的详细日志输出了。

勾选所有的日志输出级别:

然后就能看到SDK中详细的日志输出了(就像下图这样),方便调试和研究:

十二、引用资料

[1] WebSocket 标准API手册

[2] MobileIMSDK开源框架的API文档

[3] MobileIMSDK开源IM框架源码Github地址点此

[4] MobileIMSDK-H5端基本介绍

[5] MobileIMSDK-H5端的开发手册(* 精编PDF版)

[6] MobileIMSDK的Demo使用帮助:Server端

[7] WebSocket从入门到精通,半小时就够!

posted @ 2023-06-15 11:55 Jack Jiang 阅读(124) | 评论 (0)编辑 收藏

一、关于RainbowChat-Web

RainbowChat-Web是一套Web网页端IM系统,是RainbowChat的姊妹系统(RainbowChat是一套基于开源IM聊天框架 MobileIMSDK(Github地址) 的产品级移动端IM系统)。

二、v5.0 版更新内容

此版更新内容更多历史更新日志):

  • 1)[bug][前端]     - 解决了当首页“消息”无item时,从好友列表中删除某人时不自动清空聊天面板和右侧详情面板的问题;
  • 2)[bug][前端]     - 解决了处于群列表Tab时,退群或解散群不会更新群列表中“当前群聊”数字的问题 ;
  • 3)[bug][前端]     - 解决了处于群列表Tab时,点击创建群聊后,不会在群聊列表中自动选中此创建的群的问题;
  • 4)[优化]             - 升级核心通信层框架MobileIMSDK-Web至最新v5.1版;
  • 5)[优化][前端]    - 优化了当发送名片消息时,如名片者未设置头像,则在聊天消息界面中显示默认头像(提升体验);
  • 6)[优化][服务端] - 进一步加固了uid登陆时的sql注入风险;
  • 7)[优化][服务端] - 解决与最新rabbitmq-client库不兼容从而断线重连不成功,导致MQ中消息堆积的问题:
  • 8)[优化][服务端] - 解决MQ断线自动恢复时消费者Chennal未主动清理,导致空channel越来越多的问题;
  • 9)[优化][前端]    - 解决了被踢出群的情况下,仍能退群、邀请别人入群等问题;
  • 10)[优化][前端]  - 解决了高版本Tomcat下文件名中包含了特殊符号的大文件无法下载的问题。
  • 11)[新增][前端]  - 聊天区上方实现了聊天对象信息的显示(可显示昵称、群名称等信息);
  • 12)[新增][前端]  - 新增了消息送达状态图标的显示(包括发送中、发送成功、发送失败3种状态)。

三、v5.0 版新增特性截图

聊天区上方聊天对象信息的演示运行截图更多运行截图):

消息送达状态的演示运行截图更多运行截图):

四、主要界面截图概览

 ▲ 主界面(更多截图更多演示视频

▲ 主界面(聊天窗全屏时)(更多截图更多演示视频

▲ 主界面(聊天窗关闭时)(更多截图更多演示视频 

posted @ 2023-06-12 12:27 Jack Jiang 阅读(99) | 评论 (0)编辑 收藏

     摘要: 本文由will分享,个人博客zhangyaoo.github.io,原题“基于Netty的IM系统设计与实现”,有修订和重新排版。1、引言本文将要分享的是如何从零实现一套基于Netty框架的分布式高可用IM系统,它将支持长连接网关管理、单聊、群聊、聊天记录查询、离线消息存储、消息推送、心跳、分布式唯一ID、红包、消息同步等功能,并且还支持集群部署。本文中针对这套架构和系统设...  阅读全文

posted @ 2023-06-08 14:57 Jack Jiang 阅读(153) | 评论 (0)编辑 收藏

为了更好地分类阅读 52im.net 总计1000多篇精编文章,我将在每周三推送新的一期技术文集,本次是第16 期。

[- 1 -] 浅谈IM系统的架构设计

[链接] http://www.52im.net/thread-307-1-1.html

[摘要] 下面把我近年来从技术上我对IM系统(即时消息的传输,不包括语音,视频,文件的传输)的理解和设计分享出来,浅薄之见,望大家别见笑,欢迎给出批评意见。


[- 2 -] 简述移动端IM开发的那些坑:架构设计、通信协议和客户端

[链接] http://www.52im.net/thread-289-1-1.html

[摘要] 有过移动端开发经历的开发者都深有体会:移动端IM的开发,与传统PC端IM有很大的不同,尤其无线网络的不可靠性、移动端硬件设备资源的有限性等问题,导致一个完整的移动端IM架构设计和实现都充满着大量的挑战。本文将简述移动端IM最重要的架构设计和通信协议选择方面的坑点,希望为IM开发者同行带来些许启发。


[- 3 -] 一套海量在线用户的移动端IM架构设计实践分享(含详细图文

[链接] http://www.52im.net/thread-812-1-1.html

[摘要] 本文分享了一套完整的海量在线用户的移动端IM架构设计,来自于作者的真实项目实践总结,包含了详细的算法原理图、数据结构定义、表结构定义等等。


[- 4 -] 一套原创分布式即时通讯(IM)系统理论架构方案

[链接] http://www.52im.net/thread-151-1-1.html

[摘要] 无论是IM消息通信系统还是客户消息系统,其本质都是一套消息发送与投递系统,或者说是一套网络通信系统,其本质两个词:存储与转发。推荐:如有兴趣,本文作者的另一篇《一套高可用、易伸缩、高并发的IM群聊架构方案设计实践》,适合进行IM群聊架构设计的参考。


[- 5 -] 从零到卓越:京东客服即时通讯系统的技术架构演进历程

[链接] http://www.52im.net/thread-152-1-1.html

[摘要] 京东的客服即时通讯系统名为咚咚是。咚咚之于京东相当于旺旺之于淘宝,它们都是服务于买家和卖家的沟通。自从京东开始为第三方卖家提供入驻平台服务后,咚咚也就随之诞生了。


[- 6-] 蘑菇街即时通讯/IM服务器开发之架构选择

[链接] http://www.52im.net/thread-31-1-1.html

[摘要] 由于IM服务器里面的内容比较多,这个可以是一个系列的内容,所以这里只介绍服务器的架构以及为什么选择这样的架构。


[- 7 -] 腾讯QQ1.4亿在线用户的技术挑战和架构演进之路PPT

[链接] http://www.52im.net/thread-158-1-1.html

[摘要] 众所周知海量互联网服务能力是世界公认的技术难题。经过十多年的发展,腾讯在海量互联网服务方面已有不少技术积累。PPT中以QQ IM后台服务为例,重现了QQ在线用户从百万级到亿级的整个过程中遇到的技术挑战,并与与会者分享了众多在海量互联网后台服务研发运营方面不为人知的秘密。


[- 8-]  微信后台基于时间序的海量数据冷热分级架构设计实践

[链接] http://www.52im.net/thread-895-1-1.html

[摘要] 时隔3年,微信团队再次分享了本文所述架构的最新升级版本及其改造过程,有兴趣可以前往阅读《微信后台基于时间序的新一代海量数据存储架构的设计实践》。


[- 9 -] 微信技术总监谈架构:微信之道——大道至简(演讲全文)

[链接] http://www.52im.net/thread-200-1-1.html

[摘要] 微信——腾讯战略级产品,创造移动互联网增速记录,10个月5000万手机用户,433天之内完成用户数从零到一亿的增长过程,千万级用户同时在线,摇一摇每天次数过亿...在技术架构上,微信是如何做到的?日前,在腾讯大讲堂在中山大学校园宣讲活动上,腾讯广研助理总经理、微信技术总监周颢在两小时的演讲中揭开了微信背后的秘密。


[- 10-] 如何解读《微信技术总监谈架构:微信之道——大道至简》

[链接] http://www.52im.net/thread-201-1-1.html

[摘要] 最近在朋友圈看到有人分享腾讯微信技术总监周颢的一个技术报告,题目是《微信技术总监谈架构:微信之道——大道至简》(演讲全文整理、演讲PPT讲稿下载),我也转发了一下。然后就被本司妹子看到了,非让我解释一下。


[- 11-] 快速裂变:见证微信强大后台架构从0到1的演进历程(一)

[链接] http://www.52im.net/thread-168-1-1.html

[摘要] 2个月的开发时间,微信后台系统经历了从0到1的过程。从小步慢跑到快速成长,经历了平台化到走出国门,微信交出的这份优异答卷,解题思路是怎样的?


[- 12-] 17年的实践:腾讯海量产品的技术方法论

[链接] http://www.52im.net/thread-159-1-1.html

[摘要] 在首届腾讯云技术峰会上,腾讯公司副总裁姚星完整的介绍了腾讯整体技术发展脉络。


[- 13-] 移动端IM中大规模群消息的推送如何保证效率、实时性?

[链接] http://www.52im.net/thread-1221-1-1.html

[摘要] 当然,实际在生产环境下,群消息的发送都会想尽办法进行压缩,并开展各种改善性能的处理办法,而不是像上述举例里的直接扩散写(即2000人群里,一条消息被简单地复制为2000条一对一的消息投递)。具体有哪些优先策略?本文或许可以带给你一些启发。


[- 14-] 现代IM系统中聊天消息的同步和存储方案探讨

[链接] http://www.52im.net/thread-1230-1-1.html

[摘要] 本文内容主要涉及IM系统中的消息系统架构,探讨一种适用于大用户量的消息同步以及存储系统的架构实现,能够支持消息系统中的高级特性『多端同步』以及『消息漫游』。在性能和规模上,能够做到全量消息云端存储,百万TPS以及毫秒级延迟的消息同步能力。


[- 15-]WhatsApp技术实践分享:32人工程团队创造的技术神话

[链接] http://www.52im.net/thread-1542-1-1.html

[摘要] 我们再次回顾了当时HighScalability创始人Tod Hoff撰文分析的收购原因和WhatsApp的高可靠架构,内容虽然并不完整,以今天的眼前来看成,仍有有许多值得学习的地方。


[- 16-]微信朋友圈千亿访问量背后的技术挑战和实践总结

[链接]http://www.52im.net/thread-1569-1-1.html

[摘要] 朋友圈的数据是永远存储的,而且随着业务的快速发展,存储容量、带宽和设备的消耗大量增加,尤其重大节日带来的使用量增长,更加剧了消耗,也给运维人员的保障带来了巨大压力。


[- 17-]王者荣耀2亿用户量的背后:产品定位、技术架构、网络方案等

[链接] http://www.52im.net/thread-1595-1-1.html

[摘要] 今天分几部分和大家介绍王者后台开发过程中的一些内容和思考:包括王者整个背景的介绍,后端的架构,上线之后做了什么样的调整,还有网络同步方案,反作弊方案等。

👉52im社区本周新文:《跟着源码学IM(十一):一套基于Netty的分布式高可用IM详细设计与实现(有源码) http://www.52im.net/thread-4257-1-1.html》,欢迎阅读!👈

我是Jack Jiang,我为自已带盐!https://github.com/JackJiang2011/MobileIMSDK/

posted @ 2023-06-05 11:59 Jack Jiang 阅读(114) | 评论 (0)编辑 收藏

本文内容由百度技术团队分享,原题“基于公共信箱的全量消息实现”,为了帮助理解,有较多修订、内容重组和重新排版。

1、引言

百度的IM消息中台为百度APP以及厂内百度系产品提供即时通讯的能力,提供包括私聊、群聊、聊天室、直播弹幕等用户沟通场景,并帮助业务通过消息推送触达用户。

如今,百度APP新增了一种需要以“低用户打扰”的形式触达全量用户的场景需求,而现有的IM消息中台主要是基于用户“私有信箱”通知拆分的机制(通俗了说也就是IM里的“扩散写”),所以如果不进行改造,是很难低成本、高时效的满足该场景诉求。

基于上述问题,本文介绍了百度现有IM消息中台系统的主要组成,并对比多种实现方案的优劣,以“公有信箱”通知读扩散的技术方案对现有IM消息中台系统进行改造,从而达成了低成本、高时效地实现全量用户通知推送需求。

技术交流:

本文已同步发布于:http://www.52im.net/thread-4235-1-1.html

2、全量用户消息推送需求背景

百度APP新增了需要通过IM实时通知触达全量用户的诉求,比如2022年12月7日解除疫情管控结束后,将经过筛选的官方政策解读、专题汇总、知识科普、实用工具类介绍等信息,通过官方号“x度小助手”下发触达到百度APP用户,从而来有效体现人文关怀,提高用户粘性。

在以IM消息服务进行全量用户消息触达时,需要满足以下诉求:

具体就是:

  • 1)在触达范围上:希望尽量扩大用户触达范围,包括百度APP月活用户、以及非月活用户但是近期新注册或登录的用户;
  • 2)在时效上:一次全量触达,希望短时间内完成(比如小时级、甚至分钟级),抢占时效性;
  • 3)在用户打扰方面:消息触达不能给用户带来较大的打扰,每次消息下发,只触达一次,不能重复打扰用户(但是需要保留回访入口,满足用户二次查看的诉求)。

3、现有IM消息中台的技术痛点

我们现有的IM(即时通讯)服务中,每个IM用户对应一个用户信箱。

基于现有的IM技术实现方案,如果想完成全量用户的消息触达,需要把消息推送到每个用户的信箱(也就是IM中的扩散写)。

这样的话,要完成6亿以上的消息写入(假定每条占用存储4KB,每秒写入2W条消息),在消息写入时效性以及存储资源消耗上,都是很难接受的。

且现有的基于用户私有信箱的方案,在同时支持多条全量用户通知消息的场景下,扩展性也较差。

基于上述需求背景和技术痛点,我们本次的改造目的,就是要找到一种技术方案,从而在特定业务场景下通过改造后的消息服务,低成本、高时效的给全量用户推送内容一致的消息通知。

4、现有IM消息中台的主要技术实现

在讨论改造方案前,我们有必要介绍一下目前IM消息系统的现状,包括消息系统的组成、通知拉取模式、用户信箱等。

4.1 消息系统组成

从普通用户的直观体验上看,一个IM系统可以包括如下元素:

  • 1)用户主体;
  • 2)用户账号;
  • 3)账号关系;
  • 4)聊天会话;
  • 5)聊天消息。

用自然语言串一下以上元素就是:

  • 1)“用户主体”具有“用户账号”;
  • 2)“用户主体”具有头像、昵称等用户属性;
  • 3)“用户主体”通过“用户账号”登录IM系统,进行聊天;
  • 4)“用户账号”之间的关注、屏蔽、免打扰等构成“用户关系”;
  • 5)通过用户之间的互动环节可以产生“聊天消息”;
  • 6)聊天记录构成了一个“聊天会话”。

下面这张图可能更直观一些:

从集成消息服务的业务方角度看:

  • 1)一个IM系统可以包括消息客户端(消息客户端UI组件、消息SDK)和消息服务端;
  • 2)IM消息可以作为一种服务,嵌入到各业务系统中,为业务系统提供“实时交互”能力;
  • 3)业务通过集成IM服务,提升其用户体验;
  • 4)业务APP集成IM SDK,通过IM SDK与IM Server交互,完成用户上行通讯能力;
  • 5)业务APP Server通过与IM Server交互,完成通知下行触达用户。

下图为一个集成了IM SDK的业务架构图:

从使用场景来看,消息包括:

  • 1)“私信消息”(包括用户上下行消息);
  • 2)“通知消息”(业务方给用户推送的下行消息);
  • 3)“群聊”、“聊天室”;
  • 4)“直播间弹幕”等。

4.2 消息的通知拉取模式

百度的IM消息系统,采用通知拉取(notify-pull)模式来感知新消息、拉取新消息。

IM SDK登录时,与IM 服务端建立长连接(LCS, Long Connect Service),用户有新的消息时,通过长连接下发notify,实时通知用户的IM SDK。

实时notify不写用户信箱,因为noitfy不是消息(可以理解为提醒在线用户有新消息的信号),IM SDK根据这个信号,来服务端拉取消息。

业务方server或者其他用户给该用户发送消息后,经过IM业务处理模块,把消息写入接收者信箱,IM Server会根据用户的登录和路由信息,给消息接收者(私信场景下也包括“消息发送者”,用于消息的多端同步)发送新消息notify,接收到notify的IM设备,通过IM SDK来IM Server端拉取(pull)消息。

4.3 用户信箱介绍

为了暂存尚未拉取到IM SDK本地的离线消息,需要对消息进行服务端存储,而消息的离线存储是通过消息信箱服务完成的。

目前百度的IM用户消息信箱主要包括:

  • 1)用户私有信箱;
  • 2)群公共信箱(非下文提到的用户公共信箱);
  • 3)直播间弹幕mcast等。

用户信箱通过“消息所属应用”+“IM标识用户的唯一ID”来标识。

就一条消息而言:消息参与者有“消息发送者”和“消息接收者”,消息收发双方的信箱都是相互独立的(假设发送方删除了自己信箱的某一条消息,不会影响消息接受者信箱的消息)。

对于有查看历史消息诉求的一方来说:消息需要入该方的信箱,比如用户之间的私信(也就是一对一单聊)消息需要入发送者和接收者的信箱。

而对于全量用户消息通知的场景:消息不需要存储发送者信箱,而只需要存接收者的信箱。而用户的信箱排序,是基于信箱Timeline(详见《现代IM系统中聊天消息的同步和存储方案探讨》)。即消息在信箱内部基于时间线存储,每条消息对应一个unix 微秒时间戳(如第一条消息1679757323320865),用户进行信箱拉取时,基于时间范围正序或者逆序拉取。

如下为信箱Timeline的示例:

用户信箱中的每一条消息记录都包含四个主要部分:

  • 1)“消息ID”;
  • 2)“消息用户标识”;
  • 3)“消息通用属性”;
  • 4)“消息业务属性”。

下面详细介绍以上四个部分:

  • 1)消息ID:为unix微秒时间戳,不需要全局唯一,只需要特定用户信箱范围内唯一即可;
  • 2)消息用户标识:包括from_uid、to_uid、contacter;
  • 3)消息通用属性:包括create_time、expire、is_read;
  • 4)消息业务属性:包括category、type、priority、business_type、APP_id、msgkey、content等。

如下为一条消息记录示例:

5、全量用户消息推送技术方案选型

5.1 需求分析

目前百度的IM消息推送机制中,主要支持:

  • 1)单播:消息推送方式,每次给一个用户推送一条消息;
  • 2)批量单播:每次给小范围用户推送消息,比如30个;
  • 3)广播:基于关注关系的推送,如给全量粉丝推送。

上述三种消息推送机制推送的消息,均需要存储服务端的用户私有信箱。为了完成百度APP 6亿以上全量月活用户的消息推送,目前有三种可选的方案,接下来我们逐一分析。

5.2 方案1:全流程从通知入口推送

该种方式下:需要获取全量的月活用户列表,经过IM Server推送入口,给每一个用户推送疫情相关通知。

该通知写入到用户信箱时:

  • 1)若用户在线,在实时拉取该通知;
  • 2)若用户离线,再下次登录IM服务时,拉取离线通知。

该种方案下:推送行为会覆盖IM的全流程,推送的通知会进入每个月活用户的私有信箱,服务压力大。其中增量用户不会收到通知推送(这里增量用户指的是不在月活用户列表的用户)。

5.3 方案2:跳过通知入口直接写信箱

该种方式跳过IM消息推送流程中的中间环节,直接把通知消息写入用户信箱。

由于跳过了中间流程直接写入信箱,通知写入速度主要取决于信箱底层存储的压力承受情况。

该种方案下,同方案1一样,无法给用户发送实时通知,依赖用户IM SDK的主动消息拉取(断链后重新登录/新消息提醒拉取),无法给增量用户发送通知。

该方案由于跳过中间环节直接写信箱,风险较大,无法直接提供给业务方使用,不建议如此操作。

5.4 方案3:公有信箱实现机制

该种公有信箱机制的逻辑是把通知消息写入“公共信箱”。在用户消息拉取时,合并“用户私信信箱”+“公共信箱”的消息。

5.5 三种方案比较

方案1和2都是写扩散方式,基于现有“用户私有信箱”的机制,把通知消息写入每个接收通知的用户私有信箱。

方案2与方案1的差别主要是跳过了消息中间流程,可以避免因为中间环节负载瓶颈导致整体消息写入速度过低。

方案3是读扩散方式,消息不用再写入接收通知的用户私有信箱,而只需要在公共信箱存储一份。在用户拉取消息时,实时拉取公共信箱的消息。方案③中可以采用内存缓存方案,解决对公共信箱的读压力。

本质上来说:方案3与方案前两种相比,是用读成本(CPU)换写成本(存储)。

6、基于公有信箱技术方案的全量用户消息推送实现

6.1 概述

基于上述方案3的思路,我们进行基于公有信箱的全量消息设计与实现。

该种方案中包含两个主要流程:

  • 1)全量消息的管理;
  • 2)用户私有+公有信箱的拉取。

6.2 全量消息的管理

全量消息管理主要分为:

  • 1)运营O端操作平台:复用运营消息平台;
  • 2)全量消息处理服务:复用IM服务的连接层、逻辑处理层、信箱代理、信箱处理。

运营O端平台为运营同学提供可视化界面,可以对全量消息进行编辑、预发布、发布、修改、停止、撤回等操作。

具体就是:

  • 1)接入层:对接运营O端,进行参数校验、转发IM后端逻辑处理模块;
  • 2)逻辑处理层:进行全量消息的创建、修改、停止、删除、撤回等逻辑操作;
  • 3)信箱代理层:复用IM服务的信箱CRUD操作;信箱存储层公共信箱的底层存储。

全量消息管理流程:

6.3 用户信箱拉取

用户通过IM SDK,以长连接的方式,在逻辑处理层进行消息拉拉取。

在用户拉取信箱消息时,需要对“用户个人信箱”和“公有信箱”进行合并。于是每次用户信箱拉取,都需要进行信箱的合并拉取。

6.3.1)公共信箱内存缓存机制:

百度APP的IM用户,在IM SDK登录时需要拉取信箱中的消息。每次消息拉取时,需要检查公共信箱中是否有消息。

因此,公共信箱需要能抗住日常和峰值流量(拉取峰值为4.7Wqps)。为了防止流量击穿,流量打到底层的持久化公共信箱MYSQL存储,我们设计了基于内存的公共信箱缓存机制。同时公共信箱内容变化时,也要实时(或者在能容忍的范围内做到准实时)变更内存缓存信箱中的消息,我们采用Bthread定期轮询持久化公共信箱,更新内存公共信箱,轮询间隔可配置(比如设置1秒)。

6.3.2)分级发布机制:

同时,在逻辑层实现白名单机制,支持全量消息在“预发布”状态下,仅对白名单用户可见,从而达到分级验证的效果。

白名单的用户列表通过逻辑处理成的配置加载,也支持通过CURL请求动态修改白名单的配置。

7、基于公有信箱技术方案的技术挑战

公有信箱的技术方案,需要解决如下问题:

8、基于公有信箱技术方案的优缺点总结

8.1 优点

以公共信箱的方式,实现全量用户消息分发,具有:“分发速度快”、“资源成本低”的特点。

8.2 缺点

但公共信箱的方式也存在一定的局限性。

8.2.1)不适用于个性化要求高的场景:

由于消息在公共信箱只存储一份,下发消息内容固定,无法很大程度下,下发个性化消息(当然也不是一定无法下发个性化的消息,可以通过在公共信箱存储消息模板,根据拉取消息的用户ID获取个性化信息,在消息拉取时,临时拼装消息,这样就增大了消息拉取时的代价)。

8.2.2)不适用于实时消息提醒场景:

1)从业务场景上看:全量消息优先级低,不需要在全量生效的瞬间让用户感知。

2)从实现上看:全量消息实时消息提醒成本高。因为实时消息提醒Notify,需要以类似单播的形式实时通知用户。和单播的区别是,Notify不用触达离线用户,也就是不用写用户信箱,只需实时触达在线用户。

3)从系统压力看:全量在线用户均收到实时新消息提醒,会带来信箱拉取请求的瞬时流量(手机百度IM SDK长连接峰值在线1550W,假定新消息提醒在瞬间下发,同时在线用户信箱拉取请求,会把db打挂的)。

9、基于公有信箱技术方案的落地实施效果

全量消息目前已经在百度APP得到应用,包括:重大通知的下发;百度APP功能更新介绍通知;消息的撤回,后续还将推广到其他的矩阵APP的全量通知推送场景。

举个具体的例子:22年Q4宣布疫情解封时,利用全量消息推送,低成本、高时效的完成3条“疫情解封专项”全量消息下发。

 

在这个例子中,三次全量消息下发,到达数据在2亿+(该值小于月活的6亿+),主要因为几个原因:

  • 1)本次全量消息有效期仅3天左右,全量消息有效期内登录IM SDK的用户才有机会拉到全量消息;
  • 2)本次下发使用了新的消息展示模板,所以限制了拉取全量消息的百度APP版本,只有高版本百度APP可以拉到;
  • 3)本次全量消息,限制了仅有百度APP登录用户拉取。

10、未来展望

本文介绍了现有IM消息中台系统,并通过公有信箱技术方案的改造,达成了低成本、高分发速度完成全量用户消息下发的设计、实现与应用。

在全量用户消息应用方面,除了业务上的使用,后续也可以用于广播消息、批量单播消息的撤回。比如由于误操作发送了广播消息,用户已经把广播消息拉到了端,并持久化到端,这是可以“以全量消息的方式,下发删除指令”,删除已经缓存到端的垃圾消息。

我们希望,通过消息系统持续不断优化,为更多的业务提供低成本、高稳定性的即时通讯能力。

11、相关资料

[1] 现代IM系统中聊天消息的同步和存储方案探讨

[2] 百度APP移动端网络深度优化实践分享(一):DNS优化篇

[3] 百度APP移动端网络深度优化实践分享(二):网络连接优化篇

[4] 百度APP移动端网络深度优化实践分享(三):移动端弱网优化篇

[5] 百度直播的海量用户实时消息系统架构演进实践

[6] 深入了解百度开源的分布式RPC框架brpc的方方面面

[7] 百度网盘千万节点的P2P架构设计(PPT)

[8] 零基础IM开发入门(一):什么是IM系统?

[9] 一套海量在线用户的移动端IM架构设计实践分享(含详细图文)

[10] 一套原创分布式即时通讯(IM)系统理论架构方案

[11] 一套亿级用户的IM架构技术干货(上篇):整体架构、服务拆分等

[12] 基于实践:一套百万消息量小规模IM系统技术要点总结

[13] 一套十万级TPS的IM综合消息系统的架构实践与思考

[14] 从新手到专家:如何设计一套亿级消息量的分布式IM系统

[15] 闲鱼亿级IM消息系统的架构演进之路

[16] 深度解密钉钉即时消息服务DTIM的技术设计

[17] 一套高可用、易伸缩、高并发的IM群聊、单聊架构方案设计实践

[18] 企业微信的IM架构设计揭秘:消息模型、万人群、已读回执、消息撤回等

本文已同步发布于:http://www.52im.net/thread-4235-1-1.html

posted @ 2023-05-26 10:57 Jack Jiang 阅读(205) | 评论 (0)编辑 收藏

     摘要: ► 相关链接:① MobileIMSDK-Uniapp端的详细介绍② MobileIMSDK-Uniapp端的开发手册new(* 精编PDF版)一、理论知识准备您需要对Uniapp和Vue开发有所了解:1)Uniapp 官方入门教程2)可能是最好的 uniapp 入门教程3)Uniapp 官方 Vue 快速入门教程您需要对...  阅读全文

posted @ 2023-05-18 12:04 Jack Jiang 阅读(164) | 评论 (0)编辑 收藏

为了更好地分类阅读 52im.net 总计1000多篇精编文章,我将在每周三推送新的一期技术文集,本次是第15 期。

[- -] IM跨平台技术学习(一):快速了解新一代跨平台桌面技术——Electron

[链接] http://www.52im.net/thread-2616-1-1.html

[摘要] 本文将从入门者的角度,为你快速讲解Electron到底是个什么技术,包括案例介绍、技术优势、技术体验、实现原理等。

[- 2 -] IM跨平台技术学习(二):Electron初体验(快速开始、跨进程通信、打包、踩坑等)

[链接] http://www.52im.net/thread-4039-1-1.html

[摘要] 本篇将带你简单上手Electron框架开发跨平台桌面端,内容包括一个快速开始例子、跨进程通信原理、打包和分发、以及一些典型的技术踩坑等。希望能带给你启发。

[- 3 -] IM跨平台技术学习(三):vivo的Electron技术栈选型、全方位实践总结

[链接] http://www.52im.net/thread-4044-1-1.html

[摘要] 本篇将基于vivo技术团队的技术实践,详细阐述了vivo在使用Electron进行跨端桌面开发时的技术栈选型考量,同时分享了在打包构建、版本更新、性能优化、质量保障、安全性等方面的实践方案和踩坑总结。

[- 4 -] IM跨平台技术学习(四):蘑菇街基于Electron开发IM客户端的技术实践

[链接] http://www.52im.net/thread-4051-1-1.html

[摘要] 本篇将回到IM即时通讯技术本身,根据蘑菇街的实际技术实践,总结和分享基于Electron开发跨平台IM客户端的过程中,需要考虑的典型技术问题以及我们的解决方案。

[- 5 -] IM跨平台技术学习(五):融云基于Electron的IM跨平台SDK改造实践总结

[链接] http://www.52im.net/thread-332-1-1.html

[摘要] 本文分享的是融云基于Electron的IM跨平台PC端SDK改造过程中所总结的一些实践经验,希望对你有用。

[- 6 -] IM跨平台技术学习(六):网易云信基于Electron的IM消息全文检索技术实践

[链接] http://www.52im.net/thread-4065-1-1.html

[摘要] 本文将要分享的是,网易云信基于Electron的PC端是如何实现IM客户端全文检索能力的。

[- 7 -] IM跨平台技术学习(七):得物基于Electron开发客服IM桌面端的技术实践

[链接] http://www.52im.net/thread-4159-1-1.html

[摘要] 本文要分享的是得物技术团队基于Electron开发客服IM桌面端的技术实践过程,内容包括桌面技术选型、Electron的基础概念、具体的实施技术方案、遇到的棘手问题等。

[- 8-]  社交软件红包技术解密(一):全面解密QQ红包技术方案——架构、技术实现等

[链接] http://www.52im.net/thread-2202-1-1.html

[摘要] 本文将从架构开始,到手机 QQ 移动端优化,再到个性化红包和 AR 新玩法,为大家全面解密 QQ 红包技术方案。

[- 9 -] 社交软件红包技术解密(二):解密微信摇一摇红包从0到1的技术演进

[链接] http://www.52im.net/thread-2519-1-1.html

[摘要] 本文要分享的是微信团队是如何从0到1实现“有把握”的微信春晚摇一摇红包系统的。

[- 10-] 社交软件红包技术解密(三):微信摇一摇红包雨背后的技术细节

[链接] http://www.52im.net/thread-2533-1-1.html

[摘要] 本文将由微信团队工程师张文瑞分享微信春节摇一摇红包技术背后的方方面面,希望能给同行们带来启发。

[- 11-] 社交软件红包技术解密(四):微信红包系统是如何应对高并发的

[链接] http://www.52im.net/thread-2548-1-1.html

[摘要] 本文将为读者介绍微信百亿级别红包背后的高并发设计实践,内容包括微信红包系统的技术难点、解决高并发问题通常使用的方案,以及微信红包系统的所采用高并发解决方案。

[- 12-] 社交软件红包技术解密(五):微信红包系统是如何实现高可用性的

[链接] http://www.52im.net/thread-2564-1-1.html

[摘要] 本次分享介绍了微信红包后台系统的高可用实践经验,主要包括后台的 set 化设计、异步化设计、订单异地存储设计、存储层容灾设计与平行扩缩容等。听众可以了解到微信红包后台架构的设计细节,共同探讨高可用设计实践上遇到的问题与解决方案。

[- 13-] 社交软件红包技术解密(六):微信红包系统的存储层架构演进实践

[链接] http://www.52im.net/thread-2568-1-1.html

[摘要] 微信红包本质是小额资金在用户帐户流转,有发、抢、拆三大步骤。在这个过程中对事务有高要求,所以订单最终要基于传统的RDBMS,这方面是它的强项,最终订单的存储使用互联网行业最通用的MySQL数据库。支持事务、成熟稳定,我们的团队在MySQL上有长期技术积累。但是传统数据库的扩展性有局限,需要通过架构解决。

[- 14-] 社交软件红包技术解密(七):支付宝红包的海量高并发技术实践

[链接] http://www.52im.net/thread-2573-1-1.html

[摘要] 经过多年的发展,口碑和社交业务的崛起让支付宝架构进一步在原有架构基础上拓展出支持线下市场和社交的生活互动型架构。2015 年钱包 9.0 的发布,这个里程碑式的项目初步奠定了支付 + 移动互联网金融 + 生活互动型混合架构。

[- 15-]社交软件红包技术解密(八):全面解密微博红包技术方案

[链接] http://www.52im.net/thread-2576-1-1.html

[摘要] 在服务器数量一定的情况下,如何构建高并发操作、瞬间峰值高的稳定服务?对于团队和架构师都是一个极大的挑战。这时候系统的架构尤为重要!本文将为你分享这些内容。

[- 16-]社交软件红包技术解密(九):谈谈手Q红包的功能逻辑、容灾、运维、架构等

[链接] http://www.52im.net/thread-2583-1-1.html

[摘要] 本文将会详细介绍手Q春节红包项目的功能设计/逻辑、容灾、运维、架构以及实践总结。

[- 17-]社交软件红包技术解密(十):手Q客户端针对2020年春节红包的技术实践

[链接] http://www.52im.net/thread-2966-1-1.html

[摘要] 对于这种大体量的IM社交应用运营活动,技术上除了前端、后台的大力支撑,对于手Q客户端来说,又是从哪些方面来保证整个红包活动的灵活性、稳定性和用户体验的呢?带着这个问题,我们一起来阅读余下的文字。

[- 18-]社交软件红包技术解密(十一):解密微信红包随机算法(含代码实现)

[链接] http://www.52im.net/thread-3125-1-1.html

[摘要] 本文根据有限的资料,分享了微信红包随机算法实现中的一些技术要点,并整理了两种比较靠谱的红包算法实现思路(含可运行的实现代码),希望能给你的红包算法开发带来启发。

[- 19-]社交软件红包技术解密(十二):解密抖音春节红包背后的技术设计与实践

[链接] http://www.52im.net/thread-3945-1-1.html

[摘要] 本文将要分享的是春节期间海量红包社交活动为抖音所带来的各种技术挑战,以及抖音技术团队是如何在实践中一一解决这些问题的。

👉52im社区本周新文:《即时通讯框架MobileIMSDK的Uniapp端开发者手册(精编PDF导出图片) http://www.52im.net/thread-4234-1-1.html》,欢迎阅读!👈

我是Jack Jiang,我为自已带盐!https://github.com/JackJiang2011/MobileIMSDK/

posted @ 2023-05-16 13:29 Jack Jiang 阅读(114) | 评论 (0)编辑 收藏

一、基本介绍

MobileIMSDK-Uniapp端是一套基于Uniapp跨端框架的即时通讯库:

  • 1)超轻量级、无任何第3方库依赖(开箱即用);
  • 2)纯JS编写、ES6语法、高度提炼,简单易用;
  • 3)基于Uniapp标准WebSocket API,简洁优雅;
  • 4)理论上可运行于任何支持Uniapp跨端框架的平台上;
  • 5)能与 MobileIMSDKGithub托管链接) 的各种客户端完美互通;
  • 6)可应用于基于Uniapp的跨平台App或Web的消息推送、客服聊天、企业OA、IM等场景。

详细开发资料:

二、与MobileIMSDK的关系

MobileIMSDK-Uniapp端 是基于Uniapp标准 WebSocket API的 MobileIMSDK配套客户端库。

以下是MobileIMSDK的最新通信架构图:

 

MobileIMSDK是一套专为移动端开发的原创开源IM通信层框架:

  • 1)历经8年、久经考验;
  • 2)超轻量级、高度提炼,lib包50KB以内;
  • 3)精心封装,一套API同时支持UDP、TCP、WebSocket三种协议(可能是全网唯一开源的);
  • 4)客户端支持iOS、Android、标准Java、H5(暂未开源)、微信小程序(暂未开源)、Uniapp:new:(暂未开源);
  • 5)服务端基于Netty,性能卓越、易于扩展;
  • 6)可与姊妹工程 MobileIMSDK-Web 无缝互通实现网页端聊天或推送等;
  • 7)可应用于跨设备、跨网络的聊天APP、企业OA、消息推送等各种场景。

PS: MobileIMSDK一直在持续开发和升级中,本Uniapp客户端是MobileIMSDK工程的最新成果。

三、设计目标

直接使用Uniapp的WebSocket API开撸,有以下问题和劣势:

  • 1)功能有限: 没有心跳保活、断线重连、消息送达保证(重传和去重)等即时通讯关键算法和逻辑;
  • 2)API简陋: 在如此有限的API接口下,能逻辑清晰且健壮地实现并组合心跳保活、断线重连、消息送达保证等算法,需要相当高的技术掌控力;
  • 3)逻辑耦合: 经验欠缺的开发人员,会将WebSocket通信与前端UI界面代码混在一起,使得UI界面的编写、维护、改版都非常困难。

针对以上问题: MobileIMSDK-Uniapp端库将让开发者专注于UI应用层的开发,网络通信层的专业代码交由SDK开发人员,从而解偶UI前端和通信层的逻辑耦合性,大大降低技术复杂度和应用门槛。

MobileIMSDK-Uniapp端库的设计目标是为您的开发带来以下便利:

  • 1)界面与通信解偶: UI界面与网络通信代码解耦,UI界面的重构、维护、改版都非常容易和优雅;
  • 2)轻量级和兼容性: 受益于坚持使用Uniapp的标准WebSocket API,简洁轻量,无需任何额外库依赖;
  • 3)核心内聚和收敛: 得益于长期的提炼和经验积累,SDK核心层高度封装,开发者无需理解复杂算法即可简单上手。
  • 4)纯JS轻量级实现: 纯JS编写、ES6语法,无重量级框架和库依赖(更无Native代码),可干净利落地对接各种既有系统;
  • 5)跨平台运行能力: 受益于Uniapp框架的跨端特性,理论上本SDK可运行于任何支持Uniapp的平台上。

四、技术亮点

  • 1)轻量易使用: 超轻量级——纯JS编写且无任何第3方库依赖,高度提炼——简单易用;
  • 2)代码现代感: 尽可能优先使用ES6语法,摒弃旧式JS语法的年代感;
  • 3)跨端支持好: 基于Uniapp的标准WebSocket API(无Native代码依赖),理论上可很好地运行于任何支持Uniapp的平台上;
  • 4)断网恢复能力: 拥有网络状况自动检测、断网自动治愈的能力;
  • 5)送达保证机制: 完善的QoS消息送达保证机制(自动重传、消息去重、状态反馈等),不漏过每一条消息;
  • 6)通信协议封装: 实现了一个对上层透明的即时通讯通信协议模型;
  • 7)身份认证机制: 实现了简单合理的身份认证机制;
  • 8)完善的log信息: 在开发调试阶段,确保每一个算法关键步骤都有日志输出,让您的运行调试更为便利;
  • 9)界面代码解耦: 实现了UI界面代码与SDK网络通信代码解偶,防止界面代码跟IM核心代码混在一起,不利于持续升级、重用和维护;
  • 10)多端协议兼容: 实现了与MobileIMSDK各种客户端完全兼容的协议模型。

五、文件组成

SDK代码文件概览:

SDK代码文件用途说明:

六、Demo运行效果和说明

七、跨平台运行效果演示

1)Demo在内置浏览器中的运行效果:

2)Demo在电脑浏览器中的运行效果(以Chrome为例):

3)Demo在Android真机上的运行效果:

4)Demo在iOS模拟器上的运行效果:

5)Demo在iOS真机上的运行效果:

6)Demo在微信小程序上的运行效果:

7)Demo在支付宝小程序上的运行效果:

其它更多平台的运行效果就不一一列举了,因为都要安装各自的开发工具,硬盘空间吃紧 。。。

八、详细资料

① MobileIMSDK-Uniapp端的详细介绍:点此查看 👈
② MobileIMSDK-Uniapp端的开发手册(网页版):点此查看 👈
 MobileIMSDK-Uniapp端的开发手册(精编PDF版):点此查看 👈 (* 推荐
④ MobileIMSDK-开源框架的详细介绍:https://gitee.com/jackjiang/MobileIMSDK (Github托管链接)👈

posted @ 2023-05-12 11:44 Jack Jiang 阅读(103) | 评论 (0)编辑 收藏

仅列出标题
共51页: First 上一页 12 13 14 15 16 17 18 19 20 下一页 Last 
Jack Jiang的 Mail: jb2011@163.com, 联系QQ: 413980957, 微信: hellojackjiang