* MQ服务器的配置参加上篇*
4-绑定模式连接
Java应用程序必须和MQ服务器安装在同一个机器上,通过进程间通信机制,不通过TCP/IP进行通信,减少网络开销。
MQ提供32和64位版本的MQ JNI库,默认在/opt/mqm/java/lib/和/opt/mqm/java/lib64/目录下:
mqjbnd.so: 该库为应用提供绑定模式连接MQ;
修改代码:其实变动更小,只要将主机地址和端口去掉即可,因为绑定模式不需要网络通信,此外也不需要TCP通道参数。
import java.io.IOException;
import com.ibm.mq.MQEnvironment;
import com.ibm.mq.MQException;
import com.ibm.mq.MQGetMessageOptions;
import com.ibm.mq.MQMessage;
import com.ibm.mq.MQPutMessageOptions;
import com.ibm.mq.MQQueue;
import com.ibm.mq.MQQueueManager;
import com.ibm.mq.constants.CMQC;
import com.ibm.mq.constants.MQConstants;
public class MQTest2 {
public static void main(String[] args) throws MQException, IOException
{
//发送消息给队列
put();
//从队列读取消息
get();
}
static void put() throws MQException, IOException
{
//配置MQ服务器连接参数
//用户名和密码
MQEnvironment.userID = "mquser1";
MQEnvironment.password = "mqtest2016";
//设置应用名称,方便服务器MQ 查看应用连接
MQEnvironment.properties.put(MQConstants.APPNAME_PROPERTY, "MQ Test By Java");
//设置绑定模式通信
MQEnvironment.properties.put(CMQC.TRANSPORT_PROPERTY,CMQC.TRANSPORT_MQSERIES_BINDINGS);
//创建实例,连接队列管理器
MQQueueManager queueManager = new MQQueueManager("JAVA.QUEUE.MANAGER.1");
//以可写的方式访问队列管理器已定义的队列QUEUE1,当然也可以创建队列
MQQueue putQueue = queueManager.accessQueue("QUEUE1", CMQC.MQOO_OUTPUT);
//新建并发送消息给队列
MQMessage myMessage = new MQMessage();
String name = "MePlusPlus's 博客2";
myMessage.writeUTF(name);
//使用默认的消息选项
MQPutMessageOptions pmo = new MQPutMessageOptions();
//发送消息
putQueue.put(myMessage, pmo);
putQueue.close();
//断开连接
queueManager.disconnect();
}
static void get() throws MQException, IOException
{
//配置MQ服务器连接参数
MQEnvironment.userID = "mquser1";
MQEnvironment.password = "mqtest2016";
//设置应用名称,方便服务器MQ 查看应用连接
MQEnvironment.properties.put(MQConstants.APPNAME_PROPERTY, "MQ Test By Java");
MQEnvironment.properties.put(CMQC.TRANSPORT_PROPERTY, CMQC.TRANSPORT_MQSERIES_BINDINGS);
//创建实例,连接队列管理器
MQQueueManager queueManager = new MQQueueManager("JAVA.QUEUE.MANAGER.1");
//以可读的方式访问队列管理器已定义的队列QUEUE1
MQQueue getQueue = queueManager.accessQueue("QUEUE1", CMQC.MQOO_INPUT_AS_Q_DEF);
//从队列读取消息
MQMessage theMessage = new MQMessage();
MQGetMessageOptions gmo = new MQGetMessageOptions();
getQueue.get(theMessage, gmo);
String name = theMessage.readUTF();
System.out.println(name);
getQueue.close();
//断开连接
queueManager.disconnect();
}
}
编译运行:
# cd /home/mq
# /home/jdk1.8.0_102/bin/javac -cp /opt/mqm/java/lib/com.ibm.mq.allclient.jar MQTest2.java
# /home/jdk1.8.0_102/bin/java -cp /opt/mqm/java/lib/com.ibm.mq.allclient.jar:/home/mq MQTest2
出现以下错误:
Exception in thread "main" com.ibm.mq.MQException: MQJE001: 完成代码为 '2',原因为 '2495'。
at com.ibm.mq.MQSESSION.(MQSESSION.java:2065)
at com.ibm.mq.MQSESSION.getSession(MQSESSION.java:2105)
at com.ibm.mq.MQManagedConnectionJ11.(MQManagedConnectionJ11.java:210)
at com.ibm.mq.MQBindingsManagedConnectionFactoryJ11._createManagedConnection(MQBindingsManagedConnectionFactoryJ11.java:187)
at com.ibm.mq.MQBindingsManagedConnectionFactoryJ11.createManagedConnection(MQBindingsManagedConnectionFactoryJ11.java:233)
at com.ibm.mq.StoredManagedConnection.(StoredManagedConnection.java:96)
at com.ibm.mq.MQSimpleConnectionManager.allocateConnection(MQSimpleConnectionManager.java:194)
at com.ibm.mq.MQQueueManagerFactory.obtainBaseMQQueueManager(MQQueueManagerFactory.java:767)
at com.ibm.mq.MQQueueManagerFactory.procure(MQQueueManagerFactory.java:715)
at com.ibm.mq.MQQueueManagerFactory.constructQueueManager(MQQueueManagerFactory.java:678)
at com.ibm.mq.MQQueueManagerFactory.createQueueManager(MQQueueManagerFactory.java:148)
at com.ibm.mq.MQQueueManager.(MQQueueManager.java:675)
at MQTest2.put(MQTest2.java:38)
at MQTest2.main(MQTest2.java:18)
Caused by: com.ibm.mq.jmqi.local.LocalMQ$3: CC=2;RC=2495;AMQ8598: 未能装入 WebSphere MQ 本机 JNI 库“'mqjbnd'”。
at com.ibm.mq.jmqi.local.LocalMQ.loadLib(LocalMQ.java:1236)
at com.ibm.mq.jmqi.local.LocalMQ$1.run(LocalMQ.java:280)
at java.security.AccessController.doPrivileged(Native Method)
at com.ibm.mq.jmqi.local.LocalMQ.initialise_inner(LocalMQ.java:268)
at com.ibm.mq.jmqi.local.LocalMQ.initialise(LocalMQ.java:231)
at com.ibm.mq.jmqi.local.LocalMQ.(LocalMQ.java:1318)
at com.ibm.mq.jmqi.local.LocalServer.(LocalServer.java:229)
at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62)
at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
at java.lang.reflect.Constructor.newInstance(Constructor.java:423)
at com.ibm.mq.jmqi.JmqiEnvironment.getInstance(JmqiEnvironment.java:672)
at com.ibm.mq.jmqi.JmqiEnvironment.getMQI(JmqiEnvironment.java:606)
at com.ibm.mq.MQSESSION.(MQSESSION.java:2058)
... 13 more
Caused by: java.lang.UnsatisfiedLinkError: no mqjbnd in java.library.path
at java.lang.ClassLoader.loadLibrary(ClassLoader.java:1867)
at java.lang.Runtime.loadLibrary0(Runtime.java:870)
at java.lang.System.loadLibrary(System.java:1122)
at com.ibm.mq.jmqi.local.LocalMQ.loadLib(LocalMQ.java:1208)
... 26 more
很明显,上述错误,是因为找不到MQ JNI库,libmqjbnd.so,重新运行,指明该库位置(注意本机是64位操作系统)
# /home/jdk1.8.0_102/bin/java -Djava.library.path=/opt/mqm/java/lib64 -cp /opt/mqm/java/lib/com.ibm.mq.allclient.jar:/home/mq MQTest2
再次出错
Exception in thread "main" com.ibm.mq.MQException: MQJE001: 完成代码为 '2',原因为 '2035'。
at com.ibm.mq.MQManagedConnectionJ11.(MQManagedConnectionJ11.java:251)
at com.ibm.mq.MQBindingsManagedConnectionFactoryJ11._createManagedConnection(MQBindingsManagedConnectionFactoryJ11.java:187)
at com.ibm.mq.MQBindingsManagedConnectionFactoryJ11.createManagedConnection(MQBindingsManagedConnectionFactoryJ11.java:233)
at com.ibm.mq.StoredManagedConnection.(StoredManagedConnection.java:96)
at com.ibm.mq.MQSimpleConnectionManager.allocateConnection(MQSimpleConnectionManager.java:194)
at com.ibm.mq.MQQueueManagerFactory.obtainBaseMQQueueManager(MQQueueManagerFactory.java:767)
at com.ibm.mq.MQQueueManagerFactory.procure(MQQueueManagerFactory.java:715)
at com.ibm.mq.MQQueueManagerFactory.constructQueueManager(MQQueueManagerFactory.java:678)
at com.ibm.mq.MQQueueManagerFactory.createQueueManager(MQQueueManagerFactory.java:148)
at com.ibm.mq.MQQueueManager.(MQQueueManager.java:675)
at MQTest2.put(MQTest2.java:38)
at MQTest2.main(MQTest2.java:18)
···
很明显,表明MQ访问的权限错误,说明在绑定模式下,MQ未能充分授权,可能是队列管理器、通道或者队列对象。
经过搜索和阅读文档,发现如下几种方式,尝试了以下方法,最终通过最后一种方法解决:
第一种尝试(**该方法不起效果**):仔细检查服务器端队列管理器和通道等配置发现,
> DEFINE CHANNEL(JAVA.CLIENT.CHANNEL1) CHLTYPE(SVRCONN) TRPTYPE(TCP)
> SET CHLAUTH(JAVA.CLIENT.CHANNEL1) TYPE(ADDRESSMAP) ADDRESS('127.0.0.1') MCAUSER('mquser1')
这里的channel配置是针对IP地址进行过滤和限制访问的,建立的TCP通道,因此需要新增另一种类型的通道。
需要新建一个通道,非TCP通道,命名为JAVA.CLIENT.CHANNEL2
此外发现错误:少配置了 channel的参数,加入代码:
MQEnvironment.channel = "JAVA.CLIENT.CHANNEL2";
# su - mqm
~ cd /opt/mqm/bin
~ source setmqenv -s
启动脚本执行器,进行设置
~ runmqsc JAVA.QUEUE.MANAGER.1
DEFINE CHANNEL(JAVA.CLIENT.CHANNEL2) CHLTYPE(SVRCONN)
SET CHLAUTH('JAVA.CLINET.CHANNEL2') TYPE(USERMAP) ACTION(ADD) CLNTUSER('mquser1') USERSRC(MAP) MCAUSER('mquser1')
**仍然出错,其实通过绑定模式,不需要通道参数,因为它不通过网络进行通信,是利用进程间通信机制进行消息传递,上述方法无法解决**
第二种方法:(**该方法有效**)
在寻找文档和查找MQ Java API文档以后,发现了以下说明,进而找到解决方法,主要是因为执行MQTest2时,
需要切换到特定用户,本文定义队列管理器和队列时授权给mquser1。
此外,在类 com.ibm.mq.MQEnvironment的
[文档](https://www.ibm.com/support/knowledgecenter/SSFKSJ_7.5.0/com.ibm.mq.javadoc.doc/WMQJavaClasses/com/ibm/mq/MQEnvironment.html)
中有该表述:
> All the methods and attributes of this class apply to the WebSphere MQ classes for Java client connections,
> but only enableTracing(), disableTracing(), properties, version_notice, userID, connOptions and connTag apply to bindings connections.
翻译出来,很简单,对于绑定模式,MQEnvironment类中只有 enableTracing(), disableTracing(), properties,
version_notice, userID, connOptions and connTag 这几个方法和属性有效,所以不用设置通道,主机名等属性,
此外,properties里面很多key也就是重写了类中方法和属性而已。
因此需要修改MQTest2.java文件,删除不必要的属性,修改后的代码如下:
```java
import java.io.IOException;
import com.ibm.mq.MQEnvironment;
import com.ibm.mq.MQException;
import com.ibm.mq.MQGetMessageOptions;
import com.ibm.mq.MQMessage;
import com.ibm.mq.MQPutMessageOptions;
import com.ibm.mq.MQQueue;
import com.ibm.mq.MQQueueManager;
import com.ibm.mq.constants.CMQC;
import com.ibm.mq.constants.MQConstants;
public class MQTest2 {
public static void main(String[] args) throws MQException, IOException
{
//发送消息给队列
put();
//从队列读取消息
get();
}
static void put() throws MQException, IOException
{
//设置应用名称,方便服务器MQ 查看应用连接
MQEnvironment.properties.put(MQConstants.APPNAME_PROPERTY, "MQ Test By Java");
//设置绑定模式通信
MQEnvironment.properties.put(CMQC.TRANSPORT_PROPERTY,CMQC.TRANSPORT_MQSERIES_BINDINGS);
//创建实例,连接队列管理器
MQQueueManager queueManager = new MQQueueManager("JAVA.QUEUE.MANAGER.1");
//以可写的方式访问队列管理器已定义的队列QUEUE1,当然也可以创建队列
MQQueue putQueue = queueManager.accessQueue("QUEUE1", CMQC.MQOO_OUTPUT);
//新建并发送消息给队列
MQMessage myMessage = new MQMessage();
String name = "MePlusPlus's 博客2";
myMessage.writeUTF(name);
//使用默认的消息选项
MQPutMessageOptions pmo = new MQPutMessageOptions();
//发送消息
putQueue.put(myMessage, pmo);
putQueue.close();
//断开连接
queueManager.disconnect();
}
static void get() throws MQException, IOException
{
//设置应用名称,方便服务器MQ 查看应用连接
MQEnvironment.properties.put(MQConstants.APPNAME_PROPERTY, "MQ Test By Java");
MQEnvironment.properties.put(CMQC.TRANSPORT_PROPERTY, CMQC.TRANSPORT_MQSERIES_BINDINGS);
//创建实例,连接队列管理器
MQQueueManager queueManager = new MQQueueManager("JAVA.QUEUE.MANAGER.1");
//以可读的方式访问队列管理器已定义的队列QUEUE1
MQQueue getQueue = queueManager.accessQueue("QUEUE1", CMQC.MQOO_INPUT_AS_Q_DEF);
//从队列读取消息
MQMessage theMessage = new MQMessage();
MQGetMessageOptions gmo = new MQGetMessageOptions();
getQueue.get(theMessage, gmo);
String name = theMessage.readUTF();
System.out.println(name);
getQueue.close();
//断开连接
queueManager.disconnect();
}
}
编译运行:
# cd /home/mq
# /home/jdk1.8.0_102/bin/javac -cp /opt/mqm/java/lib/com.ibm.mq.allclient.jar:/home/mq MQTest2.java
切换到mquser1执行:(**这个是关键**)
# su - mquser1
~ /home/jdk1.8.0_102/bin/java -Djava.library.path=/opt/mqm/java/lib64 -cp /opt/mqm/java/lib/com.ibm.mq.allclient.jar:/home/mq MQTest2
5-MQEnvironment类中的userID和password属性作用初探
MQEnvironment类中的userID和password属性:用来认证MQ应用的用户身份。
如果设置了securityExit属性,应用特定的安全验证流程,则userID和password属性将不起作用被忽略;
默认情况下,如果userID为空时,将默认发送JRE 属性(user.name)作为该属性值;如果设置了,则发送设置值;
此外,目前SecurityExit该接口已经过期,对于绑定模式(binding mode),SecurityExit接口不支持。
因此,userID和password表明应用身份,要和MQ服务器端的安全设置配合使用,本文的示例程序中并没有用上该属性。
上述MQ设置:
SET AUTHREC PROFILE(QUEUE1) OBJTYPE(QUEUE) PRINCIPAL('mquser1') AUTHADD(PUT,GET)
principal属性表明 用户mquser1对于该队列管理器中的队列具有put,get权限。
SET AUTHREC OBJTYPE(QMGR) PRINCIPAL('mquser1') AUTHADD(CONNECT)
principal属性表明mquser1对于队列管理器具有连接的权限
DEFINE CHANNEL(JAVA.CLIENT.CHANNEL1) CHLTYPE(SVRCONN) TRPTYPE(TCP)
SET CHLAUTH(JAVA.CLIENT.CHANNEL1) TYPE(ADDRESSMAP) ADDRESS('127.0.0.1') MCAUSER('mquser1')
这条很重要,表明通过IP地址进行连接过滤,表明127.0.0.1这台机器可以通过通道JAVA.CLIENT.CHANNEL1,并以mquser1的身份访问队列管理器。
也就是说127.0.0.1这台机器无需传递任何身份信息。
那么该userID和password作用有哪些?
实验:对于上述实例中的TCP方式访问的代码,1.不设置该属性,正常运行;2.设置为mquser1和正确的password,正常运行;
3.设置mquser1和不设置password属性,正常运行
4.设置为mquser1和错误的密码无法访问 5.设置不存在的用户mquser2,也无法访问; 6.添加用户mquser2,正常运行
说明MQ服务器对于应用传递过来的userID和password属性并没有忽略,而是一种方式在验证,从上看出是以验证操作系统用户的方式在验证。
此外配合MQ的权限设置,可以
SET CHLAUTH(' generic-channel-name ') TYPE (USERMAP) CLNTUSER(client-user-name) USERSRC(MAP) MCAUSER(user)
通过用户映射,将应用传递过来的userID(也就是client-user-name) 映射成 系统的user(MCAUSER属性)