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;    }}
原创粉丝点击