实现IM通讯的基本方式:
- 基于bio阻塞的socket,消息分发是activemq
- 私聊activeMQ做持久化(弃用数据库,因为数据库容易挂掉)
- 并发底、扩展性底、集群不了
优化思路:(使用netty)nio
- 客户端访问,经过Nginx转发到im service集群下
- 通过service集群,先执行登录,成功后返回im server的ip,port(轮询),并保存用户及ip,port连接关系
- 客户端获得ip,port,执行连接im server
- 连接到im server
示意图:
注意事项:
- 对客户端来讲,需要处理的是重连情况,应该要设置重连次数和间隔重连时间
- im service和server都是通过zookeeper维护,如果某一台服务挂掉,通过zookeeper移除,挂掉的服务上的连接进入重连过程,这样就不会再次连到已挂的服务上
- 对于粘包拆包问题,就是通过消息头定长策略
关于粘包拆包代码:
/**
* 解socket数据包体
* @param is
* @return
* @throws IOException
*/
public static String getDataBody(InputStream is) throws IOException {
String dataBody = null;
// 获取头部
byte[] head = getData(is, 4);
int dataLength = ByteUtil.toInt(head);
// 获取数据
byte[] data = getData(is, dataLength);
dataBody = GZipUtil.uncompressToString(data);
return dataBody;
}
/**
* 拆包
* @param is
* @param length
* @return
* @throws IOException
*/
private static byte[] getData(InputStream is, int length) throws IOException {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
byte[] buffer = new byte[1024];
int nIdx = 0; //累计读取了多少位
int nReadLen = 0; //一次读取了多少位
while (nIdx < length) { //循环读取足够长度的数据
if(length - nIdx >= buffer.length){ //剩余数据大于缓存,则全部读取
nReadLen = is.read(buffer);
}else{ //剩余数据小于缓存,则注意拆分其他包,只取当前包剩余数据
nReadLen = is.read(buffer, 0, length - nIdx);
}
if (nReadLen > 0) {
baos.write(buffer, 0, nReadLen);
nIdx = nIdx + nReadLen;
} else {
break;
}
}
return baos.toByteArray();
}
public void send(String content, OutputStream writer){
****代码省略
BufferedOutputStream bops = new BufferedOutputStream(writer,
4 * SIZE);
// 压缩过后的byte数
byte[] contentbytes = GZipUtil.compressToBtyes(content
.getBytes("UTF-8"));
// 压缩过后 内容的长度
int length = contentbytes.length;
// 最后传输的数据
byte[] data = new byte[length + 4];
// 内容长度的字节数
byte[] lengthdata = ByteUtil.toByteArray(length, 4);
// 发送的字节 要先拼了内容长度的字节数 再拼上真实内容的字节数
for (int i = 0; i < (length + 4); i++) {
if (i < 4) {
data[i] = lengthdata[i];
} else {
data[i] = contentbytes[i - 4];
}
}
bops.write(data);
bops.flush();
****代码省略
}
特别鸣谢:翔哥