博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
小门禁系统服务端(实现特定协议的服务端应用)开发
阅读量:6722 次
发布时间:2019-06-25

本文共 7908 字,大约阅读时间需要 26 分钟。

  hot3.png

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配置文件	}}

 

转载于:https://my.oschina.net/ljc94/blog/768889

你可能感兴趣的文章
为什么要购买企业即时通讯
查看>>
Python 基础语法
查看>>
java中的Closeable接口
查看>>
html特殊字符 对应content编码 支持css3
查看>>
Mac设置环境变量
查看>>
行政笔记
查看>>
腾讯空间、新浪微博、腾讯微博登录接口
查看>>
nginx 随机启动脚本配置说明
查看>>
2.MySQL源码安装
查看>>
RabbitMQ系列二:管理RabbitMQ
查看>>
ospf虚拟链路
查看>>
Spring的一些配置文件的使用
查看>>
芒果数据库配置文件
查看>>
UIImagePickerController-设置相机(全屏)
查看>>
PL/SQL结构
查看>>
TX Text Control文字处理教程(7)邮件合并
查看>>
ActiveReports 报表应用教程 (6)---分组报表
查看>>
date
查看>>
ReflectUtil
查看>>
MySQL show processlist;命令详解
查看>>