• java_web学习(8)会话与状态管


    HTTP简介

          WEB浏览器与WEB服务器之间的一问一答的交互过程必须遵循一定的规则,这个规则就是HTTP协议。HTTP是hypertext transfer protocol(超文本传输协议)的简写,它是TCP/IP协议集中的一个应用层协议,用于定义WEB浏览器与WEB服务器之间交换数据的过程以及数据本身的格式。
          HTTP协议的版本:  HTTP/1.0、HTTP/1.1、HTTP-NG
          HTTP 的会话方式                                                                        浏览器访问多图网页的过程
        
           浏览器与WEB服务器的连接过程是短暂的,每次连接只处理一个请求和响应。对每一个页面的访问,浏览器与WEB服务器都要建立一次单独的连接。浏览器到WEB服务器之间的所有通讯都是完全独立分开的请求和响应对。
          HTTP请求消息与响应消息
     
          请求消息的结构:一个请求行、若干消息头、以及实体内容,其中的一些消息头和实体内容都是可选的,消息头和实体内容之间要用空行隔开。
                                一个使用GET方式的请求消息中不能包含实体内容,只有使用POST、PUT和DELETE方式的请求消息中才可以包含实体内容。 
          响应消息的结构:一个状态行、若干消息头、以及实体内容 ,其中的一些消息头和实体内容都是可选的,消息头和实体内容之间要用空行隔开。
                                响应消息的实体内容就是网页文件的内容,也就是在浏览器中使用查看源文件的方式所看到的内容。 
                              
           HTTP消息头
           1>使用消息头,可以实现HTTP客户机与服务器之间的条件请求和应答,消息头相当于服务器和浏览器之间的一些暗号指令。
           2>每个消息头包含一个头字段名称,然后依次是冒号、空格、值、回车和换行符。
           3>消息头字段名是不区分大小写的,但习惯上将每个单词的第一个字母大写。
           4>整个消息头部分中的各行消息头可按任何顺序排列。 
           5>消息头又可以分为通用信息头、请求头、响应头、实体头等四类。
           6>许多请求头字段都允许客户端在值部分指定多个可接受的选项,多个项之间以逗号分隔。
           7>有些头字段可以出现多次。
           请求行与状态行
           请求行

               格式:请求方式 资源路径 HTTP版本号<CRLF>     举例:GET /test.html HTTP/1.1

               请求方式:POST、HEAD、OPTIONS、DELETE、TRACE、PUT

          状态行

               格式: HTTP版本号 状态码 原因叙述<CRLF>       举例:HTTP/1.1 200 OK

          使用GET和POST方式传递参数
          在URL地址后面可以附加一些参数   举例:http://www.it315.org/servlet/ParamsServlet?param1=abc&param2=xyz
          GET方式:
                     1)当用户在浏览器地址栏中直接输入某个url地址或者单击网页上一个超链接时,浏览器使用get方式发送请求。
                     2)当网页行的form标单的method属性设定为"GET",或者没有设置method属性(默认为GET),提交表单时,浏览器也用get方式发送请求。

                     使用GET请求方式给WEB服务器传递参数的格式:http://www.it315.org/counter.jsp?name=zhangsan&password=123

                     使用GET方式传送的数据量一般限制在1KB以下。

    GET /servlet/ParamsServlet?param1=abc&param2=xyz HTTP/1.1   
          POST方式:
                    1)POST请求方式主要用于向WEB服务器端程序提交FORM表单中的数据,当网页行的form标单的method属性设定为"POST"时,提交表单时,浏览器使用post方式发送请求。

                    2)POST方式将各个表单字段元素及其数据作为HTTP消息的实体内容发送给WEB服务器,传送的数据量要比使用GET方式传送的数据量大得多。 

                    <form>表单元素的enctype属性用于指定浏览器使用哪种编码方法将表单中的数据传送给WEB服务器,该属性可以有两种取值:

    application/x-www-form-urlencoded
    multipart/form-data
    POST /servlet/ParamsServlet HTTP/1.1
    Host:
    Content-Type: application/x-www-form-urlencoded
    Content-Length: 28
    
    param1=abc&param2=xyz

             get方式和post方式的区别:
             使用get方式提交表单时,浏览器将各个表单字段元素及其数据按照url参数的方式附加在请求行的资源后面。使用get方式传递的数据量是有限的,一般限制在1K以下。
             使用post方式时,浏览器把各表单字段元素及其数据作为HTTP消息的实体内容发送给web服务器,而不是作为url地址参数传递,因此,使用post方式,要比使用get方式大的多。     

            1>get是从服务器上获取数据,post是向服务器传送数据。

            2>在客户端,Get方式在通过URL提交数据,数据在URL中可以看到;POST方式,数据放置在HTML HEADER内提交。

            3>对于get方式,服务器端用Request.QueryString获取变量的值,对于post方式,服务器端用Request.Form获取提交的数据。

            4>GET方式提交的数据最多只能有1024字节,而POST则没有此限制。

            5>安全性问题。正如在1>中提到,使用Get的时候,参数会显示在地址栏上,而Post不会。所以,如果这些数据是中文数据而且是非敏感数据,那么使用get;如果用户输入的数据不是中文字符而且包含敏感数据,那么还是使用post为好。

             响应状态码

             响应状态码用于表示服务器对请求的各种不同处理结果和状态,它是一个三位的十进制数。响应状态码可归为5种类别,使用最高位为1到5来进行分类,如下所示:

            (1)100~199表示成功接收请求,要求客户端继续提交下一次请求才能完成整个处理过程 。(2)200~299表示成功接收请求并已完成整个处理过程 。(3)300~399为完成请求,客户需进一步细化请求。例如,请求的资源已经移动一个新地址。(4)400~499客户端的请求有错误。(5)500~599服务器端出现错误。

            200(正常)表示一切正常,返回的是正常请求结果。206(部分内容)客户发送了一个带有Range头(要求服务器只返回文档中的部分内容)的GET请求,服务器按要求完成了这个请求。302/307(临时重定向)指出被请求的文档已被临时移动到别处,此文档的新的URL在Location响应头中给出。304(未修改)表示客户机缓存的版本是最新的,客户机应该继续使用它。
    401(未经授权)表示客户机访问的是一个受口令和密码保护的页面,结合使用一个WWW-Authenticate响应头提示客户机应重新发出一个带有Authorization头的请求消息。404(找不到)服务器上不存在客户机所请求的资源。500(内部服务器错误)服务器端的CGI、ASP、JSP等程序发生错误。

            通用信息头

            通用信息头字段既能用于请求消息,也能用于响应消息,它包括一些与被传输的实体内容没有关系的常用消息头字段。

    Cache-Control: no-cache    
    Connection: close/Keep-Alive     
    Date: Tue, 11 Jul 2014 18:23:51 GMT
    Pragma: no-cache           
    Trailer: Date 
    Transfer-Encoding: chunked    
    Upgrade: HTTP/2.0, SHTTP/1.3 
    Via: HTTP/1.1 Proxy1, HTTP/1.1 Proxy2   
    Warning: any text 

           请求头

           请求头字段用于客户端在请求消息中向服务器传递附加信息,主要包括客户端可以接受的数据类型、压缩方法、语言、以及发出请求的超链接所属网页的URL地址等信息。
    Accept: text/html,image/*    
    Accept-Charset: ISO-8859-1,unicode-1-1  
    Accept-Encoding: gzip,compress  
    Accept-Language: en-gb,zh-cn 
    Authorization: Basic enh4OjEyMzQ1Ng== 
    Expect: 100-continue
    From: zxx@it315.org 
    Host: www.it315.org:80   
    If-Match: "xyzzy", "r2d2xxxx" 
    If-Modified-Since: Tue, 11 Jul 2000 18:23:51 GMT 
    If-None-Match: "xyzzy", "r2d2xxxx"
    If-Range: Tue, 11 Jul 2000 18:23:51 GMT
    If-Unmodified-Since: Tue, 11 Jul 2000 18:23:51 GMT
    Max-Forwards: 1 
    Proxy-Authorization: Basic enh4OjEyMzQ1Ng==
    Range: bytes=100-599 
        Range: bytes=100- 
        Range: bytes=-100 
    Referer: http://www.it315.org/index.jsp 
    TE: trailers,deflate 
    User-Agent: Mozilla/4.0 (compatible; MSIE 5.5; Windows NT 5.0) 

             响应头

             响应头字段用于服务器在响应消息中向客户端传递附加信息,包括服务程序名,被请求资源需要的认证方式,被请求资源已移动到的新地址等信息。

    Accept-Range: bytes 
    Age: 315315315
    Etag: b38b9-17dd-367c5dcd
    Location: http://www.it315.org/index.jsp 
    Proxy-Authenticate: BASIC realm="it315" 
    Retry-After: Tue, 11 Jul 2000 18:23:51 GMT
    Server: Microsoft-IIS/5.0 
    Vary: Accept-Language 
    WWW-Authenticate: BASIC realm="it315" 

            实体头

            实体头用作实体内容的元信息,描述了实体内容的属性,包括实体信息类型、长度、压缩方法、最后一次修改时间、数据有效期等。
    Allow: GET,POST
    Content-Encoding: gzip 
    Content-Language: zh-cn 
    Content-Length: 80 
    Content-Location: http://www.it315.org/java_cn.html 
    Content-MD5: ABCDABCDABCDABCDABCDAB== 
    Content-Range: bytes 2543-4532/7898 
    Content-Type: text/html; charset=GB2312 
    Expires: Tue, 11 Jul 2000 18:23:51 GMT
    Last-Modified: Tue, 11 Jul 2000 18:23:51 GMT

            扩展头

            在HTTP消息中,也可以使用一些在HTTP 1.1正式规范里没有定义的头字段,这些头字段统称为自定义的HTTP头或扩展头,它们通常被当作是一种实体头处理。现在流行的浏览器实际上都支持Cookie、Set-Cookie、Refresh和Content-Disposition等几个常用的扩展头字段。
    //Refresh头字段
            Refresh: 1 
            Refresh: 1;url=http://www.it315.org
    //Content-Disposition头字段   
            Content-Type: application/octet-stream
            Content-Disposition: attachment; filename=aaa.zip

           问题

           HTTP协议是一种无状态的协议,WEB服务器本身不能识别出哪些请求是同一个浏览器发出的 ,浏览器的每一次请求都是完全孤立的。即使 HTTP1.1 支持持续连接,但当用户有一段时间没有提交请求,连接也会关闭。
           怎么才能实现网上商店中的购物车呢:某个用户从网站的登录页面登入后,再进入购物页面购物时,负责处理购物请求的服务器程序必须知道处理上一次请求的程序所得到的用户信息。
    作为 web 服务器,必须能够采用一种机制来唯一地标识一个用户,同时记录该用户的状态。
          会话和会话状态

          WEB应用中的会话是指一个客户端浏览器与WEB服务器之间连续发生的一系列请求和响应过程。WEB应用的会话状态是指WEB服务器与浏览器在会话过程中产生的状态信息,借助会话状态,WEB服务器能够把属于同一会话中的一系列的请求和响应过程关联起来。

    实现有状态的会话

          WEB服务器端程序要能从大量的请求消息中区分出哪些请求消息属于同一个会话,即能识别出来自同一个浏览器的访问请求,这需要浏览器对其发出的每个请求消息都进行标识:属于同一个会话中的请求消息都附带同样的标识号,而属于不同会话的请求消息总是附带不同的标识号,这个标识号就称之为会话ID(SessionID)。在 Servlet 规范中,常用以下两种机制完成会话跟踪Cookie和Session。

    cookie机制

           Cookie机制采用的是在客户端保持 HTTP 状态信息的方案。

           Cookie是在浏览器访问WEB服务器的某个资源时,由WEB服务器在HTTP响应消息头中附带传送给浏览器的一个小文本文件。一旦WEB浏览器保存了某个Cookie,那么它在以后每次访问该WEB服务器时,都会在HTTP请求头中将这个Cookie回传给WEB服务器。

           底层的实现原理: WEB服务器通过在HTTP响应消息中增加Set-Cookie响应头字段将Cookie信息发送给浏览器,浏览器则通过在HTTP请求消息中增加Cookie请求头字段将Cookie回传给WEB服务器。

           一个Cookie只能标识一种信息,它至少含有一个标识该信息的名称(NAME)和设置值(VALUE)。一个WEB站点可以给一个WEB浏览器发送多个Cookie,一个WEB浏览器也可以存储多个WEB站点提供的Cookie。浏览器一般只允许存放300个Cookie,每个站点最多存放20个Cookie,每个Cookie的大小限制为4KB。

                                                                

            在Servlet程序中使用Cookie

            Servlet API中提供了一个javax.servlet.http.Cookie类来封装Cookie信息,它包含有生成Cookie信息和提取Cookie信息的各个属性的方法。 

    //Cookie类的方法: 
    //构造方法: 
    public Cookie(String name,String value)
    getName方法 
    setValue与getValue方法 
    setMaxAge与getMaxAge方法 
    setPath与getPath方法 
            HttpServletResponse接口中定义了一个addCookie方法,它用于在发送给浏览器的HTTP响应消息中增加一个Set-Cookie响应头字段。
            HttpServletRequest接口中定义了一个getCookies方法,它用于从HTTP请求消息的Cookie请求头字段中读取所有的Cookie项。
            cookie的发送
            1>.创建Cookie对象
            2>.设置最大时效
            3>.将Cookie放入到HTTP响应报头
                 如果创建了一个cookie,并将他发送到浏览器,默认情况下它是一个会话级别的cookie; 存储在浏览器的内存中,用户退出浏览器之后被删除。若希望浏览器将该cookie存储在磁盘上,则需要使用maxAge,并给出一个以秒为单位的时间。将最大时效设为0则是命令浏览器删除该cookie。发送cookie需要使用HttpServletResponse的addCookie方法,将cookie插入到一个 Set-Cookie HTTP响应报头中。由于这个方法并不修改任何之前指定的Set-Cookie报头,而是创建新的报头,因此将这个方法称为是addCookie,而非setCookie。
          会话cookie和持久cookie的区别
          如果不设置过期时间,则表示这个cookie生命周期为浏览器会话期间,只要关闭浏览器窗口,cookie就消失了。这种生命期为浏览器会话期的cookie被称为会话cookie。会话cookie一般不保存在硬盘上而是保存在内存里。如果设置了过期时间,浏览器就会把cookie保存到硬盘上,关闭后再次打开浏览器,这些cookie依然有效直到超过设定的过期时间。存储在硬盘上的cookie可以在不同的浏览器进程间共享,比如两个IE窗口。而对于保存在内存的cookie,不同的浏览器有不同的处理方式。
          cookie的读取
          1>.调用request.getCookies,要获取浏览器发送来的cookie,需要调用HttpServletRequest的getCookies方法,这个调用返回Cookie对象的数组,对应由HTTP请求中Cookie报头输入的值。
          2>.对数组进行循环,调用每个cookie的getName方法,直到找到感兴趣的cookie为止.
          使用cookie属性的注意问题
          属性是从服务器发送到浏览器的报头的一部分;但它们不属于由浏览器返回给服务器的报头。因此除了名称和值之外,cookie属性只适用于从服务器输出到客户端的cookie;服务器端来自于浏览器的cookie并没有设置这些属性。
     
    Session机制
          Session机制采用的是在服务器端保持 HTTP 状态信息的方案 。
          服务器使用一种类似于散列表的结构(也可能就是使用散列表)来保存信息。当程序需要为某个客户端的请求创建一个session时,服务器首先检查这个客户端的请求里是否包含了一个session标识(即sessionId),如果已经包含一个sessionId则说明以前已经为此客户创建过session,服务器就按照session id把这个session检索出来使用(如果检索不到,可能会新建一个,这种情况可能出现在服务端已经删除了该用户对应的session对象,但用户人为地在请求的URL后面附加上一个JSESSION的参数)。如果客户请求不包含sessionId,则为此客户创建一session 并且生成一个与此session相关联的sessionId,这个session id将在本次响应中返回给客户端保存。
          保存session id的几种方式
          1>保存session id的方式可以采用cookie,这样在交互过程中浏览器可以自动的按照规则把这个标识发送给服务器。
          2>由于cookie可以被人为的禁用,必须有其它的机制以便在cookie被禁用时仍然能够把session id传递回服务器,经常采用的一种技术叫做URL重写,就是把session id附加在URL路径的后面,附加的方式也有两种,一种是作为URL路径的附加信息,另一种是作为查询字符串附加在URL后面。网络在整个交互过程中始终保持状态,就必须在每个客户端可能请求的路径后面都包含这个session id。
          Session cookie
          1>session通过SessionID来区分不同的客户, session是以cookie或URL重写为基础的,默认使用cookie来实现,系统会创造一个名为JSESSIONID的输出cookie,这称之为session cookie,以区别persistent cookies(也就是我们通常所说的cookie),session cookie是存储于浏览器内存中的,并不是写到硬盘上的,通常看不到JSESSIONID,但是当把浏览器的cookie禁止后,web服务器会采用URL重写的方式传递Sessionid,这时地址栏可看到;
          2>session cookie针对某一次会话而言,会话结束session cookie也就随着消失了,而persistent cookie只是存在于客户端硬盘上的一段文本;
          3>关闭浏览器,只会是浏览器端内存里的session cookie消失,但不会使保存在服务器端的session对象消失,同样也不会使已经保存到硬盘上的持久化cookie消失。
          Session的创建与删除
          一个常见的错误是以为session在有客户端访问时就被创建,然而事实是直到某server端程序(如Servlet)调用HttpServletRequest.getSession(true)这样的语句时才会被创建。
          session在下列情况下被删除:
               1>程序调用HttpSession.invalidate()
               2>距离上一次收到客户端发送的session id时间间隔超过了session的最大有效时间
               3>服务器进程被停止
          注意:关闭浏览器只会使存储在客户端浏览器内存中的session cookie失效,不会使服务器端的session对象失效。
          两个浏览器窗口访问应用程序会使用同一个session
          通常session cookie是不能跨窗口使用的,当你新开了一个浏览器窗口进入相同页面时,系统会赋予你一个新的session id,这样信息共享的目的就达不到了。
          此时可以先把session id保存在persistent cookie中(通过设置cookie的最大有效时间),然后在新窗口中读出来,就可以得到上一个窗口的session id了,这样通过session cookie和persistent cookie的结合就可以实现了跨窗口的会话跟踪。
          Session的超时管理
          WEB服务器无法判断当前的客户端浏览器是否还会继续访问,也无法检测客户端浏览器是否关闭,所以,即使客户已经离开或关闭了浏览器,WEB服务器还要保留与之对应HttpSession对象。随着时间的推移而不断增加新的访问客户端,WEB服务器内存中将会因此积累起大量的不再被使用的HttpSession对象,并将最终导致服务器内存耗尽。WEB服务器采用“超时限制”的办法来判断客户端是否还在继续访问,如果某个客户端在一定的时间之内没有发出后续请求,WEB服务器则认为客户端已经停止了活动,结束与该客户端的会话并将与之对应的HttpSession对象变成垃圾。如果客户端浏览器超时后再次发出访问请求,WEB服务器则认为这是一个新的会话的开始,将为之创建新的HttpSession对象和分配新的会话标识号。会话的超时间隔可以在web.xml文件中设置,其默认值由Servlet容器定义。
      <session-config>
               <session-timeout>30</session-timeout>
      </session-config>

           HttpSession接口中的方法

    getId方法
    getCreationTime方法
    getLastAccessedTime方法
    setMaxInactiveInterval方法
    getMaxInactiveInterval方法
    isNew方法
    //如果客户端请求消息中返回了一个与Servlet程序当前获得的HttpSession对象的会话标识号相同的会话标识号,则认为这个HttpSession对象不是新建的。
    invalidate方法
    getServletContext方法
    setAttribute方法
    getAttribute方法
    removeAttribute方法
    getAttributeNames方法

           HttpServletRequest接口中的Session方法

    getSession方法 
    public HttpSession getSession(boolean create)
    public HttpSession getSession()
    isRequestedSessionIdValid方法 
    isRequestedSessionIdFromCookie方法 
    isRequestedSessionIdFromURL方法 

           利用URL重写实现Session跟踪

           Servlet规范中引入了一种补充的会话管理机制,它允许不支持Cookie的浏览器也可以与WEB服务器保持连续的会话。这种补充机制要求在响应消息的实体内容中必须包含下一次请求的超链接,并将会话标识号作为超链接的URL地址的一个特殊参数。
           将会话标识号以参数形式附加在超链接的URL地址后面的技术称为URL重写。如果在浏览器不支持Cookie或者关闭了Cookie功能的情况下,WEB服务器还要能够与浏览器实现有状态的会话,就必须对所有可能被客户端访问的请求路径(包括超链接、form表单的action属性设置和重定向的URL)进行URL重写。
           HttpServletResponse接口中定义了两个用于完成URL重写方法:
           encodeURL方法
           encodeRedirectURL方法

          application与Session域范围的属性 

     

            Application与session域范围的属性比较 

    HttpSession session = request.getSession();
    Integer sessionCount = (Integer)session.getAttribute("count");
    int count = 0;
    if(sessionCount != null)
    {
        count = sessionCount.intValue();
    }
    out.println("当前会话中发生了" + (++count) + "次访问<br>");
    session.setAttribute("count",new Integer(count));
            
    count = 0;
    ServletContext application = getServletContext();
    Integer applicationCount = (Integer)application.getAttribute("count");
    if(applicationCount != null)
    {
        count = applicationCount.intValue();
    }
    out.println("WEB应用程序中发生了" + (++count) + "次访问<br>");
    application.setAttribute("count",new Integer(count));

           Session的典型案例

           1>使用Session实现购物车;2>利用Session防止表单重复提交;3>利用Session实现一次性验证码

           避免表单的重复提交:

           调用 RequestDispatcher.forward() 方法,浏览器所保留的URL 是先前的表单提交的 URL,此时点击”刷新”, 浏览器将再次提交用户先前输入的数据,引起重复提交.如果采用 HttpServletResponse.sendRedirct() 方法将客户端重定向到成功页面,将不会出现重复一条问题.

           Js 客户端避免表单重复提交,如下所示,其不足:当用户单击”刷新”,或单击”后退”再次提交表单,将导致表单重复提交。

                                            

            利用Session防止表单重复提交

            包含有FORM表单的页面必须通过一个服务器程序动态产生,服务器程序为每次产生的页面中的FORM表单都分配一个唯一的随机标识号,并在FORM表单的一个隐藏字段中设置这个标识号,同时在当前用户的Session域中保存这个标识号。当用户提交FORM表单时,负责接收这一请求的服务器程序比较FORM表单隐藏字段中的标识号与存储在当前用户的Session域中的标识号是否相同,如果相同则处理表单数据,处理完后清除当前用户的Session域中存储的标识号。

            在下列情况下,服务器程序将忽略提交的表单请求:

            1>当前用户的Session中不存在表单标识号;
            2>用户提交的表单数据中没有标识号字段;
            3>存储在当前用户的Session域中的表单标识号与表单数据中的标识号不同.
            浏览器只有重新向WEB服务器请求包含FORM表单的页面时,服务器程序才又产生另外一个随机标识号,并将这个标识号保存在Session域中和作为新返回的FORM表单中的隐藏字段值。

            TokenProcessor.java:用于管理表单标识号的工具类,它主要用于产生、比较和清除存储在当前用户Session中的表单标识号。为了保证表单标识号的唯一性,每次将当前SessionID和系统时间的组合值按MD5算法计算的结果作为表单标识号,并且将TokenProcessor类设计为单件类.

            问题:

                  同一个用户打开同一个浏览器进程的多个窗口来并发访问同一个WEB站点的多个FORM表单页面时,将会出现表单无法正常提交的情况。

            解决方案:
                 将FORM表单的标识号作为表单隐藏字段的名称,如下所示:
    <input type='hidden' name='4b15c6b2f573831b4b5107d849fcafb8' value=''>

                将所有的表单标识号存储进一个Vector集合对象中,并将Vector集合对象存储进Session域中。当表单提交时,先从Session域中取出Vector集合对象,然后再从Vector集合对象中逐一取出每个表单标识号作为参数调用HttpServletRequest.getParameter方法,如果其中有一次调用的返回值不为null,则接受并处理该表单数据,处理完后将该表单标识号从Vector集合对象中删除。

           利用Session实现一次性验证码 

                                         

           一次性验证码的主要目的就是为了限制人们利用工具软件来暴力猜测密码,其原理与利用Session防止表单重复提交的原理基本一样,只是将表单标识号变成了验证码的形式,并且要求用户将提示的验证码手工填写进一个表单字段中,而不是通过表单的隐藏字段自动回传给服务器。
           服务器程序接收到表单数据后,首先判断用户是否填写了正确的验证码,只有该验证码与服务器端保存的验证码匹配时,服务器程序才开始正常的表单处理流程。密码猜测工具要逐一尝试每个密码的前题条件是先输入正确的验证码,而验证码是一次性有效的,这样基本上就阻断了密码猜测工具的自动地处理过程。
  • 相关阅读:
    如何写出优秀的代码[转载]
    [转载]Java中常用日期功能综合
    JS WebBrowser 实现打印预览
    想成为优秀的技术人员你必须做到的几件事情【转载】
    JS打印
    js阿拉伯数字转中文大写
    从 SQL Server 2005 中处理 XML
    Visual Studio 2005 Express October 2004 CTP完整版本的下载
    Debug和Release的区别
    ASP.NET 中的正则表达式
  • 原文地址:https://www.cnblogs.com/Vae1990Silence/p/4699973.html
Copyright © 2020-2023  润新知