【问题标题】:Special Character Issue : MQ message PUT error : java.nio.charset.UnmappableCharacterException特殊字符问题:MQ 消息 PUT 错误:java.nio.charset.UnmappableCharacterException
【发布时间】:2020-09-19 06:11:03
【问题描述】:

我有一个设置,其中有一个 JMS 生产者和 JMS 接收器。发件人应用程序发送如下消息:

source text ⟨е, ё, и, ю, я⟩  abcdefg

JMS 接收者收到消息后,使用纯 IBM MQ API 类将其放入 IBM MQ 队列。

在将此消息发送到 MQ 时,我收到以下异常:

INFO  | 2020-09-17 09:45:19 | [main] mimq.MQReceiver (MQReceiver.java:211) - IO Exception Occurred: Input length = 1
java.nio.charset.UnmappableCharacterException: Input length = 1
        at java.nio.charset.CoderResult.throwException(CoderResult.java:282)
        at java.nio.charset.CharsetEncoder.encode(CharsetEncoder.java:816)
        at com.ibm.mq.jmqi.system.JmqiCodepage.stringToBytes(JmqiCodepage.java:923)
        at com.ibm.mq.MQMessage.writeString(MQMessage.java:2848)
        at com.ibm.mimq.MQReceiver.sendToAnotherQueue(MQReceiver.java:192)
        at com.ibm.mimq.MQReceiver.main(MQReceiver.java:113)

下面是我的 MQ PUT 代码:

public static void sendToLocalQueue(String msg) {
    
    int port = 1414;
    String host = "some-host";
    String channel = "some-channel";
    String manager = "some-QM";
    String user = "user"; 
    String passwd = "passwd";
    String qname = "TEST";
    String qmname = "some-QM";
    
    MQQueueManager qMgr;
    MQQueue inputQ;
    
    try {       
        
        Hashtable<String, String> h = new Hashtable<String, String>();
        h.put(MQC.TRANSPORT_PROPERTY, MQC.TRANSPORT_MQSERIES_CLIENT);
        MQEnvironment.properties = h;
        MQEnvironment.hostname  = host;
        MQEnvironment.port      = port;
        MQEnvironment.channel   = channel;
        
        MQEnvironment.userID    = user;
        MQEnvironment.password  = passwd;
        MQEnvironment.disableTracing();
        MQException.log = null;
        qMgr = new MQQueueManager(manager);
        
        MQMessage m = new MQMessage();      
        m.applicationOriginData = "AMPS";
        m.messageType   = MQC.MQMT_DATAGRAM;
        m.format        = MQC.MQFMT_STRING;
        m.encoding      = MQC.MQENC_NATIVE;
        m.priority      = 4;
        m.persistence   = MQC.MQPER_PERSISTENT;
        m.characterSet  = MQC.MQCCSI_Q_MGR;
        //m.characterSet = 1208;
        m.expiry        =  MQC.MQEI_UNLIMITED;
        m.writeString(msg);
        
        MQPutMessageOptions putOptions = new MQPutMessageOptions();
        putOptions.options = MQC.MQPMO_SYNCPOINT | MQC.MQPMO_FAIL_IF_QUIESCING; 
        
        logger.info("Putting message to LAN MQ (TEST queue)....");          
        qMgr.put(qname, qmname, m, putOptions);
        qMgr.commit();
        
        
    } catch(MQException me) {
        logger.info("Error Code       : "  +me.getErrorCode());
        logger.info("LocalizedMessage : "  +me.getLocalizedMessage());
        logger.info("Message          : "  +me.getMessage());
        logger.info("Reason           : "  +me.getReason());
        me.printStackTrace();
        
    } catch (IOException e) {
        // TODO Auto-generated catch block
        logger.info("IO Exception Occurred       : "  +e.getLocalizedMessage());
        e.printStackTrace();
    }       
}

由于不可映射字符,无法将消息放入队列。编码在队列管理器级别设置为 UTF-8。

但是,当我替换以下行时:m.characterSet = MQC.MQCCSI_Q_MGR;

使用该行:m.characterSet = 1208; 问题不再存在。

我的问题是为什么这种转换没有在 MQ 级别完成。我需要检查哪些设置以确保正确转换。我尝试了以下技术,但不起作用:

Setting java parameter as : -Dfile.encoding=UTF-8 

我的环境

Server : Linux
MQ : 9.0  or 7.5
Java : 1.8

还要提一下,相同的设置在 7.5 上工作,但在迁移后不能在 MQ 9.0 上工作。我知道通过上面的一行代码更改,我可以传递消息。但是如果我错过了一些配置,我想了解 MQ 级别。任何建议将不胜感激。

谢谢。

更新

我将消息发送到 MQ 的客户端计算机具有 CCSID:MQMD.CodedCharSetId = 1208

我连接和发送消息的 MQ 服务器有这个:

getDefaultProperty(Object) returns [819(0x333)] Integer
setCCSID(int) setter [819(0x333)]

所以当我在我的代码中明确设置 1208 时,它正在工作。如果不是,则转换失败。

UPDATE-2

我在罐子里看到的值MQC.MQCCSI_Q_MGR is Zero。因此代码是这样设计的,如果值为零,它将从设置为 819 的 Jar 中获取默认值。我在打开 MQ 跟踪时了解到这一点。代码是这样的:

getDefaultProperty(Object) returns [819(0x333)] Integer
setCCSID(int) setter [819(0x333)]

此代码存在于 jar 中。所以我们需要在消息上显式设置字符集值。就我而言,它是 1208。

【问题讨论】:

    标签: java character-encoding jms ibm-mq


    【解决方案1】:

    当本地连接到队列管理器(即使用TRANSPORT_MQSERIES_BINDINGS)时,代码:-

    m.characterSet     = MQC.MQCCSI_Q_MGR;
    

    表示“采用队列管理器属性 CCSID 中设置的 CCSID”。您可以使用以下 MQSC 命令查看此属性:-

    DISPLAY QMGR CCSID
    

    当作为客户端连接时(如您的代码所示),则代码:-

    m.characterSet     = MQC.MQCCSI_Q_MGR;
    

    表示“从客户端机器区域设置中查找 CCSID”。

    IBM Knowledge Center 状态:-

    对于客户端应用程序,MQCCSI_Q_MGR 会根据客户端的区域设置而不是队列管理器上的区域设置来填写。

    如果更改代码行以将消息 CCSID 显式设置为 1208 可以解决问题,那么您的客户端计算机区域设置似乎未设置为 UTF-8。

    您可以通过浏览队列上的消息(不转换它)并查看MQMD.CodedCharSetId 字段中的内容来查看客户端设置的内容。

    【讨论】:

    • :我更新了我的问题。请再看一遍。
    • 嗨@Som - 我看到你已经更新了你的问题,但不清楚你还需要知道什么。您是否检查过客户端计算机的语言环境?
    • 另外,@Som 的另一件事 - 当您没有将其覆盖到 1208 时,您是否发现消息的 MQMD.CodedCharSetId 中填充了什么?您是否尝试浏览队列中的消息(不进行转换)?
    • 我看到的是,当我设置MQC.MQCCSI_Q_MGR 时,我尝试从MQ jar 获取默认CCSID 为819 的消息。该字段现在已被弃用,其值 = 0。因此它正在获取默认值 819,并且无法对消息进行编码。主要问题在于这个默认值。我们需要在客户端明确设置 1208。我在做了一个跟踪之后得出了这个结论。但是你的回答给了我一个非常棒的经历。不过我会接受你的回答。非常感谢 !赞赏。
    • 对于您的最后一条评论,我看到该值为 1208。Encoding : 273 CodedCharSetId : 1208。我浏览了这条消息,我看到了这个。但是我的客户端代码设置为MQC.MQCCSI_Q_MGR,因此它正在更改。因此出现错误。
    【解决方案2】:

    这在 MQ v7.5 而不是在 MQ v9.0 中起作用的原因是,在 IBM MQ v8.0 之前,用于 Java 编码数据的 IBM MQ 类使用 java.nio.charset.Charset.encode(CharBuffer) 导致默认替换格式错误或无法翻译的数据。由于默认 characterSet 是 819 (ASCII),这将导致您发送的任何无法转换为 ASCII 的字符都被默认替换字符透明地替换,在大多数情况下,这意味着数据被替换为 @987654324 @ 字符。

    在 v8.0 之后,默认行为更改为将此情况报告为错误,并且不再默认替换格式错误或无法翻译的数据。


    将字符集设置为 UTF-8 的解决方案是最佳解决方案,因为这会导致发送您要发送的确切数据。

    另一种选择是告诉 MQ 使用先前的行为。


    IBM MQ 9.0 知识中心页面Developing applications>Developing JMS and Java applications>Using IBM MQ classes for Java>Character string conversions in IBM MQ classes for Java 中描述了新行为以及如何配置 IBM MQ 类 Java 以使用先前行为:

    从 IBM® MQ 版本 8.0 开始,一些默认行为与 IBM MQ Java™ 类中的字符串转换具有 改变了。

    在 IBM MQ 版本 8.0 之前,IBM MQ 类中的字符串转换为 Java 是通过调用 java.nio.charset.Charset.decode(ByteBuffer) 和 Charset.encode(CharBuffer) 方法。

    使用这两种方法中的任何一种都会导致默认替换 ( REPLACE) 格式错误或不可翻译的数据。

    这种行为会掩盖应用程序中的错误,并导致 翻译数据中的意外字符,例如?来自 IBM MQ 8.0 版,为了更早、更有效地检测到此类问题, 用于 Java 的 IBM MQ 类使用 CharsetEncoders 和 CharsetDecoders 直接配置畸形和不可翻译的处理 明确的数据。

    从 IBM MQ 版本 8.0 开始,默认行为是报告此类问题 通过抛出一个合适的 MQException。

    ...

    设置系统默认值 --

    从 IBM MQ 版本 8.0 开始,以下两个 Java 系统属性是 可用于配置有关字符串的默认行为 转换。

    com.ibm.mq.cfg.jmqi.UnmappableCharacterAction 指定在编码和解码时对不可翻译数据采取的操作。 该值可以是 REPORT、REPLACE 或 IGNORE。

    com.ibm.mq.cfg.jmqi.UnmappableCharacterReplacement 设置或获取在无法映射字符时应用的替换字节 在编码操作中使用默认的 Java 替换字符串 在解码操作中。

    为了避免混淆 Java 字符和本机字节 表示,您应该指定 com.ibm.mq.cfg.jmqi.UnmappableCharacterReplacement 作为十进制数 表示本机字符集中的替换字节。

    例如,作为本机字节的 ? 的十进制值是 63,如果 本机字符集是基于 ASCII 的,例如 ISO-8859-1,而它是 如果本机字符集是 EBCDIC,则为 111。


    如果您想模仿之前的行为,您可以设置以下系统属性:

    -Dcom.ibm.mq.cfg.jmqi.UnmappableCharacterAction=REPLACE
    -Dcom.ibm.mq.cfg.jmqi.UnmappableCharacterReplacement=63
    

    您也可以使用以下方式以编程方式设置它:

    System.setProperty("com.ibm.mq.cfg.jmqi.UnmappableCharacterAction", "REPLACE");
    System.setProperty("com.ibm.mq.cfg.jmqi.UnmappableCharacterReplacement", "63");
    

    【讨论】:

    • 完美答案。我后来才知道这件事。上投票。谢谢@JoshMc
    猜你喜欢
    • 1970-01-01
    • 2020-03-02
    • 2011-07-20
    • 2011-04-19
    • 1970-01-01
    • 1970-01-01
    • 2012-04-28
    相关资源
    最近更新 更多