mirror of
https://gitee.com/farsunset/cim.git
synced 2025-06-17 07:35:25 +08:00
新增对websocket的支持
添加web sdk,以及使用示例,文档
This commit is contained in:
parent
b101c46fbf
commit
2b3bd38f12
@ -32,7 +32,7 @@ public class HeartbeatRequest implements Serializable,Protobufable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
private static final String TAG = "SERVER_HEARTBEAT_REQUEST";
|
||||
private static final String CMD_HEARTBEAT_RESPONSE = "SR";
|
||||
private static final String CMD_HEARTBEAT_REQUEST = "SR";
|
||||
|
||||
private static HeartbeatRequest object = new HeartbeatRequest();
|
||||
|
||||
@ -46,7 +46,7 @@ public class HeartbeatRequest implements Serializable,Protobufable {
|
||||
|
||||
@Override
|
||||
public byte[] getByteArray() {
|
||||
return CMD_HEARTBEAT_RESPONSE.getBytes();
|
||||
return CMD_HEARTBEAT_REQUEST.getBytes();
|
||||
}
|
||||
|
||||
public String toString(){
|
||||
|
3
cim_for_mina/cim-client-android/.idea/gradle.xml
generated
3
cim_for_mina/cim-client-android/.idea/gradle.xml
generated
@ -3,8 +3,9 @@
|
||||
<component name="GradleSettings">
|
||||
<option name="linkedExternalProjectsSettings">
|
||||
<GradleProjectSettings>
|
||||
<option name="distributionType" value="DEFAULT_WRAPPED" />
|
||||
<option name="distributionType" value="LOCAL" />
|
||||
<option name="externalProjectPath" value="$PROJECT_DIR$" />
|
||||
<option name="gradleHome" value="D:\devtools\dev\gradle-3.4" />
|
||||
<option name="modules">
|
||||
<set>
|
||||
<option value="$PROJECT_DIR$" />
|
||||
|
@ -55,7 +55,7 @@
|
||||
<!-- ****************************************CIM推送配置 end*************************************** -->
|
||||
|
||||
<!--消息接受广播注册-->
|
||||
<receiver android:name="com.farsunset.ichat.example.receiver.CIMPushManagerReceiver" android:exported="false">
|
||||
<receiver android:name="com.farsunset.ichat.example.receiver.CIMPushManagerReceiver">
|
||||
<intent-filter android:priority="0x7fffffff">
|
||||
<action android:name="android.net.conn.CONNECTIVITY_CHANGE"/> <!-- 网络变化广播 -->
|
||||
<action android:name="com.farsunset.cim.MESSAGE_RECEIVED"/><!-- 消息广播action -->
|
||||
|
@ -24,7 +24,7 @@ package com.farsunset.ichat.example.app;
|
||||
public interface Constant {
|
||||
|
||||
//服务端IP地址
|
||||
public static final String CIM_SERVER_HOST = "172.168.11.13";
|
||||
public static final String CIM_SERVER_HOST = "172.168.11.18";
|
||||
|
||||
|
||||
//注意,这里的端口不是tomcat的端口,CIM端口在服务端spring-cim.xml中配置的,没改动就使用默认的23456
|
||||
|
@ -1,9 +1,20 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<classpath>
|
||||
<classpathentry kind="src" path="src"/>
|
||||
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/>
|
||||
<classpathentry kind="lib" path="libs/log4j-1.2.17.jar"/>
|
||||
<classpathentry kind="lib" path="libs/mina-core-2.0.16.jar"/>
|
||||
<classpathentry kind="lib" path="libs/protobuf-java-3.2.0.jar"/>
|
||||
<classpathentry kind="lib" path="libs/fastjson-1.2.39.jar"/>
|
||||
<classpathentry kind="con" path="org.eclipse.jst.j2ee.internal.module.container"/>
|
||||
<classpathentry kind="con" path="org.eclipse.jst.server.core.container/org.eclipse.jst.server.tomcat.runtimeTarget/Apache Tomcat v7.0">
|
||||
<attributes>
|
||||
<attribute name="owner.project.facets" value="jst.utility"/>
|
||||
</attributes>
|
||||
</classpathentry>
|
||||
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/jdk1.8.0_131">
|
||||
<attributes>
|
||||
<attribute name="owner.project.facets" value="java"/>
|
||||
</attributes>
|
||||
</classpathentry>
|
||||
<classpathentry kind="output" path="bin"/>
|
||||
</classpath>
|
||||
|
@ -5,13 +5,26 @@
|
||||
<projects>
|
||||
</projects>
|
||||
<buildSpec>
|
||||
<buildCommand>
|
||||
<name>org.eclipse.wst.common.project.facet.core.builder</name>
|
||||
<arguments>
|
||||
</arguments>
|
||||
</buildCommand>
|
||||
<buildCommand>
|
||||
<name>org.eclipse.jdt.core.javabuilder</name>
|
||||
<arguments>
|
||||
</arguments>
|
||||
</buildCommand>
|
||||
<buildCommand>
|
||||
<name>org.eclipse.wst.validation.validationbuilder</name>
|
||||
<arguments>
|
||||
</arguments>
|
||||
</buildCommand>
|
||||
</buildSpec>
|
||||
<natures>
|
||||
<nature>org.eclipse.jem.workbench.JavaEMFNature</nature>
|
||||
<nature>org.eclipse.wst.common.modulecore.ModuleCoreNature</nature>
|
||||
<nature>org.eclipse.jdt.core.javanature</nature>
|
||||
<nature>org.eclipse.wst.common.project.facet.core.nature</nature>
|
||||
</natures>
|
||||
</projectDescription>
|
||||
|
@ -0,0 +1,7 @@
|
||||
eclipse.preferences.version=1
|
||||
org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled
|
||||
org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.6
|
||||
org.eclipse.jdt.core.compiler.compliance=1.6
|
||||
org.eclipse.jdt.core.compiler.problem.assertIdentifier=error
|
||||
org.eclipse.jdt.core.compiler.problem.enumIdentifier=error
|
||||
org.eclipse.jdt.core.compiler.source=1.6
|
@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?><project-modules id="moduleCoreId" project-version="1.5.0">
|
||||
<wb-module deploy-name="cim-server-sdk">
|
||||
<wb-resource deploy-path="/" source-path="/src"/>
|
||||
</wb-module>
|
||||
</project-modules>
|
@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<faceted-project>
|
||||
<runtime name="Apache Tomcat v7.0"/>
|
||||
<fixed facet="java"/>
|
||||
<fixed facet="jst.utility"/>
|
||||
<installed facet="java" version="1.6"/>
|
||||
<installed facet="jst.utility" version="1.0"/>
|
||||
</faceted-project>
|
BIN
cim_for_mina/cim-server-sdk/libs/fastjson-1.2.39.jar
Normal file
BIN
cim_for_mina/cim-server-sdk/libs/fastjson-1.2.39.jar
Normal file
Binary file not shown.
3
cim_for_mina/cim-server-sdk/src/META-INF/MANIFEST.MF
Normal file
3
cim_for_mina/cim-server-sdk/src/META-INF/MANIFEST.MF
Normal file
@ -0,0 +1,3 @@
|
||||
Manifest-Version: 1.0
|
||||
Class-Path:
|
||||
|
@ -46,19 +46,16 @@ public interface CIMConstant {
|
||||
|
||||
|
||||
public static interface ProtobufType{
|
||||
byte C_H_RS = 0;
|
||||
byte S_H_RQ = 1;
|
||||
byte C_H_RS = 0;
|
||||
byte MESSAGE = 2;
|
||||
byte SENTBODY = 3;
|
||||
byte REPLYBODY = 4;
|
||||
}
|
||||
|
||||
|
||||
public static interface MessageAction{
|
||||
|
||||
//被其他设备登录挤下线消息
|
||||
String ACTION_999 ="999";
|
||||
//被系统禁用消息
|
||||
String ACTION_444 ="444";
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -21,89 +21,18 @@
|
||||
*/
|
||||
package com.farsunset.cim.sdk.server.filter;
|
||||
|
||||
import org.apache.log4j.Logger;
|
||||
import org.apache.mina.core.buffer.IoBuffer;
|
||||
import org.apache.mina.core.session.IoSession;
|
||||
import org.apache.mina.filter.codec.CumulativeProtocolDecoder;
|
||||
import org.apache.mina.filter.codec.ProtocolDecoderOutput;
|
||||
import org.apache.mina.filter.codec.demux.DemuxingProtocolDecoder;
|
||||
|
||||
import com.farsunset.cim.sdk.server.constant.CIMConstant;
|
||||
import com.farsunset.cim.sdk.server.model.HeartbeatResponse;
|
||||
import com.farsunset.cim.sdk.server.model.SentBody;
|
||||
import com.farsunset.cim.sdk.server.model.proto.SentBodyProto;
|
||||
import com.farsunset.cim.sdk.server.filter.decoder.AppMessageDecoder;
|
||||
import com.farsunset.cim.sdk.server.filter.decoder.WebMessageDecoder;
|
||||
/**
|
||||
* 服务端接收消息解码
|
||||
*/
|
||||
public class ServerMessageDecoder extends CumulativeProtocolDecoder {
|
||||
public class ServerMessageDecoder extends DemuxingProtocolDecoder {
|
||||
|
||||
protected final Logger logger = Logger.getLogger(ServerMessageDecoder.class);
|
||||
@Override
|
||||
public boolean doDecode(IoSession iosession, IoBuffer iobuffer, ProtocolDecoderOutput out) throws Exception {
|
||||
/**
|
||||
* 消息头3位
|
||||
*/
|
||||
if(iobuffer.remaining() < CIMConstant.DATA_HEADER_LENGTH){
|
||||
return false;
|
||||
}
|
||||
|
||||
iobuffer.mark();
|
||||
|
||||
byte conetnType = iobuffer.get();
|
||||
byte lv =iobuffer.get();//int 低位
|
||||
byte hv =iobuffer.get();//int 高位
|
||||
|
||||
int conetnLength = getContentLength(lv,hv);
|
||||
|
||||
|
||||
//如果消息体没有接收完整,则重置读取,等待下一次重新读取
|
||||
if(conetnLength > iobuffer.remaining()){
|
||||
iobuffer.reset();
|
||||
return false;
|
||||
}
|
||||
|
||||
byte[] dataBytes = new byte[conetnLength];
|
||||
iobuffer.get(dataBytes, 0, conetnLength);
|
||||
|
||||
Object message = mappingMessageObject(dataBytes,conetnType);
|
||||
if(message != null){
|
||||
out.write(message);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public Object mappingMessageObject(byte[] data,byte type) throws Exception
|
||||
{
|
||||
|
||||
if(CIMConstant.ProtobufType.C_H_RS == type)
|
||||
{
|
||||
HeartbeatResponse response = HeartbeatResponse.getInstance();
|
||||
logger.info(response.toString());
|
||||
return response;
|
||||
}
|
||||
|
||||
if(CIMConstant.ProtobufType.SENTBODY == type)
|
||||
{
|
||||
SentBodyProto.Model bodyProto = SentBodyProto.Model.parseFrom(data);
|
||||
SentBody body = new SentBody();
|
||||
body.setKey(bodyProto.getKey());
|
||||
body.setTimestamp(bodyProto.getTimestamp());
|
||||
body.putAll(bodyProto.getDataMap());
|
||||
logger.info(body.toString());
|
||||
|
||||
return body;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析消息体长度
|
||||
* @param type
|
||||
* @param length
|
||||
* @return
|
||||
*/
|
||||
private int getContentLength(byte lv,byte hv){
|
||||
int l = (lv & 0xff);
|
||||
int h = (hv & 0xff);
|
||||
return (l| (h <<= 8));
|
||||
public ServerMessageDecoder() {
|
||||
addMessageDecoder(new AppMessageDecoder());
|
||||
addMessageDecoder(new WebMessageDecoder());
|
||||
}
|
||||
}
|
||||
|
@ -26,10 +26,10 @@ import org.apache.mina.core.buffer.IoBuffer;
|
||||
import org.apache.mina.core.session.IoSession;
|
||||
import org.apache.mina.filter.codec.ProtocolEncoderAdapter;
|
||||
import org.apache.mina.filter.codec.ProtocolEncoderOutput;
|
||||
|
||||
import com.farsunset.cim.sdk.server.constant.CIMConstant;
|
||||
import com.farsunset.cim.sdk.server.model.Protobufable;
|
||||
|
||||
import com.farsunset.cim.sdk.server.model.WebsocketResponse;
|
||||
import com.farsunset.cim.sdk.server.model.feature.EncodeFormatable;
|
||||
import com.farsunset.cim.sdk.server.session.CIMSession;
|
||||
|
||||
/**
|
||||
* 服务端发送消息前编码
|
||||
@ -37,41 +37,118 @@ import com.farsunset.cim.sdk.server.model.Protobufable;
|
||||
public class ServerMessageEncoder extends ProtocolEncoderAdapter {
|
||||
|
||||
protected final Logger logger = Logger.getLogger(ServerMessageEncoder.class);
|
||||
|
||||
@Override
|
||||
public void encode(IoSession iosession, Object object, ProtocolEncoderOutput out) throws Exception {
|
||||
|
||||
if(object instanceof Protobufable){
|
||||
|
||||
Protobufable data = (Protobufable) object;
|
||||
byte[] byteArray = data.getByteArray();
|
||||
|
||||
IoBuffer buff = IoBuffer.allocate(byteArray.length + CIMConstant.DATA_HEADER_LENGTH).setAutoExpand(true);
|
||||
|
||||
buff.put(createHeader(data.getType(),byteArray.length));
|
||||
buff.put(byteArray);
|
||||
|
||||
buff.flip();
|
||||
|
||||
Object channel = iosession.getAttribute("channel");
|
||||
|
||||
/**
|
||||
* websocket的握手响应
|
||||
*/
|
||||
if (CIMSession.CHANNEL_BROWSER.equals(channel) && object instanceof WebsocketResponse) {
|
||||
WebsocketResponse data = (WebsocketResponse) object;
|
||||
byte[] byteArray = data.getBytes();
|
||||
IoBuffer buff = IoBuffer.allocate(byteArray.length).setAutoExpand(true);
|
||||
buff.put(byteArray);
|
||||
buff.flip();
|
||||
out.write(buff);
|
||||
|
||||
//打印出收到的消息
|
||||
logger.info(data.toString());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* websocket的数据传输使用JSON编码数据格式,因为Protobuf还没有支持js
|
||||
*/
|
||||
if (CIMSession.CHANNEL_BROWSER.equals(channel) && object instanceof EncodeFormatable) {
|
||||
EncodeFormatable data = (EncodeFormatable) object;
|
||||
byte[] byteArray = encodeDataFrame(data.getJSONBody());
|
||||
IoBuffer buff = IoBuffer.allocate(byteArray.length).setAutoExpand(true);
|
||||
/**
|
||||
* 由于websocket没有黏包和断包的问题,所以不必知道消息体的大小
|
||||
*/
|
||||
buff.put(byteArray);
|
||||
buff.flip();
|
||||
out.write(buff);
|
||||
logger.info(data.toString());
|
||||
}
|
||||
|
||||
/**
|
||||
* 非websocket的数据传输使用Protobuf编码数据格式
|
||||
*/
|
||||
if (!CIMSession.CHANNEL_BROWSER.equals(channel) && object instanceof EncodeFormatable) {
|
||||
|
||||
EncodeFormatable data = (EncodeFormatable) object;
|
||||
byte[] byteArray = data.getProtobufBody();
|
||||
IoBuffer buff = IoBuffer.allocate(byteArray.length + CIMConstant.DATA_HEADER_LENGTH).setAutoExpand(true);
|
||||
buff.put(createHeader(data.getDataType(), byteArray.length));
|
||||
buff.put(byteArray);
|
||||
|
||||
buff.flip();
|
||||
out.write(buff);
|
||||
logger.info(data.toString());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 消息体最大为65535
|
||||
*
|
||||
* @param type
|
||||
* @param length
|
||||
* @return
|
||||
*/
|
||||
private byte[] createHeader(byte type,int length){
|
||||
private byte[] createHeader(byte type, int length) {
|
||||
byte[] header = new byte[CIMConstant.DATA_HEADER_LENGTH];
|
||||
header[0] = type;
|
||||
header[1] = (byte) (length & 0xff);
|
||||
header[2] = (byte) ((length >> 8) & 0xff);
|
||||
header[2] = (byte) ((length >> 8) & 0xff);
|
||||
return header;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 发送到websocket的数据需要进行相关格式转换
|
||||
* 对传入数据进行无掩码转换
|
||||
* @param data
|
||||
* @return
|
||||
*/
|
||||
public static byte[] encodeDataFrame(byte[] data) {
|
||||
// 掩码开始位置
|
||||
int maskIndex = 2;
|
||||
|
||||
// 计算掩码开始位置
|
||||
if (data.length <= 125) {
|
||||
maskIndex = 2;
|
||||
} else if (data.length > 65536) {
|
||||
maskIndex = 10;
|
||||
} else if (data.length > 125) {
|
||||
maskIndex = 4;
|
||||
}
|
||||
|
||||
// 创建返回数据
|
||||
byte[] result = new byte[data.length + maskIndex];
|
||||
|
||||
// 开始计算ws-frame
|
||||
// frame-fin + frame-rsv1 + frame-rsv2 + frame-rsv3 + frame-opcode
|
||||
result[0] = (byte) 0x81; // 129
|
||||
|
||||
// frame-masked+frame-payload-length
|
||||
// 从第9个字节开始是 1111101=125,掩码是第3-第6个数据
|
||||
// 从第9个字节开始是 1111110>=126,掩码是第5-第8个数据
|
||||
if (data.length <= 125) {
|
||||
result[1] = (byte) (data.length);
|
||||
} else if (data.length > 65536) {
|
||||
result[1] = 0x7F; // 127
|
||||
} else if (data.length > 125) {
|
||||
result[1] = 0x7E; // 126
|
||||
result[2] = (byte) (data.length >> 8);
|
||||
result[3] = (byte) (data.length % 256);
|
||||
}
|
||||
|
||||
// 将数据编码放到最后
|
||||
for (int i = 0; i < data.length; i++) {
|
||||
result[i + maskIndex] = data[i];
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -0,0 +1,126 @@
|
||||
/**
|
||||
* Copyright 2013-2023 Xia Jun(3979434@qq.com).
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
***************************************************************************************
|
||||
* *
|
||||
* Website : http://www.farsunset.com *
|
||||
* *
|
||||
***************************************************************************************
|
||||
*/
|
||||
package com.farsunset.cim.sdk.server.filter.decoder;
|
||||
import org.apache.log4j.Logger;
|
||||
import org.apache.mina.core.buffer.IoBuffer;
|
||||
import org.apache.mina.core.session.IoSession;
|
||||
import org.apache.mina.filter.codec.ProtocolDecoderOutput;
|
||||
import org.apache.mina.filter.codec.demux.MessageDecoderAdapter;
|
||||
import org.apache.mina.filter.codec.demux.MessageDecoderResult;
|
||||
|
||||
import com.farsunset.cim.sdk.server.constant.CIMConstant;
|
||||
import com.farsunset.cim.sdk.server.model.HeartbeatResponse;
|
||||
import com.farsunset.cim.sdk.server.model.SentBody;
|
||||
import com.farsunset.cim.sdk.server.model.proto.SentBodyProto;
|
||||
/**
|
||||
* 服务端接收消息解码
|
||||
*/
|
||||
public class AppMessageDecoder extends MessageDecoderAdapter {
|
||||
|
||||
protected final Logger logger = Logger.getLogger(AppMessageDecoder.class);
|
||||
|
||||
|
||||
@Override
|
||||
public MessageDecoderResult decodable(IoSession arg0, IoBuffer iobuffer) {
|
||||
|
||||
if(iobuffer.remaining() < CIMConstant.DATA_HEADER_LENGTH){
|
||||
return NEED_DATA;
|
||||
}
|
||||
|
||||
/**
|
||||
* 原生SDK只会发送2种类型消息 1个心跳类型 另一个是sendbody,报文的第一个字节为消息类型
|
||||
*/
|
||||
byte conetnType = iobuffer.get();
|
||||
if(conetnType == CIMConstant.ProtobufType.C_H_RS || conetnType == CIMConstant.ProtobufType.SENTBODY) {
|
||||
return OK;
|
||||
}
|
||||
|
||||
return NOT_OK;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
public MessageDecoderResult decode(IoSession iosession, IoBuffer iobuffer, ProtocolDecoderOutput out) throws Exception {
|
||||
iobuffer.mark();
|
||||
|
||||
byte conetnType = iobuffer.get();
|
||||
byte lv =iobuffer.get();//int 低位
|
||||
byte hv =iobuffer.get();//int 高位
|
||||
|
||||
int conetnLength = getContentLength(lv,hv);
|
||||
|
||||
//如果消息体没有接收完整,则重置读取,等待下一次重新读取
|
||||
if(conetnLength > iobuffer.remaining()){
|
||||
iobuffer.reset();
|
||||
return NEED_DATA;
|
||||
}
|
||||
|
||||
byte[] dataBytes = new byte[conetnLength];
|
||||
iobuffer.get(dataBytes, 0, conetnLength);
|
||||
|
||||
Object message = mappingMessageObject(dataBytes,conetnType);
|
||||
if(message != null){
|
||||
out.write(message);
|
||||
}
|
||||
return OK;
|
||||
}
|
||||
|
||||
public Object mappingMessageObject(byte[] data,byte type) throws Exception
|
||||
{
|
||||
|
||||
if(CIMConstant.ProtobufType.C_H_RS == type)
|
||||
{
|
||||
HeartbeatResponse response = HeartbeatResponse.getInstance();
|
||||
logger.info(response.toString());
|
||||
return response;
|
||||
}
|
||||
|
||||
if(CIMConstant.ProtobufType.SENTBODY == type)
|
||||
{
|
||||
SentBodyProto.Model bodyProto = SentBodyProto.Model.parseFrom(data);
|
||||
SentBody body = new SentBody();
|
||||
body.setKey(bodyProto.getKey());
|
||||
body.setTimestamp(bodyProto.getTimestamp());
|
||||
body.putAll(bodyProto.getDataMap());
|
||||
logger.info(body.toString());
|
||||
|
||||
return body;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析消息体长度
|
||||
* @param type
|
||||
* @param length
|
||||
* @return
|
||||
*/
|
||||
private int getContentLength(byte lv,byte hv){
|
||||
int l = (lv & 0xff);
|
||||
int h = (hv & 0xff);
|
||||
return (l| (h <<= 8));
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
@ -0,0 +1,171 @@
|
||||
/**
|
||||
* Copyright 2013-2023 Xia Jun(3979434@qq.com).
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
***************************************************************************************
|
||||
* *
|
||||
* Website : http://www.farsunset.com *
|
||||
* *
|
||||
***************************************************************************************
|
||||
*/
|
||||
package com.farsunset.cim.sdk.server.filter.decoder;
|
||||
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import org.apache.log4j.Logger;
|
||||
import org.apache.mina.core.buffer.IoBuffer;
|
||||
import org.apache.mina.core.session.IoSession;
|
||||
import org.apache.mina.filter.codec.ProtocolDecoderOutput;
|
||||
import org.apache.mina.filter.codec.demux.MessageDecoderAdapter;
|
||||
import org.apache.mina.filter.codec.demux.MessageDecoderResult;
|
||||
import com.alibaba.fastjson.JSON;
|
||||
import com.farsunset.cim.sdk.server.constant.CIMConstant;
|
||||
import com.farsunset.cim.sdk.server.handler.CIMNioSocketAcceptor;
|
||||
import com.farsunset.cim.sdk.server.model.HeartbeatResponse;
|
||||
import com.farsunset.cim.sdk.server.model.SentBody;
|
||||
|
||||
/**
|
||||
* 服务端接收消息解码
|
||||
*/
|
||||
public class WebMessageDecoder extends MessageDecoderAdapter {
|
||||
public static final byte MASK = 0x1;// 1000 0000
|
||||
public static final byte HAS_EXTEND_DATA = 126;
|
||||
public static final byte HAS_EXTEND_DATA_CONTINUE = 127;
|
||||
public static final byte PAYLOADLEN = 0x7F;// 0111 1111
|
||||
public static final Pattern SEC_KEY_PATTERN = Pattern.compile("^(Sec-WebSocket-Key:).+",Pattern.CASE_INSENSITIVE | Pattern.MULTILINE);
|
||||
|
||||
protected final Logger logger = Logger.getLogger(WebMessageDecoder.class);
|
||||
|
||||
@Override
|
||||
public MessageDecoderResult decodable(IoSession arg0, IoBuffer iobuffer) {
|
||||
if (iobuffer.remaining() < 2) {
|
||||
return NEED_DATA;
|
||||
}
|
||||
|
||||
/**
|
||||
* 原生SDK只会发送2种类型消息 1个心跳类型 另一个是sendbody,报文的第一个字节为消息类型
|
||||
* 如果非原生sdk发出的消息,则认为是websocket发送的消息
|
||||
* websocket发送的消息 第一个字节不可能等于C_H_RS或者SENTBODY
|
||||
*/
|
||||
byte conetnType = iobuffer.get();
|
||||
if (conetnType == CIMConstant.ProtobufType.C_H_RS || conetnType == CIMConstant.ProtobufType.SENTBODY) {
|
||||
return NOT_OK;
|
||||
}
|
||||
|
||||
byte head = iobuffer.get();// 第二个字节
|
||||
byte datalength = (byte) (head & PAYLOADLEN);// 得到第二个字节后七位的值
|
||||
int length = 0;
|
||||
if (datalength < HAS_EXTEND_DATA) {// 第一种是消息内容少于126存储消息长度
|
||||
length = datalength;
|
||||
} else if (datalength == HAS_EXTEND_DATA) {// 第二种是消息长度大于等于126且少于UINT16的情况此值为126
|
||||
if (iobuffer.remaining() < 2) {
|
||||
return NEED_DATA;
|
||||
}
|
||||
byte[] extended = new byte[2];
|
||||
iobuffer.get(extended);
|
||||
int shift = 0;
|
||||
length = 0;
|
||||
for (int i = extended.length - 1; i >= 0; i--) {
|
||||
length = length + ((extended[i] & 0xFF) << shift);
|
||||
shift += 8;
|
||||
}
|
||||
} else if (datalength == HAS_EXTEND_DATA_CONTINUE) {// 第三种是消息长度大于UINT16的情况下此值为127
|
||||
if (iobuffer.remaining() < 4) {
|
||||
return NEED_DATA;
|
||||
}
|
||||
byte[] extended = new byte[4];
|
||||
iobuffer.get(extended);
|
||||
int shift = 0;
|
||||
length = 0;
|
||||
for (int i = extended.length - 1; i >= 0; i--) {
|
||||
length = length + ((extended[i] & 0xFF) << shift);
|
||||
shift += 8;
|
||||
}
|
||||
}
|
||||
|
||||
int ismask = head >> 7 & MASK;// 得到第二个字节第一位的值
|
||||
if ((ismask == 1 && iobuffer.remaining() < 4 + length) || (ismask == 0 && iobuffer.remaining() < length)) {// 有掩码
|
||||
return NEED_DATA;
|
||||
}
|
||||
return OK;
|
||||
}
|
||||
|
||||
@Override
|
||||
public MessageDecoderResult decode(IoSession iosession, IoBuffer in, ProtocolDecoderOutput out)throws Exception {
|
||||
in.get();
|
||||
byte head = in.get();
|
||||
byte datalength = (byte) (head & PAYLOADLEN);
|
||||
if (datalength < HAS_EXTEND_DATA) {
|
||||
} else if (datalength == HAS_EXTEND_DATA) {
|
||||
in.get(new byte[2]);
|
||||
} else if (datalength == HAS_EXTEND_DATA_CONTINUE) {
|
||||
in.get(new byte[4]);
|
||||
}
|
||||
|
||||
int ismask = head >> 7 & MASK;
|
||||
byte[] data = null;
|
||||
if (ismask == 1) {// 有掩码
|
||||
// 获取掩码
|
||||
byte[] mask = new byte[4];
|
||||
in.get(mask);
|
||||
|
||||
data = new byte[in.remaining()];
|
||||
in.get(data);
|
||||
for (int i = 0; i < data.length; i++) {
|
||||
// 数据进行异或运算
|
||||
data[i] = (byte) (data[i] ^ mask[i % 4]);
|
||||
}
|
||||
handleSentBodyAndHeartPing(data,out);
|
||||
} else {
|
||||
data = new byte[in.remaining()];
|
||||
in.get(data);
|
||||
handleWebsocketHandshake(new String(data, "UTF-8"),out);
|
||||
}
|
||||
return OK;
|
||||
}
|
||||
|
||||
private void handleWebsocketHandshake(String message,ProtocolDecoderOutput out) {
|
||||
SentBody body = new SentBody();
|
||||
body.setKey(CIMNioSocketAcceptor.WEBSOCKET_HANDLER_KEY);
|
||||
|
||||
Matcher m = SEC_KEY_PATTERN.matcher(message);
|
||||
if (m.find()) {
|
||||
String foundstring = m.group();
|
||||
body.put("key", foundstring.split(":")[1].trim());
|
||||
}
|
||||
out.write(body);
|
||||
}
|
||||
|
||||
|
||||
public void handleSentBodyAndHeartPing(byte[] data,ProtocolDecoderOutput out) throws UnsupportedEncodingException {
|
||||
String message = new String(data, "UTF-8");
|
||||
|
||||
/**
|
||||
* 只处理心跳响应以及,sentbody消息
|
||||
*/
|
||||
if (HeartbeatResponse.CMD_HEARTBEAT_RESPONSE.equals(message)) {
|
||||
HeartbeatResponse response = HeartbeatResponse.getInstance();
|
||||
logger.info(response.toString());
|
||||
out.write(response);
|
||||
}else if(data.length > 2)
|
||||
{
|
||||
SentBody body = JSON.parseObject(message, SentBody.class);
|
||||
logger.info(body.toString());
|
||||
out.write(body);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
@ -49,7 +49,8 @@ import com.farsunset.cim.sdk.server.session.CIMSession;
|
||||
|
||||
public class CIMNioSocketAcceptor extends IoHandlerAdapter implements KeepAliveMessageFactory{
|
||||
|
||||
private final static String CIMSESSION_CLOSED_HANDLER_KEY = "client_cimsession_closed";
|
||||
public final static String WEBSOCKET_HANDLER_KEY = "client_websocket_handshake";
|
||||
public final static String CIMSESSION_CLOSED_HANDLER_KEY = "client_cimsession_closed";
|
||||
private Logger logger = Logger.getLogger(CIMNioSocketAcceptor.class);
|
||||
private HashMap<String, CIMRequestHandler> handlers = new HashMap<String, CIMRequestHandler>();
|
||||
private IoAcceptor acceptor;
|
||||
@ -60,6 +61,12 @@ public class CIMNioSocketAcceptor extends IoHandlerAdapter implements KeepAliveM
|
||||
|
||||
public void bind() throws IOException
|
||||
{
|
||||
|
||||
/**
|
||||
* 预制websocket握手请求的处理
|
||||
*/
|
||||
handlers.put(WEBSOCKET_HANDLER_KEY, new WebsocketHandler());
|
||||
|
||||
acceptor = new NioSocketAcceptor();
|
||||
acceptor.getSessionConfig().setReadBufferSize(READ_BUFFER_SIZE);
|
||||
((DefaultSocketSessionConfig)acceptor.getSessionConfig()).setKeepAlive(true);
|
||||
|
@ -0,0 +1,53 @@
|
||||
/**
|
||||
* Copyright 2013-2023 Xia Jun(3979434@qq.com).
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
***************************************************************************************
|
||||
* *
|
||||
* Website : http://www.farsunset.com *
|
||||
* *
|
||||
***************************************************************************************
|
||||
*/
|
||||
package com.farsunset.cim.sdk.server.handler;
|
||||
|
||||
import java.security.MessageDigest;
|
||||
|
||||
import com.farsunset.cim.sdk.server.model.ReplyBody;
|
||||
import com.farsunset.cim.sdk.server.model.SentBody;
|
||||
import com.farsunset.cim.sdk.server.model.WebsocketResponse;
|
||||
import com.farsunset.cim.sdk.server.session.CIMSession;
|
||||
/**
|
||||
* 处理websocket握手请求,返回响应的报文给浏览器
|
||||
* @author Iraid
|
||||
*
|
||||
*/
|
||||
public class WebsocketHandler implements CIMRequestHandler {
|
||||
|
||||
private final static String GUID = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
|
||||
|
||||
public ReplyBody process(CIMSession session, SentBody body) {
|
||||
session.setChannel(CIMSession.CHANNEL_BROWSER);
|
||||
String secKey = body.get("key") + GUID;
|
||||
try {
|
||||
MessageDigest md = MessageDigest.getInstance("SHA-1");
|
||||
md.update(secKey.getBytes("iso-8859-1"), 0, secKey.length());
|
||||
byte[] sha1Hash = md.digest();
|
||||
secKey = new String(org.apache.mina.util.Base64.encodeBase64(sha1Hash));
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
session.write(new WebsocketResponse(secKey));
|
||||
return null;
|
||||
}
|
||||
}
|
@ -22,13 +22,15 @@
|
||||
package com.farsunset.cim.sdk.server.model;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
|
||||
import com.farsunset.cim.sdk.server.constant.CIMConstant;
|
||||
import com.farsunset.cim.sdk.server.model.feature.EncodeFormatable;
|
||||
/**
|
||||
* 服务端心跳请求
|
||||
*
|
||||
*/
|
||||
public class HeartbeatRequest implements Serializable,Protobufable {
|
||||
public class HeartbeatRequest implements Serializable,EncodeFormatable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
private static final String TAG = "SERVER_HEARTBEAT_REQUEST";
|
||||
@ -45,17 +47,27 @@ public class HeartbeatRequest implements Serializable,Protobufable {
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] getByteArray() {
|
||||
public byte[] getProtobufBody() {
|
||||
return CMD_HEARTBEAT_RESPONSE.getBytes();
|
||||
}
|
||||
|
||||
public String toString(){
|
||||
return TAG;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public byte[] getJSONBody() {
|
||||
try {
|
||||
return CMD_HEARTBEAT_RESPONSE.getBytes("UTF-8");
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public byte getType() {
|
||||
public byte getDataType() {
|
||||
return CIMConstant.ProtobufType.S_H_RQ;
|
||||
}
|
||||
|
||||
|
@ -23,16 +23,15 @@ package com.farsunset.cim.sdk.server.model;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
import com.farsunset.cim.sdk.server.constant.CIMConstant;
|
||||
|
||||
/**
|
||||
* 客户端心跳响应
|
||||
*/
|
||||
public class HeartbeatResponse implements Serializable, Protobufable {
|
||||
public class HeartbeatResponse implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
private static final String TAG = "CLIENT_HEARTBEAT_RESPONSE";
|
||||
private static final String CMD_HEARTBEAT_RESPONSE = "CR";
|
||||
public static final String CMD_HEARTBEAT_RESPONSE = "CR";
|
||||
private static HeartbeatResponse object = new HeartbeatResponse();
|
||||
|
||||
private HeartbeatResponse() {
|
||||
@ -43,18 +42,10 @@ public class HeartbeatResponse implements Serializable, Protobufable {
|
||||
return object;
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] getByteArray() {
|
||||
return CMD_HEARTBEAT_RESPONSE.getBytes();
|
||||
}
|
||||
|
||||
|
||||
public String toString() {
|
||||
return TAG;
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte getType() {
|
||||
return CIMConstant.ProtobufType.C_H_RS;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
@ -22,13 +22,15 @@
|
||||
package com.farsunset.cim.sdk.server.model;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import com.alibaba.fastjson.JSONObject;
|
||||
import com.farsunset.cim.sdk.server.constant.CIMConstant;
|
||||
import com.farsunset.cim.sdk.server.model.feature.EncodeFormatable;
|
||||
import com.farsunset.cim.sdk.server.model.proto.MessageProto;
|
||||
/**
|
||||
* 消息对象
|
||||
*/
|
||||
public class Message implements Serializable,Protobufable {
|
||||
public class Message implements Serializable,EncodeFormatable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@ -74,6 +76,7 @@ public class Message implements Serializable,Protobufable {
|
||||
private long timestamp;
|
||||
|
||||
|
||||
|
||||
public Message()
|
||||
{
|
||||
timestamp = System.currentTimeMillis();
|
||||
@ -156,7 +159,6 @@ public class Message implements Serializable,Protobufable {
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
|
||||
StringBuffer buffer = new StringBuffer();
|
||||
buffer.append("#Message#").append("\n");
|
||||
buffer.append("mid:").append(mid).append("\n");
|
||||
@ -175,7 +177,7 @@ public class Message implements Serializable,Protobufable {
|
||||
return txt != null && txt.trim().length()!=0;
|
||||
}
|
||||
@Override
|
||||
public byte[] getByteArray() {
|
||||
public byte[] getProtobufBody() {
|
||||
MessageProto.Model.Builder builder = MessageProto.Model.newBuilder();
|
||||
builder.setMid(mid);
|
||||
builder.setAction(action);
|
||||
@ -200,10 +202,30 @@ public class Message implements Serializable,Protobufable {
|
||||
}
|
||||
return builder.build().toByteArray();
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte getType() {
|
||||
public byte[] getJSONBody() {
|
||||
JSONObject json = new JSONObject();
|
||||
json.put("contentType", getClass().getSimpleName());
|
||||
json.put("mid", mid);
|
||||
json.put("action", action);
|
||||
json.put("title", title);
|
||||
json.put("content", content);
|
||||
json.put("extra", extra);
|
||||
json.put("sender", sender);
|
||||
json.put("receiver", receiver);
|
||||
json.put("format", format);
|
||||
json.put("timestamp", timestamp);
|
||||
String data = json.toJSONString();
|
||||
try {
|
||||
return data.getBytes("UTF-8");
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
@Override
|
||||
public byte getDataType() {
|
||||
return CIMConstant.ProtobufType.MESSAGE;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
@ -22,17 +22,20 @@
|
||||
package com.farsunset.cim.sdk.server.model;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import com.alibaba.fastjson.JSONObject;
|
||||
import com.farsunset.cim.sdk.server.constant.CIMConstant;
|
||||
import com.farsunset.cim.sdk.server.model.feature.EncodeFormatable;
|
||||
import com.farsunset.cim.sdk.server.model.proto.ReplyBodyProto;
|
||||
/**
|
||||
* 请求应答对象
|
||||
*
|
||||
*/
|
||||
public class ReplyBody implements Serializable ,Protobufable{
|
||||
public class ReplyBody implements Serializable ,EncodeFormatable{
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@ -141,7 +144,7 @@ public class ReplyBody implements Serializable ,Protobufable{
|
||||
return buffer.toString();
|
||||
}
|
||||
@Override
|
||||
public byte[] getByteArray() {
|
||||
public byte[] getProtobufBody() {
|
||||
ReplyBodyProto.Model.Builder builder = ReplyBodyProto.Model.newBuilder();
|
||||
builder.setCode(code);
|
||||
if(message!=null){
|
||||
@ -157,9 +160,24 @@ public class ReplyBody implements Serializable ,Protobufable{
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte getType() {
|
||||
// TODO Auto-generated method stub
|
||||
public byte getDataType() {
|
||||
return CIMConstant.ProtobufType.REPLYBODY;
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] getJSONBody() {
|
||||
JSONObject json = new JSONObject();
|
||||
json.put("contentType", getClass().getSimpleName());
|
||||
json.put("key", key);
|
||||
json.put("code", code);
|
||||
json.put("message", message);
|
||||
json.put("data", data);
|
||||
String data = json.toJSONString();
|
||||
try {
|
||||
return data.getBytes("UTF-8");
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
// TODO Auto-generated catch block
|
||||
e.printStackTrace();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
@ -35,7 +35,7 @@ public class SentBody implements Serializable {
|
||||
|
||||
private String key;
|
||||
|
||||
private HashMap<String, String> data = new HashMap<String, String>();
|
||||
public HashMap<String, String> data = new HashMap<String, String>();
|
||||
|
||||
private long timestamp;
|
||||
|
||||
|
@ -0,0 +1,61 @@
|
||||
/**
|
||||
* Copyright 2013-2023 Xia Jun(3979434@qq.com).
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
***************************************************************************************
|
||||
* *
|
||||
* Website : http://www.farsunset.com *
|
||||
* *
|
||||
***************************************************************************************
|
||||
*/
|
||||
package com.farsunset.cim.sdk.server.model;
|
||||
|
||||
import java.io.UnsupportedEncodingException;
|
||||
/**
|
||||
*websocket握手响应结果
|
||||
*
|
||||
*/
|
||||
public class WebsocketResponse{
|
||||
|
||||
private String token;
|
||||
|
||||
public WebsocketResponse(String token) {
|
||||
this.token = token;
|
||||
}
|
||||
|
||||
public byte[] getBytes() {
|
||||
try {
|
||||
return toString().getBytes("UTF-8");
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
@Override
|
||||
public String toString() {
|
||||
StringBuilder builder = new StringBuilder();
|
||||
builder.append("HTTP/1.1 101 Switching Protocols");
|
||||
builder.append("\r\n");
|
||||
builder.append("Upgrade: websocket");
|
||||
builder.append("\r\n");
|
||||
builder.append("Connection: Upgrade");
|
||||
builder.append("\r\n");
|
||||
builder.append("Sec-WebSocket-Accept:").append(token);
|
||||
builder.append("\r\n");
|
||||
builder.append("\r\n");
|
||||
|
||||
return builder.toString();
|
||||
|
||||
}
|
||||
}
|
@ -19,13 +19,12 @@
|
||||
* *
|
||||
***************************************************************************************
|
||||
*/
|
||||
package com.farsunset.cim.sdk.server.model;
|
||||
package com.farsunset.cim.sdk.server.model.feature;
|
||||
/**
|
||||
* 需要向另一端发送的结构体
|
||||
*/
|
||||
public interface Protobufable {
|
||||
|
||||
byte[] getByteArray();
|
||||
|
||||
byte getType();
|
||||
public interface EncodeFormatable {
|
||||
byte[] getProtobufBody();
|
||||
byte[] getJSONBody();
|
||||
byte getDataType();
|
||||
}
|
@ -52,7 +52,8 @@ public class CIMSession implements Serializable{
|
||||
public transient static String CHANNEL_ANDROID = "android";
|
||||
public transient static String CHANNEL_WINDOWS = "windows";
|
||||
public transient static String CHANNEL_WP = "wp";
|
||||
|
||||
public transient static String CHANNEL_BROWSER = "browser";
|
||||
|
||||
private transient IoSession session;
|
||||
|
||||
private String gid;//session全局ID
|
||||
|
@ -61,8 +61,6 @@ public class DefaultSessionManager implements SessionManager{
|
||||
return sessions.get(account);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
public List<CIMSession> queryAll() {
|
||||
List<CIMSession> list = new ArrayList<CIMSession>();
|
||||
|
@ -3,6 +3,7 @@
|
||||
<name>cim-server</name>
|
||||
<comment></comment>
|
||||
<projects>
|
||||
<project>cim-server-sdk</project>
|
||||
</projects>
|
||||
<buildSpec>
|
||||
<buildCommand>
|
||||
|
@ -3,6 +3,9 @@
|
||||
<wb-resource deploy-path="/" source-path="/WebContent" tag="defaultRootSource"/>
|
||||
<wb-resource deploy-path="/WEB-INF/classes" source-path="/src/main/java"/>
|
||||
<wb-resource deploy-path="/WEB-INF/classes" source-path="/src/main/resource"/>
|
||||
<dependent-module archiveName="cim-server-sdk.jar" deploy-path="/WEB-INF/lib" handle="module:/resource/cim-server-sdk/cim-server-sdk">
|
||||
<dependency-type>uses</dependency-type>
|
||||
</dependent-module>
|
||||
<property name="context-root" value="cim-server"/>
|
||||
<property name="java-output-path" value="/cim-server/build/classes"/>
|
||||
</wb-module>
|
||||
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -8,7 +8,14 @@
|
||||
<div id="_main_nav" class="ui-vnav">
|
||||
<ul class="ui-nav-inner">
|
||||
|
||||
|
||||
<li style="height: 50px; text-align: center; margin-top: 10px;">
|
||||
<div class="btn-group" style="margin-top: 5px;">
|
||||
<a type="button" class="btn btn-danger" target="_blank"
|
||||
href="javascript:openWebclient();">CIM for Web</a>
|
||||
</div>
|
||||
|
||||
</li>
|
||||
<li style="border-bottom: 1px solid #D1D6DA;"></li>
|
||||
<li class="ui-item" id="sessionMenu">
|
||||
<a href="<%=navBasePath%>/admin/session_list.action"> <span
|
||||
class="ui-text">在线用户</span> <i class="ui-bg nav-recycle"></i> </a>
|
||||
|
@ -0,0 +1,109 @@
|
||||
var CIM_URI="ws://127.0.0.1:23456";// 修改为服务器的真实IP
|
||||
var CMD_HEARTBEAT_REQUEST = "SR";
|
||||
var CMD_HEARTBEAT_RESPONSE = "CR";
|
||||
var SDK_VERSION = "1.0.0";
|
||||
var SDK_CHANNEL = "browser";
|
||||
var APP_PACKAGE = "com.farsunset.webcim";
|
||||
var ACCOUNT;
|
||||
var socket;
|
||||
var manualStop = false;
|
||||
var CIMWebBridge = new Object();
|
||||
CIMWebBridge.connection = function(){
|
||||
manualStop = false;
|
||||
socket = new WebSocket(CIM_URI);
|
||||
socket.onopen = CIMWebBridge.innerOnConnectionSuccessed;
|
||||
socket.onmessage = CIMWebBridge.innerOnMessageReceived;
|
||||
socket.onclose = CIMWebBridge.innerOnConnectionClosed;
|
||||
};
|
||||
CIMWebBridge.bindAccount = function(id,deviceId){
|
||||
ACCOUNT=id;
|
||||
|
||||
var browser = getBrowser();
|
||||
var body = {};
|
||||
body.key = "client_bind";
|
||||
body.data = {};
|
||||
body.data.account = id;
|
||||
body.data.channel = SDK_CHANNEL;
|
||||
body.data.version = SDK_VERSION;
|
||||
body.data.osVersion = browser.version;
|
||||
body.data.packageName = APP_PACKAGE;
|
||||
body.data.deviceId = deviceId;
|
||||
body.data.device = browser.name;
|
||||
CIMWebBridge.sendRequest(body);
|
||||
};
|
||||
|
||||
CIMWebBridge.stop = function(){
|
||||
manualStop = true;
|
||||
socket.close();
|
||||
};
|
||||
|
||||
CIMWebBridge.innerOnConnectionSuccessed = function(){
|
||||
if(typeof onConnectionSuccessed != 'undefined' && onConnectionSuccessed instanceof Function){
|
||||
onConnectionSuccessed();
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
|
||||
|
||||
CIMWebBridge.innerOnMessageReceived = function(e){
|
||||
var data = e.data;
|
||||
/**
|
||||
* 收到服务端发来的心跳请求,立即回复响应,否则服务端会在10秒后断开连接
|
||||
*/
|
||||
if(data == CMD_HEARTBEAT_REQUEST){
|
||||
socket.send(CMD_HEARTBEAT_RESPONSE);
|
||||
return
|
||||
}
|
||||
|
||||
var json = JSON.parse(data);
|
||||
if(json.contentType == "Message" && typeof onMessageReceived != 'undefined' && onMessageReceived instanceof Function){
|
||||
onMessageReceived(json);
|
||||
}
|
||||
|
||||
if(json.contentType == "ReplyBody" && typeof onReplyReceived != 'undefined' && onReplyReceived instanceof Function){
|
||||
onReplyReceived(json);
|
||||
}
|
||||
};
|
||||
CIMWebBridge.innerOnConnectionClosed = function(e){
|
||||
if(!manualStop){
|
||||
CIMWebBridge.connection();
|
||||
}
|
||||
};
|
||||
|
||||
CIMWebBridge.sendRequest = function(body){
|
||||
var json = JSON.stringify(body);
|
||||
socket.send(json);
|
||||
};
|
||||
|
||||
function getBrowser() {
|
||||
var explorer = window.navigator.userAgent.toLowerCase() ;
|
||||
// ie
|
||||
if (explorer.indexOf("msie") >= 0) {
|
||||
var ver=explorer.match(/msie ([\d.]+)/)[1];
|
||||
return {name:"IE",version:ver};
|
||||
}
|
||||
// firefox
|
||||
else if (explorer.indexOf("firefox") >= 0) {
|
||||
var ver=explorer.match(/firefox\/([\d.]+)/)[1];
|
||||
return {name:"Firefox",version:ver};
|
||||
}
|
||||
// Chrome
|
||||
else if(explorer.indexOf("chrome") >= 0){
|
||||
var ver=explorer.match(/chrome\/([\d.]+)/)[1];
|
||||
return {name:"Chrome",version:ver};
|
||||
}
|
||||
// Opera
|
||||
else if(explorer.indexOf("opera") >= 0){
|
||||
var ver=explorer.match(/opera.([\d.]+)/)[1];
|
||||
return {name:"Opera",version:ver};
|
||||
}
|
||||
// Safari
|
||||
else if(explorer.indexOf("Safari") >= 0){
|
||||
var ver=explorer.match(/version\/([\d.]+)/)[1];
|
||||
return {name:"Safari",version:ver};
|
||||
}
|
||||
|
||||
return {name:"Other",version:"1.0.0"};
|
||||
}
|
||||
|
@ -0,0 +1,50 @@
|
||||
<%@ page language="java" pageEncoding="utf-8"%>
|
||||
<%
|
||||
String loginBasePath = request.getScheme() + "://"
|
||||
+ request.getServerName() + ":" + request.getServerPort()
|
||||
+ request.getContextPath();
|
||||
|
||||
%>
|
||||
<script type="text/javascript">
|
||||
function doLogin()
|
||||
{
|
||||
ACCOUNT = $('#account').val();
|
||||
if($.trim(ACCOUNT)=='' )
|
||||
{
|
||||
return;
|
||||
}
|
||||
showProcess('正在接入请稍后......');
|
||||
CIMWebBridge.connection();
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<div class="modal fade" id="LoginDialog" tabindex="-1" role="dialog" data-backdrop="static">
|
||||
<div class="modal-dialog" style="width: 300px;margin: 30px auto;">
|
||||
<div class="modal-content" >
|
||||
<div class="modal-body" style="padding:0px;" >
|
||||
<div style="height:150px;text-align: center; background: #5FA0D3; color: #ffffff; border: 0px; border-top-left-radius: 4px; border-top-right-radius: 4px;">
|
||||
<img src="<%=loginBasePath %>/resource/img/icon.png" style="height: 60px;width: 60px;margin-top:30px;"/>
|
||||
<div style="margin-top: 20px; color: #ffffff;font-size: 16px;">使用用户帐号登录</div>
|
||||
</div>
|
||||
|
||||
<div class="input-group" style="margin-top: 20px;margin-left:10px;margin-right:10px;margin-bottom:20px;">
|
||||
<span class="input-group-addon"><span class="glyphicon glyphicon-user" aria-hidden="true"></span></span>
|
||||
<input type="text" class="form-control" id="account" maxlength="32" placeholder="帐号"
|
||||
style="display: inline; width: 100%; height: 50px;" />
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<div class="modal-footer" style="text-align: center;">
|
||||
<a type="button" class="btn btn-success btn-lg" onclick="doLogin()"
|
||||
style="width: 250px;">登录</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
@ -0,0 +1,70 @@
|
||||
<%@ page language="java" pageEncoding="utf-8"%>
|
||||
<%
|
||||
String path = request.getContextPath();
|
||||
String basePath = request.getScheme() + "://"
|
||||
+ request.getServerName() + ":" + request.getServerPort()
|
||||
+ path;
|
||||
|
||||
%>
|
||||
|
||||
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
|
||||
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8"/>
|
||||
<title>cim for web(beta) </title>
|
||||
<link charset="utf-8" rel="stylesheet" href="<%=basePath%>/resource/bootstrap-3.3.6-dist/css/bootstrap.min.css" />
|
||||
<link charset="utf-8" rel="stylesheet" href="<%=basePath%>/resource/css/dialog.css" />
|
||||
<script type="text/javascript" src="<%=basePath%>/resource/js/jquery-2.2.3.min.js"></script>
|
||||
<script type="text/javascript" src="<%=basePath%>/resource/bootstrap-3.3.6-dist/js/bootstrap.min.js"></script>
|
||||
<script type="text/javascript" src="<%=basePath%>/resource/js/framework.js"></script>
|
||||
<script type="text/javascript" src="cim.web.sdk.js"></script>
|
||||
</head>
|
||||
|
||||
|
||||
<script type="text/javascript">
|
||||
|
||||
|
||||
/** 当socket连接成功回调 **/
|
||||
function onConnectionSuccessed()
|
||||
{
|
||||
var sessionId = '<%=session.getId()%>';
|
||||
CIMWebBridge.bindAccount($('#account').val(),sessionId);
|
||||
}
|
||||
|
||||
/** 当收到请求回复时候回调 **/
|
||||
function onReplyReceived(json)
|
||||
{
|
||||
if(json.key=='client_bind' && json.code==200)
|
||||
{
|
||||
hideProcess();
|
||||
doHideDialog('LoginDialog');
|
||||
doShowDialog('MessageDialog');
|
||||
$("#current_account").text(ACCOUNT);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/** 当收到消息时候回调 **/
|
||||
|
||||
function onMessageReceived(message)
|
||||
{
|
||||
$("#messageList").append("<div class='alert alert-info' >"+message.content+"</div>");
|
||||
}
|
||||
|
||||
$(document).ready(function(){
|
||||
doShowDialog('LoginDialog');
|
||||
});
|
||||
|
||||
|
||||
|
||||
</script>
|
||||
|
||||
|
||||
<body style="background-color: rgb(190, 209, 216);width: 600px;">
|
||||
<div id="global_mask" style="display: none; position: absolute; top: 0px; left: 0px; z-index: 998; background-color: rgb(190, 209, 216); opacity: 0.5; width: 100%; height: 100%; overflow: hidden; background-position: initial initial; background-repeat: initial initial;"></div>
|
||||
|
||||
<%@include file="loginDialog.jsp"%>
|
||||
<%@include file="messageDialog.jsp"%>
|
||||
</body>
|
||||
</html>
|
@ -0,0 +1,21 @@
|
||||
<%@ page language="java" pageEncoding="utf-8"%>
|
||||
|
||||
|
||||
<div class="modal fade" data-backdrop="static" id="MessageDialog" tabindex="-1" role="dialog" >
|
||||
<div class="modal-dialog" style="width: 500px;margin: 30px auto;">
|
||||
<div class="modal-content" >
|
||||
<div class="modal-header" style="text-align: center;">
|
||||
<h4>消息列表</h4>
|
||||
<span style="float: left;">请在管理页面推送一条消息</span>
|
||||
<span style="float: right;color: #4caf50;">当前帐号:<span id="current_account">10000</span></span>
|
||||
</div>
|
||||
<div class="modal-body" id="messageList" style="min-height: 600px;" >
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
|
@ -45,7 +45,8 @@ public class SessionAction extends ActionSupport {
|
||||
|
||||
public String list()
|
||||
{
|
||||
ServletActionContext.getRequest().setAttribute("sessionList", ((DefaultSessionManager) ContextHolder.getBean("CIMSessionManager")).queryAll());
|
||||
|
||||
ServletActionContext.getRequest().setAttribute("sessionList", ((DefaultSessionManager) ContextHolder.getBean("CIMSessionManager")).queryAll());
|
||||
|
||||
return "list";
|
||||
}
|
||||
|
@ -3,8 +3,9 @@
|
||||
<component name="GradleSettings">
|
||||
<option name="linkedExternalProjectsSettings">
|
||||
<GradleProjectSettings>
|
||||
<option name="distributionType" value="DEFAULT_WRAPPED" />
|
||||
<option name="distributionType" value="LOCAL" />
|
||||
<option name="externalProjectPath" value="$PROJECT_DIR$" />
|
||||
<option name="gradleHome" value="D:\devtools\dev\gradle-3.4" />
|
||||
<option name="modules">
|
||||
<set>
|
||||
<option value="$PROJECT_DIR$" />
|
||||
|
@ -55,7 +55,7 @@
|
||||
<!-- ****************************************CIM推送配置 end*************************************** -->
|
||||
|
||||
<!--消息接受广播注册-->
|
||||
<receiver android:name="com.farsunset.ichat.example.receiver.CIMPushManagerReceiver" android:exported="false">
|
||||
<receiver android:name="com.farsunset.ichat.example.receiver.CIMPushManagerReceiver">
|
||||
<intent-filter android:priority="0x7fffffff">
|
||||
<action android:name="android.net.conn.CONNECTIVITY_CHANGE"/> <!-- 网络变化广播 -->
|
||||
<action android:name="com.farsunset.cim.MESSAGE_RECEIVED"/><!-- 消息广播action -->
|
||||
|
@ -24,7 +24,7 @@ package com.farsunset.ichat.example.app;
|
||||
public interface Constant {
|
||||
|
||||
//服务端IP地址
|
||||
public static final String CIM_SERVER_HOST = "172.168.141.13";
|
||||
public static final String CIM_SERVER_HOST = "172.168.11.18";
|
||||
|
||||
|
||||
//注意,这里的端口不是tomcat的端口,CIM端口在服务端spring-cim.xml中配置的,没改动就使用默认的23456
|
||||
|
@ -1,7 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<classpath>
|
||||
<classpathentry kind="src" path="src"/>
|
||||
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/>
|
||||
<classpathentry kind="lib" path="libs/log4j-1.2.17.jar"/>
|
||||
<classpathentry kind="lib" path="libs/protobuf-java-3.2.0.jar"/>
|
||||
<classpathentry kind="lib" path="libs/netty-buffer-4.1.9.Final.jar"/>
|
||||
@ -9,5 +8,19 @@
|
||||
<classpathentry kind="lib" path="libs/netty-common-4.1.9.Final.jar"/>
|
||||
<classpathentry kind="lib" path="libs/netty-handler-4.1.9.Final.jar"/>
|
||||
<classpathentry kind="lib" path="libs/netty-transport-4.1.9.Final.jar"/>
|
||||
<classpathentry kind="lib" path="libs/fastjson-1.2.39.jar"/>
|
||||
<classpathentry kind="lib" path="libs/netty-codec-http-4.1.16.Final-sources.jar"/>
|
||||
<classpathentry kind="lib" path="libs/netty-codec-http-4.1.16.Final.jar" sourcepath="libs/netty-codec-http-4.1.16.Final-sources.jar"/>
|
||||
<classpathentry kind="con" path="org.eclipse.jst.j2ee.internal.module.container"/>
|
||||
<classpathentry kind="con" path="org.eclipse.jst.server.core.container/org.eclipse.jst.server.tomcat.runtimeTarget/Apache Tomcat v7.0">
|
||||
<attributes>
|
||||
<attribute name="owner.project.facets" value="jst.utility"/>
|
||||
</attributes>
|
||||
</classpathentry>
|
||||
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/jdk1.8.0_131">
|
||||
<attributes>
|
||||
<attribute name="owner.project.facets" value="java"/>
|
||||
</attributes>
|
||||
</classpathentry>
|
||||
<classpathentry kind="output" path="bin"/>
|
||||
</classpath>
|
||||
|
@ -5,13 +5,26 @@
|
||||
<projects>
|
||||
</projects>
|
||||
<buildSpec>
|
||||
<buildCommand>
|
||||
<name>org.eclipse.wst.common.project.facet.core.builder</name>
|
||||
<arguments>
|
||||
</arguments>
|
||||
</buildCommand>
|
||||
<buildCommand>
|
||||
<name>org.eclipse.jdt.core.javabuilder</name>
|
||||
<arguments>
|
||||
</arguments>
|
||||
</buildCommand>
|
||||
<buildCommand>
|
||||
<name>org.eclipse.wst.validation.validationbuilder</name>
|
||||
<arguments>
|
||||
</arguments>
|
||||
</buildCommand>
|
||||
</buildSpec>
|
||||
<natures>
|
||||
<nature>org.eclipse.jem.workbench.JavaEMFNature</nature>
|
||||
<nature>org.eclipse.wst.common.modulecore.ModuleCoreNature</nature>
|
||||
<nature>org.eclipse.jdt.core.javanature</nature>
|
||||
<nature>org.eclipse.wst.common.project.facet.core.nature</nature>
|
||||
</natures>
|
||||
</projectDescription>
|
||||
|
@ -0,0 +1,7 @@
|
||||
eclipse.preferences.version=1
|
||||
org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled
|
||||
org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.6
|
||||
org.eclipse.jdt.core.compiler.compliance=1.6
|
||||
org.eclipse.jdt.core.compiler.problem.assertIdentifier=error
|
||||
org.eclipse.jdt.core.compiler.problem.enumIdentifier=error
|
||||
org.eclipse.jdt.core.compiler.source=1.6
|
@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?><project-modules id="moduleCoreId" project-version="1.5.0">
|
||||
<wb-module deploy-name="cim-server-sdk">
|
||||
<wb-resource deploy-path="/" source-path="/src"/>
|
||||
</wb-module>
|
||||
</project-modules>
|
@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<faceted-project>
|
||||
<runtime name="Apache Tomcat v7.0"/>
|
||||
<fixed facet="java"/>
|
||||
<fixed facet="jst.utility"/>
|
||||
<installed facet="java" version="1.6"/>
|
||||
<installed facet="jst.utility" version="1.0"/>
|
||||
</faceted-project>
|
BIN
cim_for_netty/cim-server-sdk/libs/fastjson-1.2.39.jar
Normal file
BIN
cim_for_netty/cim-server-sdk/libs/fastjson-1.2.39.jar
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
3
cim_for_netty/cim-server-sdk/src/META-INF/MANIFEST.MF
Normal file
3
cim_for_netty/cim-server-sdk/src/META-INF/MANIFEST.MF
Normal file
@ -0,0 +1,3 @@
|
||||
Manifest-Version: 1.0
|
||||
Class-Path:
|
||||
|
@ -48,7 +48,8 @@ public interface CIMConstant {
|
||||
|
||||
//消息头长度为3个字节,第一个字节为消息类型,第二,第三字节 转换int后为消息长度
|
||||
int DATA_HEADER_LENGTH = 3;
|
||||
|
||||
//WEBSOCKET消息头长度为2个字节
|
||||
int WS_DATA_HEADER_LENGTH = 2;
|
||||
public static interface ProtobufType{
|
||||
byte C_H_RS = 0;
|
||||
byte S_H_RQ = 1;
|
||||
|
@ -23,93 +23,42 @@ package com.farsunset.cim.sdk.server.filter;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.apache.log4j.Logger;
|
||||
|
||||
import com.farsunset.cim.sdk.server.constant.CIMConstant;
|
||||
import com.farsunset.cim.sdk.server.model.HeartbeatResponse;
|
||||
import com.farsunset.cim.sdk.server.model.SentBody;
|
||||
import com.farsunset.cim.sdk.server.model.proto.SentBodyProto;
|
||||
|
||||
import com.farsunset.cim.sdk.server.filter.decoder.AppMessageDecoder;
|
||||
import com.farsunset.cim.sdk.server.filter.decoder.WebMessageDecoder;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.handler.codec.ByteToMessageDecoder;
|
||||
/**
|
||||
* 服务端接收消息解码
|
||||
* 服务端接收消息路由解码,通过消息类型分发到不同的真正解码器
|
||||
*/
|
||||
public class ServerMessageDecoder extends ByteToMessageDecoder {
|
||||
|
||||
protected final Logger logger = Logger.getLogger(ServerMessageDecoder.class);
|
||||
private WebMessageDecoder webMessageDecoder;
|
||||
private AppMessageDecoder appMessageDecoder;
|
||||
|
||||
public ServerMessageDecoder() {
|
||||
webMessageDecoder = new WebMessageDecoder();
|
||||
appMessageDecoder = new AppMessageDecoder();
|
||||
|
||||
}
|
||||
@Override
|
||||
protected void decode(ChannelHandlerContext arg0, ByteBuf buffer, List<Object> queue) throws Exception {
|
||||
/**
|
||||
* 消息头3位
|
||||
*/
|
||||
if (buffer.readableBytes() < CIMConstant.DATA_HEADER_LENGTH) {
|
||||
return;
|
||||
}
|
||||
|
||||
buffer.markReaderIndex();
|
||||
|
||||
buffer.markReaderIndex();
|
||||
|
||||
byte conetnType = buffer.readByte();
|
||||
|
||||
byte lv = buffer.readByte();// int 低位
|
||||
byte hv = buffer.readByte();// int 高位
|
||||
|
||||
int conetnLength = getContentLength(lv, hv);
|
||||
|
||||
// 如果消息体没有接收完整,则重置读取,等待下一次重新读取
|
||||
if (conetnLength > buffer.readableBytes()) {
|
||||
buffer.resetReaderIndex();
|
||||
return;
|
||||
}
|
||||
|
||||
byte[] dataBytes = new byte[conetnLength];
|
||||
buffer.readBytes(dataBytes);
|
||||
|
||||
Object message = mappingMessageObject(dataBytes,conetnType);
|
||||
if(message != null){
|
||||
queue.add(message);
|
||||
buffer.resetReaderIndex();
|
||||
|
||||
/**
|
||||
* 原生SDK只会发送2种类型消息 1个心跳类型 另一个是sendbody,报文的第一个字节为消息类型,否则才是websocket的消息
|
||||
*/
|
||||
if(conetnType == CIMConstant.ProtobufType.C_H_RS || conetnType == CIMConstant.ProtobufType.SENTBODY) {
|
||||
appMessageDecoder.decode(arg0, buffer, queue);
|
||||
}else
|
||||
{
|
||||
webMessageDecoder.decode(arg0, buffer, queue);
|
||||
}
|
||||
}
|
||||
|
||||
public Object mappingMessageObject(byte[] data,byte type) throws Exception
|
||||
{
|
||||
|
||||
if(CIMConstant.ProtobufType.C_H_RS == type)
|
||||
{
|
||||
HeartbeatResponse response = HeartbeatResponse.getInstance();
|
||||
logger.info(response.toString());
|
||||
SentBody body = new SentBody();
|
||||
body.setKey(CIMConstant.CLIENT_HEARTBEAT);
|
||||
body.setTimestamp(System.currentTimeMillis());
|
||||
return body;
|
||||
}
|
||||
|
||||
if(CIMConstant.ProtobufType.SENTBODY == type)
|
||||
{
|
||||
SentBodyProto.Model bodyProto = SentBodyProto.Model.parseFrom(data);
|
||||
SentBody body = new SentBody();
|
||||
body.setKey(bodyProto.getKey());
|
||||
body.setTimestamp(bodyProto.getTimestamp());
|
||||
body.putAll(bodyProto.getDataMap());
|
||||
logger.info(body.toString());
|
||||
|
||||
return body;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析消息体长度
|
||||
* @param type
|
||||
* @param length
|
||||
* @return
|
||||
*/
|
||||
private int getContentLength(byte lv,byte hv){
|
||||
int l = (lv & 0xff);
|
||||
int h = (hv & 0xff);
|
||||
return (l| (h <<= 8));
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -24,11 +24,14 @@ package com.farsunset.cim.sdk.server.filter;
|
||||
import org.apache.log4j.Logger;
|
||||
|
||||
import com.farsunset.cim.sdk.server.constant.CIMConstant;
|
||||
import com.farsunset.cim.sdk.server.model.Protobufable;
|
||||
import com.farsunset.cim.sdk.server.model.WebsocketResponse;
|
||||
import com.farsunset.cim.sdk.server.model.feature.EncodeFormatable;
|
||||
import com.farsunset.cim.sdk.server.session.CIMSession;
|
||||
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.handler.codec.MessageToByteEncoder;
|
||||
import io.netty.util.AttributeKey;
|
||||
|
||||
|
||||
/**
|
||||
@ -39,21 +42,45 @@ public class ServerMessageEncoder extends MessageToByteEncoder<Object> {
|
||||
protected final Logger logger = Logger.getLogger(ServerMessageEncoder.class.getSimpleName());
|
||||
|
||||
@Override
|
||||
protected void encode(ChannelHandlerContext ctx, Object message, ByteBuf out) throws Exception {
|
||||
protected void encode(ChannelHandlerContext ctx, Object object, ByteBuf out) throws Exception {
|
||||
|
||||
Object channel = ctx.channel().attr(AttributeKey.valueOf("channel")).get();
|
||||
|
||||
/**
|
||||
* websocket的握手响应
|
||||
*/
|
||||
if (CIMSession.CHANNEL_BROWSER.equals(channel) && object instanceof WebsocketResponse) {
|
||||
WebsocketResponse data = (WebsocketResponse) object;
|
||||
byte[] byteArray = data.getBytes();
|
||||
out.writeBytes(byteArray);
|
||||
logger.info(data.toString());
|
||||
}
|
||||
|
||||
|
||||
if(message instanceof Protobufable){
|
||||
|
||||
Protobufable data = (Protobufable) message;
|
||||
byte[] byteArray = data.getByteArray();
|
||||
|
||||
|
||||
out.writeBytes(createHeader(data.getType(),byteArray.length));
|
||||
out.writeBytes(byteArray);
|
||||
|
||||
}
|
||||
|
||||
logger.info(message.toString());
|
||||
/**
|
||||
* websocket的数据传输使用JSON编码数据格式,因为Protobuf还没有支持js
|
||||
*/
|
||||
if (CIMSession.CHANNEL_BROWSER.equals(channel) && object instanceof EncodeFormatable) {
|
||||
EncodeFormatable data = (EncodeFormatable) object;
|
||||
byte[] byteArray = encodeDataFrame(data.getJSONBody());
|
||||
/**
|
||||
* 由于websocket没有黏包和断包的问题,所以不必知道消息体的大小
|
||||
*/
|
||||
out.writeBytes(byteArray);
|
||||
logger.info(data.toString());
|
||||
}
|
||||
|
||||
/**
|
||||
* 非websocket的数据传输使用Protobuf编码数据格式
|
||||
*/
|
||||
if (!CIMSession.CHANNEL_BROWSER.equals(channel) && object instanceof EncodeFormatable) {
|
||||
|
||||
EncodeFormatable data = (EncodeFormatable) object;
|
||||
byte[] byteArray = data.getProtobufBody();
|
||||
out.writeBytes(createHeader(data.getDataType(),byteArray.length));
|
||||
out.writeBytes(byteArray);
|
||||
logger.info(data.toString());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -70,5 +97,52 @@ public class ServerMessageEncoder extends MessageToByteEncoder<Object> {
|
||||
return header;
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送到websocket的数据需要进行相关格式转换
|
||||
* 对传入数据进行无掩码转换
|
||||
* @param data
|
||||
* @return
|
||||
*/
|
||||
public static byte[] encodeDataFrame(byte[] data) {
|
||||
// 掩码开始位置
|
||||
int maskIndex = 2;
|
||||
|
||||
// 计算掩码开始位置
|
||||
if (data.length <= 125) {
|
||||
maskIndex = 2;
|
||||
} else if (data.length > 65536) {
|
||||
maskIndex = 10;
|
||||
} else if (data.length > 125) {
|
||||
maskIndex = 4;
|
||||
}
|
||||
|
||||
// 创建返回数据
|
||||
byte[] result = new byte[data.length + maskIndex];
|
||||
|
||||
// 开始计算ws-frame
|
||||
// frame-fin + frame-rsv1 + frame-rsv2 + frame-rsv3 + frame-opcode
|
||||
result[0] = (byte) 0x81; // 129
|
||||
|
||||
// frame-masked+frame-payload-length
|
||||
// 从第9个字节开始是 1111101=125,掩码是第3-第6个数据
|
||||
// 从第9个字节开始是 1111110>=126,掩码是第5-第8个数据
|
||||
if (data.length <= 125) {
|
||||
result[1] = (byte) (data.length);
|
||||
} else if (data.length > 65536) {
|
||||
result[1] = 0x7F; // 127
|
||||
} else if (data.length > 125) {
|
||||
result[1] = 0x7E; // 126
|
||||
result[2] = (byte) (data.length >> 8);
|
||||
result[3] = (byte) (data.length % 256);
|
||||
}
|
||||
|
||||
// 将数据编码放到最后
|
||||
for (int i = 0; i < data.length; i++) {
|
||||
result[i + maskIndex] = data[i];
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
@ -0,0 +1,113 @@
|
||||
/**
|
||||
* Copyright 2013-2023 Xia Jun(3979434@qq.com).
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
***************************************************************************************
|
||||
* *
|
||||
* Website : http://www.farsunset.com *
|
||||
* *
|
||||
***************************************************************************************
|
||||
*/
|
||||
package com.farsunset.cim.sdk.server.filter.decoder;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.apache.log4j.Logger;
|
||||
|
||||
import com.farsunset.cim.sdk.server.constant.CIMConstant;
|
||||
import com.farsunset.cim.sdk.server.model.HeartbeatResponse;
|
||||
import com.farsunset.cim.sdk.server.model.SentBody;
|
||||
import com.farsunset.cim.sdk.server.model.proto.SentBodyProto;
|
||||
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.handler.codec.ByteToMessageDecoder;
|
||||
/**
|
||||
* 服务端接收来自应用的消息解码
|
||||
*/
|
||||
public class AppMessageDecoder extends ByteToMessageDecoder {
|
||||
|
||||
protected final Logger logger = Logger.getLogger(AppMessageDecoder.class);
|
||||
@Override
|
||||
public void decode(ChannelHandlerContext arg0, ByteBuf buffer, List<Object> queue) throws Exception {
|
||||
/**
|
||||
* 消息头3位
|
||||
*/
|
||||
if (buffer.readableBytes() < CIMConstant.DATA_HEADER_LENGTH) {
|
||||
return;
|
||||
}
|
||||
|
||||
buffer.markReaderIndex();
|
||||
|
||||
byte conetnType = buffer.readByte();
|
||||
|
||||
byte lv = buffer.readByte();// int 低位
|
||||
byte hv = buffer.readByte();// int 高位
|
||||
|
||||
int conetnLength = getContentLength(lv, hv);
|
||||
|
||||
// 如果消息体没有接收完整,则重置读取,等待下一次重新读取
|
||||
if (conetnLength > buffer.readableBytes()) {
|
||||
buffer.resetReaderIndex();
|
||||
return;
|
||||
}
|
||||
|
||||
byte[] dataBytes = new byte[conetnLength];
|
||||
buffer.readBytes(dataBytes);
|
||||
|
||||
Object message = mappingMessageObject(dataBytes,conetnType);
|
||||
if(message != null){
|
||||
queue.add(message);
|
||||
}
|
||||
}
|
||||
|
||||
public Object mappingMessageObject(byte[] data,byte type) throws Exception
|
||||
{
|
||||
|
||||
if(CIMConstant.ProtobufType.C_H_RS == type)
|
||||
{
|
||||
HeartbeatResponse response = HeartbeatResponse.getInstance();
|
||||
logger.info(response.toString());
|
||||
SentBody body = new SentBody();
|
||||
body.setKey(CIMConstant.CLIENT_HEARTBEAT);
|
||||
body.setTimestamp(System.currentTimeMillis());
|
||||
return body;
|
||||
}
|
||||
|
||||
if(CIMConstant.ProtobufType.SENTBODY == type)
|
||||
{
|
||||
SentBodyProto.Model bodyProto = SentBodyProto.Model.parseFrom(data);
|
||||
SentBody body = new SentBody();
|
||||
body.setKey(bodyProto.getKey());
|
||||
body.setTimestamp(bodyProto.getTimestamp());
|
||||
body.putAll(bodyProto.getDataMap());
|
||||
logger.info(body.toString());
|
||||
|
||||
return body;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析消息体长度
|
||||
* @param type
|
||||
* @param length
|
||||
* @return
|
||||
*/
|
||||
private int getContentLength(byte lv,byte hv){
|
||||
int l = (lv & 0xff);
|
||||
int h = (hv & 0xff);
|
||||
return (l| (h <<= 8));
|
||||
}
|
||||
}
|
@ -0,0 +1,180 @@
|
||||
/**
|
||||
* Copyright 2013-2023 Xia Jun(3979434@qq.com).
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
***************************************************************************************
|
||||
* *
|
||||
* Website : http://www.farsunset.com *
|
||||
* *
|
||||
***************************************************************************************
|
||||
*/
|
||||
package com.farsunset.cim.sdk.server.filter.decoder;
|
||||
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.util.List;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import org.apache.log4j.Logger;
|
||||
|
||||
import com.alibaba.fastjson.JSON;
|
||||
import com.farsunset.cim.sdk.server.constant.CIMConstant;
|
||||
import com.farsunset.cim.sdk.server.handler.CIMNioSocketAcceptor;
|
||||
import com.farsunset.cim.sdk.server.model.HeartbeatResponse;
|
||||
import com.farsunset.cim.sdk.server.model.SentBody;
|
||||
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.handler.codec.ByteToMessageDecoder;
|
||||
/**
|
||||
* 服务端接收来自websocket消息解码
|
||||
*/
|
||||
public class WebMessageDecoder extends ByteToMessageDecoder {
|
||||
public static final byte MASK = 0x1;// 1000 0000
|
||||
public static final byte HAS_EXTEND_DATA = 126;
|
||||
public static final byte HAS_EXTEND_DATA_CONTINUE = 127;
|
||||
public static final byte PAYLOADLEN = 0x7F;// 0111 1111
|
||||
public static final Pattern SEC_KEY_PATTERN = Pattern.compile("^(Sec-WebSocket-Key:).+",Pattern.CASE_INSENSITIVE | Pattern.MULTILINE);
|
||||
protected final Logger logger = Logger.getLogger(WebMessageDecoder.class);
|
||||
@Override
|
||||
public void decode(ChannelHandlerContext arg0, ByteBuf iobuffer, List<Object> queue) throws Exception {
|
||||
/**
|
||||
* 消息头2位
|
||||
*/
|
||||
if (iobuffer.readableBytes() < CIMConstant.WS_DATA_HEADER_LENGTH) {
|
||||
return;
|
||||
}
|
||||
|
||||
iobuffer.markReaderIndex();
|
||||
|
||||
byte head = iobuffer.readByte();// 第二个字节
|
||||
byte datalength = (byte) (head & PAYLOADLEN);// 得到第二个字节后七位的值
|
||||
int length = 0;
|
||||
if (datalength < HAS_EXTEND_DATA) {// 第一种是消息内容少于126存储消息长度
|
||||
length = datalength;
|
||||
} else if (datalength == HAS_EXTEND_DATA) {// 第二种是消息长度大于等于126且少于UINT16的情况此值为126
|
||||
if (iobuffer.readableBytes() < 2) {
|
||||
iobuffer.resetReaderIndex();
|
||||
return ;
|
||||
}
|
||||
byte[] extended = new byte[2];
|
||||
iobuffer.readBytes(extended);
|
||||
int shift = 0;
|
||||
length = 0;
|
||||
for (int i = extended.length - 1; i >= 0; i--) {
|
||||
length = length + ((extended[i] & 0xFF) << shift);
|
||||
shift += 8;
|
||||
}
|
||||
} else if (datalength == HAS_EXTEND_DATA_CONTINUE) {// 第三种是消息长度大于UINT16的情况下此值为127
|
||||
if (iobuffer.readableBytes() < 4) {
|
||||
iobuffer.resetReaderIndex();
|
||||
return ;
|
||||
}
|
||||
byte[] extended = new byte[4];
|
||||
iobuffer.readBytes(extended);
|
||||
int shift = 0;
|
||||
length = 0;
|
||||
for (int i = extended.length - 1; i >= 0; i--) {
|
||||
length = length + ((extended[i] & 0xFF) << shift);
|
||||
shift += 8;
|
||||
}
|
||||
}
|
||||
|
||||
int ismask = head >> 7 & MASK;// 得到第二个字节第一位的值
|
||||
if ((ismask == 1 && iobuffer.readableBytes() < 4 + length) || (ismask == 0 && iobuffer.readableBytes() < length)) {// 有掩码
|
||||
iobuffer.resetReaderIndex();
|
||||
return ;
|
||||
}
|
||||
iobuffer.resetReaderIndex();
|
||||
|
||||
decodeDataBody(iobuffer,queue);
|
||||
}
|
||||
|
||||
|
||||
public void decodeDataBody(ByteBuf iobuffer, List<Object> queue) {
|
||||
iobuffer.readByte();
|
||||
byte head = iobuffer.readByte();
|
||||
byte datalength = (byte) (head & PAYLOADLEN);
|
||||
if (datalength < HAS_EXTEND_DATA) {
|
||||
} else if (datalength == HAS_EXTEND_DATA) {
|
||||
iobuffer.readBytes(new byte[2]);
|
||||
} else if (datalength == HAS_EXTEND_DATA_CONTINUE) {
|
||||
iobuffer.readBytes(new byte[4]);
|
||||
}
|
||||
|
||||
int ismask = head >> 7 & MASK;
|
||||
byte[] data = null;
|
||||
if (ismask == 1) {// 有掩码
|
||||
// 获取掩码
|
||||
byte[] mask = new byte[4];
|
||||
iobuffer.readBytes(mask);
|
||||
|
||||
data = new byte[iobuffer.readableBytes()];
|
||||
iobuffer.readBytes(data);
|
||||
for (int i = 0; i < data.length; i++) {
|
||||
// 数据进行异或运算
|
||||
data[i] = (byte) (data[i] ^ mask[i % 4]);
|
||||
}
|
||||
handleSentBodyAndHeartPing(data,queue);
|
||||
} else {
|
||||
data = new byte[iobuffer.readableBytes()];
|
||||
iobuffer.readBytes(data);
|
||||
handleWebsocketHandshake(data,queue);
|
||||
}
|
||||
}
|
||||
|
||||
private void handleWebsocketHandshake(byte[] data,List<Object> queue) {
|
||||
String message = null;
|
||||
try {
|
||||
message = new String(data, "UTF-8");
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
// TODO Auto-generated catch block
|
||||
e.printStackTrace();
|
||||
}
|
||||
SentBody body = new SentBody();
|
||||
body.setKey(CIMNioSocketAcceptor.WEBSOCKET_HANDLER_KEY);
|
||||
|
||||
Matcher m = SEC_KEY_PATTERN.matcher(message);
|
||||
if (m.find()) {
|
||||
String foundstring = m.group();
|
||||
body.put("key", foundstring.split(":")[1].trim());
|
||||
}
|
||||
queue.add(body);
|
||||
}
|
||||
|
||||
|
||||
public void handleSentBodyAndHeartPing(byte[] data,List<Object> queue) {
|
||||
String message = null;
|
||||
try {
|
||||
message = new String(data, "UTF-8");
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
// TODO Auto-generated catch block
|
||||
e.printStackTrace();
|
||||
}
|
||||
/**
|
||||
* 只处理心跳响应以及,sentbody消息
|
||||
*/
|
||||
if (HeartbeatResponse.CMD_HEARTBEAT_RESPONSE.equals(message)) {
|
||||
HeartbeatResponse response = HeartbeatResponse.getInstance();
|
||||
logger.info(response.toString());
|
||||
queue.add(response);
|
||||
}else if(data.length > 2)
|
||||
{
|
||||
SentBody body = JSON.parseObject(message, SentBody.class);
|
||||
logger.info(body.toString());
|
||||
queue.add(body);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
@ -50,7 +50,7 @@ import io.netty.util.AttributeKey;
|
||||
|
||||
@Sharable
|
||||
public class CIMNioSocketAcceptor extends SimpleChannelInboundHandler<SentBody>{
|
||||
|
||||
public final static String WEBSOCKET_HANDLER_KEY = "client_websocket_handshake";
|
||||
private final static String CIMSESSION_CLOSED_HANDLER_KEY = "client_cimsession_closed";
|
||||
private Logger logger = Logger.getLogger(CIMNioSocketAcceptor.class);
|
||||
private HashMap<String, CIMRequestHandler> handlers = new HashMap<String, CIMRequestHandler>();
|
||||
@ -66,6 +66,11 @@ public class CIMNioSocketAcceptor extends SimpleChannelInboundHandler<SentBody>{
|
||||
|
||||
public void bind() throws IOException
|
||||
{
|
||||
|
||||
/**
|
||||
* 预制websocket握手请求的处理
|
||||
*/
|
||||
handlers.put(WEBSOCKET_HANDLER_KEY, new WebsocketHandler());
|
||||
ServerBootstrap bootstrap = new ServerBootstrap();
|
||||
bootstrap.group(new NioEventLoopGroup(), new NioEventLoopGroup());
|
||||
bootstrap.childOption(ChannelOption.TCP_NODELAY, true);
|
||||
@ -73,7 +78,6 @@ public class CIMNioSocketAcceptor extends SimpleChannelInboundHandler<SentBody>{
|
||||
bootstrap.childHandler(new ChannelInitializer<SocketChannel>() {
|
||||
@Override
|
||||
public void initChannel(SocketChannel ch) throws Exception {
|
||||
|
||||
ch.pipeline().addLast(new ServerMessageDecoder());
|
||||
ch.pipeline().addLast(new ServerMessageEncoder());
|
||||
ch.pipeline().addLast(new IdleStateHandler(READ_IDLE_TIME,WRITE_IDLE_TIME,0));
|
||||
|
@ -0,0 +1,54 @@
|
||||
/**
|
||||
* Copyright 2013-2023 Xia Jun(3979434@qq.com).
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
***************************************************************************************
|
||||
* *
|
||||
* Website : http://www.farsunset.com *
|
||||
* *
|
||||
***************************************************************************************
|
||||
*/
|
||||
package com.farsunset.cim.sdk.server.handler;
|
||||
|
||||
import java.security.MessageDigest;
|
||||
import java.util.Base64;
|
||||
|
||||
import com.farsunset.cim.sdk.server.model.ReplyBody;
|
||||
import com.farsunset.cim.sdk.server.model.SentBody;
|
||||
import com.farsunset.cim.sdk.server.model.WebsocketResponse;
|
||||
import com.farsunset.cim.sdk.server.session.CIMSession;
|
||||
/**
|
||||
* 处理websocket握手请求,返回响应的报文给浏览器
|
||||
* @author Iraid
|
||||
*
|
||||
*/
|
||||
public class WebsocketHandler implements CIMRequestHandler {
|
||||
|
||||
private final static String GUID = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
|
||||
|
||||
public ReplyBody process(CIMSession session, SentBody body) {
|
||||
session.setChannel(CIMSession.CHANNEL_BROWSER);
|
||||
String secKey = body.get("key") + GUID;
|
||||
try {
|
||||
MessageDigest md = MessageDigest.getInstance("SHA-1");
|
||||
md.update(secKey.getBytes("iso-8859-1"), 0, secKey.length());
|
||||
byte[] sha1Hash = md.digest();
|
||||
secKey = new String(Base64.getEncoder().encode(sha1Hash));
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
session.write(new WebsocketResponse(secKey));
|
||||
return null;
|
||||
}
|
||||
}
|
@ -24,16 +24,16 @@ package com.farsunset.cim.sdk.server.model;
|
||||
import java.io.Serializable;
|
||||
|
||||
import com.farsunset.cim.sdk.server.constant.CIMConstant;
|
||||
import com.farsunset.cim.sdk.server.model.feature.EncodeFormatable;
|
||||
/**
|
||||
* 服务端心跳请求
|
||||
*
|
||||
*/
|
||||
public class HeartbeatRequest implements Serializable,Protobufable {
|
||||
public class HeartbeatRequest implements Serializable,EncodeFormatable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
private static final String TAG = "SERVER_HEARTBEAT_REQUEST";
|
||||
private static final String CMD_HEARTBEAT_RESPONSE = "SR";
|
||||
|
||||
private static HeartbeatRequest object = new HeartbeatRequest();
|
||||
|
||||
private HeartbeatRequest(){
|
||||
@ -44,18 +44,22 @@ public class HeartbeatRequest implements Serializable,Protobufable {
|
||||
return object;
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] getByteArray() {
|
||||
return CMD_HEARTBEAT_RESPONSE.getBytes();
|
||||
}
|
||||
|
||||
public String toString(){
|
||||
return TAG;
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] getProtobufBody() {
|
||||
return CMD_HEARTBEAT_RESPONSE.getBytes();
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte getType() {
|
||||
public byte[] getJSONBody() {
|
||||
return CMD_HEARTBEAT_RESPONSE.getBytes();
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte getDataType() {
|
||||
return CIMConstant.ProtobufType.S_H_RQ;
|
||||
}
|
||||
|
||||
|
@ -23,16 +23,15 @@ package com.farsunset.cim.sdk.server.model;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
import com.farsunset.cim.sdk.server.constant.CIMConstant;
|
||||
|
||||
/**
|
||||
* 客户端心跳响应
|
||||
*/
|
||||
public class HeartbeatResponse implements Serializable, Protobufable {
|
||||
public class HeartbeatResponse implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
private static final String TAG = "CLIENT_HEARTBEAT_RESPONSE";
|
||||
private static final String CMD_HEARTBEAT_RESPONSE = "CR";
|
||||
public static final String CMD_HEARTBEAT_RESPONSE = "CR";
|
||||
private static HeartbeatResponse object = new HeartbeatResponse();
|
||||
|
||||
private HeartbeatResponse() {
|
||||
@ -43,18 +42,9 @@ public class HeartbeatResponse implements Serializable, Protobufable {
|
||||
return object;
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] getByteArray() {
|
||||
return CMD_HEARTBEAT_RESPONSE.getBytes();
|
||||
}
|
||||
|
||||
|
||||
public String toString() {
|
||||
return TAG;
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte getType() {
|
||||
return CIMConstant.ProtobufType.C_H_RS;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
@ -22,13 +22,15 @@
|
||||
package com.farsunset.cim.sdk.server.model;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import com.alibaba.fastjson.JSONObject;
|
||||
import com.farsunset.cim.sdk.server.constant.CIMConstant;
|
||||
import com.farsunset.cim.sdk.server.model.feature.EncodeFormatable;
|
||||
import com.farsunset.cim.sdk.server.model.proto.MessageProto;
|
||||
/**
|
||||
* 消息对象
|
||||
*/
|
||||
public class Message implements Serializable,Protobufable {
|
||||
public class Message implements Serializable,EncodeFormatable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@ -74,6 +76,7 @@ public class Message implements Serializable,Protobufable {
|
||||
private long timestamp;
|
||||
|
||||
|
||||
|
||||
public Message()
|
||||
{
|
||||
timestamp = System.currentTimeMillis();
|
||||
@ -156,7 +159,6 @@ public class Message implements Serializable,Protobufable {
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
|
||||
StringBuffer buffer = new StringBuffer();
|
||||
buffer.append("#Message#").append("\n");
|
||||
buffer.append("mid:").append(mid).append("\n");
|
||||
@ -175,7 +177,7 @@ public class Message implements Serializable,Protobufable {
|
||||
return txt != null && txt.trim().length()!=0;
|
||||
}
|
||||
@Override
|
||||
public byte[] getByteArray() {
|
||||
public byte[] getProtobufBody() {
|
||||
MessageProto.Model.Builder builder = MessageProto.Model.newBuilder();
|
||||
builder.setMid(mid);
|
||||
builder.setAction(action);
|
||||
@ -200,10 +202,30 @@ public class Message implements Serializable,Protobufable {
|
||||
}
|
||||
return builder.build().toByteArray();
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte getType() {
|
||||
public byte[] getJSONBody() {
|
||||
JSONObject json = new JSONObject();
|
||||
json.put("contentType", getClass().getSimpleName());
|
||||
json.put("mid", mid);
|
||||
json.put("action", action);
|
||||
json.put("title", title);
|
||||
json.put("content", content);
|
||||
json.put("extra", extra);
|
||||
json.put("sender", sender);
|
||||
json.put("receiver", receiver);
|
||||
json.put("format", format);
|
||||
json.put("timestamp", timestamp);
|
||||
String data = json.toJSONString();
|
||||
try {
|
||||
return data.getBytes("UTF-8");
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
@Override
|
||||
public byte getDataType() {
|
||||
return CIMConstant.ProtobufType.MESSAGE;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
@ -22,17 +22,20 @@
|
||||
package com.farsunset.cim.sdk.server.model;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import com.alibaba.fastjson.JSONObject;
|
||||
import com.farsunset.cim.sdk.server.constant.CIMConstant;
|
||||
import com.farsunset.cim.sdk.server.model.feature.EncodeFormatable;
|
||||
import com.farsunset.cim.sdk.server.model.proto.ReplyBodyProto;
|
||||
/**
|
||||
* 请求应答对象
|
||||
*
|
||||
*/
|
||||
public class ReplyBody implements Serializable ,Protobufable{
|
||||
public class ReplyBody implements Serializable ,EncodeFormatable{
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@ -141,7 +144,7 @@ public class ReplyBody implements Serializable ,Protobufable{
|
||||
return buffer.toString();
|
||||
}
|
||||
@Override
|
||||
public byte[] getByteArray() {
|
||||
public byte[] getProtobufBody() {
|
||||
ReplyBodyProto.Model.Builder builder = ReplyBodyProto.Model.newBuilder();
|
||||
builder.setCode(code);
|
||||
if(message!=null){
|
||||
@ -157,9 +160,24 @@ public class ReplyBody implements Serializable ,Protobufable{
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte getType() {
|
||||
// TODO Auto-generated method stub
|
||||
public byte getDataType() {
|
||||
return CIMConstant.ProtobufType.REPLYBODY;
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] getJSONBody() {
|
||||
JSONObject json = new JSONObject();
|
||||
json.put("contentType", getClass().getSimpleName());
|
||||
json.put("key", key);
|
||||
json.put("code", code);
|
||||
json.put("message", message);
|
||||
json.put("data", data);
|
||||
String data = json.toJSONString();
|
||||
try {
|
||||
return data.getBytes("UTF-8");
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
// TODO Auto-generated catch block
|
||||
e.printStackTrace();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
@ -35,7 +35,7 @@ public class SentBody implements Serializable {
|
||||
|
||||
private String key;
|
||||
|
||||
private HashMap<String, String> data = new HashMap<String, String>();
|
||||
public HashMap<String, String> data = new HashMap<String, String>();
|
||||
|
||||
private long timestamp;
|
||||
|
||||
|
@ -0,0 +1,61 @@
|
||||
/**
|
||||
* Copyright 2013-2023 Xia Jun(3979434@qq.com).
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
***************************************************************************************
|
||||
* *
|
||||
* Website : http://www.farsunset.com *
|
||||
* *
|
||||
***************************************************************************************
|
||||
*/
|
||||
package com.farsunset.cim.sdk.server.model;
|
||||
|
||||
import java.io.UnsupportedEncodingException;
|
||||
/**
|
||||
*websocket握手响应结果
|
||||
*
|
||||
*/
|
||||
public class WebsocketResponse{
|
||||
|
||||
private String token;
|
||||
|
||||
public WebsocketResponse(String token) {
|
||||
this.token = token;
|
||||
}
|
||||
|
||||
public byte[] getBytes() {
|
||||
try {
|
||||
return toString().getBytes("UTF-8");
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
@Override
|
||||
public String toString() {
|
||||
StringBuilder builder = new StringBuilder();
|
||||
builder.append("HTTP/1.1 101 Switching Protocols");
|
||||
builder.append("\r\n");
|
||||
builder.append("Upgrade: websocket");
|
||||
builder.append("\r\n");
|
||||
builder.append("Connection: Upgrade");
|
||||
builder.append("\r\n");
|
||||
builder.append("Sec-WebSocket-Accept:").append(token);
|
||||
builder.append("\r\n");
|
||||
builder.append("\r\n");
|
||||
|
||||
return builder.toString();
|
||||
|
||||
}
|
||||
}
|
@ -19,13 +19,12 @@
|
||||
* *
|
||||
***************************************************************************************
|
||||
*/
|
||||
package com.farsunset.cim.sdk.server.model;
|
||||
package com.farsunset.cim.sdk.server.model.feature;
|
||||
/**
|
||||
* 需要向另一端发送的结构体
|
||||
*/
|
||||
public interface Protobufable {
|
||||
|
||||
byte[] getByteArray();
|
||||
|
||||
byte getType();
|
||||
public interface EncodeFormatable {
|
||||
byte[] getProtobufBody();
|
||||
byte[] getJSONBody();
|
||||
byte getDataType();
|
||||
}
|
@ -51,7 +51,7 @@ public class CIMSession implements Serializable{
|
||||
public transient static String CHANNEL_ANDROID = "android";
|
||||
public transient static String CHANNEL_WINDOWS = "windows";
|
||||
public transient static String CHANNEL_WP = "wp";
|
||||
|
||||
public transient static String CHANNEL_BROWSER = "browser";
|
||||
private transient Channel session;
|
||||
|
||||
private String gid;//session全局ID
|
||||
|
@ -2,11 +2,6 @@
|
||||
<classpath>
|
||||
<classpathentry kind="src" path="src/main/java"/>
|
||||
<classpathentry kind="src" path="src/main/resource"/>
|
||||
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/jdk1.8.0_45">
|
||||
<attributes>
|
||||
<attribute name="owner.project.facets" value="java"/>
|
||||
</attributes>
|
||||
</classpathentry>
|
||||
<classpathentry kind="con" path="org.eclipse.jst.server.core.container/org.eclipse.jst.server.tomcat.runtimeTarget/Apache Tomcat v7.0">
|
||||
<attributes>
|
||||
<attribute name="owner.project.facets" value="jst.web"/>
|
||||
@ -14,5 +9,6 @@
|
||||
</classpathentry>
|
||||
<classpathentry kind="con" path="org.eclipse.jst.j2ee.internal.web.container"/>
|
||||
<classpathentry kind="con" path="org.eclipse.jst.j2ee.internal.module.container"/>
|
||||
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/jdk1.8.0_131"/>
|
||||
<classpathentry kind="output" path="WebContent/WEB-INF/classes"/>
|
||||
</classpath>
|
||||
|
@ -3,6 +3,7 @@
|
||||
<name>cim-server</name>
|
||||
<comment></comment>
|
||||
<projects>
|
||||
<project>cim-server-sdk</project>
|
||||
</projects>
|
||||
<buildSpec>
|
||||
<buildCommand>
|
||||
|
@ -3,6 +3,9 @@
|
||||
<wb-resource deploy-path="/" source-path="/WebContent" tag="defaultRootSource"/>
|
||||
<wb-resource deploy-path="/WEB-INF/classes" source-path="/src/main/java"/>
|
||||
<wb-resource deploy-path="/WEB-INF/classes" source-path="/src/main/resource"/>
|
||||
<dependent-module archiveName="cim-server-sdk.jar" deploy-path="/WEB-INF/lib" handle="module:/resource/cim-server-sdk/cim-server-sdk">
|
||||
<dependency-type>uses</dependency-type>
|
||||
</dependent-module>
|
||||
<property name="context-root" value="cim-server"/>
|
||||
<property name="java-output-path" value="/cim-server/build/classes"/>
|
||||
</wb-module>
|
||||
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -8,7 +8,14 @@
|
||||
<div id="_main_nav" class="ui-vnav">
|
||||
<ul class="ui-nav-inner">
|
||||
|
||||
|
||||
<li style="height: 50px; text-align: center; margin-top: 10px;">
|
||||
<div class="btn-group" style="margin-top: 5px;">
|
||||
<a type="button" class="btn btn-danger" target="_blank"
|
||||
href="javascript:openWebclient();">CIM for Web</a>
|
||||
</div>
|
||||
|
||||
</li>
|
||||
<li style="border-bottom: 1px solid #D1D6DA;"></li>
|
||||
<li class="ui-item" id="sessionMenu">
|
||||
<a href="<%=navBasePath%>/admin/session_list.action"> <span
|
||||
class="ui-text">在线用户</span> <i class="ui-bg nav-recycle"></i> </a>
|
||||
|
@ -0,0 +1,97 @@
|
||||
var CIM_URI="ws://127.0.0.1:23456";// 修改为服务器的真实IP
|
||||
var CMD_HEARTBEAT_REQUEST = "SR";
|
||||
var CMD_HEARTBEAT_RESPONSE = "CR";
|
||||
var SDK_VERSION = "1.0.0";
|
||||
var SDK_CHANNEL = "browser";
|
||||
var APP_PACKAGE = "com.farsunset.webcim";
|
||||
var ACCOUNT;
|
||||
var socket;
|
||||
var manualStop = false;
|
||||
var CIMWebBridge = new Object();
|
||||
CIMWebBridge.connection = function(){
|
||||
manualStop = false;
|
||||
socket = new WebSocket(CIM_URI);
|
||||
socket.onopen = CIMWebBridge.innerOnConnectionSuccessed;
|
||||
socket.onmessage = CIMWebBridge.innerOnMessageReceived;
|
||||
socket.onclose = CIMWebBridge.innerOnConnectionClosed;
|
||||
};
|
||||
CIMWebBridge.bindAccount = function(id,deviceId){
|
||||
ACCOUNT=id;
|
||||
var browser = getBrowser();
|
||||
var body = {};
|
||||
body.key = "client_bind";
|
||||
body.data = {};
|
||||
body.data.account = id;
|
||||
body.data.channel = SDK_CHANNEL;
|
||||
body.data.version = SDK_VERSION;
|
||||
body.data.osVersion = browser.version;
|
||||
body.data.packageName = APP_PACKAGE;
|
||||
body.data.deviceId = deviceId;
|
||||
body.data.device = browser.name;
|
||||
CIMWebBridge.sendRequest(body);
|
||||
};
|
||||
CIMWebBridge.stop = function(){
|
||||
manualStop = true;
|
||||
socket.close();
|
||||
};
|
||||
CIMWebBridge.resume = function(){
|
||||
manualStop = false;
|
||||
CIMWebBridge.connection();
|
||||
};
|
||||
CIMWebBridge.innerOnConnectionSuccessed = function(){
|
||||
if(typeof onConnectionSuccessed != 'undefined' && onConnectionSuccessed instanceof Function){
|
||||
onConnectionSuccessed();
|
||||
}
|
||||
};
|
||||
CIMWebBridge.innerOnMessageReceived = function(e){
|
||||
var data = e.data;
|
||||
/**
|
||||
* 收到服务端发来的心跳请求,立即回复响应,否则服务端会在10秒后断开连接
|
||||
*/
|
||||
if(data == CMD_HEARTBEAT_REQUEST){
|
||||
socket.send(CMD_HEARTBEAT_RESPONSE);
|
||||
return
|
||||
}
|
||||
var json = JSON.parse(data);
|
||||
if(json.contentType == "Message" && typeof onMessageReceived != 'undefined' && onMessageReceived instanceof Function){
|
||||
onMessageReceived(json);
|
||||
}
|
||||
if(json.contentType == "ReplyBody" && typeof onReplyReceived != 'undefined' && onReplyReceived instanceof Function){
|
||||
onReplyReceived(json);
|
||||
}
|
||||
};
|
||||
CIMWebBridge.innerOnConnectionClosed = function(e){
|
||||
if(!manualStop){
|
||||
CIMWebBridge.connection();
|
||||
}
|
||||
};
|
||||
CIMWebBridge.sendRequest = function(body){
|
||||
var json = JSON.stringify(body);
|
||||
socket.send(json);
|
||||
};
|
||||
|
||||
function getBrowser() {
|
||||
var explorer = window.navigator.userAgent.toLowerCase() ;
|
||||
if (explorer.indexOf("msie") >= 0) {
|
||||
var ver=explorer.match(/msie ([\d.]+)/)[1];
|
||||
return {name:"IE",version:ver};
|
||||
}
|
||||
else if (explorer.indexOf("firefox") >= 0) {
|
||||
var ver=explorer.match(/firefox\/([\d.]+)/)[1];
|
||||
return {name:"Firefox",version:ver};
|
||||
}
|
||||
else if(explorer.indexOf("chrome") >= 0){
|
||||
var ver=explorer.match(/chrome\/([\d.]+)/)[1];
|
||||
return {name:"Chrome",version:ver};
|
||||
}
|
||||
else if(explorer.indexOf("opera") >= 0){
|
||||
var ver=explorer.match(/opera.([\d.]+)/)[1];
|
||||
return {name:"Opera",version:ver};
|
||||
}
|
||||
else if(explorer.indexOf("Safari") >= 0){
|
||||
var ver=explorer.match(/version\/([\d.]+)/)[1];
|
||||
return {name:"Safari",version:ver};
|
||||
}
|
||||
return {name:"Other",version:"1.0.0"};
|
||||
}
|
||||
|
@ -0,0 +1,50 @@
|
||||
<%@ page language="java" pageEncoding="utf-8"%>
|
||||
<%
|
||||
String loginBasePath = request.getScheme() + "://"
|
||||
+ request.getServerName() + ":" + request.getServerPort()
|
||||
+ request.getContextPath();
|
||||
|
||||
%>
|
||||
<script type="text/javascript">
|
||||
function doLogin()
|
||||
{
|
||||
ACCOUNT = $('#account').val();
|
||||
if($.trim(ACCOUNT)=='' )
|
||||
{
|
||||
return;
|
||||
}
|
||||
showProcess('正在接入请稍后......');
|
||||
CIMWebBridge.connection();
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<div class="modal fade" id="LoginDialog" tabindex="-1" role="dialog" data-backdrop="static">
|
||||
<div class="modal-dialog" style="width: 300px;margin: 30px auto;">
|
||||
<div class="modal-content" >
|
||||
<div class="modal-body" style="padding:0px;" >
|
||||
<div style="height:150px;text-align: center; background: #5FA0D3; color: #ffffff; border: 0px; border-top-left-radius: 4px; border-top-right-radius: 4px;">
|
||||
<img src="<%=loginBasePath %>/resource/img/icon.png" style="height: 60px;width: 60px;margin-top:30px;"/>
|
||||
<div style="margin-top: 20px; color: #ffffff;font-size: 16px;">使用用户帐号登录</div>
|
||||
</div>
|
||||
|
||||
<div class="input-group" style="margin-top: 20px;margin-left:10px;margin-right:10px;margin-bottom:20px;">
|
||||
<span class="input-group-addon"><span class="glyphicon glyphicon-user" aria-hidden="true"></span></span>
|
||||
<input type="text" class="form-control" id="account" maxlength="32" placeholder="帐号"
|
||||
style="display: inline; width: 100%; height: 50px;" />
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<div class="modal-footer" style="text-align: center;">
|
||||
<a type="button" class="btn btn-success btn-lg" onclick="doLogin()"
|
||||
style="width: 250px;">登录</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
@ -0,0 +1,70 @@
|
||||
<%@ page language="java" pageEncoding="utf-8"%>
|
||||
<%
|
||||
String path = request.getContextPath();
|
||||
String basePath = request.getScheme() + "://"
|
||||
+ request.getServerName() + ":" + request.getServerPort()
|
||||
+ path;
|
||||
|
||||
%>
|
||||
|
||||
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
|
||||
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8"/>
|
||||
<title>cim for web(beta) </title>
|
||||
<link charset="utf-8" rel="stylesheet" href="<%=basePath%>/resource/bootstrap-3.3.6-dist/css/bootstrap.min.css" />
|
||||
<link charset="utf-8" rel="stylesheet" href="<%=basePath%>/resource/css/dialog.css" />
|
||||
<script type="text/javascript" src="<%=basePath%>/resource/js/jquery-2.2.3.min.js"></script>
|
||||
<script type="text/javascript" src="<%=basePath%>/resource/bootstrap-3.3.6-dist/js/bootstrap.min.js"></script>
|
||||
<script type="text/javascript" src="<%=basePath%>/resource/js/framework.js"></script>
|
||||
<script type="text/javascript" src="cim.web.sdk.js"></script>
|
||||
</head>
|
||||
|
||||
|
||||
<script type="text/javascript">
|
||||
|
||||
|
||||
/** 当socket连接成功回调 **/
|
||||
function onConnectionSuccessed()
|
||||
{
|
||||
var sessionId = '<%=session.getId()%>';
|
||||
CIMWebBridge.bindAccount($('#account').val(),sessionId);
|
||||
}
|
||||
|
||||
/** 当收到请求回复时候回调 **/
|
||||
function onReplyReceived(json)
|
||||
{
|
||||
if(json.key=='client_bind' && json.code==200)
|
||||
{
|
||||
hideProcess();
|
||||
doHideDialog('LoginDialog');
|
||||
doShowDialog('MessageDialog');
|
||||
$("#current_account").text(ACCOUNT);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/** 当收到消息时候回调 **/
|
||||
|
||||
function onMessageReceived(message)
|
||||
{
|
||||
$("#messageList").append("<div class='alert alert-info' >"+message.content+"</div>");
|
||||
}
|
||||
|
||||
$(document).ready(function(){
|
||||
doShowDialog('LoginDialog');
|
||||
});
|
||||
|
||||
|
||||
|
||||
</script>
|
||||
|
||||
|
||||
<body style="background-color: rgb(190, 209, 216);width: 600px;">
|
||||
<div id="global_mask" style="display: none; position: absolute; top: 0px; left: 0px; z-index: 998; background-color: rgb(190, 209, 216); opacity: 0.5; width: 100%; height: 100%; overflow: hidden; background-position: initial initial; background-repeat: initial initial;"></div>
|
||||
|
||||
<%@include file="loginDialog.jsp"%>
|
||||
<%@include file="messageDialog.jsp"%>
|
||||
</body>
|
||||
</html>
|
@ -0,0 +1,21 @@
|
||||
<%@ page language="java" pageEncoding="utf-8"%>
|
||||
|
||||
|
||||
<div class="modal fade" data-backdrop="static" id="MessageDialog" tabindex="-1" role="dialog" >
|
||||
<div class="modal-dialog" style="width: 500px;margin: 30px auto;">
|
||||
<div class="modal-content" >
|
||||
<div class="modal-header" style="text-align: center;">
|
||||
<h4>消息列表</h4>
|
||||
<span style="float: left;">请在管理页面推送一条消息</span>
|
||||
<span style="float: right;color: #4caf50;">当前帐号:<span id="current_account">10000</span></span>
|
||||
</div>
|
||||
<div class="modal-body" id="messageList" style="min-height: 600px;" >
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
|
BIN
doc/CIM客户端文档.doc
BIN
doc/CIM客户端文档.doc
Binary file not shown.
BIN
doc/WebSDK使用文档.doc
Normal file
BIN
doc/WebSDK使用文档.doc
Normal file
Binary file not shown.
Loading…
x
Reference in New Issue
Block a user