1.系统采用Jboss netty框架作为socket框架
1.1框架使用
1.1.1服务器端
服务器启动代码
// private IoAcceptor acceptor;// 服务端socket对象 public GateMachineServer(int port) { ChannelFactory factory = new NioServerSocketChannelFactory( Executors.newCachedThreadPool(), Executors.newCachedThreadPool()); bootstrap = new ServerBootstrap(factory); bootstrap.setPipelineFactory(new ChannelPipelineFactory() { public ChannelPipeline getPipeline() { ChannelPipeline pipeline = Channels.pipeline(); // 调用messageReceived方法之后调用的方法,则decode方法为messageReceived之前调用的方法 pipeline.addLast("decode", new ServerMessageDecoder()); pipeline.addLast("encode", new StringEncoder()); pipeline.addLast("handler", new ServerHandler()); return pipeline; } }); bootstrap.setOption("child.tcpNoDelay", Boolean.TRUE); bootstrap.setOption("child.keepAlive", Boolean.TRUE); bootstrap.bind(new InetSocketAddress(port)); NettyChannelMap.setMap(new HashMap()); }
上面是通过netty框架启动一个socket服务的代码
1.1.2代码说明
Channel 对象是负责数据读,写的对象,有点类似于老的io里面的stream。它和stream的区别,channel是双向的,既可以write 也可以read,而stream要分outstream和inputstream。而且在NIO中用户不应该直接从channel中读写数据,而是应该通过buffer,通过buffer再将数据读写到channel中。
ChannelFactory 是一个创建和管理Channel通道及其相关资源的工厂接口,它处理所有的I/O请求并产生相应的I/O ChannelEvent通道事件。
ServerBootstrap 是一个设置服务的帮助类。
任何时候当服务器接收到一个新的连接,一个新的ChannelPipeline管道对象将被创建,并且所有在这里添加的ChannelHandler对象将被添加至这个新的 ChannelPipeline管道对象。这很像是一种浅拷贝操作(a shallow-copy operation);所有的Channel通道以及其对应的ChannelPipeline实例将分享相同的DiscardServerHandler 实例。
具体的可以参考:http://blog.csdn.net/gd2008/article/details/8172845
网上还有很多类似的资料,多多借鉴他人的想法,就有自己的理解了。
那么如何将netty与我们的业务结合呢?
正如上面所说每个连接创建的时候ServerBootstrap都会给配置一个ChannelPipeline管道和对应的ChannelHandler对象,我们可以实现自己的ChannelHandler即上面的ServerHandler。
public void messageReceived(ChannelHandlerContext ctx, MessageEvent e) { if (e.getMessage() instanceof Msg) {// // 保存有效通道 Msg msg = (Msg) e.getMessage(); if (msg instanceof LoginMsg) {// 如果是请求登录服务器 LoginMsg loginMsg = (LoginMsg) msg; if (loginMsg.isHasNo()) {// 如果请求中含有机器编号 // 请求api得到gateId try { loginMsg.setId(esApi.getGateMachineIdByGateNo(loginMsg.getGateNo())); NettyChannelMap.add(loginMsg.getId(), (SocketChannel) e.getChannel()); e.getChannel().write( Encodes.encodeHex(LoginMsg.getReturnLoginByteSuccess(loginMsg.getId()))); LOGGER.info("登录成功"); ServerFrame.consolePrint("编号为" + loginMsg.getGateNo() + "成功登录",console); } catch (IOException e1) { // 没有获取成功 LOGGER.info("登录失败"); ServerFrame.consolePrint( "编号为" + loginMsg.getGateNo() + "登录失败", console); e.getChannel().write( Encodes.encodeHex(LoginMsg .getReturnLoginByteFail())); e1.printStackTrace(); } } else { // 如果是带有id的登录请求 NettyChannelMap.add(msg.getId(), (SocketChannel) e.getChannel()); e.getChannel().write( Encodes.encodeHex(LoginMsg .getReturnLoginByteSuccess(loginMsg .getId()))); LOGGER.info("登录成功"); ServerFrame.consolePrint("id为" + loginMsg.getId() + "成功登录", console); } if (e.getMessage() instanceof IOSOpenDoorMsg) { // 判断是否有连上对应机器 IOSOpenDoorMsg openDoorMsg = (IOSOpenDoorMsg) e.getMessage(); Channel e1 = NettyChannelMap.get(openDoorMsg.getId()); if (e1 != null) { byte[] sendMsg = OpenDoorMsg.generateOpenDoorMsgToGate(); e1.write(Encodes.encodeHex(sendMsg)); // 插入门禁记录 try { esApi.addEnterLog(new GatemachineEnterInfor( (OpenDoor) e.getMessage())); } catch (Exception e2) { e2.printStackTrace(); } ServerFrame.consolePrint("id为" + openDoorMsg.getId() + "成功", console); // 开门成功返回 e.getChannel().write(Protocol.iosReturnSuccess); } else { // 开门失败返回 e.getChannel().write(Protocol.iosReturnFail); } } }
逻辑处理主要放在ServerHandler里的messageReceived方法里,这个类继承SimpleChannelUpstreamHandler监听服务端跟客户端的连接事件和消息接收事件,以及销毁事件,在消息接收事件里我们可以将消息转换成自己的类,这个类是基于自己的协议构建的,由于我们的逻辑需要(对门口机分发消息,门口机要通过id关联通道),所以我们只需保存有效的通道,其他无效的通道将不记录到当前所有连接map中,有效连接指的是带有我们定义的协议数据的请求,连接数数据,读取到的数据,以及返回数据和相应的对象将在前台的JFrame里显示。
1.1.3数据接收及主要逻辑
我们可以自己实现数据编码与解码类来实现自己的协议,上面用了StringEncoder和StringDecoder这是netty框架自带的数据编码与解码类,我们可以自己继承OneToOneEncoder和FrameDecoder来编写自己协议的实现,其中我们可以将数据转换成通过我们协议构建的数据类,然后在serverHandler里的messageReceived方法里转成我们自己的协议数据类,里面包括协议头数据,id,请求头数据,通过这些我们可以判断该请求是什么请求,然后做出相应的响应,即根据id找出相应的channel写入我们已经构建的协议数据,机器做出相应的处理。
2.HttpClient框架实现数据交互
2.1向门口机下发数据
客户端连接socket服务器,发送协议数据,服务端接到数据,判断逻辑下发指定门口机。
启动客户端连接服务器,
下面是主要的代码:
public GateMachineClient(String ip, int port, String userId, String gateMachineId, OpenDoorNotify openDoorNotify) { if (StringAndByteUtils.isNullOrEmpty(userId) || StringAndByteUtils.isNullOrEmpty(gateMachineId) || StringAndByteUtils.isNullOrEmpty(ip) || (Integer) port == null) { throw new NullPointerException(); } this.openDoorNotify = openDoorNotify; this.userId = userId; this.gateMachineId = gateMachineId; ChannelFactory factory = new NioClientSocketChannelFactory( Executors.newCachedThreadPool(), Executors.newCachedThreadPool()); bootstrap = new ClientBootstrap(factory); bootstrap.setPipelineFactory(new ChannelPipelineFactory() { public ChannelPipeline getPipeline() { ChannelPipeline pipeline = Channels.pipeline(); pipeline.addLast("decode", new ClientMessageDecoder());// // 调用messageReceived方法之后调用的方法,则decode方法为messageReceived之前调用的方法 pipeline.addLast("encode", new ClientMessageEncoder()); pipeline.addLast("handler", new ClientBaseHandler()); return pipeline; } }); bootstrap.setOption("tcpNoDelay", Boolean.TRUE); bootstrap.setOption("keepAlive", Boolean.TRUE); bootstrap.connect(new InetSocketAddress(ip, port)); }
设置相应的handler向服务器发送协议数据。
/** * 客户端发送信息 * * @author liyf * */ private class ClientHandler extends SimpleChannelUpstreamHandler { public void channelConnected(ChannelHandlerContext ctx, ChannelStateEvent e) { OpenDoorMsg openDoorMsg = new OpenDoorMsg(); openDoorMsg.setId(getGateMachineId()); openDoorMsg.setUserId(getUserId()); e.getChannel().write( Encodes.encodeHex(OpenDoorMsg .generateOpenDoorMsg(openDoorMsg))); } public void messageReceived(ChannelHandlerContext ctx, MessageEvent e) { if (e.getMessage() instanceof NotifyReturn) { openDoorNotify.openDoorCallBack((NotifyReturn) e.getMessage()); } e.getChannel().close(); // Shut down thread pools to exit. if (bootstrap != null) { bootstrap.releaseExternalResources(); } } public void exceptionCaught(ChannelHandlerContext ctx, ExceptionEvent e) { e.getCause().printStackTrace(); e.getChannel().close(); } } /** * 客户端发送登录信息 * * @author liyf * */ private class ClientBaseHandler extends SimpleChannelUpstreamHandler { public void channelConnected(ChannelHandlerContext ctx, ChannelStateEvent e) { Msg loginMsg = new LoginMsg(); loginMsg.setId(StringAndByteUtils.uuid()); e.getChannel().write( Encodes.encodeHex(LoginMsg.getSendLoginByte(loginMsg .getId()))); } public void messageReceived(ChannelHandlerContext ctx, MessageEvent e) { System.err.println("接到服务器的数据为:" + e.getMessage()); e.getChannel().close(); } public void exceptionCaught(ChannelHandlerContext ctx, ExceptionEvent e) { e.getCause().printStackTrace(); e.getChannel().close(); } }
3.用户交互的界面
3.1界面图
3.2功能介绍
输入端口号后可以启动服务,重启服务,关闭服务,当前连接数显示当前的有效连接数,最下面的文本框显示客户端与服务器交互信息。右上角关闭按钮点击,程序进入后台但并未关闭。
最后只需将写好的程序打包成java应用桌面程序,在服务器里加入服务,设置开机自启,点击关闭按钮,程序依然在后台运行。
public class Main { public static void main(String[] arg) throws IOException { initLogger();// LOG加载配置文件 new ServerFrame();//启动界面 } /** * LOG加载配置文件 * * @throws IOException */ public static void initLogger() throws IOException { InputStream input = Main.class .getResourceAsStream("resources/log4j.properties"); Properties p = new Properties(); p.load(input); PropertyConfigurator.configure(p); //加载logger4j配置文件 }}