简要说明
jimclient连接失败的问题。
实际上连接成功了,但是呢,因为线程的原因,却导致了返回失败。
这个bug符合多线程问题的一贯表现。要么不出现,要么总是出现,要么随机。
官方的demo总也不出现,我自己照着官网的demo撸了一遍,开始的时候不出现,后来总是出现。
问题描述
项目代码
https://gitee.com/xchao/j-im/tree/master
具体代码
https://gitee.com/xchao/j-im/blob/master/jim-client-demo/src/main/java/org/jim/client/HelloClientStarter.java
中
//连接服务端
imClientChannelContext = jimClient.connect(serverNode);
会返回null
查看里面的代码
public ImClientChannelContext connect(Node serverNode, Integer timeout) throws Exception {
log.warn("J-IM client connect");
this.tioClient = new TioClient((ClientTioConfig)this.imClientConfig.getTioConfig());
ClientChannelContext clientChannelContext = this.tioClient.connect(serverNode, this.imClientConfig.getBindIp(), this.imClientConfig.getBindPort(), timeout);
if (Objects.nonNull(clientChannelContext)) {
log.warn("J-IM client connected success at serverAddress:[{}], bind localAddress:[{}]", serverNode.toString(), this.imClientConfig.toBindAddressString());
return (ImClientChannelContext)clientChannelContext.get("im_channel_context_key");
} else {
return null;
}
}
里面的
(ImClientChannelContext)clientChannelContext.get(“im_channel_context_key”);
里面有一个map,在connect成功之后调用线程进行填充。
但是呢,就是因为线程的原因,填充之前就进行了获取。
自己打断点调试就知道了。
解决办法
方法1 (放弃了)
跟进去,里面有tio的代码,添加线程锁啥的,进行同步。
我跟到里面的一个connect
CountDownLatch countDownLatch = new CountDownLatch(1);
attachment.setCountDownLatch(countDownLatch);
asynchronousSocketChannel.connect(inetSocketAddress, attachment, this.clientTioConfig.getConnectionCompletionHandler());
boolean f = countDownLatch.await((long)realTimeout, TimeUnit.SECONDS);
if (f) {
return attachment.getChannelContext();
} else {
log.error("countDownLatch.await(realTimeout, TimeUnit.SECONDS) 返回false ");
return attachment.getChannelContext();
}
我放弃了,里面又启动了一个线程。
本人刚学java不久,改不了这么精密的程序,故放弃~~
方法2 (可用)
重新写一个JimClient类
package cn.xxxx.yyy.service.JimClients;
import org.jim.client.ImClientChannelContext;
import org.jim.client.JimClient;
import org.jim.client.config.ImClientConfig;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.tio.client.ClientChannelContext;
import org.tio.client.ClientTioConfig;
import org.tio.client.TioClient;
import org.tio.core.Node;
import java.util.Objects;
/***
* auth: robin
* date: 2021/01/21
* 这里基本复制的JimClient,没直接用JimClient的原因是,
* clientChannelContext.get("im_channel_context_key") 很大的概率会返回null,
* 因为里面有个map,赋值是通过多线程来完成的,如果另一个线程没有赋值的话,这里得到的就是null
*
*/
public class WJimClient {
private static Logger log = LoggerFactory.getLogger(JimClient.class);
private TioClient tioClient = null;
private ImClientConfig imClientConfig;
public WJimClient(ImClientConfig imClientConfig) {
this.imClientConfig = imClientConfig;
}
public ImClientChannelContext connect(Node serverNode) throws Exception {
return this.connect(serverNode, (Integer)null);
}
public ImClientChannelContext connect(Node serverNode, Integer timeout) throws Exception {
log.warn("J-IM client connect");
this.tioClient = new TioClient((ClientTioConfig)this.imClientConfig.getTioConfig());
ClientChannelContext clientChannelContext = this.tioClient.connect(serverNode, this.imClientConfig.getBindIp(), this.imClientConfig.getBindPort(), timeout);
if (Objects.nonNull(clientChannelContext)) {
log.warn("J-IM client connected success at serverAddress:[{}], bind localAddress:[{}]", serverNode.toString(), this.imClientConfig.toBindAddressString());
ImClientChannelContext t_rtnContext = (ImClientChannelContext)clientChannelContext.get("im_channel_context_key");
int t_maxCount = 200; //最大循环次数,实际测试中只循环了一次
while (null == t_rtnContext
&& t_maxCount > 0){//这里是在等待另一个线程写入
Thread.sleep(50);
t_rtnContext = (ImClientChannelContext)clientChannelContext.get("im_channel_context_key");
t_maxCount--;
}
System.out.println("robin:count:"+(200-t_maxCount));
return t_rtnContext;
} else {
return null;
}
}
public void stop() {
this.tioClient.stop();
}
}
主要是里面加了一个sleep。虽然里面最大sleep时间是10s,但是实际上只循环了一次就正常了。
搞定。
提交bug了
那个项目很久没提交了,已经提交了issue。