1. Start with the most basic to write interfaces and implementation classes
public interface IUserService {
public String getList();
public String getList(Integer id,String name);
}
import cn.ensoft.service.IUserService;
public class UserServiceImpl implements IUserService{
@Override
public String getList() {
return "{'id':'abc','name':'hello'}";
}
@Override
public String getList(Integer id, String name) {
return "{'id':'"+id+"','name':'"+name+"'}";
}
}
2. t-io custom public class
2.1 Service message package
import java.io.UnsupportedEncodingException;
import org.tio.core.intf.Packet;
public class MsgPacket extends Packet{
public static final String CHARSET = "GB18030";
private byte[] body;
public MsgPacket() {
}
public MsgPacket(String msg) {
try {
this.body = msg.getBytes(MsgPacket.CHARSET);
} catch (UnsupportedEncodingException e) {
}
}
/**
* @return the body
*/
public byte[] getBody(){
return body;
}
/**
* @param body the body to set
*/
public void setBody(byte[] body){
this.body = body;
}
}
2.2 Encoding & Decoding
import java.nio.ByteBuffer;
import org.tio.core.ChannelContext;
import org.tio.core.GroupContext;
import org.tio.core.exception.AioDecodeException;
import org.tio.core.intf.AioHandler;
/**
* 服务器端和客户端的编码解码算法是一样的,所以抽象一个公共的父类出来
* @author tanyaowu
*/
public abstract class MsgHandler implements AioHandler<Object, MsgPacket, Object>
{
/**
* 编码:把业务消息包编码为可以发送的ByteBuffer
* 总的消息结构:消息头 + 消息体
* 消息头结构: 4个字节,存储消息体的长度
* 消息体结构: 对象的json串的byte[]
*/
@Override
public ByteBuffer encode(MsgPacket packet, GroupContext<Object, MsgPacket, Object> groupContext, ChannelContext<Object, MsgPacket, Object> channelContext){
byte[] body = packet.getBody();
int bodyLen = 0;
if (body != null){
bodyLen = body.length;
}
//bytebuffer的总长度是 = 消息头的长度 + 消息体的长度
int allLen = bodyLen;
//创建一个新的bytebuffer
ByteBuffer buffer = ByteBuffer.allocate(allLen);
//设置字节序
buffer.order(groupContext.getByteOrder());
//写入消息体
if (body != null){
buffer.put(body);
}
return buffer;
}
/**
* 解码:把接收到的ByteBuffer,解码成应用可以识别的业务消息包
*/
@Override
public MsgPacket decode(ByteBuffer buffer, ChannelContext<Object, MsgPacket, Object> channelContext) throws AioDecodeException{
int readableLength = buffer.limit() - buffer.position();//真实数据长度
if (readableLength == 0){
return null;
}
byte[] dst = new byte[readableLength];
MsgPacket imPacket = new MsgPacket();
buffer.get(dst, 0, readableLength);
imPacket.setBody(dst);
return imPacket;
}
}
2.3 The server and the client agree on the port, IP and client connection timeout time
public class MsgConst
{
public static final int PORT = 6789;
public static final String IP = "127.0.0.1";
public static final int TIME_OUT = 10000;
}
2.4 Content encoding & decoding => hex
import java.io.ByteArrayOutputStream;
import java.io.UnsupportedEncodingException;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
/**
* 进制之间的转换
* @author jwzhangjie
*
*/
public class HexadecimalConver {
private static String hexString="0123456789ABCDEF;{}\"'";
/**
* 将字符串编码成16进制数字,适用于所有字符(包括中文)
* @throws UnsupportedEncodingException
*/
public static String encode(String str) throws UnsupportedEncodingException{
//根据默认编码获取字节数组
byte[] bytes= str.getBytes(MsgPacket.CHARSET);
StringBuilder sb = new StringBuilder(bytes.length*2);
//将字节数组中每个字节拆解成2位16进制整数
for(int i=0;i<bytes.length;i++){
sb.append(hexString.charAt((bytes[i]&0xf0)>>4));
sb.append(hexString.charAt((bytes[i]&0x0f)>>0));
}
return sb.toString();
}
/**
* 将16进制数字解码成字符串,适用于所有字符(包括中文)
* @throws UnsupportedEncodingException
*/
public static String decode(String bytes) throws UnsupportedEncodingException{
ByteArrayOutputStream baos=new ByteArrayOutputStream(bytes.length()/2);
//将每2位16进制整数组装成一个字节
for(int i=0;i<bytes.length();i+=2)
baos.write((hexString.indexOf(bytes.charAt(i))<<4 |hexString.indexOf(bytes.charAt(i+1))));
return new String(baos.toByteArray(), MsgPacket.CHARSET);
}
public static String genWriteJson(String inf,String method,Class<?>[] argsType,Object[] args){
JSONObject jsonObject = new JSONObject();
jsonObject.put("inf", inf);
jsonObject.put("method", method);
jsonObject.put("argsType", argsType);
jsonObject.put("args", args);
return JSON.toJSONString(jsonObject);
}
public static String genWriteCode(String json){
try {
return HexadecimalConver.encode(json);
} catch (UnsupportedEncodingException e) {
return null;
}
}
}
3. Write tio-server for client remote calls
3.1 Server entry
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import org.tio.server.AioServer;
import org.tio.server.ServerGroupContext;
import org.tio.server.intf.ServerAioHandler;
import org.tio.server.intf.ServerAioListener;
public class MsgServerStarter{
public static Map<String,Class<?>> regist;
//handler, 包括编码、解码、消息处理
public static ServerAioHandler<Object, MsgPacket, Object> aioHandler = new MsgServerAioHandler();
//事件监听器,可以为null,但建议自己实现该接口,可以参考showcase了解些接口
public static ServerAioListener<Object, MsgPacket, Object> aioListener = null;
//一组连接共用的上下文对象
public static ServerGroupContext<Object, MsgPacket, Object> serverGroupContext = new ServerGroupContext<>(aioHandler,aioListener);
//aioServer对象
public static AioServer<Object, MsgPacket, Object> aioServer = new AioServer<>(serverGroupContext);
//有时候需要绑定ip,不需要则null
public static String serverIp = MsgConst.IP;
//监听的端口
public static int serverPort = MsgConst.PORT;
/**
* 启动程序入口
* @throws IOException
* @throws IllegalAccessException
* @throws InstantiationException
*/
public static void main(String[] args) throws IOException
{
regist = new HashMap<String,Class<?>>();
regist.put(IUserService.class.getName(),UserServiceImpl.class);
aioServer.start(serverIp, serverPort);
}
}
3.2 Message Processor
import java.io.UnsupportedEncodingException;
import org.nutz.log.Log;
import org.nutz.log.Logs;
import org.tio.core.Aio;
import org.tio.core.ChannelContext;
import org.tio.server.intf.ServerAioHandler;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
public class MsgServerAioHandler extends MsgHandler implements ServerAioHandler<Object, MsgPacket, Object>
{
private static final Log log = Logs.get();
/**
* 处理消息
* @throws UnsupportedEncodingException
*/
@Override
public Object handler(MsgPacket packet, ChannelContext<Object, MsgPacket, Object> channelContext) throws UnsupportedEncodingException
{
MsgPacket resppacket = new MsgPacket();
byte[] body = packet.getBody();
if (body != null)
{
String encryCode = new String(body, MsgPacket.CHARSET);
log.debugf("服务端收到消息:%s", encryCode);
try {
String requestJson = HexadecimalConver.decode(encryCode);
log.debugf("解析数据:%s", requestJson);
JSONObject requestObj = JSON.parseObject(requestJson);
String inf = requestObj.getString("inf");
String method = requestObj.getString("method");
JSONArray argsTypeB = requestObj.getJSONArray("argsType");
Class<?>[] argsType = new Class[argsTypeB.size()];
for (int i = 0; i < argsTypeB.size(); i++) {
String classzz = argsTypeB.getString(i);
argsType[i] = Class.forName(classzz);
}
JSONArray argsB = requestObj.getJSONArray("args");
Object[] args = new Object[argsB.size()];
for (int i = 0; i < argsB.size(); i++) {
Object atp = argsB.get(i);
args[i] = atp;
}
Class<?> bindClazz = MsgServerStarter.regist.get(inf);
Object bindObj = bindClazz.newInstance();
String responseMsg = (String)bindClazz.getMethod(method,argsType).invoke(bindObj,args);
String responseCode = HexadecimalConver.genWriteCode(responseMsg);
resppacket.setBody(responseCode.getBytes(MsgPacket.CHARSET));
} catch (Exception e) {
log.error("该消息无法解析",e);
log.errorf("msg", e);
resppacket.setBody(FAIL.getBytes(MsgPacket.CHARSET));
}
Aio.send(channelContext, resppacket);
}
return null;
}
}
4. Write tio-client client
4.1 Client Portal
import java.util.HashMap;
import java.util.Map;
import org.tio.client.AioClient;
import org.tio.client.ClientChannelContext;
import org.tio.client.ClientGroupContext;
import org.tio.client.ReconnConf;
import org.tio.client.intf.ClientAioHandler;
import org.tio.client.intf.ClientAioListener;
import org.tio.core.Node;
public class MsgClientStarter
{
public static Map<String, String> result;
//服务器节点
public static Node serverNode = new Node(MsgConst.IP,MsgConst.PORT);
//handler, 包括编码、解码、消息处理
public static ClientAioHandler<Object, MsgPacket, Object> aioClientHandler = new MsgClientAioHandler();
//事件监听器,可以为null,但建议自己实现该接口,可以参考showcase了解些接口
public static ClientAioListener<Object, MsgPacket, Object> aioListener = null;
//new MsgClientAioListener();
//断链后自动连接的,不想自动连接请设为null
private static ReconnConf<Object, MsgPacket, Object> reconnConf = null;
//private static ReconnConf<Object, MsgPacket, Object> reconnConf = new ReconnConf<Object, MsgPacket, Object>(5000L);
//一组连接共用的上下文对象
public static ClientGroupContext<Object, MsgPacket, Object> clientGroupContext = new ClientGroupContext<>(aioClientHandler, aioListener, reconnConf);
public static AioClient<Object, MsgPacket, Object> aioClient = null;
public static ClientChannelContext<Object, MsgPacket, Object> clientChannelContext = null;
/**
* 启动程序入口
* @throws Exception
*/
public static void instance() throws Exception
{
if(aioClient==null){
aioClient = new AioClient<Object, MsgPacket, Object>(clientGroupContext);
clientChannelContext = aioClient.connect(serverNode);
clientChannelContext.setUserid(java.util.UUID.randomUUID().toString().toUpperCase());
result = new HashMap<String,String>();
}
}
}
4.2 Message Processor
import org.tio.client.intf.ClientAioHandler;
import org.tio.core.ChannelContext;
import cn.ensoft.tio.MsgHandler;
import cn.ensoft.tio.MsgPacket;
import cn.ensoft.util.HexadecimalConver;
public class MsgClientAioHandler extends MsgHandler implements ClientAioHandler<Object, MsgPacket, Object>
{
/**
* 处理消息
*/
@Override
public Object handler(MsgPacket packet, ChannelContext<Object, MsgPacket, Object> channelContext) throws Exception
{
byte[] body = packet.getBody();
if (body != null)
{
String str = new String(body, MsgPacket.CHARSET);
System.out.println("CLIENT=>>收到消息:" + str);
System.out.println("CLIENT=>>收到消息:" + HexadecimalConver.decode(str));
MsgClientStarter.result.put(channelContext.getUserid(), HexadecimalConver.decode(str));
}
return null;
}
@Override
public MsgPacket heartbeatPacket()
{
return null;
}
}
5. Proxy
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import org.nutz.log.Log;
import org.nutz.log.Logs;
import org.tio.core.Aio;
import cn.ensoft.client.MsgClientStarter;
import cn.ensoft.tio.MsgPacket;
import cn.ensoft.tio.MsgConst;
public class ClientProxy<T> implements InvocationHandler{
private static final Log log = Logs.get();
private Object targetClazz;
public ClientProxy(final Class<T> clazz) throws InstantiationException, IllegalAccessException {
this.targetClazz = clazz.newInstance();
}
public Object bind() {
return Proxy.newProxyInstance(targetClazz.getClass().getClassLoader(),targetClazz.getClass().getInterfaces(), this);
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
MsgClientStarter.instance();
String impl = targetClazz.getClass().getCanonicalName();
String inf = targetClazz.getClass().getInterfaces()[0].getName();
String methodName = method.getName();
Class<?>[] argsType = method.getParameterTypes();
log.debugf("参数-IMPL=%s", impl);
log.debugf("参数-INF=%s", inf);
log.debugf("参数-method=%s", methodName);
log.debugf("参数-argsType=%s",JSON.toJSONString(argsType));
log.debugf("参数-args=%s", JSON.toJSONString(args));
String requestJson = HexadecimalConver.genWriteJson(inf, methodName,argsType, args);
String requestCode = HexadecimalConver.genWriteCode(requestJson);
log.debugf("requestJson=%s", requestJson);
log.debugf("requestCode=%s", requestCode);
log.debugf("requestCodeD=%s", HexadecimalConver.decode(requestCode));
MsgPacket packet = new MsgPacket();
packet.setBody(requestCode.getBytes(MsgPacket.CHARSET));
Aio.send(MsgClientStarter.clientChannelContext, packet);
long stimes = System.currentTimeMillis();
String result = null;
while(true){
result = MsgClientStarter.result.get(MsgClientStarter.clientChannelContext.getUserid());
if(null != result){
break;
}else{
if((System.currentTimeMillis()-stimes)>=MsgConst.TIME_OUT){
break;
}
}
}
Aio.remove(MsgClientStarter.clientChannelContext, "sucess");
return result;
}
}
6. Test
public class Test {
public static void main(String[] args) {
try {
IUserService userService = (IUserService) new ClientProxy<UserServiceImpl>(UserServiceImpl.class).bind();
String msg = userService.getList(888,"hello-rpc-"+java.util.UUID.randomUUID());
System.out.println("============"+msg);
System.exit(0);
} catch (Exception e) {
e.printStackTrace();
}
}
}
Start the server first
10:28:26,196 WARN AioServer:90 - t-io server started, listen on 127.0.0.1:6789
then run Test
10:34:18,356 INFO AioClient:338 - [1]: curr:0, closed:0, received:(0p)(0b), handled:0, sent:(0p)(0b)
10:34:18,356 INFO ConnectionCompletionHandler:98 - connected to 127.0.0.1:6789
10:34:18,356 DEBUG ClientProxy:39 - 参数-IMPL=cn.ensoft.service.impl.UserServiceImpl
10:34:18,356 DEBUG ClientProxy:40 - 参数-INF=cn.ensoft.service.IUserService
10:34:18,356 DEBUG ClientProxy:41 - 参数-method=getList
10:34:18,387 DEBUG ClientProxy:42 - 参数-argsType=["java.lang.Integer","java.lang.String"]
10:34:18,387 DEBUG ClientProxy:43 - 参数-args=[888,"hello-rpc-e7f99901-3f9c-41cc-8b94-59cf6d0d31e6"]
10:34:18,402 DEBUG ClientProxy:46 - requestJson={"inf":"cn.ensoft.service.IUserService","args":[888,"hello-rpc-e7f99901-3f9c-41cc-8b94-59cf6d0d31e6"],"method":"getList","argsType":["java.lang.Integer","java.lang.String"]}
10:34:18,402 DEBUG ClientProxy:47 - requestCode=7B22696E66223A22636E2E656E736F66742E736572766963652E495573657253657276696365222C2261726773223A5B3838382C2268656C6C6F2D7270632D65376639393930312D336639632D343163632D386239342D353963663664306433316536225D2C226D6574686F64223A226765744C697374222C226172677354797065223A5B226A6176612E6C616E672E496E7465676572222C226A6176612E6C616E672E537472696E67225D7D
10:34:18,402 DEBUG ClientProxy:48 - requestCodeD={"inf":"cn.ensoft.service.IUserService","args":[888,"hello-rpc-e7f99901-3f9c-41cc-8b94-59cf6d0d31e6"],"method":"getList","argsType":["java.lang.Integer","java.lang.String"]}
CLIENT=>>收到消息:7B276964273A27383838272C276E616D65273A2768656C6C6F2D7270632D65376639393930312D336639632D343163632D386239342D353963663664306433316536277D
CLIENT=>>收到消息:{'id':'888','name':'hello-rpc-e7f99901-3f9c-41cc-8b94-59cf6d0d31e6'}
10:34:18,408 INFO DecodeRunnable:151 - 0.0.0.0:57205 收到消息
10:34:18,408 DEBUG DecodeRunnable:168 - 0.0.0.0:57205,组包后,数据刚好用完
============{'id':'888','name':'hello-rpc-e7f99901-3f9c-41cc-8b94-59cf6d0d31e6'}
10:34:18,408 INFO CloseRunnable:120 - 准备关闭连接:0.0.0.0:57205, isNeedRemove:true, sucess
10:34:18,408 INFO Aio:78 - 0.0.0.0:57205 正在等待被关闭
The writing is not good, please enlighten me, do not spray, thank you!