小门禁系统服务端(实现特定协议的服务端应用)开发

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<String, SocketChannel>());
	}

上面是通过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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值