自己实现的服务通信框架
学习了公司的服务通信中间件,自己实现了一个最简单的服务通信框架。
框架结构
由下向上分为4层:
- 实体层。对服务的请求参数和服务的返回值进行封装,提供序列化和反序列化方法。
- 协议层。规定数据在网络上传输应该按照什么样的格式来组织。
- 通信层。就是Socket通信了,使用了NIO技术。
- 服务代理层。客户端需要通过代理去请求远程服务的某个方法,代理所做的事情实际上就是把请求参数按照协议的规定组织好,给服务端发送一个数据包;等服务端以socket数据包的形式返回结果后,再把结果反序列化解析一下,最终把结果返回给调用端。
先上一个类结构图,后文再细讲。
图1. 类结构图
实体层
位于实体层的是Param、ServiceRequest和ServiceResponse这3个类。
Param对单个参数进行封装,比如int a=3;封装成Param,其type就是Integer.class,其value就是"3",其toValue()方法返值就是int型数据3。
ServiceRequest中需要指定接口、实现类、方法名、方法参数列表。
Param类代码:
public class Param { Class<?> type; String value; public String toJson() { JSONObject jsonObject = new JSONObject(); jsonObject.put("type", type.getCanonicalName()); jsonObject.put("value", value); return jsonObject.toJSONString(); } public Object toValue() { if (type == Integer.class || type == Integer.TYPE) { return Integer.parseInt(value); } if (type == Double.class || type == Double.TYPE) { return Double.parseDouble(value); } if (type == Boolean.class || type == Boolean.TYPE) { return Boolean.parseBoolean(value); } if (type == Byte.class || type == Byte.TYPE) { return Byte.parseByte(value); } if (type == Character.class || type == Character.TYPE) { return new Character((char) value.charAt(0)); } if (type == Short.class || type == Short.TYPE) { return Short.parseShort(value); } if (type == Long.class || type == Long.TYPE) { return Long.parseLong(value); } if (type == Float.class || type == Float.TYPE) { return Float.parseFloat(value); } if (type == String.class) { return value; } throw new RuntimeException("value convert failed. type is " + type.getCanonicalName() + ", value is " + value); } public static Param parse(String json) { Param param = new Param(); JSONParser parser = new JSONParser(JSONParser.MODE_PERMISSIVE); try { JSONObject jsonObject = (JSONObject) parser.parse(json); String value = jsonObject.get("value").toString(); param.setValue(value); String className = jsonObject.get("type").toString(); Class<?> type = Class.forName(className); param.setType(type); } catch (Exception e) { } return param; } }
协议
协议如下:
endian指出所有数据是按大端序序列化还是小端序序列化。占一个byte。
totalLen是整个数据的长度。占一个int。
sessionID:请求数据和响应数据之间用sessionID对应进来。占一个int。
msgType标记数据是请求,还是响应。占一个byte。
msg是真正的请求或响应数据,即ServiceRequest或ServiceResponse的json表现形式。
postfix标记数据包的结尾,是5个固定的byte:9,11,13,17,18。用来做校验。
Protocol类代码:
public class Protocol { byte lowEndian = 1;// 0大端字节序,1小端字节序 int totalLen; int sessionID; byte msgType;// 1请求,2响应 String msg; public final byte[] postfix = new byte[] { (byte) 9, (byte) 11, (byte) 13, (byte) 17, (byte) 18 }; public byte[] serialize() { ByteBuffer bbufer = ByteBuffer.allocate(1024); bbufer.put(lowEndian); byte[] msgB = msg.getBytes(); totalLen = 15 + msgB.length; if (lowEndian == (byte) 1) { bbufer.put(DataTransform.intToBytes(totalLen, true)); bbufer.put(DataTransform.intToBytes(sessionID, true)); } else { bbufer.put(DataTransform.intToBytes(totalLen, false)); bbufer.put(DataTransform.intToBytes(sessionID, false)); } bbufer.put(msgType); bbufer.put(msgB); bbufer.put(postfix); byte[] bytes = new byte[bbufer.position()]; bbufer.flip(); bbufer.get(bytes); return bytes; } public static Protocol deserialize(byte[] bytes) { Protocol protocol = new Protocol(); int totalLen = bytes.length; assert totalLen > 15; ByteArrayInputStream bais = new ByteArrayInputStream(bytes); try { byte little = (byte) (bais.read()); protocol.setLowEndian(little); byte[] lenb = new byte[4]; byte[] sidb = new byte[4]; bais.read(lenb); bais.read(sidb); int len = 0; int ssid = 0; if (little == (byte) 1) { len = DataTransform.bytesToInt(lenb, true); ssid = DataTransform.bytesToInt(sidb, true); } else { len = DataTransform.bytesToInt(lenb, false); ssid = DataTransform.bytesToInt(sidb, false); } assert len == totalLen; protocol.setTotalLen(len); protocol.setSessionID(ssid); protocol.setMsgType((byte) (bais.read())); byte[] msgB = new byte[len - 15]; bais.read(msgB); protocol.setMsg(new String(msgB)); byte[] post = new byte[5]; bais.read(post); assert post[0] == (byte) 9; assert post[1] == (byte) 11; assert post[2] == (byte) 13; assert post[3] == (byte) 17; assert post[4] == (byte) 18; return protocol; } catch (Exception e) { throw new RuntimeException("deserialize protocol failed.", e); } finally { try { bais.close(); } catch (IOException e) { } } } }
在上述代码中,当要把多个变量序列化到一个byte数组中时,我们借助了ByteBuffer,当要从一个byte数组里反序列化出若干数据时我们借助了ByteArrayInputStream。
通信层
该层有3个类:ResponseWaiter、Sender、Receiver。
从图1中我们看到ResponseWaiter有2个成员:ServiceResponse response和CountDownLatch latch。在ResponseWaiter的构造函数中初始化latch,值设为1。当接收到服务端返回的ServiceResponse后,赋成本类的成员response,同时调用latch的countDown()方法,通知在latch上等待的线程,告诉它们已经有结果返回,并且结果已存储在reponse成员变量中。
Sender把数据按照协议的规定组织好,通过socket发送给服务端。每次发送请求会生成一个新的SessionID,并创建一个与之对应的ResponseWaiter,存储在一个sessionMap里。
Receiver接收到socket数据后,取出seesionID和ServiceResponse,拿seesionID到Sender的sessionMap中找到对应的ResponseWaiter,将ServiceResponse赋给该ResponseWaiter的response成员,并调用该ResponseWaiter的latch的countDown()方法。
Sender代码:
package communication; import java.io.IOException; import java.net.InetSocketAddress; import java.net.SocketAddress; import java.nio.ByteBuffer; import java.nio.channels.SocketChannel; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.atomic.AtomicInteger; import protocol.Protocol; import serialize.ServiceRequest; public class Sender { private static final String host = "127.0.0.1"; private static final int port = 5858; private static SocketChannel socketChannel = null; private static final byte lowEndian = (byte) 1;// 小端 private static final byte msgType = (byte) 1;// 请求 private static AtomicInteger sessionID = new AtomicInteger(0); public static Map<Integer,ResponseWaiter> sessionMap=new ConcurrentHashMap<Integer,ResponseWaiter>(); public static int send(ServiceRequest request) { SocketChannel socketChannel = null; try { socketChannel = SocketChannel.open(); SocketAddress address = new InetSocketAddress(host, port); socketChannel.connect(address); Protocol protocol = new Protocol(); protocol.setLowEndian(lowEndian); protocol.setMsg(request.toJson()); // System.out.println(request.toJson()); protocol.setMsgType(msgType); protocol.setSessionID(sessionID.incrementAndGet()); sessionMap.put(sessionID.get(), new ResponseWaiter()); byte[] bytes=protocol.serialize(); ByteBuffer buffer = ByteBuffer.wrap(bytes); socketChannel.write(buffer); } catch (IOException e) { e.printStackTrace(); } finally { try { socketChannel.socket().shutdownOutput(); } catch (IOException e) { e.printStackTrace(); } } return sessionID.get(); } public static void destroy() { try { if (!socketChannel.socket().isClosed()) { socketChannel.socket().close(); } if (socketChannel.isOpen()) { socketChannel.close(); } } catch (IOException e) { } } }
Receiver代码:
public class Receiver { private static final int port = 5859; private static Selector selector = null; private static ServerSocketChannel serverChannel = null; public static void work() { try { selector = Selector.open(); serverChannel = ServerSocketChannel.open(); serverChannel.configureBlocking(false); serverChannel.socket().setReuseAddress(true); serverChannel.socket().bind(new InetSocketAddress(port)); System.out.println("===========Client listen on port " + port + " waiting for response==========="); serverChannel.register(selector, SelectionKey.OP_ACCEPT); while (selector.select() > 0) { Iterator<SelectionKey> itr = selector.selectedKeys().iterator(); while (itr.hasNext()) { SelectionKey key = itr.next(); if (key.isAcceptable()) { ServerSocketChannel ssc = (ServerSocketChannel) key.channel(); handleRequest(ssc); } itr.remove(); } } } catch (IOException e) { e.printStackTrace(); } finally { try { selector.close(); } catch (Exception ex) { try { serverChannel.close(); } catch (Exception ee) { ee.printStackTrace(); } } } } private static void handleRequest(ServerSocketChannel ssc) { SocketChannel socketChannel = null; try { socketChannel = ssc.accept(); BufferedInputStream bis = new BufferedInputStream(socketChannel.socket().getInputStream()); ByteBuffer buffer = ByteBuffer.allocate(1024); int b = -1; while ((b = bis.read()) != -1) { buffer.put((byte) b); } byte[] bytes = new byte[buffer.position()]; buffer.flip(); buffer.get(bytes); if (bytes.length > 0) { Protocol protocol = Protocol.deserialize(bytes); assert protocol.getMsgType() == (byte) 2; ServiceResponse response = ServiceResponse.parse(protocol.getMsg()); int sessionID = protocol.getSessionID(); // System.out.println("session " + sessionID + " get result:" + response.getValue() + "\t" + response.getType().getCanonicalName()); ResponseWaiter waiter=Sender.sessionMap.get(sessionID); if(waiter!=null){ waiter.setResponse(response); waiter.set(); } } } catch (IOException e) { e.printStackTrace(); } finally { try { socketChannel.socket().shutdownInput(); } catch (IOException e) { e.printStackTrace(); } } } public static void destroy() { try { if (selector.isOpen()) { selector.close(); } if (!serverChannel.socket().isClosed()) { serverChannel.socket().close(); } if (serverChannel.isOpen()) { serverChannel.close(); } } catch (IOException e) { } } }
从上述代码中可以学习一下java网络编程的NIO技术。
服务代理
java中的代理类必须实现InvocationHandler接口。java的代理机制允许我们在调用一个接口的某个方法时转而去执行另外一段毫不相干的代码,这段代码就是在InvocationHandler的invoke()方法中指定的。
public class ServiceProxy implements InvocationHandler { private Class<?> iface; private String implClass; public Object bind(Class<?> iface, String impl) { this.iface = iface; this.implClass = impl; return Proxy.newProxyInstance(iface.getClassLoader(), new Class<?>[] { iface }, this); } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Exception { if (!method.isAnnotationPresent(OperationContract.class)) { throw new Exception("method " + method.getName() + " is not in contract."); } long begin = System.currentTimeMillis(); try { ServiceRequest request = new ServiceRequest(); request.setIface(iface); request.setImplClass(implClass); request.setMethodName(method.getName()); Param[] params = new Param[args.length]; for (int i = 0; i < args.length; i++) { Object obj = args[i]; Param param = new Param(); param.setType(obj.getClass()); param.setValue(String.valueOf(obj)); params[i] = param; } request.setParams(params); int sessionID = Sender.send(request); if (Sender.sessionMap.get(sessionID).waitResponse(200)) { ServiceResponse response = Sender.sessionMap.get(sessionID).getResponse(); Param param = new Param(); param.setType(response.getType()); param.setValue(response.getValue()); Object result = param.toValue(); return result; } else { System.err.println("call service " + iface.getCanonicalName() + " timeout!"); Sender.sessionMap.remove(sessionID); } } catch (Exception e) { System.err.println("remote service invocation failed."+e.getMessage()); System.exit(1); } long end = System.currentTimeMillis(); if (end - begin > 200) { System.err.println(method.getName() + " execute over 200 miliseconds!"); } return null; } }
上述代码的invoke()方法执行流程是这样的:
- 获取到请求参数后,通过Sender发送给服务方。同时Sender的send()方法会返回一个SessionID。
- 在SessionID对应的ResponseWaiter(的CountDownLatch)上等待。
- ResponseWaiter的栅栏通过后,从ResponseWaiter中取出ServiceResponse,并将其返回。
解释一下这行代码:
Proxy.newProxyInstance(iface.getClassLoader(), new Class<?>[] { iface }, this);
第一个参数是代理类的加载器。每二个参数代表要对哪些接口进行代理。每三个参数代理方法的真正实现类,这个类是一个InvocationHandler。
本文来自博客园,作者:张朝阳,转载请注明原文链接:https://www.cnblogs.com/zhangchaoyang/articles/4197369.html