手机网游

来源:互联网 发布:小户型沙发淘宝 编辑:程序博客网 时间:2024/04/28 21:17

(一)基于HTTP的手机网络游戏
    因为在所有的MIDP规范中规定:都必须支持HTTP协议,而据业内人士透露消息,中国电信在将来也只会支持HTTP,所以现在很多的手机网游都是架构在HTTP上的。但由于HTTP协议封装上的完整性,给它带来了好处,也带来了坏处。

    首先我们看HTTP协议的优点:
    1:servelt容器会自动管理线程池,在我们的程序里可以不必自己去管理线程了,当然,我说的线程是客户端发送请求的连接到服务器端产生的一个线程。
    2:HTTP是安全的,利用session来管理每个会话,省去了让人头疼的客户端冒充问题。
    3:几乎所有支持java的手机都支持HTTP协议。
    当然,还有其它优点,我不可能一一道来,自己去体会吧......

   其次就是HTTP协议的缺点:
   1:就是大家都比较头疼的HTTP协议的无连接性,曾经有人提过去修改HTTP协议,不知道成功了没?当然,这个不在我们讨论的范围之内。
   2:就是网络流量的问题,这个也是大家都比较头疼的问题。如果不是包月,对用户来说,这个费用确实是一大笔开支。

   下面我先讲解一下比较出名的手机网络游戏“fruite-machine”的客户端和服务器端的架构:

   Phone ---------------→Servlet--------------------→Web Browser

   上面的是“水果机”的整体的架构图。
   “水果机”曾一度流行于各个电玩厅内,做为一种赌博机的形式出现。这个游戏虽然设计的简单,但却很耐玩,勘称能和“俄罗斯方块”想媲美的一个经典游戏。

   在架构后面的web Browser一层,是用于管理用户的web界面,可以操作数据库,从而达到管理用户的目的。

   因为用户在登陆时会在手机上面输入“username”和password“,所以,安全性是个很大的问题。
   在fruite-machine里的设计文档里,是这么解决这个问题的:
   1:用端到端的加密连接HTTS来代替HTTP
  2:基于一个安全的无线网络上面用HTTP,经由一个安全的无线网关把username和password传送到servlet端。
   3:和servlet在同一个防火墙内传送username和password。
  
   在解决问户欺骗的问题上,因为一个用户可能把MIDlet客户端下载后修改源代码,从而可能传送假报文给servlet端,“水果机”里面把一些用户可能修改的数据在servlet端生成,然后传送给MIDlet,这样用户就无法修改了。比如MIDlet并不能生成随即旋转的结果,而是由服务器端生成的。

(二)
   有关通信的协议部分,其实就是客户端和服务器端约定一种规则来进行通信。因为客户端的请求和服务器端的回复内容都在HTTP的body里面,而这个body只不过是一个字节流,因此客户端和服务器端必须在理解这些字节流上保持一致。

   Fruite-machine里面是用↓来代表一行新的字符信息,如果新的字符信息里面还需要隔离的话,就利用/来进行隔离。

   所以整个发送的报文看起来就是这样的:login↓drap↓ secret

   做为例子,我们来看看玩家在选中一个pet后和服务器端的报文交互过程:
                  MIDelt---------------------servlet
  首先,MIDlet会发送旋转请求到servlet服务器端。这个请求的报文body中包含选择宠物的位置,以及宠物下面的标志(true或者false来表示)。
  然后,服务器端在接受到这个报文后,会处理。并根据处理的结果返回相应的报文。如果是赢了的话,服务器端会返回玩家赢的位置,以及盈后的积分,还有旋转后停的位置。如果失败的话,服务器端也会返回一个失败的报文给玩家。

  客户端的程序我就不说了,我来重点讲讲服务器端的程序。
  下面先看看整体的结构:
  当fruitemachineservlet接收到一个Request的请求的时候,首先分析这个请求是来自哪里:是手机终端的请求还是web管理页面的请求,并把请求交给相应的程序处理。Web页面的请求主要是一些更新数据库的操作。手机终端请求会先分析请求的类型:是登陆,还是游戏,还是其它的……并把它们交给相应的程序处理。如果是登陆的话,游戏处理程序会从数据库内取出用户的username和password,验证用户。并产生一个新的HTTPsession会话来管理这个连接。如果用户是退出的话,游戏逻辑就会销毁Httpsession。
首先我们来看看servlet程序:
import java.io.*;
import javax.servlet.*;
import javax.servlet.http.*;

public class FruitMachineServelt extends HttpServlet{
  private UserDatabase userDatabase;
  private AdminProtocolHandler adminProtocolHandler;
  private GameProtocolHandler gameProtocolHandler;

  public void init(ServletConfig config) throws ServletException{
super.init(config);
userDatabase = new UserDatabase();

userDatabase.createUser(“guest”,””);

adminProtocolHandler = new AdminProtocolHandler(userDatabase);
gameProtocolHandler = new GameProtocolHandler(userDatabase);
}

public void doGet(HttpServletRequest request,HttpServletResponse response)
throws IOException,ServletException{
try{
String pathInfo = request.getPathInfo();
If(pathInfo == null){
Reponse.sendError(HttpServletResponse.SC_BAD_REQUEST,”Missing path info”);
}else if(pathInfo.startsWith(“/admin”)){
adminProtocolHandler.doGet(request,response);
}else{
response.sendError(HttpServletResponse.SC_BAD_REQUEST,”Unexpected path info”);
}
}catch(IOException e){
e.printStackTrace();
throw e;
}catch(Exception e){
e.printStackTrace();
throw new ServletException(e.getMessage());
}
}

(三)
Fruite-machine严格上来说,只是一个排列高低分的游戏,并不是真正意义上的网络游戏,但是它也实现了网络游戏的一些简单的功能。

Fruite-machine虽然用了HTTP协议,却并不轮循服务器,而是通过callback的方式,向服务器端发送一个报文后,再接受一个报文处理。这样在真正的网络游戏中就容易产生问题,因为在真正的网络游戏中,服务器可能会主动的给一个玩家发送报文。而在fruite-machine中,服务器是无法给玩家主动的发送报文的,它只是在用户发送报文给服务器端时,再回调函数处理response,这样一应一答的报文传输方式。

严格意义上来说,在真正的网络游戏中采用HTTP协议只能采用轮循服务器的方式来解决服务器主动发送报文给某个客户端的问题。就是所谓的心跳报文。

下面我们来看看实现callback功能的程序:
import java.io.*;
import java.util.*;
import javax.microedition.io.*;


/**
 * This class accepts and queues POST requests for a particular URL, and
 * services them in first-in-first-out order. Using the queue allows it
 * to be thread-safe without forcing its clients ever to block.
 */
public class HttpPoster
    implements Runnable
{
    private String url;
    private volatile boolean aborting = false;
    private Vector requestQueue = new Vector();
    private Vector listenerQueue = new Vector();


    public HttpPoster(String url)
    {
        this.url = url;
        Thread thread = new Thread(this);
        thread.start();
    }


    public synchronized void sendRequest(String request,
                                         HttpPosterListener listener)
        throws IOException
    {
        requestQueue.addElement(request);
        listenerQueue.addElement(listener);
        notify();                    // wake up sending thread
    }


    public void run()
    {
    running:
        while (!aborting)
        {
            String request;
            HttpPosterListener listener;

            synchronized (this)
            {
                while (requestQueue.size() == 0)
                {
                    try
                    {
                        wait();     // releases lock
                    }
                    catch (InterruptedException e)
                    {
                    }

                    if (aborting)
                        break running;
                }

                request = (String)(requestQueue.elementAt(0));
                listener = (HttpPosterListener)(listenerQueue.elementAt(0));
                requestQueue.removeElementAt(0);
                listenerQueue.removeElementAt(0);
            }

            // sendRequest must have notified us
            doSend(request, listener);
        }
    }


    private void doSend(String request,
                        HttpPosterListener listener)
    {
        HttpConnection conn = null;
        InputStream in = null;
        OutputStream out = null;
        String responseStr = null;
        String errorStr = null;
        boolean wasError = false;

        try
        {
            conn = (HttpConnection)Connector.open(url);

            // Set the request method and headers
            conn.setRequestMethod(HttpConnection.POST);
            conn.setRequestProperty("Content-Length",Integer.toString(request.length()));

            // Getting the output stream may flush the headers
            out = conn.openOutputStream();
            int requestLength = request.length();
            for (int i = 0; i < requestLength; ++i)
            {
                out.write(request.charAt(i));
            }

            // Opening the InputStream will open the connection
            // and read the HTTP headers. They are stored until
            // requested.
            in = conn.openInputStream();

            // Get the length and process the data
            StringBuffer responseBuf;
            long length = conn.getLength();
            if (length > 0)
            {
                responseBuf = new StringBuffer((int)length);
            }
            else
            {
                responseBuf = new StringBuffer();  // default length
            }

            int ch;
            while ((ch = in.read()) != -1)
            {
                responseBuf.append((char)ch);
            }
            responseStr = responseBuf.toString();

            // support URL rewriting for session handling
            String rewrittenUrl = conn.getHeaderField("X-RewrittenURL");
            if (rewrittenUrl != null)
            {
                url = rewrittenUrl;    // use this new one in future
            }
        }
        catch (IOException e)
        {
            wasError = true;
            errorStr = e.getMessage();
        }
        catch (SecurityException e)
        {
            wasError = true;
            errorStr = e.getMessage();
        }
        finally
        {
            if (in != null)
            {
                try
                {
                    in.close();
                }
                catch (IOException e)
                {
                }
            }
            if (out != null)
            {
                try
                {
                    out.close();
                }
                catch (IOException e)
                {
                }
            }
            if (conn != null)
            {
                try
                {
                    conn.close();
                }
                catch (IOException e)
        listener.receiveHttpResponse(responseStr);
        {
                }
            }
        }

        if (wasError)
        {
            listener.handleHttpError(errorStr);
        }
        else
        {
            listener.receiveHttpResponse(responseStr);
        }
    }


    // This is just for tidying up - the instance is useless after it has
    // been called
    public void abort()
    {
        aborting = true;
        synchronized (this)
        {
            notify();    // wake up our posting thread and kill it
        }
    }
}
从HttpPoster类中我们可以看出来:HttpPoster类采用单独的一个线程,只要调用HttpPoster类的sendRequest(String request,HttpPosterListener listener)方法,线程就会运行,然后调用doSend(String request, HttpPosterListener listener)方法来把request传送到服务器端。

注意listener.receiveHttpResponse(responseStr);这句程序回调HttpPosterListener的receiveHttpResponse(String response)方法,这里也运用了多态性,listener会根据相应实现的类调用相应类的receiveHttpResponse(String reponse)方法来处理服务器返回的报文。

原创粉丝点击