热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

XMPP服务器Openfire的Emoji支持问题(进行部分修改)

当前最新版3.9.3已经可以支持Emoji-----------------------------------------------------------------------

当前最新版3.9.3已经可以支持Emoji 

----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------


在为领航信息开发 eMessage 支持的时候,我们曾使用著名的开源 XMPP 服务器软件 Openfire。但在使用中遇到了几个问题,并通过修改源代码将这些问题解决掉了。接下来的几篇文章,我会介绍一下这些问题并讲述是如何解决掉的。

先介绍一下背景。XMPP 是一个开放的即时通讯协议,非常不错,有很多开源软件实现了 XMPP 协议,Openfire 算是实现得比较全的,而且安装配置比较容易。其他比较流行的开源 XMPP 服务器还有 Tigase 和 ejabberd。我们现在已经切换到了 ejabberd 上,毕竟 WhatsApp 最初也使用的是 ejabberd 嘛,呵呵。读者如果有兴趣,我也可以跟大家讲讲这几个 XMPP 服务器的区别和潜在问题。

问题描述

Emoji 现在基本已经成为一种工业事实标准,最早在日本流行,由日本的 DoKoMo 等运营商支持,后来苹果在 iOS 中支持了这一技术,最终变得全世界流行起来。Emoji 使用了一些 UNICODE 字符集中尚未定义的码位来表示一个个的表情图案。和一般的字体不同,应用程序在遇到这些字符的时候,需要使用位图来显示对应的表情图标,而不是直接使用字体中定义的字型来显示(当然,也可以用字体来显示 Emoji 字符,比如某些 Linux 控制台就可以显示部分 Emoji 字符)。前者可以是彩色的,而后者只能是某个特定的颜色。所以,要显示 Emoji 就要在应用程序中做扩展,在输出这些特定字符的时候做特殊处理,将其用对应的表情位图显示出来。让客户端应用支持 Emoji 并不是很难的工作,Android 的短信应用不支持 Emoji,但支持预先定义的特定字符序列来表示特定的表情,比如将“:)”显示成笑脸。处理这种字符序列的方法和处理 Emoji 表情的方法本质上一样的。

在使用 Openfire 作为 XMPP 服务器,将表示 Emoji 的 UNICODE 字符发送给其他用户的时候,就会出现问题。问题的原因在于 Emoji 使用的 UNICODE 字符集码位尚未被 UNICODE 标准化组织标准化,而 Openfire 会将 Emoji 字符看成是不符合标准的字符而直接忽略掉或者干脆断开客户端的连接。因此,要解决这个问题,其实相当容易,通过搜索引擎可以很快找到了解决方案。

解决方案

在 Openfire 3.8.2 版本源代码术中,修改 openfire_src/src/java/org/jivesoftware/openfire/net 目录下的 MXParser.java 文件的最后一个函数:
[java]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1.      /** 
  2.       * Makes sure that each individual character is a valid XML character. 
  3.       *  
  4.       * Note that when MXParser is being modified to handle multibyte chars correctly, this method needs to change (as 
  5.       * then, there are more codepoints to check). 
  6.       */  
  7.  @Override  
  8.  protected char more() throws IOException, XmlPullParserException {  
  9.      final char codePoint  = super.more(); // note - this does NOT return a codepoint now, but simply a (single byte) char  
  10. er!  
  11.              if ((codePoint == 0x0) ||  // 0x0 is not allowed, but flash clients insist on sending this as the very first   
  12. racter of a stream. We should stop allowing this codepoint after the first byte has been parsed.  
  13.                              (codePoint == 0x9) ||  
  14.                              (codePoint == 0xA) ||  
  15.                              (codePoint == 0xD) ||  
  16.                              ((codePoint >= 0x20) && (codePoint <= 0xD7FF)) ||  
  17.                              ((codePoint >= 0xE000) && (codePoint <= 0xFFFD))) {  
  18.                       return codePoint;  
  19.              throw new XmlPullParserException("Illegal XML character: " + Integer.parseInt(codePoint+""16));  
  20.  }  
先看看这个函数的作用。如注释所言,这个函数用来判定特定字符是否是一个合法的 XML 字符。XML 一般要求按照 UTF8 编码的方式存储和传输字符,在程序处理时,会直接转换成 UNICODE 的 UCS 形式,这样便于程序做处理。

在 Linux 控制台上运行 $ man utf8 命令,你可以迅速知悉 UNICODE 码位范围以及和 UTF-8 编码之间的对应关系:
[plain]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. The following byte sequences are used to represent a character.  The sequence to be used depends on the  UCS  code  
  2. number of the character:  
  3.   
  4. 0x00000000 - 0x0000007F:  
  5.     0xxxxxxx  
  6.   
  7. 0x00000080 - 0x000007FF:  
  8.     110xxxxx 10xxxxxx  
  9.   
  10. 0x00000800 - 0x0000FFFF:  
  11.     1110xxxx 10xxxxxx 10xxxxxx  
  12.   
  13. 0x00010000 - 0x001FFFFF:  
  14.     11110xxx 10xxxxxx 10xxxxxx 10xxxxxx  
  15.   
  16. 0x00200000 - 0x03FFFFFF:  
  17.     111110xx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx  
  18.   
  19. 0x04000000 - 0x7FFFFFFF:  
  20.     1111110x 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx  

从上面的对应关系就可以知悉,UNICODE 可能的码位(code point 或者 code number)最大可以到 0x7FFFFFFF!而 Openfire 判断是否为合法 XML 字符的范围只到 0xFFFD!显然,不能显示正确处理 Emoji 字符的原因是 Openfire 将用来表示 Emoji 字符的那些码位范围给当成非法 XML 字符了。一种比较简单粗暴的修改办法是:
[java]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1.     /** 
  2.      * Makes sure that each individual character is a valid XML character. 
  3.      *  
  4.      * Note that when MXParser is being modified to handle multibyte chars correctly, this method needs to change (as 
  5.      * then, there are more codepoints to check). 
  6.      */  
  7. @Override  
  8. protected char more() throws IOException, XmlPullParserException {  
  9.     final char codePoint  = super.more(); // note - this does NOT return a codepoint now, but simply a (single byte) char  
  10. r!  
  11.             if ((codePoint == 0x0) ||  // 0x0 is not allowed, but flash clients insist on sending this as the very first   
  12. acter of a stream. We should stop allowing this codepoint after the first byte has been parsed.  
  13.                             (codePoint == 0x9) ||  
  14.                             (codePoint == 0xA) ||  
  15.                             (codePoint == 0xD) ||  
  16.                             ((codePoint >= 0x20) && (codePoint <= 0xFFFD)) ||  
  17.                             ((codePoint >= 0x10000) && (codePoint <= 0x10FFFF))) {  
  18.                     return codePoint;  
  19.             }  
  20.   
  21.             throw new XmlPullParserException("Illegal XML character: " + Integer.parseInt(codePoint+""16));  
  22. }  
但马上有高手指出,这个方法太粗暴了,可能带来一些安全隐患(参见: http://community.igniterealtime.org/thread/48846),所以更加正确的方法是:



@Override
	protected char more() throws IOException, XmlPullParserException {


		final char codePoint = super.more(); // note - this does NOT return a codepoint now, but simply a (double byte) character!


		boolean validCodepoint = false;


		boolean isLowSurrogate = Character.isLowSurrogate(codePoint);


		if ((codePoint == 0x0)|| // 0x0 is not allowed, but flash clients insist on sending this as the very first character of a stream. We should stop allowing this codepoint after the first byte has been parsed.
		    (codePoint == 0x9) ||
			(codePoint == 0xA) ||
			(codePoint == 0xD)|| 
			((codePoint >= 0x20) && (codePoint <= 0xD7FF)) ||
			((codePoint >= 0xE000) && (codePoint <= 0xFFFD))) {
			validCodepoint = true;
		}


		else if (highSurrogateSeen) {
			if (isLowSurrogate) {


				validCodepoint = true;
			} else {


				throw new XmlPullParserException(
						"High surrogate followed by non low surrogate '0x"
								+ String.format("%x", (int) codePoint) + "'");


			}
		}


		else if (isLowSurrogate) {
			throw new XmlPullParserException("Low surrogate '0x "+ String.format("%x", (int) codePoint)+ " without preceeding high surrogate");
		}


		else if (Character.isHighSurrogate(codePoint)) {
			highSurrogateSeen = true;// Return here so that highSurrogateSeen is not reset
			return codePoint;
		}


		// Always reset high surrogate seen
		highSurrogateSeen = false;
		if (validCodepoint)
			return codePoint;


		throw new XmlPullParserException("Illegal XML character '0x"+ String.format("%x", (int) codePoint) + "'");


	}



有了上述修改,你的 Openfire 服务器就可以正确处理 Emoji 了。Openfire 最新的版本是 3.9.1,估计已经修改掉这个问题了吧,但本人未确认。

在 MySQL 中存储 Emoji 字符

没想到,为了在 MySQL 数据库中保存 Emoji 字符,需要使用 MySQL 5.5 以上版本引入的 utf8mb4 的字符集。原来 MySQL 的 utf8 字符集只支持编码为一个、两个字节或者三个字节的情形,也就是 UNICODE  UCS 编码范围为 0 到 0xFFFD 这种情形。要支持超过这个范围的 UTF8 编码字符,就需要使用 MySQL 5.5 中引入的 utf8mb4 字符集。从名字中可以看出,这个字符集专门用来支持单个 UNICODE 的 UTF8 编码长度达到四个字节的情形。当然,超过也许也是可以的。至于为什么不能直接用 utf8 编码来兼容这些字符,我就不知道了,也许是历史原因吧。

大家可以用“mysql utf8mb4”为关键词搜索一下就知道如何设置/配置 mysql 来支持这个字符集了。但是,要让 openfire 能够和 mysql 正确打交道,还需要升级一下 openfire 使用的 JAVA mysql 数据库连接器(connnector),要升级到最新的版本。否则,如果使用老的 mysql 数据库连接器,会出现无法理解 utf8mb4 字符集的情形。

吐槽一下,我实在是不能理解为什么 MySQL 要引入 utf8mb4 这个字符集,难不成将来还需要引入 utf8mb6、utf8mb8 这样的字符集不成?哪位大侠可以帮我解答这个疑惑?

后记

俺是不太喜欢 JAVA 语言的,至今未能使用 JAVA 语言完整编写过一个程序,这实在是本人二十多年码农生涯的一大遗憾,但打打补丁这事儿还是可以做做的。下一篇文章给大家介绍一个针对 Openfire 服务器在集群环境下处理 SOCKS5 代理的问题,就修改了几行代码,但解决了一个大问题。

敬请期待。


推荐阅读
  • 自动轮播,反转播放的ViewPagerAdapter的使用方法和效果展示
    本文介绍了如何使用自动轮播、反转播放的ViewPagerAdapter,并展示了其效果。该ViewPagerAdapter支持无限循环、触摸暂停、切换缩放等功能。同时提供了使用GIF.gif的示例和github地址。通过LoopFragmentPagerAdapter类的getActualCount、getActualItem和getActualPagerTitle方法可以实现自定义的循环效果和标题展示。 ... [详细]
  • android listview OnItemClickListener失效原因
    最近在做listview时发现OnItemClickListener失效的问题,经过查找发现是因为button的原因。不仅listitem中存在button会影响OnItemClickListener事件的失效,还会导致单击后listview每个item的背景改变,使得item中的所有有关焦点的事件都失效。本文给出了一个范例来说明这种情况,并提供了解决方法。 ... [详细]
  • CF:3D City Model(小思维)问题解析和代码实现
    本文通过解析CF:3D City Model问题,介绍了问题的背景和要求,并给出了相应的代码实现。该问题涉及到在一个矩形的网格上建造城市的情景,每个网格单元可以作为建筑的基础,建筑由多个立方体叠加而成。文章详细讲解了问题的解决思路,并给出了相应的代码实现供读者参考。 ... [详细]
  • 欢乐的票圈重构之旅——RecyclerView的头尾布局增加
    项目重构的Git地址:https:github.comrazerdpFriendCircletreemain-dev项目同步更新的文集:http:www.jianshu.comno ... [详细]
  • 如何自行分析定位SAP BSP错误
    The“BSPtag”Imentionedintheblogtitlemeansforexamplethetagchtmlb:configCelleratorbelowwhichi ... [详细]
  • Java太阳系小游戏分析和源码详解
    本文介绍了一个基于Java的太阳系小游戏的分析和源码详解。通过对面向对象的知识的学习和实践,作者实现了太阳系各行星绕太阳转的效果。文章详细介绍了游戏的设计思路和源码结构,包括工具类、常量、图片加载、面板等。通过这个小游戏的制作,读者可以巩固和应用所学的知识,如类的继承、方法的重载与重写、多态和封装等。 ... [详细]
  • 在Android开发中,使用Picasso库可以实现对网络图片的等比例缩放。本文介绍了使用Picasso库进行图片缩放的方法,并提供了具体的代码实现。通过获取图片的宽高,计算目标宽度和高度,并创建新图实现等比例缩放。 ... [详细]
  • 本文介绍了使用kotlin实现动画效果的方法,包括上下移动、放大缩小、旋转等功能。通过代码示例演示了如何使用ObjectAnimator和AnimatorSet来实现动画效果,并提供了实现抖动效果的代码。同时还介绍了如何使用translationY和translationX来实现上下和左右移动的效果。最后还提供了一个anim_small.xml文件的代码示例,可以用来实现放大缩小的效果。 ... [详细]
  • VScode格式化文档换行或不换行的设置方法
    本文介绍了在VScode中设置格式化文档换行或不换行的方法,包括使用插件和修改settings.json文件的内容。详细步骤为:找到settings.json文件,将其中的代码替换为指定的代码。 ... [详细]
  • http:my.oschina.netleejun2005blog136820刚看到群里又有同学在说HTTP协议下的Get请求参数长度是有大小限制的,最大不能超过XX ... [详细]
  • XML介绍与使用的概述及标签规则
    本文介绍了XML的基本概念和用途,包括XML的可扩展性和标签的自定义特性。同时还详细解释了XML标签的规则,包括标签的尖括号和合法标识符的组成,标签必须成对出现的原则以及特殊标签的使用方法。通过本文的阅读,读者可以对XML的基本知识有一个全面的了解。 ... [详细]
  • Python正则表达式学习记录及常用方法
    本文记录了学习Python正则表达式的过程,介绍了re模块的常用方法re.search,并解释了rawstring的作用。正则表达式是一种方便检查字符串匹配模式的工具,通过本文的学习可以掌握Python中使用正则表达式的基本方法。 ... [详细]
  • Linux环境变量函数getenv、putenv、setenv和unsetenv详解
    本文详细解释了Linux中的环境变量函数getenv、putenv、setenv和unsetenv的用法和功能。通过使用这些函数,可以获取、设置和删除环境变量的值。同时给出了相应的函数原型、参数说明和返回值。通过示例代码演示了如何使用getenv函数获取环境变量的值,并打印出来。 ... [详细]
  • [大整数乘法] java代码实现
    本文介绍了使用java代码实现大整数乘法的过程,同时也涉及到大整数加法和大整数减法的计算方法。通过分治算法来提高计算效率,并对算法的时间复杂度进行了研究。详细代码实现请参考文章链接。 ... [详细]
  • 本文介绍了南邮ctf-web的writeup,包括签到题和md5 collision。在CTF比赛和渗透测试中,可以通过查看源代码、代码注释、页面隐藏元素、超链接和HTTP响应头部来寻找flag或提示信息。利用PHP弱类型,可以发现md5('QNKCDZO')='0e830400451993494058024219903391'和md5('240610708')='0e462097431906509019562988736854'。 ... [详细]
author-avatar
鄙人fisher_779
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有