您的位置:首页 > 编程语言 > Java开发

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

2016-10-22 00:00 381 查看
摘要: Netty提供异步的、事件驱动的网络应用程序框架和工具,用以快速开发高性能、高可靠性的网络服务器和客户端程序。也就是说,Netty 是一个基于NIO的客户,服务器端编程框架,使用Netty 可以确保你快速和简单的开发出一个网络应用,例如实现了某种协议的客户,服务端应用。Netty相当简化和流线化了网络应用的编程开发过程,例如,TCP和UDP的socket服务开发。

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配置文件
}
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  Netty socket Java EE
相关文章推荐