Netty搭建服务器
来源:互联网 发布:mac文档怎么转换格式 编辑:程序博客网 时间:2024/06/09 23:54
Netty搭建服务器
协议格式
服务器和客户端通信,要基于一定的格式,这个格式成为通信协议。我们的通信协议是这样的:
下面的内容按顺序排列:
1.一个字节:0xFF ,是一个开始标志
2.四个字节:消息长度,Netty需要这个字段处理TCP粘包拆包问题
3.四个字节:msgId,服务端根据这个字节分发请求
4.一个字节:type,表示消息的类型,1代表JSON格式
5.一个字节:flag,表示消息处理是否成功(1表示成功,0表示失败)
6.其余字节:消息内容
服务器架构设计
大家都知道,Netty是一个基于NIO的并发框架。使用Netty可以搭建高负载的服务器,通过有限的线程去处理大量的连接。
我的目标是,基于Netty,并将业务逻辑从Netty分离出去。类似Web开发框架,如SpringMVC,其核心思想是定义一个核心控制器,即DispatcherServlet,处理所有的请求,然后将请求分发到多个Controller的多个业务方法中去。那么我们的服务器,也会定义一个Netty的ChannelInboundHandler,获得请求消息后,根据业务参数将请求分发出去。
Web服务器是基于Http协议,而Http协议是基于url去分发请求的,而SpringMVC也正是这样做的。然而,当我们自定义协议格式时,不需要使用url这么复杂的格式。现在我会设定,我们分发请求基于一个int型参数,该参数名为msgId。
同样的,我会基于注解去加载我们的业务处理器,但是实现方式和SpringMVC会有所不同。
Java代码
为了表达出架构的设计,我不会贴出所有代码,只贴出核心部分,后期可能补上。同时代码也还没完善,是模仿项目代码编写,写这篇博文是对使用Netty搭建服务器的一个总结。
Server.java:
package org.lin.nettys;import org.lin.nettys.coder.MessageDecoder;import org.lin.nettys.coder.MessageEncoder;import org.lin.nettys.handler.MessageHandler;import org.lin.nettys.service.HandlerMgr;import io.netty.bootstrap.ServerBootstrap;import io.netty.channel.Channel;import io.netty.channel.ChannelInitializer;import io.netty.channel.ChannelPipeline;import io.netty.channel.nio.NioEventLoopGroup;import io.netty.channel.socket.nio.NioServerSocketChannel;/** * 使用Netty自己搭一个服务器 * @author linjingfu * */public class Server { private int port; private String ip; public Server(String ip, int port) { this.ip = ip; this.port = port; } //启动服务器 public void start() { /**业务初始化*/ //装载Handler实例 HandlerMgr.getInstance().init("org.lin.nettys"); /**启动netty*/ int processors = Runtime.getRuntime().availableProcessors(); ServerBootstrap bootstrap = new ServerBootstrap(); bootstrap.group(new NioEventLoopGroup(), new NioEventLoopGroup(processors)); //用一个线程接收连接,用多个线程处理异步IO bootstrap.channel(NioServerSocketChannel.class); bootstrap.childHandler(new ChannelInitializer<Channel>() { @Override protected void initChannel(Channel ch) throws Exception { //设置ChannelPipeLine,netty处理消息走的就是ChannelHandler里的逻辑 ChannelPipeline pipeline = ch.pipeline(); //编码器 pipeline.addLast(new MessageEncoder()); //解码器 (继承Netty的LengthFieldBasedFrameDecoder,处理TCP粘包拆包问题) pipeline.addLast(new MessageDecoder(1024 * 10, 1, 4)); //消息处理器,相当于SpringMVC的DispatcherServlet pipeline.addLast(new MessageHandler()); } }); try { bootstrap.bind(ip, port).sync(); } catch (InterruptedException e) { e.printStackTrace(); System.out.println("服务器启动失败..."); return; } System.out.println("服务器启动成功..."); } public static void main(String[] args) { new Server("localhost", 80).start(); }}
编解码器:
package org.lin.nettys.coder;import java.util.Map;import org.lin.nettys.message.Message;import org.lin.nettys.util.JsonUtil;import org.slf4j.Logger;import org.slf4j.LoggerFactory;import com.alibaba.fastjson.TypeReference;import io.netty.buffer.ByteBuf;import io.netty.channel.ChannelHandlerContext;import io.netty.handler.codec.LengthFieldBasedFrameDecoder;import io.netty.util.CharsetUtil;/** * 协议格式: * @author linjingfu * */public class MessageDecoder extends LengthFieldBasedFrameDecoder{ private static final Logger log = LoggerFactory.getLogger(MessageDecoder.class); public MessageDecoder(int maxFrameLength, int lengthFieldOffset, int lengthFieldLength) { super(maxFrameLength, lengthFieldOffset, lengthFieldLength); } @Override protected Object decode(ChannelHandlerContext ctx, ByteBuf in) throws Exception { //已经处理了TCP的粘包拆包问题,我们需要把字节流解码为业务对象Message ByteBuf buf = (ByteBuf)super.decode(ctx, in); if (buf == null) { return null; } //协议头部是一个开始的标志OxFF byte startFlag = buf.readByte(); if (startFlag != 0xFF) { return null; } //长度 @SuppressWarnings("unused") int length = buf.readInt(); //这个在父类中用 //获取Message对象 int msgId = buf.readInt(); byte type = buf.readByte(); byte flag = buf.readByte(); byte[] values = new byte[buf.readableBytes()]; buf.readBytes(values); Message message = new Message(); message.setFlag(flag); message.setType(type); message.setMsgId(msgId); if (type == Message.JSON) { String json = new String(values, CharsetUtil.UTF_8); try { Map<String, Object> params = JsonUtil.toJava(json, new TypeReference<Map<String, Object>>(){}); if (params != null) { message.setParams(params); } } catch (Exception e) { log.error(e.getMessage()); } } return message; }}
package org.lin.nettys.coder;import org.lin.nettys.message.Message;import org.lin.nettys.util.JsonUtil;import io.netty.buffer.ByteBuf;import io.netty.channel.ChannelHandlerContext;import io.netty.handler.codec.MessageToByteEncoder;import io.netty.util.CharsetUtil;/** * 协议格式: * startFlag 0xFF * length 四个字节 * msgId * type * flag * content * @author linjingfu * */public class MessageEncoder extends MessageToByteEncoder<Message>{ @Override protected void encode(ChannelHandlerContext ctx, Message msg, ByteBuf out) throws Exception { int length = msg.getContent().length + 11; out.writeByte(0xFF); out.writeInt(length); out.writeInt(msg.getMsgId()); out.writeByte(msg.getType()); out.writeByte(msg.getFlag()); out.writeBytes(msg.getContent()); ctx.flush(); }}
处理请求分发的核心控制器:
package org.lin.nettys.handler;import java.util.Map;import org.lin.nettys.constant.MsgConst;import org.lin.nettys.data.UserMgr;import org.lin.nettys.executor.ExecutorMgr;import org.lin.nettys.logic.UserLogic;import org.lin.nettys.message.Message;import org.lin.nettys.packet.Packet;import org.lin.nettys.packet.PacketHandler;import org.lin.nettys.service.HandlerMgr;import org.lin.nettys.session.SessionData;import org.lin.nettys.session.SessionMgr;import io.netty.channel.ChannelHandlerContext;import io.netty.channel.SimpleChannelInboundHandler;/** * 整个服务器只需要一个ChannelHandler,目标是将业务逻辑从Netty分离出去 * @author linjingfu * */public class MessageHandler extends SimpleChannelInboundHandler<Message> { @Override protected void channelRead0(ChannelHandlerContext ctx, Message msg) throws Exception { //处理登录 if (!login(ctx, msg)) { return; } SessionData sessionData = SessionMgr.getInstance().getByCtx(ctx); if (sessionData == null) { return; } //登录已成功,获取相应的PacketHandler,进行请求分发 Packet packet = new Packet(); packet.setCtx(ctx); packet.setMsg(msg); PacketHandler handler = HandlerMgr.getInstance().getHandler(msg.getMsgId()); if (handler == null) { System.out.println("请求的PacketHander为null"); return; } packet.setHandler(handler); //这些业务消息放到线程池中执行 ExecutorMgr.MSG_EXECUTOR.addPacket(packet); } @Override public void channelInactive(ChannelHandlerContext ctx) throws Exception { super.channelInactive(ctx); } private boolean login(ChannelHandlerContext ctx, Message msg) { try { if (msg.getMsgId() != MsgConst.LOGIN) { return true; } //登录要实时处理,所以不能做成Handler Map<String, Object> params = msg.getParams(); int id = Integer.parseInt(params.get("id").toString()); boolean result = UserLogic.login(id); if (!result) { return result; } //保存登录用户的信息,以及发消息的连接 SessionData session = new SessionData(); session.setCtx(ctx); session.setUser(UserMgr.getInstance().getUser(id)); SessionMgr.getInstance().addSession(session); //返回flag=1,即代表处理成功的消息给客户端 Message m = new Message(); m.setFlag(Message.SUCCESS); m.setMsgId(MsgConst.LOGIN); m.setType(Message.JSON); ctx.writeAndFlush(m); return result; } catch (NumberFormatException e) { e.printStackTrace(); return false; } }}
请求分发了,那我们的业务逻辑到底写在哪里呢?答案如下:
package org.lin.nettys.service;import java.util.HashMap;import java.util.Map;import org.lin.nettys.annotation.Handler;import org.lin.nettys.annotation.Service;import org.lin.nettys.constant.MsgConst;import org.lin.nettys.packet.Packet;import org.lin.nettys.packet.PacketHandler;@Servicepublic class TestService { @Handler(MsgConst.TEST) private PacketHandler test = new PacketHandler() { @Override public void handler(Packet packet) { System.out.println("正在处理消息..."); Map<String, Object> sendMsg = new HashMap<>(); sendMsg.put("msg", "消息处理成功,返回..."); packet.replyJson(sendMsg); } };}
可以看到两个注解:
package org.lin.nettys.annotation;import java.lang.annotation.ElementType;import java.lang.annotation.Retention;import java.lang.annotation.RetentionPolicy;import java.lang.annotation.Target;@Target(ElementType.TYPE)@Retention(RetentionPolicy.RUNTIME)public @interface Service {}
package org.lin.nettys.annotation;import java.lang.annotation.ElementType;import java.lang.annotation.Retention;import java.lang.annotation.RetentionPolicy;import java.lang.annotation.Target;@Target(ElementType.FIELD)@Retention(RetentionPolicy.RUNTIME)public @interface Handler { //msgId public int value();}
@Service标注在类上,表明这是个业务处理类,方便扫描
@Handler标注在数据域上,其value属性即为msgId,该数据域必须为PacketHandler接口的实例,最后就是通过PacketHandler实例处理请求消息的
package org.lin.nettys.packet;public interface PacketHandler { public void handler(Packet packet);}
package org.lin.nettys.packet;import java.util.Map;import org.lin.nettys.message.Message;import org.lin.nettys.util.JsonUtil;import io.netty.channel.ChannelHandlerContext;import io.netty.util.CharsetUtil;public class Packet implements Runnable{ private ChannelHandlerContext ctx; private Message msg; private PacketHandler handler; public void replyJson(Map<String, Object> sendMsg) { Message msg = new Message(); String json = JsonUtil.toJson(sendMsg); msg.setMsgId(this.msg.getMsgId()); msg.setFlag(Message.SUCCESS); msg.setType(Message.JSON); msg.setContent(json.getBytes(CharsetUtil.UTF_8)); ctx.writeAndFlush(msg); //netty的编码器会帮我们将消息解析成协议格式 } public ChannelHandlerContext getCtx() { return ctx; } public void setCtx(ChannelHandlerContext ctx) { this.ctx = ctx; } public Message getMsg() { return msg; } public void setMsg(Message msg) { this.msg = msg; } public PacketHandler getHandler() { return handler; } public void setHandler(PacketHandler handler) { this.handler = handler; } @Override public void run() { handler.handler(this); }}
至于Packet对象为何存在,解释起来太麻烦就不解释了,总而言之它会作为参数传给PacketHandler来处理请求消息。
最后就是扫描@Service和@PacketHandler,装载PacketHandler的过程了,这个过程应该很多框架都会有的了,只能说注解真是个好东西!
package org.lin.nettys.service;import java.lang.reflect.Field;import java.util.HashMap;import java.util.Map;import java.util.Set;import org.lin.nettys.annotation.Handler;import org.lin.nettys.annotation.Service;import org.lin.nettys.packet.PacketHandler;import org.lin.nettys.util.ClassUtil;import org.slf4j.Logger;import org.slf4j.LoggerFactory;public class HandlerMgr { private static final Logger log = LoggerFactory.getLogger(HandlerMgr.class); private static HandlerMgr instance = new HandlerMgr(); private Map<Integer, PacketHandler> handlerMap = new HashMap<>(); public static HandlerMgr getInstance() { return instance; } public PacketHandler getHandler(int msgId) { return handlerMap.get(msgId); } public void init(String basePackage) { Set<Class<?>> classes = ClassUtil.getAllAnnoTopClasses(basePackage, Service.class); for (Class<?> clazz : classes) { try { Object instance = clazz.newInstance(); Field[] fields = clazz.getDeclaredFields(); for (Field field :fields) { if (field.isAnnotationPresent(Handler.class)) { field.setAccessible(true); Handler handler = field.getAnnotation(Handler.class); int msgId = handler.value(); PacketHandler packetHandler = (PacketHandler)field.get(instance); handlerMap.put(msgId, packetHandler); log.debug("msgId:" + msgId + ", PacketHandler:" + packetHandler.getClass()); } } } catch (InstantiationException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } } }}
package org.lin.nettys.util;import java.io.File;import java.lang.annotation.Annotation;import java.util.HashSet;import java.util.Set;public class ClassUtil { /** * 扫描某包下所有的子类 * @param basePackage * @param clazz * @return */ @SuppressWarnings("unchecked") public static <T> Set<Class<? extends T>> getAllSubClasses(String basePackage, Class<T> clazz) { String filePath = basePackage.replace('.', '/'); filePath = ClassUtil.class.getResource("/" + filePath).getPath(); File file = new File(filePath); File[] files = file.listFiles(); Set<Class<?>> classSet = new HashSet<>(); getClasses(basePackage, files, classSet); Set<Class<? extends T>> returnSet = new HashSet<>(); for (Class<?> clz : classSet) { if (clz.isAssignableFrom(clazz)) { returnSet.add((Class<? extends T>)clz); } } return returnSet; } /** * 扫描某包下所有被注解标示的类 * @param basePackage * @param clazz * @return */ public static Set<Class<?>> getAllAnnoTopClasses(String basePackage, Class<? extends Annotation> clazz) { String filePath = basePackage.replace('.', '/'); filePath = ClassUtil.class.getResource("/" + filePath).getPath(); File file = new File(filePath); File[] files = file.listFiles(); Set<Class<?>> classSet = new HashSet<>(); getClasses(basePackage, files, classSet); Set<Class<?>> returnSet = new HashSet<>(); for (Class<?> clz : classSet) { if (clz.isAnnotationPresent(clazz)) { returnSet.add(clz); } } return returnSet; } private static void getClasses(String basePackage, File[] files, Set<Class<?>> classSet) { if (files == null) { return; } for (File file : files) { if (file.isFile() && file.getName().endsWith(".class")) { try { classSet.add(Class.forName(basePackage + "." + file.getName().substring(0, file.getName().length()-6))); } catch (ClassNotFoundException e) { e.printStackTrace(); } } else if (file.isDirectory()) { String pkg = basePackage + "." + file.getName(); getClasses(pkg, file.listFiles(), classSet); } } }}
最后的最后,看一下特别编写的一个线程队列,它是为了保证对于同一个连接,使用同一个线程去处理请求消息。为什么要这样呢?想象一下,客户端连续发送多个请求,如果服务端使用多个线程处理该客户端的请求,将增大出现线程安全问题的概率。
package org.lin.nettys.executor;import java.util.ArrayList;import java.util.Collections;import java.util.List;import java.util.Map;import java.util.concurrent.ConcurrentHashMap;import java.util.concurrent.ExecutorService;import java.util.concurrent.Executors;import java.util.concurrent.ThreadFactory;import java.util.concurrent.atomic.AtomicInteger;import org.lin.nettys.packet.Packet;import io.netty.channel.ChannelHandlerContext;/** * 之所以有这个,是为了确保同一个连接,用同一个线程去执行业务逻辑,减少出现线程安全问题的情况 * @author linjingfu * */public class ExecutorMgr { //创建有限个实例 private static final int PROCESS_NUM = Runtime.getRuntime().availableProcessors(); public static final ExecutorMgr MSG_EXECUTOR = new ExecutorMgr("MSG_EXECUTOR", PROCESS_NUM); private int threadNum; private String name; private List<ExecutorService> threadPool = Collections.synchronizedList(new ArrayList<>()); private Map<ChannelHandlerContext, ExecutorService> threadMap = new ConcurrentHashMap<>(); private AtomicInteger index = new AtomicInteger(0); private ExecutorMgr(String name, int threadNum) { this.name = name; this.threadNum = threadNum; for (int i = 0; i < threadNum; i++) { ExecutorService executor = Executors.newSingleThreadExecutor(new MyThreadFactory(name, i)); threadPool.add(executor); } } public void addPacket(Packet packet) { ChannelHandlerContext ctx = packet.getCtx(); ExecutorService executor = threadMap.get(ctx); if (executor == null) { executor = threadPool.get(index.incrementAndGet() % threadPool.size()); threadMap.put(ctx, executor); } executor.execute(packet); } private class MyThreadFactory implements ThreadFactory { private String name; private int index; public MyThreadFactory(String name, int index) { this.name = name; this.index = index; } @Override public Thread newThread(Runnable r) { Thread t = new Thread(); t.setDaemon(true); t.setName(name + "" + index); return t; } } public int getThreadNum() { return threadNum; } public void setThreadNum(int threadNum) { this.threadNum = threadNum; } public String getName() { return name; } public void setName(String name) { this.name = name; }}
- netty服务器搭建-http
- spring+netty服务器搭建
- Netty搭建服务器
- Java-->使用netty搭建Http服务器
- 使用Netty搭建APP推送服务器
- netty游戏服务器搭建之服务端
- netty游戏服务器搭建之客户端
- 基于netty的rts游戏服务器搭建
- 《从零开始搭建游戏服务器》Netty导入创建一个Socket服务器
- 《从零开始搭建游戏服务器》Netty导入创建一个Socket服务器
- netty搭建
- Netty搭建手记1
- Netty服务器部署
- spring+netty 服务器
- Netty写一个时间服务器
- Netty服务器入门
- netty 配置启动服务器
- netty实现http服务器
- excel打开多出现一个sheet1窗口
- 【Atcoder Regular Contest 085F】 NRE
- java使用fileupload接收上传文件
- 【网络】绘制基本网络图
- Eclipse与GitHub的整合(二)——Eclipse clone远程项目到本地
- Netty搭建服务器
- phpqrcode生成二维码
- 基于redis锁的实现
- HDU 1874 畅通工程续
- Big-O Algorithm Complexity Cheat Sheet [译]
- 几种任务调度的 Java 实现方法与比较
- 模拟post登陆
- jsp笔记
- Servlet