Java游戏服务器编程

java游戏服引入jetty

2019-12-28  本文已影响0人  小圣996

心怀不惧,才能翱翔于天际 --赵云

不管java游戏服采用何种通信协议,几乎都要有处理http请求的需求。因为现在很多游戏服都会接入第三方平台的登录、查询、验证、执行命令等操作(如游戏Web后台需查询玩家数据,修改玩家数据,封号禁言发奖等),而这第三方的请求就几乎都为http请求,因而java游戏服需要有相应的http请求处理方案。

所幸游戏服的http请求往往都是轻量级的,毕竟第三方平台的操作既不频繁也不多。因此我们在选择web连接方案时,最好是能够直接 “嵌入” java游戏服的,而不需要再建立一个web工程。

早期的时候(在JDK1.6时),都是用JDK自带的HttpServer实现的,当然现在也还可以用它,也简单好用。它的使用流程是:
第1步,创建一个HttpServer,并绑定端口和设置最大连接数;
第2步,设置HttpContext服务器监听器上下文,设置匹配URL的公共路径(如/querry)和用来处理请求的HttpHandler;
第3步,调用start方法启动即可。关闭时调用相应stop(int delaySec)。

        HttpServerProvider provider = HttpServerProvider.provider();
        HttpServer httpserver = null;
        try{
            httpserver = provider.createHttpServer(new InetSocketAddress(6688), 10);// 设置端口及最大连接数
            httpserver.createContext("/querry", new HttpQuerryHandler());
            httpserver.setExecutor(Executors.newCachedThreadPool()); // 设置线程池
            httpserver.start();
        }
        catch (Exception e){
            log.error("can't start http service of querry ");
        }

注意,上述的HttpQuerryHandler需实现HttpHandler接口,继而实现其中的handle(HttpExchange var1)方法,如:

public class HttpQuerryHandler implements HttpHandler{
    @Override
    public void handle(HttpExchange httpExchange) throws IOException {
        String requestMethod = httpExchange.getRequestMethod();
        if (requestMethod.equalsIgnoreCase("GET")){
            URI requestedUri = httpExchange.getRequestURI();
            String param = requestedUri.getQuery(); //如192.168.1.5:6688/querry?user=xiaosheng996&psw=123
            System.out.println("param:" + param); //user=xiaosheng996&psw=123
            
            Headers responseHeaders = httpExchange.getResponseHeaders();
            responseHeaders.set("Content-Type", "text/plain");
            httpExchange.sendResponseHeaders(200, 0);
            OutputStream responseBody = httpExchange.getResponseBody();
            String result = "{\"ret\": \"ok\"}";
            responseBody.write(result.getBytes());
            responseBody.flush();
            responseBody.close();
        }
    }
}

这样也实现了处理Http请求。在大多数Http请求不多并发又不大的情况这种已完全足够了。

Jetty是现在用得比较多的一种轻量级Servlet容器,扩展性强而非常灵活,通过引入jetty库,便能“嵌入”在java工程中,非常方便。

Jetty和Tomcat都是使用广泛的Servlet引擎,相对而言Tomcat是重量级的,它在处理少数非常繁忙的连接上更有优势,也就是说连接生命周期如果短,Tomcat的总体性能更高。而Jetty在处理高并发且长时间连接请求的场景下显得更快速高效。

Jetty的使用流程和JDK的HttpServer差不多:
第1步,创建一个Jetty Server,并设置连接器;
第2步,设置web应用上下文WebAppContext,它既可以用传统的服务配置文件的方式设定,也可以用注解的方式设定;
第3步,调用start方法启动即可。关闭时调用相应stop()方法。

在Jetty里Context是包含了在某一特定URL或Virtual Host下的一组Handler的Handler。可以这样理解,Context本身也是一种Handler,它里面包含了许多的Handler,这些Handler都只能处理某个特定URL下的请求。Jetty里的Context有ContextHandler,ServletContext和WebAppContext。

配置文件方式如下:

    /**
     * 构造jetty web服务
     * @param descriptor 配置文件路径
     * @param resourceBase 根目录,如果需要隐藏用A,否则用.
     * @param maxThreads 连接线程数
     * @param ports 监听的端口
     * @throws Exception
     */
    public JettyServer(String descriptor, String resourceBase, int maxThreads, int port) throws Exception {
        this.port = port;
        this.server = new Server(new QueuedThreadPool(maxThreads));
        ServerConnector connector = new ServerConnector(server);
        connector.setPort(port);
        this.server.setConnectors(new ServerConnector[] { connector });
        WebAppContext context = new WebAppContext();
        context.setDescriptor(descriptor);
        context.setResourceBase(resourceBase);
        context.setClassLoader(Thread.currentThread().getContextClassLoader());
        context.setConfigurationDiscovered(true);
        context.setParentLoaderPriority(true);
        this.server.setHandler(context);
        this.server.start();
        System.out.println("started JettyServer:" + this.toString()+",config path:"+context.getResourceBase());
    }

配置文件:

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns="http://java.sun.com/xml/ns/javaee" xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
    xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
    id="WebApp" version="2.5">
    
    <servlet>
        <servlet-name>QuerryService</servlet-name>
        <servlet-class>servlet.QuerryService</servlet-class>
    </servlet>
    <servlet-mapping>
        <servlet-name>QuerryService</servlet-name>
        <url-pattern>/QuerryService</url-pattern>
    </servlet-mapping>
</web-app>

处理请求的servlet类,需实现HttpServlet:

public class QuerryService extends PubDefaultServlet {
    private static final long serialVersionUID = 3832841412181110307L;

    @Override
    protected void process(HttpServletRequest req, HttpServletResponse resp)
            throws Exception {
        System.out.println("enter");
    }
}

PubDefaultServlet.java

/**
 * servlet父类,覆盖get,post方法
 */
public abstract class PubDefaultServlet extends HttpServlet {
    private static final long serialVersionUID = 1L;
    protected Logger logger = Logger.getLogger(getClass());

    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        req.setCharacterEncoding("UTF-8");
        resp.setCharacterEncoding("UTF-8");
        resp.setContentType("text/html; charset=UTF-8");
        resp.setHeader("Content-Type", "text/html; charset=UTF-8");
        logger.info("service :" + req.getRequestURI() + "|" + req.getRemoteAddr() + "|" + req.getMethod() + "|"
                + req.getHeaderNames() + "|" + req.getParameterMap());
        super.service(req, resp);
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        try {
            process(req, resp);
        } catch (Exception e) {
            resp.getWriter().write(e.getMessage());
            logger.error("process :" + req.getRequestURI() + "|" + req.getRemoteAddr() + "|" + req.getMethod() + "|"
                    + req.getParameterMap(), e);
        }
    }

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        doPost(req, resp);
    }

    protected abstract void process(HttpServletRequest req, HttpServletResponse resp) throws Exception;
}

如果不走配置,也可以用如下注解的方式

    /**
     * 构造jetty web服务
     * 
     * @param serveltList servelt列表
     * @param maxThreads 线程池数量
     * @param ports 监听的端口
     * @throws Exception
     */
    public JettyServer(Set<Class<?>> servletClazzs, int maxThreads, int port) throws Exception {
        this.port = port;
        this.server = new Server(new QueuedThreadPool(maxThreads));
        ServerConnector connector = new ServerConnector(server);
        connector.setPort(port);
        this.server.setConnectors(new ServerConnector[] { connector });
        WebAppContext context = new WebAppContext();
        context.setConfigurationDiscovered(false);
        context.setBaseResource(Resource.newClassPathResource(""));
        for (Class<?> clazz : servletClazzs) {
            WebServletAnnotation webServlet = new WebServletAnnotation(context, clazz.getName(), null);
            webServlet.apply();
        }
        context.setClassLoader(Thread.currentThread().getContextClassLoader());
        this.server.setHandler(context);
        this.server.start();
        WEB_SERVERS.add(this);
    }

而它的处理请求的servlet实现为:

@HttpServlet
@WebServlet(urlPatterns = "HttpRequest", description = "http请求")
public class HttpRequestServlet extends PubDefaultServlet {

    private static final long serialVersionUID = 1L;

    @Override
    protected void process(HttpServletRequest req, HttpServletResponse resp) throws Exception {
        String[] username = req.getParameterValues("username");
        String[] password = req.getParameterValues("password");
        String[] content = req.getParameterValues("content");
        
        System.out.println("username:"+username[0]+"\npassword:"
                +password[0]+"\ncontent:"+content[0]+"\nthreadName:"+Thread.currentThread().getName());
        resp.getWriter().write("收到微信小程序的信息:["+username[0]+"|"+password[0]+"|"+content[0]+"]");
        //resp.getWriter().write("{\"result\":0,\"data\":\"成功\"}");
        resp.getWriter().flush();
    }
}

将所有的servlet放在一个包下,然后扫描这个包,获得所有的servlet,作为参数Set<Class<?>> servletClazzs传入JettyServer构造函数,即可实现java游戏服内置jetty,但这种方式在后台需要一个注解处理器才能起作用,所以还得针对上面的注解编写处理器WebServletAnnotation。
处理器的实现参照以下文章:
Servlet传统配置方式和Servlet3.0使用注解的方式

另外,欲了解Jetty的框架原理及源码可参考,个人觉得它和Netty框架类似:
Jetty 源码分析

上一篇下一篇

猜你喜欢

热点阅读