java游戏服引入jetty
心怀不惧,才能翱翔于天际 --赵云
不管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 源码分析