基于netty通讯的简单封装
只是一个简单的demo版本测试使用
本案例只包含服务端
netty版本:4.1
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-all</artifactId>
<version>4.1.20.Final</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.47</version>
</dependency>
数据格式 JSON UTF-8
#############################关键代码##########################
1.启动入口:
/**
* 启动server
*/
public static Boolean startServer() {
try {
if (b == null) {
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
try {
b = new Server().startServer(port);
} catch (Exception e) {
e.printStackTrace();
}
}
});
//设置为守护线程
thread.setDaemon(true);
thread.start();
return true;
}
} catch (Exception e) {
e.printStackTrace();
}
return false;
}
2.netty服务端主类:
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.codec.DelimiterBasedFrameDecoder;
import io.netty.handler.codec.Delimiters;
import io.netty.handler.codec.string.StringDecoder;
import io.netty.handler.codec.string.StringEncoder;
import java.nio.charset.Charset;
public class Server {
private static EventLoopGroup boss;
private static EventLoopGroup worker;
private static ServerBootstrap bootstrap;
/**
* netty服务端ServerBootstrap();
* */
public ServerBootstrap startServer(Integer port) throws InterruptedException {
try {
//多线程模式处理
bootstrap = new ServerBootstrap();
boss = new NioEventLoopGroup(4); //可以根据机器核心*2设置
worker = new NioEventLoopGroup(8);
bootstrap.group(boss,worker);
bootstrap.channel(NioServerSocketChannel.class);
bootstrap.childHandler(new NettyServerFilter()); //设置过滤器
// 服务器绑定端口监听
ChannelFuture f = bootstrap.bind(port).sync();
System.out.println("服务端启动成功...");
// 监听服务器关闭监听
f.channel().closeFuture().sync();
} finally {
boss.shutdownGracefully(); 关闭EventLoopGroup,释放掉所有资源包括创建的线程
}
return bootstrap;
}
}
class NettyServerHandler extends SimpleChannelInboundHandler<String> {
/*
* 收到消息时,返回信息
*/
@Override
protected void channelRead0(ChannelHandlerContext ctx, String msg)
throws Exception {
// 收到消息直接打印输出
System.out.println("服务端接受的消息 : " + msg + System.currentTimeMillis());
ServerControll.handData(ctx,msg);
}
/*
* 建立连接时,返回消息
*/
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
System.out.println("连接的客户端地址:" + ctx.channel().remoteAddress());
ServerDataPool.setConnectionList(ctx);
ctx.writeAndFlush("{\"data\": {\"inked\": \"ok\"},\"code\": \"000\",\"msg\": \"登录成功\",\"tsp\": 152695656465}\n");
super.channelActive(ctx);
}
}
class NettyServerFilter extends ChannelInitializer<SocketChannel> {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ChannelPipeline ph = ch.pipeline();
// 以("\n")为结尾分割的 解码器
ph.addLast("framer", new DelimiterBasedFrameDecoder(8192, Delimiters.lineDelimiter()));
// 解码和编码,应和客户端一致
ph.addLast("decoder", new StringDecoder(Charset.forName("UTF-8")));
ph.addLast("encoder", new StringEncoder(Charset.forName("UTF-8")));
ph.addLast("handler", new NettyServerHandler());// 服务端业务逻辑
}
}
3.服务总控 + 链接池
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.jfinal.weixin.sdk.utils.JsonUtils;
import io.netty.channel.ChannelHandlerContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class ServerControll {
private static final Logger LOGGER = LoggerFactory.getLogger(ServerControll.class);
/**
* 接收到的消息处理
*/
public static void handData(ChannelHandlerContext ctx, String msg) throws Exception {
try {
jsonObject.toJavaObject(BizCodeEnum.getClassByCode(jsonObject.get("code").toString())).analysis(ctx);
} catch (Exception e) {
e.printStackTrace();
//返回服务端错误信息
ctx.writeAndFlush("{\"data\": {\"eqpNo\": \"\"},\"code\": \"999\",\"msg\": \"数据解析错误\",\"tsp\": 152695656465}\n");
}
}
/***
* 发送处理之后需要返回的信息(登录时调用)
* */
public static void sendData(BaseTransfer data, ChannelHandlerContext ctx) throws Exception {
String returnStr = JSON.toJSONString(data);
if (data.getEqpNo() != null) {
ServerDataPool.getCtxByEqpNo(data.getEqpNo()).writeAndFlush(returnStr + "\n");
} else {
ctx.writeAndFlush(returnStr + "\n");
}
LOGGER.info(Thread.currentThread().getName() + "返回信息:【" + returnStr + "】");
}
/***
* 发送处理之后需要返回的信息
* */
public static void sendData(BaseTransfer data) throws Exception {
String returnStr = JsonUtils.toJson(data);
ServerDataPool.getCtxByEqpNo(data.getEqpNo()).writeAndFlush(returnStr + "\n");
LOGGER.info(Thread.currentThread().getName() + "返回信息:【" + returnStr + "】");
}
/**
* 清理无用链接
**/
private static void cleanUselessConnection() {}
/**
* 断开链接
*/
private static Integer breakConnection() {return null;}
/**
* 断开所有链接
*/
public static Boolean breakAllConnection() {return false;}
}
import io.netty.channel.ChannelHandlerContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class ServerDataPool {
private static final Logger LOGGER = LoggerFactory.getLogger(ServerDataPool.class);
/**
* 存放已经登录的connection
*/
private static final Map<String, CtxPackage> ACTIVE_POOL = new HashMap<>();
/**
* 存放所有链接到服务器的connection
*/
private static final List<CtxPackage> CONNAETION_LIST = new ArrayList<>();
/**
* 根据eqpNo获取cx
*/
public static ChannelHandlerContext getCtxByEqpNo(String eqpNo) {
CtxPackage ctxPackage = ACTIVE_POOL.get(eqpNo);
return ctxPackage.updataActiveTimeStamp().getCtx();
}
/**
* 根据eqpNo获取cxpackage
*/
public static CtxPackage getCtxPackageByEqpNo(String eqpNo) {
CtxPackage ctxPackage = ACTIVE_POOL.get(eqpNo);
return ctxPackage;
}
/**
* 清除无用的链接
*/
public static int removeUnuseCtx(Long intervalTimes) {
Long now = System.currentTimeMillis();
int count = 0;
for (CtxPackage ctxPackage : CONNAETION_LIST) {
if (!ctxPackage.getLogIn() && (ctxPackage.getLastActiveTimeStamp() + now) < now) {//未登录并且链接维持时间过长的的关闭
try {
ctxPackage.getCtx().close();
CONNAETION_LIST.remove(ctxPackage);
LOGGER.info("【删除过期链接】:"+ CONNAETION_LIST.add(ctxPackage));
count++;
} catch (Exception e) {
LOGGER.error("【关闭连接:%s , 异常!】", ctxPackage.getCtx().channel().id().asLongText());
}
}
}
return count;
}
/**
* 向CONNAETION_LIST里面添加
*/
public static Boolean setConnectionList(ChannelHandlerContext ctx) {
CtxPackage ctxPackage = new CtxPackage();
ctxPackage.setCtx(ctx);
ctxPackage.setLastActiveTimeStamp(System.currentTimeMillis());
ctxPackage.setLogIn(false);
LOGGER.info("【创建新链接】:"+ CONNAETION_LIST.add(ctxPackage));
removeUnuseCtx(60*15L);
return true;
}
/**
* 向ACTIVE_POOL里面添加对应的ctxPackage
*/
public static Boolean setActivePool(String eqpNo, ChannelHandlerContext ctx) {
CtxPackage ctxPackage = new CtxPackage();
ctxPackage.setCtx(ctx);
ctxPackage.setLastActiveTimeStamp(System.currentTimeMillis());
ctxPackage.setLogIn(true);
ctxPackage.setEqpNo(eqpNo);
ACTIVE_POOL.put(eqpNo, ctxPackage);
return true;
}
}
4.数据包
import io.netty.channel.ChannelHandlerContext;
public class CtxPackage {
/**
* 机识别码
* */
private String eqpNo;
/**
* 游戏过程用户识别码
* */
private String token;
/**
* 最近一次收到客户端消息时间
* */
private Long lastActiveTimeStamp;
/**
* 是否登录
* */
private Boolean isLogIn;
/**
* netty通道
* */
private ChannelHandlerContext ctx;
public String getEqpNo() {
return eqpNo;
}
public void setEqpNo(String eqpNo) {
this.eqpNo = eqpNo;
}
public String getToken() {
return token;
}
public void setToken(String token) {
this.token = token;
}
public Long getLastActiveTimeStamp() {
return lastActiveTimeStamp;
}
public void setLastActiveTimeStamp(Long lastActiveTimeStamp) {
this.lastActiveTimeStamp = lastActiveTimeStamp;
}
public Boolean getLogIn() {
return isLogIn;
}
public void setLogIn(Boolean logIn) {
isLogIn = logIn;
}
public ChannelHandlerContext getCtx() {
return ctx;
}
public void setCtx(ChannelHandlerContext ctx) {
this.ctx = ctx;
}
public CtxPackage updataActiveTimeStamp(){
this.setLastActiveTimeStamp(System.currentTimeMillis());
return this;
}
}
5.具体识别码
public enum BizCodeEnum {
/**
*正常业务代码
* */
BIZ_SUCCESS("000","成功",null),
BIZ_TO_LOGIN("101","登录", LoginDataTs.class),
BIZ_SEND_TIP("102","心跳包", TipDataTs.class),
BIZ_SEND_TIP_BACK("103","在线",null),
//
BIZ_START_GAME("201","启动游戏",null),
// BIZ_START_OVER("202","游戏已启动"),
BIZ_GAME_DATA("203","游戏数据上报",GameDataTs.class),
ERROR_USERNAME("901","机器用户名不存在",null),
ERROR_PASSWORD("902","机器登录密码错误",null),
ERROR_CONNECTION("903","机器用户名不存在",null),
ERROR_START("904","游戏启动失败",null),
ERROR_DATA("905","游戏数据异常",null),
ERROR_DATA_FORMAT("906","游戏数据不完整",null),
ERROR_STATISTIC("907","游戏统计数据失败",null),
ERROR_EQP("908","客户端暂不可用",null),
ERROR_SERVER("909","服务器异常",null),
ERROR_CODE("910","CODE无效",null);
private String code;
private String msg;
private Class<BaseTransfer> clazz;
BizCodeEnum(String code,String msg,Class clazz){
this.code = code;
this.msg = msg;
this.clazz =clazz;
}
public String getCode() {
return code;
}
public void setCode(String code) {
this.code = code;
}
public String getMsg() {
return msg;
}
public void setMsg(String msg) {
this.msg = msg;
}
public Class getClazz() {
return clazz;
}
public void setClazz(Class clazz) {
this.clazz = clazz;
}
}
6.数据包具体处理父类
package com.skyfly.up.server.dataModle;
import com.skyfly.up.server.BizCodeEnum;
import com.skyfly.up.server.ServerControll;
import io.netty.channel.ChannelHandlerContext;
import java.io.Serializable;
public abstract class BaseTransfer implements Serializable{
/**
* 业务代码
* */
private String code;
/**
* 时间
* */
private Long tsp = System.currentTimeMillis();
/**
* 返回的消息
* */
private String msg;
/**
*token
* 用户登录之后才会有
* */
private String token;
/**
* 识别码
* */
private String eqpNo;
public BaseTransfer(String code,String msg){
this.code = code;
this.msg = msg;
}
public BaseTransfer(){}
public abstract void analysis(ChannelHandlerContext ctx) throws Exception;
/**
* @param baseTransfer
* @param eqpNo
* @param ctx
* @throws Exception
*/
public void finalHandle(BaseTransfer baseTransfer,String eqpNo,ChannelHandlerContext ctx) throws Exception{
if(baseTransfer == null){
baseTransfer.setCode(BizCodeEnum.BIZ_SUCCESS.getCode());
baseTransfer.setMsg(BizCodeEnum.BIZ_SUCCESS.getMsg());
}
baseTransfer.setEqpNo(eqpNo);
ServerControll.sendData(baseTransfer,ctx);
}
public void finalHandle(BaseTransfer baseTransfer,String eqpNo) throws Exception{
if(baseTransfer == null){
baseTransfer.setCode(BizCodeEnum.BIZ_SUCCESS.getCode());
baseTransfer.setMsg(BizCodeEnum.BIZ_SUCCESS.getMsg());
}
baseTransfer.setEqpNo(eqpNo);
ServerControll.sendData(baseTransfer);
}
public String getCode() {
return code;
}
public void setCode(String code) {
this.code = code;
}
public Long getTsp() {
return tsp;
}
public void setTsp(Long tsp) {
this.tsp = tsp;
}
public String getMsg() {
return msg;
}
public void setMsg(String msg) {
this.msg = msg;
}
public String getToken() {
return token;
}
public void setToken(String token) {
this.token = token;
}
public String getEqpNo() {
return eqpNo;
}
public void setEqpNo(String eqpNo) {
this.eqpNo = eqpNo;
}
}
7.具体实现
import io.netty.channel.ChannelHandlerContext;
public class LoginDataTs extends BaseTransfer {
private LoginDataRe data;
public LoginDataRe getData() {
return data;
}
public void setData(LoginDataRe data) {
this.data = data;
}
/**
* 具体的处理方法
* 最后调用finalHandle(BaseTransfer baseTransfer)返回对应的数据
*/
@Override
public void analysis(ChannelHandlerContext ctx) throws Exception{
/**
*1.验证用户名密码
*2.不正确直接返回
*3.登录后保存 返回信息 带上eqpNo
* */
BizEqp bizEqp = BizEqp.dao.getEqpByName(data.getEqpName());
if (bizEqp == null) {
//返回账号错误
BaseTransfer baseTransfer = new NormalResult(BizCodeEnum.ERROR_USERNAME.getCode(),BizCodeEnum.ERROR_USERNAME.getMsg());
finalHandle(baseTransfer,getEqpNo(),ctx);
return;
}
String passMD5 = MD5Util.getMD5(data.getPassword());
if (!passMD5.equals(bizEqp.getPassword())) {
BaseTransfer baseTransfer = new NormalResult(BizCodeEnum.ERROR_PASSWORD.getCode(),BizCodeEnum.ERROR_PASSWORD.getMsg());
finalHandle(baseTransfer,getEqpNo(),ctx);
return;
}
/**
* 1.保存登录信息
* 2.返回数据
* */
this.setEqpNo(bizEqp.getEqpNo());
ServerDataPool.setActivePool(getEqpNo(),ctx);
BaseTransfer baseTransfer = new LoginResultData(BizCodeEnum.BIZ_SUCCESS.getCode(),BizCodeEnum.BIZ_SUCCESS.getMsg(),getEqpNo());
finalHandle(baseTransfer, getEqpNo());
}
}
public class LoginDataRe{
private String eqpName;
private String password;
public String getEqpName() {
return eqpName;
}
public void setEqpName(String eqpName) {
this.eqpName = eqpName;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
}
以上只是个人写的demo测试