精华内容
下载资源
问答
  • Request对象主要用于获取来自客户端数据,如用户填入表单的数据、保存在客户端的Cookie等。 来源:互联网2015-11-15浏览次数:4 1、主要属性 ApplicationPath 获取服务器上asp.net应用程序的虚拟应用...

    Request对象主要用于获取来自客户端的数据,如用户填入表单的数据、保存在客户端的Cookie等。

     来源:互联网  2015-11-15  浏览次数:4

    1、主要属性

     

     ApplicationPath 获取服务器上asp.net应用程序的虚拟应用程序根路径
     Browser 获取有关正在请求的客户端的浏览器功能的信息,该属性值为:HttpBrowserCapabilities对  象
     ContentEncoding 获取或设置实体主体的字符集。该属性值为表示客户端的字符集Encoding对象
     ContentLength 指定客户端发送的内容长度,以字节为单位 
     ContentType 获取或设置传入请求的MIME内容类型。
     Cookies 获取客户端发送的Cookie集合,该属性值为表示客户端的Cookie变量的  HttpCookieCollection对象
     CurrentExecutionFilePath 获取当前请求的虚拟路径
     FilePath 获取当前请求的虚拟路径
     Files 获取客户端上载的文件集合。该属性值为HttpFileCollection对象,表示客户端上载的文件集合
     Form 获取窗体变量集合
     HttpMethod 获取客户端使用的HTTP数据传输方法(如:get、post或head)
     Item 获取Cookies、Form、QueryString或ServerVariables集合中指定的对象
     Params 获取Cookies、Form、QueryString或ServerVariables项的组合集合
     Path 获取当前请求的虚拟路径
     PathInfo 获取具有URL扩展名的资源的附加路径信息
     PhysicalApplicationPath 获取当前正在执行的服务器应用程序的根目录的物理文件系统路径
     PhysicalPath 获取与请求的URL相对应的物理文件路径
     QueryString 获取HTTP查询字符串变量集合。该属性值为:NameValueCollection对象,它包含由客户端发送的查询字符串变量集合
     RequestType 获取或设置客户端使用HTTP数据传输的方式(get或post)
     ServerVariables 获取Web服务器变量的集合
     TotalBytes 获取当前输入流的字节数
     Url 获取有关当前请求URL的信息
     UserHostAddress 获取远程客户端的IP主机地址
      

    2、主要方法

    (1)MapPath(VirtualPath):将当前请求的URL中的虚拟路径virtualPath映射到服务器上的物理路径。参数virtualPath指定当前请求的虚拟路径,可以是绝对路径或相对路径。该方法的返回值为由virtualPath指定的服务器物理路径。

    (2)SaveAs (Filename,includeHeaders):将http请求保存到磁盘。参数filename指定物理驱动器路径,includeHeaders是一个布尔值,指定是否应将HTTP标头保存到磁盘。

    二、Request对象的应用

    1.读取窗体变量四种方式

    (1).使用Request.Form属性读取窗体变量
    (2).使用Request.QueryString属性读取窗体变量
    (3).使用Request.Params属性读取窗体变量
    (4).通过服务器控件的属性直接读取窗体变量

    (1).使用Request.Form属性读取窗体变量

    HtmlForm控件的Method属性的默认值为post。在这种情况下,当用户提交网页时,表单数据将以HTTP标头的形式发送到服务器端。此时,可以使用Request对象的Form属性来读取窗体变量。如:txtUserName和txtPassword的文本框控件,则可以通过以下形式来读取它们的值: Request.Form["txtUserName"] ;Request.Form["txtPassword"]

    (2)使用Request.QueryString属性读取窗体变量

    如果将HtmlForm控件的Method属性设置为get,则当用户提交网页时,表单数据将附加在网址后面发送到服务器端。在这种情况下,可以使用Request对象的QueryString属性读取窗体变量。Request.QueryString["txtUserName"] ;Request.QueryString["txtPassword"]

    (3)使用Request.Params属性读取窗体变量

    不论HtmlForm控件的Method属性取什么值,都可以使用Request对象的Params属性来读取窗体变量的内容,如Request.Params["txtPassword"]或者Request.["txtPassword"],优先获取GET方式提交的数据,它会在QueryString、Form、ServerVariable中都按先后顺序搜寻一遍。

    Request:包含以上两种方式(优先获取GET方式提交的数据),它会在QueryString、Form、ServerVariable中都按先后顺序搜寻一遍。Request.Params是所有post和get传过来的值的集合,request.params其实是一个集合,它依次包括request.QueryString、request.Form、request.cookies和request.ServerVariable。

    注意:当使用Request.Params的时候,这些集合项中最好不要有同名项。如果仅仅是需要Form中的一个数据,但却使用了Request而不是Request.Form,那么程序将在QueryString、ServerVariable中也搜寻一遍。如果正好QueryString或者ServerVariable里面也有同名的项,那么得到的就不是想要的值了。

    (4)通过服务器控件的属性直接读取窗体变量

    除了以上3种方式之外,也可以通过服务器控件的属性来直接读取窗体变量,这是获取表单数据的最常用、最简单的方式。例如: txtUserName.Text 

    2.读取查询字符串变量

    在浏览网页时,经常看到浏览器地址栏中显示“xxx.aspx?id=8018”之类的URL,其中xxx.aspx表示要访问的.aspx网页,问号(?)后面跟的内容便是查询字符串,其作用是将变量的名称和值传送给这个ASP.NET文件来处理。查询字符串变量可以通过以下几种方式生成。

    (1).若将HtmlForm控件的Method属性设置为get,则当用户提交网页时,窗体数据将作为查询字符串变量附在网址后面被发送到服务器端。
    (2).使用<a>…</a>标记或HyperLink控件创建超文本链接时,可以将查询字符串放在目标URL后面,并使用问号“?”来分隔URL与查询字符串
    (3).调用Response.Redirect方法时,若在网址参数后面附有变量名/值对,则打开目标网页时这些变量值附在该网址后面被发送到服务器端。
    (4).在浏览器地址栏中输入请求URL时,在URL后输入问号“?”和查询字符串。例如: http://…/t.aspx?Id=8018

    在上述场合,均可通过Request.QueryString属性来检索查询字符串变量。

    //在登陆页面     protected void Button1_Click(object sender, EventArgs e)   {   //登陆   //if (txtUserName.Text == "admin" && txtPwd.Text == "123")   //{   // Session["Info"] = "随便一个值";   // Response.Redirect("Request2_test.aspx?Info=" + txtUserName.Text);   //}   //else   //{   // Response.Redirect("Request2_test.aspx?error=登陆失败!");   //}   /***********************************方法2****************************************/   //或者   if (txtUserName.Text == "admin" && txtPwd.Text == "123")   {   Response.Redirect("Request2_test.aspx?Info=" + txtUserName.Text + "&check=1");     }   else   {   Response.Redirect("Request2_test.aspx?error=登陆失败!");   }   }
    View Code

    在验证页面

    protected void Page_Load(object sender, EventArgs e)   {   //验证页面   //if (Session["Info"] != null && Session["Info"].ToString() == "随便一个值")   //{   // Response.Write("登陆成功!<br>" + Request.QueryString["Info"] + ",欢迎访问本站");   // //Response.Write("登陆成功!<br>" + Request["Info"] + ",欢迎访问本站");   // //Response.Write("登录成功!<br>"+Request.Form["username"]+",欢迎访问本站");     //}   //else   //{   // Response.Write("登陆失败");   //}   /***************************************方法2**********************************/   if (Convert.ToInt32(Request["check"]) == 1)   {   Response.Write("登陆成功!<br>" + Request.QueryString["Info"] + ",欢迎访问本站");   }   else   {   Response.Write("登陆失败");   }   }
    View Code

    3.取得Web服务器端的系统信息

     

    Request对象使用ServerVariables集合对象保存服务器端系统信息,这些信息变量包含在HTTP头部中随HTTP请求一起传送。使用Request对象的ServerVariables集合对象取得环境变量的语法如下: Request.ServerVariables[环境变量名]

    ServerVariables集合对象中保存的常用信息变量如下:

    Response.Write(Request.ServerVariables["LOCAL_ADDR"]);//远端服务器的地址   Response.Write("<br>");   Response.Write(Request.ServerVariables["Remote_ADDR"]);//浏览器所在主机的IP地址   Response.Write("<br>");   Response.Write(Request.Browser.Type.ToString());//浏览器的类型   Response.Write("<br>");   Response.Write(Request.Browser.Platform.ToString());//浏览器所在的平台   Response.Write("<br>");   Response.Write(Request.ServerVariables["url"]);

    4.取得客户端浏览器信息

     

    通过Request对象的Browser属性得到。需要利用Browser属性生成一个HttpBrowserCapabilities类型的对象实例。HttpBrowserCapabilities类具有的常用属性如下:

     

    Response.Write("浏览器的类型是:" + Request.Browser.Browser.ToString()+"<br>");   Response.Write("浏览器的版本是:" + Request.Browser.Version.ToString()+"<br>");   Response.Write("浏览器的所在平台是:" + Request.Browser.Platform.ToString()+"<br>");   Response.Write("浏览器是否支持框架:" + Request.Browser.Frames.ToString()+"<br>");   Response.Write("浏览器是否支持Cookies:" + Request.Browser.Cookies.ToString()+"<br>");   Response.Write("浏览器是否支持Javascript:" + Request.Browser.JavaScript.ToString()+"<br>");

     


    5.读取客户端Cookie

    Cookie是在HTTP协议下服务器或脚本可以维护客户工作站上信息的一种方式。Cookie是由Web服务器保存在用户浏览器上的小文本文件,它可以包含有关用户的信息,这些信息以名/值对的形式储存在文本文件中。无论何时,只要用户连接接到服务器,Web站点就可以访问Cookie信息。Cookie保存在用户的Cookie文件中,当下一次用户返回时,仍然可以对它进行调用。

     

    Cookies集合是由一些Cookie对象组成的。Cookie对象的类名为HttpCookie。HttpCookie类的主要属性如下:

    使用Cookie时,应注意以下几点

    [1].使用Cookie保存客户端浏览器请求服务器页面的请求信息时,保存时间的长短取决于Cookie对象的Expires属性,可以根据需要来设置。若未设置Cookie的失效日期,则它们仅保存到关闭浏览器为止。若将Cookie对象的Expires属性设置为DateTime.MaxValue,则表示Cookie永远不会过期。

    [2].Cookie存储的数据量有所限制,大多数浏览器支持的最大容量为4096字节,因此不要用Cookie来保存大量数据。

    [3].  并非所有浏览器都支持Cookie,并且数据是以明文形式保存在客户端计算机中,因此最好不要用Cookie来保存敏感的未加密数据。

    [4].在ASP.NET中有两个Cookies集合,即:Response对象的Cookies集合和Request对象的Cookies集合,但两者的作用有所不同,通过前者可以将Cookie写入客户端,通过后者可以读取存储在客户端的Cookie。

    如下操作:

    protected void Page_Load(object sender, EventArgs e)   {   HttpCookie c1=Request.Cookies["UserName"];   HttpCookie c2 = Request.Cookies["Password"];   if (c1 != null || c2 != null)   {   //当保存完Cookie之后(也就是说"保存或永久保存"),这个才能输出,当第二次用统一浏览器打开该网站的时候就会输出   Response.Write(c1.Value + "欢迎光临");   }   }   protected void Button1_Click(object sender, EventArgs e)   {   //提交   if (TextBox1.Text == "admin" && TextBox2.Text == "123")   {   Response.Write("欢迎光临"+TextBox1.Text);   Response.Cookies["UserName"].Value = TextBox1.Text;   Response.Cookies["Password"].Value = TextBox2.Text;   if (DropDownList1.SelectedItem.Text == "永久保存")   {   //默认cookies失效时间是直到关闭浏览器   //Cookie保存永久   Response.Cookies["UserName"].Expires = DateTime.MaxValue;   Response.Cookies["Password"].Expires = DateTime.MaxValue;   }   else   {   //Cookie永不保存   Response.Cookies["UserName"].Expires = DateTime.Now;   Response.Cookies["Password"].Expires = DateTime.Now;   }   }     } 

     


    当我们再次打开该网站(同一浏览器)的时候,就会弹出“admin欢迎光临"

    转载于:https://www.cnblogs.com/xuedexin/articles/5617845.html

    展开全文
  • JSP内置对象

    千次阅读 2019-06-11 18:09:19
    JSP内置对象的分类 内置对象属性保存范围 JSP内置对象的常用方法(只列了一些) 小例子 一:JSP内置对象的分类 这些对象不用经过显示声明直接引用,也不用代码创建其实例,可在程序中直接使用,由Web ...
    • JSP内置对象的分类
    • 内置对象属性保存范围
    • JSP内置对象的常用方法(只列了一些)
    • 小例子

     

     

    一:JSP内置对象的分类

    这些对象不用经过显示声明直接引用,也不用代码创建其实例,可在程序中直接使用,由Web Container为对象实例化。

     

     

     

     

     

    二:内置对象属性保存范围

    (1)page:在JSP中设置一个页的属性范围,必须通过pageContext完成,属性值只能在当前页取得。

    (2)request:将属性保存在一次请求范围之内,必须使用服务器跳转<jsp:forward/>,通过客户端跳转和超链接等无法取得。

    (3)session:只保留一个用户的信息,不管什么跳转都可以取得属性,与session有关的任何打开的页面都可以取得session。

    (4)application:所有用户都可以取得此信息,此信息在整个服务器上被保留,所有网页窗口都可以取得数据。

     

    注意:这四种属性范围,都是通过pageContext对象来完成的。

    设置属性:
    public abstract void setAttribute(String name,Object value,int scope)
    取得属性:
    public abstract Object getAttribute(String name)

    其中,scope取值为:

    public static final int APPLICATION_SCOPE
    public static final int SESSION_SCOPE
    public static final int REQUEST_SCOPE
    public static final int PAGE_SCOPE
    

    例子:

     <%
        pageContext.setAttribute("name","内置对象");
        String d= (String) pageContext.getAttribute("name");
        out.print(d);
      %>

     

     

    三:JSP内置对象的常用方法(只列了一些)

    (1)request请求对象

    request对象属于 Javax. ervlet ServletRequest接口的实例化对象。
    【作用】 request对象不但可以用来设置和取得 request范围变量,还可以用来获得客户端请求参数、请求的来源、表头、 cookies等。
    【机制】当用户请求一个JSP页面时,JSP页面所在的 Tomcat服务器将用户的请求封装在内置对象 request中。 request内置对象代表了客户端的请求信息,主要用于接收客户端通过HTTP协议传送给服务器端的数据。在客户端的请求中如果有参数,则该对象就有参数列表。

     

     

    (2)response响应对象

    response对象属于Javax.servlethttpHttpservletrEsponse接口的实例化对象
    【作用】 response对象用来给客户端传送输出信息、设置标头等。
    【机制】 response对象的生命周期由JSP容器自动控制。当服务器向客户端传送数据JSP容器就会创建 response对象,并将请求信息包装到 response对象中。它封装了JSP性的响应,然后被发送到客户端以响应客户的请求,当JSP容器处理完请求后, response
    对象就会被销毁。

     

    1)页面跳转的多种方式:

      每隔1s刷新一次页面
      <%
        response.setHeader("refresh","1");
      %>
      2秒后跳转到login.jsp
      <%
        response.setHeader("refresh","2;URL=login.jsp");
      %>

     

    2)response.sendRedirect("地址")实现页面跳转(可以携带参数)

    <%
        response.sendRedirect("AA.jsp?id=1");
    %>
    
    跳转后地址为:http://localhost:8080/AA.jsp?id=1
    

    这种是客户端跳转,页面跳转时,地址栏改变,不能保存request属性,要通过URL地址重写传递参数。

    和<jsp:forward page=""/>相比,后者是服务器跳转,地址栏不改变,无条件跳转,之后的代码不执行,所以在跳转之前释放全部资源 request设置的属性能保留在下一个页面,通过<jsp:param>传递参数

     

    3)设置Cookie

    Cookie是服务器端保存客户端的一组资源。例如,登录时会问用户是否记住密码,或多长时间不用登录,这些功能都是通过 Cookie来实现的。

    Cookie是通过服务器端设置到客户端上去的,用response提供的方法实现:
    public void addCookie(Cookie cookie)
    如果要在服务器端取得 Cookie,用 request提供的方法实现:
    public Cookie[] getCookies()
    在使用 request对象取得全部 Cookie时,会出现 Jsessionid信息。 Jsessionid表示一个客户端在服务器端的唯一一个标识编号(自动获得 Session对象),服务器端在客户端第一次访问之后,会设置一个默认的 Cookie在客户端上: Jsessionid
    默认情况下, Cookie只针对当前浏览器有效,如果需要 Cookie长留在本地计算机上,可以设置 Cookie的保存时间。通过 Cookie对象的 setMaxAge0设置最大保留时间。

    Cookie是保存在客户端上的信息,安全性较差,不要把过多的隐秘信息保留在 Cookie中,这样很不安全。
     

     

    (3)session会话对象

    session对象属于Javax.servlet.http.Htpsessio接口的实例化对象。
    【作用】 session对象用来表示用户的会话状况,一般用于保存用户的各种信息,直到生命周期(一般为900s)超时或者被人为释放掉为止。
    【机制】当一个用户首次访问服务器上的一个JSP页面时,JSP引擎产生一个 session对象,同时分配一个 String类型的ID号,JP引擎同时将这个ID号发送到用户端,存放在Cookie中,这样session对象和用户之间就建立了一一对应的关系。当用户再访问连接该服务器的其他页面时,不再分配给用户新的 Session对象。直到关闭浏览器后,服务器端该用户的 Session对象才取消,与用户的对应关系也一并消失。当重新打开浏览器再连接到该服务器时,服务器会为该用户再创建一个新的 Session对象。 

     

    1) session与 Cookie比较
    session和 Cookie用于跨网页共享数据。
    1> session:将信息以对象形式保存于服务端,记录独有的个人信息,在不同页面中传递。 session对象随会话结束而结束
    2> Cookie:将信息以字符串形式保存于客户端,供浏览器与web服务器互通数据用的文本文件,当IE执行时,会在计算机中产生一个 Cookie。 Cookie可以长期保存在客户端。
    session比 Cookie更安全,但更占用资源。重要的信息使用 session保存,不重要的用信息通常用 Cookie保存
    【开发原则】尽量少向 session中保存信息, session使用了 Cookie的机制,如果 Cookie禁用,则 session也无法使用。

     

     

    (4)application应用程序对象

    application对象属于Javax.servlet.jsp.ServletContext接口的实例化对象。
    【作用】application对象用户取得和设置Servlet的相关信息,实现了用户间数据的共享,可存放全局变量
    【机制】开了于服务器的启动,知道服务器关闭,这样在用户的前后连接或不同用户之间的连接中,可以对此对象的同一属性进行操作。

     

     

    (5)其他对象

    1>pageContext页面上下文对象

        <%
        request.setAttribute("name","漫步云端");
        %>
        <%=pageContext.getRequest().getAttribute("name")%>

     

     

     

     

     

    四:小例子

    login.jsp

    <%@ page contentType="text/html;charset=UTF-8" language="java" %>
    <html>
    <head>
        <title>login</title>
        <%--正则表达式将字符串前后空格用空字符串替代--%>
        <%--/.../g:全文匹配;\s*:0个或多个空格;^:字符串必须以后面的规则开头;|:或--%>
        <%--(^\s*):0个或多个空格开头;$:字符串必须以前面的规则结尾;(\s*$):0个或多个空格结尾--%>
        <script type="text/javascript">
            function loginCheck() {
                var userName =  window.loginform.userName.value;
                var userPassword = window.loginform.userPassword.value;
    //            window.alert("s1");
    //            window.alert("s2")
    //            alert("s3");
    //            alert("s4")
                if(userName.replace(/(^\s*)|(\s*$)/g,"")==""){
                    window.alert("用户ID不能为空");
                    window.loginform.userName.focus();
                    return false;
                }
                if(userPassword.replace(/(^\s*)|(\s*$)/g,"")==""){
                    window.alert("用户密码不能为空");
                    window.loginform.userPassword.focus();
                    return false;
                }
                return true;
            }
        </script>
    </head>
    <body>
    
    <form name="loginform" action="loginSubmit.jsp" method="get">
        账号:<input type="text" name="userName" id="userName"/>
        密码:<input type="password" name="userPassword" id="userPassword"/>
        <%--当用户鼠标经过按钮执行Javascript代码--%>
        <input type="submit" value="提交" onmouseover="loginCheck()"/>
    </form>
    
    <%
        if (request.getParameter("err")!=null){
    //getParameter获得客户端传送给服务器端传送的参数值;获取表单提交的信息,以字符串形式返回客户端传来的某一个请求参数的值
            String err = request.getParameter("err");
            if (err.equals("-1")){
                out.print("账号或密码错误");
            }
            else {
                out.print("其他问题");
            }
        }
    %>
    </body>
    </html>
    
    
    

    在这个页面我遇到几个问题

    第一个就是window我把它写为大写的W就没效果了

    第二个就是正则表达式replace(/(^\s*)|(\s*$)/g,""),作用是将字符串前后空格用空字符串替代,当我输入是空或者空格都会提示

    第三个就是在form写原生的botton控件,会默认是submit类型,会提交

     

     

    loginSubmit.jsp

    <%@ page contentType="text/html;charset=UTF-8" language="java" %>
    <html>
    <head>
        <title>login处理</title>
    </head>
    <body>
        <%
            String userName = request.getParameter("userName");
            String userPassword = request.getParameter("userPassword");
            if (userName.equals("123456")&&userPassword.equals("123456")){
    //          将类型为o(Object o,右边的)存入session对象的name(String name,左边的)属性中
                session.setAttribute("userName",userName);
                session.setAttribute("userPassword",userPassword);
    //            页面重定向跳转,跳转后地址改变,属于客户端跳转
                response.sendRedirect("index.jsp");
            }
            else {
    //            因为是重定向跳转,不能保存request属性,所以重写传递参数err=-1
                response.sendRedirect("login.jsp?err=-1");
            }
        %>
    </body>
    </html>
    

     

     

    index.jsp

    <%@ page contentType="text/html;charset=UTF-8" language="java" %>
    <html>
      <head>
        <title>主页</title>
      </head>
      <body>
        <%
          if (session.getAttribute("userName")!=null){
        %>
        当前用户:<%=session.getAttribute("userName")%>
        <%
          }else{
        %>
        当前用户:游客
        <%
          }
        %>
      </body>
    </html>
    

    如果有值就是哪个值,没有就是游客,不过前面因为写了判断,所以账号密码错误也不会跳到游客,可以直接进这个页面

     

    在这个例子中:

    getParameter 是用来接受用post个get方法传递过来的参数的

    参考https://terryjs.iteye.com/blog/1317610

    展开全文
  • 但是,网络相关的部分, 如网卡、协议栈、Socket 库等功能客户端却并无二致。无论硬件和 OS 如何变化,TCP 和 IP 的功能都是一样的,或者说这些功能规格都是统一的。 不过,它们的功能相同,不代表用法也相同。在...

    客户端与服务器的区别

       
          服务器和客户端有什么区别呢 根据用途 服务器可以分为很多种类, 其硬件和操作系统与客户端是有所不同的 但是 网络相关的部分 , 如网卡、 协议栈 Socket 库等功能和客户端却并无二致 无论硬件和 OS 如何变化, TCP IP 的功能都是一样的 或者说这些功能规格都是统一的 。 不过, 它们的功能相同 不代表用法也相同 在连接过程中 客户端发起连接操作, 而服务器则是等待连接操作 因此在 Socket 库的用法上还是有一些区别的, 即应用程序调用的 Socket 库的程序组件不同 。此外, 服务器的程序可以同时和多台客户端计算机进行通信 这也是一点区别。 因此 服务器程序和客户端程序在结构上是不同的
     
     

    服务器的结构

     
         服务器需要同时和多个客户端通信 但一个程序来处理多个客户端的 请求是很难的 因为服务器必须把握每一个客户端的操作状态 。因此一般的做法是, 每有一个客户端连接进来,就启动一个新的服务器程序,确保服务器程序和客户端是一对一的状态 。 具体来说, 服务器程序的结构如图 所示
     
         首先 我们将程序分成两个模块, 等待连接模块 a )) 负责与客户端通信的模块 图( b ))
     
    1)创建等待连接模块
     
    当服务器程序启动并读取配置文件完成初始化操作后 就会运行等待连接模块( a )。 这个模块会创建套接字 然后进入等待连接的暂停状态。
     
    2)等待连接模块运行并接受连接
     
    接下来 当客户端连发起连接时 这个模块会恢复运行并接受连接 , 然后启动客户端通信模块( b ), 并移交完成连接的套接字 接下来 客户端通信模块( b 就会使用已连接的套接字与客户端进行通信。
     
    3)通信结束后
     
    通信结束后 每次有新的客户端发起连接 都会启动一个新的客户端通信模块 b ),因此( b 与客户端是一对一的关系
     
     
          这样, b 在工作时就不必考虑其他客户端的连接情况, 只要关心自己对应的客户端就可以了 通过这样的方式, 可以降低程序编写的难度 服务器操作系统具有多任务 多线程 功能, 可以同时运行多个程序 服务器程序的设计正是利用了这一功能 。 当然, 种方法在每次客户端发起连接时都需要启动新的程序,这个过程比较耗时,响应时间也会相应增加 因此 还有一种方法是事先启动几个客户端通信模块,当客户端发起连接时,从空闲的模块中挑选一个出来将套接字移交给它来处理。这个模块就退出了。
     

     
     

    服务器端的套接字与端口号

     
           刚才我们介绍了服务器程序的大体结构 但如果不深入挖掘调用Socket 库的具体过程 我们还是无法理解服务器是如何使用套接字来完成通信的。 因此 下面就来看一看服务器程序是如何调用 Socket 库的
     
     
    通过数据收发来区分
     
            首先, 我们来看看客户端与服务器的区别 从数据收发的角度来看, 区分 客户端 服务器 这两个固定的角色似乎不是一个好办法。 现在大多数应用都是由客户端去访问服务器 但其实应用的形态不止这一种。 为了能够支持各种形态的应用,最好是在数据收发层面不需要区分客户端和服务器,而是能够以左右对称的方式自由发送数据。 TCP 也正是在这样的背景下设计出来的。不过, 这其中还是存在一个无法做到左右对称的部分,那就是连接操作 连接这个操作是在有一方等待连接的情况下 另一方才能发起连接,如果双方同时发起连接是不行的, 因为在对方没有等待连接的状态下 无法单方面进行连接。 因此,只有这个部分必须区分发起连接和等待连接这两个不同的角色 从数据收发的角度来看 这就是客户端与服务器的区别 也就是说,发起连接的一方是客户端,等待连接的一方是服务器。 这个区别体现在如何调用 Socket 库上 首先 客户端的数据收发需要经过下面 4 个阶段。
     
    客户端
     
    1 创建套接字 创建套接字阶段
    2 用管道连接服务器端的套接字 连接阶段
    3 收发数据 收发阶段
    4 断开管道并删除套接字 断开阶段
     
     
    服务器
     
    相对地 服务器是将阶段 2 改成了等待连接 具体如下
     
    1 创建套接字 创建套接字阶段
    2-1 将套接字设置为等待连接状态 等待连接阶段
    2-2 接受连接 接受连接阶段
    3 收发数据 收发阶段
    4 断开管道并删除套接字 断开阶段
     
         下面我们像前面介绍客户端时一样 用伪代码来表示这个过程 如图所示。
     
           首先, 协议栈调用 socket 创建套接字 1 )), 这一步和客户端是相同的 。接下来调用 bind 将端口号写入套接字中 2-1 ))。 在客户端发起连接的操作中, 需要指定服务器端的端口号 这个端口号也就是在这一步设置的。 具体的编号是根据服务器程序的种类 按照规则来确定的 ,例如Web 服务器使用 80 号端口。

     

     
           设置好端口号之后 协议栈会调用 listen 向套接字写入等待连接状态这一控制信息( 2-1 ))。 这样一来 套接字就会开始等待来自客户端 的连接网络包。然后, 协议栈会调用 accept 来接受连接 2-2 ))。 由于等待连接的模块在服务器程序启动时就已经在运行了, 所以在刚启动时 应该还没有客户端的连接包到达。 可是,包都没来就调用 accept 接受连接,可能大家会感到有点奇怪,不过没关系,因为如果包没有到达,就会转为等待包到达的状态,并在包到达的时候继续执行接受连接操作
     
          因此 在执行accept 的时候 一般来说服务器端都是处于等待包到达的状态 这时应用程序会暂停运行。 在这个状态下 一旦客户端的包到达 就会返回响应包并开始接受连接操作。 接下来 协议栈会给等待连接的套接字复制一个副本, 然后将连接对象等控制信息写入新的套接字中 刚才我们介绍了调用 accept 时的工作过程 到这里 我们就创建了一个新的套接字 , 并和客户端套接字连接在一起了。
     
       当 accept 结束之后 等待连接的过程也就结束了 这时等待连接模块会启动客户端通信模块, 然后将连接好的新套接字转交给客户端通信模块 , 由这个模块来负责执行与客户端之间的通信操作。 之后的数据收发操作和 刚才说的一样 与客户端的工作过程是相同的。

     

     
     
        其实在这一系列操作中, 还有一部分没有讲到 那就是在复制出一个新的套接字之后, 原来那个处于等待连接状态的套接字会怎么样呢
     
     
    等待连接的套接字仅作为生产新副本的模板:
     
         其实它还会以等待连接的状态继续存在 当再次调用 accept 客户端连接包到达时, 它又可以再次执行接受连接操作 接受新的连接之后 和刚才一样 协议栈会为这个等待连接的套接字复制一个新的副本,然后让客户端连接到这个新的副本套接字上。 像这样每次为新的连接创建新的套接字就是这一步操作的一个关键点。 如果不创建新副本 而是直接让客户端连接到等待连接的套接字上, 那么就没有套接字在等待连接了 这时如果有其他客户端发起连接就会遇到问题。 为了避免出现这样的情况 协议栈采用了这种创建套接字的新副本, 并让客户端连接到这个新副本上的方法
     
     
    新副本与模板套接字同端口号
     
          此外,创建新套接字时端口号也是一个关键点 端口号是用来识别套接字的, 因此我们以前说不同的套接字应该对应不同的端口号 但如果这样做, 这里就会出现问题 因为在接受连接的时候 新创建的套接字副本就必须和原来的等待连接的套接字具有不同的端口号才行。 这样一来 比如客户端本来想要连接 80 端口上的套接字 结果从另一个端口号返回了包, 这样一来客户端就无法判断这个包到底是要连接的那个对象返回的 , 还是其他程序返回的。 因此,新创建的套接字副本必须和原来的等待连接的套接字具有相同的端口号。 但是这样一来又会引发另一个问题。 端口号是用来识别套接字的 如果一个端口号对应多个套接字, 就无法通过端口号来定位到某一个套接字了。 当客户端的包到达时 如果协议栈只看 TCP 头部中的接收方端口号 ,是无法判断这个包到底应该交给哪个套接字的。 这个问题可以用下面的方法来解决 ,即要确定某个套接字时,不仅使用服务器端套接字对应的端口号,还同时使用客户端的端口号再加上 IP 地 址 总共使用下面 4 种信息来进行判断
     
     

     

          服务器上可能存在多个端口号相同的套接字,但客户端的套接字都是对应不同端口号的,因此我们可以通过客户端的端口号来确定服务器上的某个套接字 不过 使用不同端口号的规则仅限一台客户端的内部 当有多个客户端进行连接时, 它们之间的端口号是可以重复的 因此 我们还必须加上客户端的 IP 地址才能进行判断
     
     
    描述符和端口号的区别
     
          例如 IP 地址为 198.18.203.154的客户端的 1025 端口 就和 IP 地址为 198.18.142.86 的客户端的 1025 口对应不同的套接字 如果能够理解上面这些内容 ,那么关于套接字和端口号的知识就已经掌握得差不多了。说句题外话, 既然通过客户端 IP 地址 客户端端口号 服务器 IP 地址、 服务器端口号这 4 种信息可以确定某个套接字 那么要指代某个套接字时用这 4 种信息就好了 为什么还要使用描述符呢 这个问题很好 不过我们无法用上面 4 种信息来代替描述符 原因是 在套接字刚刚创建好 还没有建立连接的状态下,这 4 种信息是不全的。此外,为了指代一个套接字,使用一种信息(描述符)比使用 4 种信息要简单。 出于上面两个原因, 应用程序和协议栈之间是使用描述符来指代套接字的
     

     

     

     

    服务器的接收操作

         
    1)电信号转数字信号
        

          现在, 客户端发送的网络包已经到达了服务器。到达服务器的网络包其本质是电信号或者光信号,接收信号的过程和客户端是一样的。接收操作的第一步是网卡接收到信号,然后将其还原成数字信息 域网中传输的网络包信号是由 1 0 组成的数字信息与用来同步的时钟信号叠加而成的,因此只要从中分离出时钟信号然后根据时钟信号进行同步,就可以读取并还原出 1 0 的数字信息了。 信号的格式随传输速率的不同而不同,因此某些操作过程可能存在细微差异,例如 10BASE-T 的工作方式如图所示首先从报头部分提取出时钟信号),报头的信号是按一定频率变化的只要测定这个变化的频率就可以和时钟信号同步了。接下来按照相同的周期延长时钟信号图  ②),并在每个时钟周期位置检测信号的变化方向 )。图中用向上和向下的箭头表示变化方向,实际的信号则是正或负的电压这里需要检测电压是从正变为负,还是从负变为正这两种变化方向分别对应 0 1 (图 )。在图中向上的箭头为 1向下的箭头为 0实际上是从负到正变化为 1从正到负变化为 0这样信号就被还原成数字信息了

     

     

    2)FCS校验

          接下来需要根据包末尾的帧校验序列(FCS)来校验错误 即根据校验公式 计算刚刚接收到的数字信息 然后与包末尾的 FCS 值进行比较 。 FCS 值是在发送时根据转换成电信号之前的数字信息进行计算得到的 , 因此如果根据信号还原出的数字信息与发送前的信息一致, 则计算出的 FCS 也应该与包末尾的 FCS 一致 如果两者不一致 则可能是因为噪声等影响导致信号失真, 数据产生了错误 这时接收的包是无效的 因此需要丢弃
     
    3)检查MAC头部
     
         当 FCS 一致 即确认数据没有错误时 接下来需要检查 MAC 头部中的接收方 MAC 地址,看看这个包是不是发给自己的 以太网的基本工作方式是将数据广播到整个网络上, 只有指定的接收者才接收数据 因此网络中还有很多发给其他设备的数据在传输, 如果包的接收者不是自己 那么就需要丢弃这个包。
     
    4)中断:提醒CPU
     
        到这里, 接收信号并还原成数字信息的操作就完成了 还原后的数字信息被保存在网卡内部的缓冲区中。 上面这些操作都是由网卡的 MAC 来完成的。在这个过程中, 服务器的 CPU 并不是一直在监控网络包的到达 而是在执行其他的任务, 因此 CPU 并不知道此时网络包已经到达了 但接下来的接收操作需要 CPU 来参与 因此网卡需要通过中断将网络包到达的事件通知给 CPU 。接下来, CPU 就会暂停当前的工作 并切换到网卡的任务
     
    5)运行网卡驱动,确定协议类型
     
           然后 网卡驱动会开始运行, 从网卡缓冲区中将接收到的包读取出来 根据 MAC头部的以太类型字段判断协议的种类,并调用负责处理该协议的软件 这里, 以太类型的值应该是表示 IP 协议 因此会调用 TCP/IP 协议栈 并将包转交给它
     
    6)调用IP模块
     
        当网络包转交到协议栈时 IP 模块会首先开始工作 检查 IP 头部 IP模块首先会检查 IP 头部的格式是否符合规范,然后检查接收方 IP 地址, 看包是不是发给自己的 当服务器启用类似路由器的包转发功能时 对于 不是发给自己的包 会像路由器一样根据路由表对包进行转发 确认包是发给自己的之后,接下来需要检查包有没有被分片 检查 IP头部的内容就可以知道是否分片 如果是分片的包 则将包暂时存放在内存中, 等所有分片全部到达之后将分片组装起来还原成原始包 如果没有分片, 则直接保留接收时的样子 不需要进行重组 到这里 我们就完成了包的接收。 接下来需要检查 IP 头部的协议号字段,并将包转交给相应的模块 例如, 如果协议号为 06 十六进制 ), 则将包转交给 TCP 模块 如果是 11 十六进制), 则转交给 UDP 模块 这里我们假设这个包被交给 TCP 模块处理, 然后继续往下看。
     

     

    7)调用TCP模块:TCP处理连接包

          前面的步骤对于任何包都是一样的 但后面的 TCP 模块的操作则根据包的内容有所区别
       
          首先 我们来看一下发起连接的包是如何处理的 。 当 TCP 头部中的控制位 SYN 1 表示这是一个发起连接的包(图 )。 这时 TCP 模块会执行接受连接的操作 不过在此之前,需要先检查包的接收方端口号,并确认在该端口上有没有与接收方端口号相同且正在处于等待连接状态的套接字 如果指定端口号没有等待连接的套接字, 则向客户端返回错误通知的包 。如果存在等待连接的套接字, 则为这个套接字复制一个新的副本 将发送方 IP 地址 端口号 、序号初始值、窗口大小等必要的参数写入这个套接字中, 同时分配用于发送缓冲区和接收缓冲区的内存空间 然后生成代表接收确认的 ACK 用于从服务器向客户端发送数据的序号初始值 , 表示接收缓冲区剩余容量的窗口大小, 并用这些信息生成 TCP 头部,委托IP模块发送给客户端 。这个包到达客户端之后, 客户端会返回表示接收确认的 ACK 当这个 ACK 号返回服务器后 连接操作就完成了 。这时, 服务器端的程序应该进入调用 accept 的暂停状态 当将新套接字的描述符转交给服务器程序之后, 服务器程序就会恢复运行
     
    如果收到的是发起连接的包,则 TCP 模块会:
     
    (1) 确认 TCP 头部的控制位 SYN;
    (2) 检查接收方端口号;
    (3) 为相应的等待连 接套接字复制一个新的副本;
    (4) 记录发送方 IP 地址和端口号等信息。
     

     

    8)调用TCP模块:TCP处理数据包

          接下来我们来看看进入数据收发阶段之后 当数据包   到达时 TCP 模块是如何处理的(   )。
     
          首先, TCP 模块会检查收到的包对应哪一个套接字 在服务器端 可能有多个已连接的套接字对应同一个端口号, 因此仅根据接收方端口号无法找到特定的套接字。 这时我们需要根据 IP 头部中的发送方 IP 地址和接收方 IP 地址,以及 TCP 头部中的接收方端口号和发送方端口号共 4 种信息,找到上述 4 种信息全部匹配的套接字
     
          找到 4 种信息全部匹配的套接字之后 TCP 模块会对比该套接字中保存的数据收发状态和收到的包的 TCP 头部中的信息是否匹配 以确定数据收发操作是否正常。 具体来说,就是根据套接字中保存的上一个序号和数据长度计算下一个序号,并检查与收到的包的 TCP 头部中的序号是否一致 如果两者一致 就说明包正常到达了服务器 没有丢失 这时 TCP模块会从包中提出数据, 并存放到接收缓冲区中 与上次收到的数据块连接起来。 这样一来 数据就被还原成分包之前的状态了 当收到的数据进入接收缓冲区后,TCP 模块就会生成确认应答的 TCP头部 并根据接收包的序号和数据长度计算出 ACK 号 然后委托 IP 模块
    发送给客户端 。 收到的数据块进入接收缓冲区, 意味着数据包接收的操作告一段落了
     
        接下来, 应用程序会调用 Socket 库的 read   来获取收到的数据 , 这时数据会被转交给应用程序。 果应用程序不来获取数据,则数据会被一直保存在缓冲区中 但一般来说 应用程序会在数据到达之前调用 read等待数据到达, 在这种情况下 TCP 模块在完成接收操作的同时 就会执行将数据转交给应用程序的操作。然后, 控制流程会转移到服务器程序 对收到的数据进行处理 也就是检查 HTTP 请求消息的内容 并根据请求的内容向浏览器返回相应的数据。

     

    收到数据包时,TCP 模块会:
     
    (1) 根据收到的包的发送方 IP 地址、发送方端口号、接收方 IP 地址、接收方端口号找到相对应的套接字;
    (2) 将数据块拼合起来并保存在接收缓冲区中;
    (3) 向客户端返回 ACK。
     
     
     
    9)调用TCP模块:断开连接操作
     
          当数据收发完成后 便开始执行断开操作 这个过程和客户端是一样的, 让我们简单复习一下 。在 TCP 协议的规则中 断开操作可以由客户端或服务器任何一方发起, 具体的顺序是由应用层协议决定的 Web 这一顺序随 HTTP 协议版本不同而不同, HTTP1.0 是服务器先发起断开操作 。这时, 服务器程序会调用 Socket 库的 close TCP 模块会生成一个控制位 FIN 1 TCP 头部 并委托 IP 模块发送给客户端 当客户端收到在返回 ACK 号之前,会先等待一段时间,看看能不能和后续的应答包合并。 这个包之后 会返回一个 ACK 接下来客户端调用 close 生成一个 FIN 1 TCP 头部发给服务器 服务器再返回 ACK 这时断开操作 就完成了 HTTP1.1 是客户端先发起断开操作 这种情况下只要将客 户端和服务器的操作颠倒一下就可以了 无论哪种情况,当断开操作完成后,套接字会在经过一段时间后被删除。
     
     
    展开全文
  • 服务器主动推送消息数据客户端

    万次阅读 2019-02-11 15:17:14
    1 引言 这个问题第一次是我在实现...再后来是在做一个机器学习的问题时候,因为机器学习模型的运行需要综合多个客户端(边缘节点)的数据,然后得到结果,而且各个客户端数据传输是不一致的,时间和数据量不定。...

    1 引言

    这个问题第一次是我在实现一个导师的方案的时候所发现的,一开始我需要实现服务器与客户端的密钥协商和数据传递,服务器需要主动分发(推送)密钥给客户端,因为以前没有做过相关编码,后来只能想到用反向连接,也就是交换C/S的身份

    再后来是在做一个机器学习的问题时候,因为机器学习模型的运行需要综合多个客户端(边缘节点)的数据,然后得到结果,而且各个客户端的数据传输是不一致的,时间和数据量不定。所以需要在服务器处理各个客户端的数据,得到结果后主动推送结果给客户端。所以准备研究一下这个问题:服务器主动推送消息数据给客户端,包括web服务器与浏览器,后台服务器与Android,服务器之间。

    定义:推送技术是指通过客户端与服务器端建立连接,客户端可以接收由服务器端不定时主动发送的消息。

    2 相关技术

    这种技术分析来看在web端和Android端应该用的很多。比如服务器主动推送一条优惠信息给某个客户端实时提醒客户端有线上订单消息,类似于邮件、消息、待办事项等的通知

    2.1 B/S架构

    B/S架构的标准是基于HTTP协议,客户端发送请求,服务器给出响应的一问一答形式进行通信。这就意味着如果客户端不主动地向服务器发送消息,服务器就无法主动给客户端推送消息。

    随着HTML、浏览器等各项技术、标准的发展,依次生成了不同的手段与方法能够实现服务端主动推送消息,包括AJAX,Comet,ServerSent以及WebSocket。

    适用场景:实时股票价格、商品价格、实时新闻、Twitter/weibo timeline、基于浏览器的聊天系统。

    F5手动刷新 --> AJAX轮询(Polling) --> Comet实时更新 --> HTML5实时通信

    meta标签

    在 Web早期,通过配置meta标签让浏览器自动刷新,从而实现服务器端的推送

     <META HTTP-RQUIV="Refresh" CONTENT=12>
    

    优点:使用方式简单,可以在JS禁用情况下使用
    缺点:不是实时更新数据,对服务器造成的压力大,带宽浪费多

    2.1.1 AJAX轮询

    正常的一个页面在浏览器中是这样工作的:

    1. 用户向浏览器输入需要访问的地址URL
    2. 浏览器根据URL访问服务器,并与服务器之间创建一个TCP连接(HTTP请求,也有可能是UDP)
    3. 服务器根据这个URL和一些参数,回复一个响应,可能是一段HTML文本,也可能是一个图片。将其写入TCP连接,然后关闭连接(长连接则会保持多个请求-响应)
    4. 浏览器得到了来自服务器的HTML文本,解析并呈现渲染到浏览器上给用户浏览

    此时,用户点击了网站上任何一个<a>或触发任何一个<form>提交时:

    1. 浏览器根据form的参数或者a的参数,作为访问的地址
    2. 与服务器创建TCP连接
    3. 服务器组建HTML文本,然后关闭连接
    4. 浏览器将当前显示的页面摧毁,并按照新的HTML文本呈现一个新的页面给用户

    我们不难发现的是整个过程中间,一旦建立了一个连接,页面就无法再维护住了。

    Http轮询:

    即定时的通过Ajax查询服务器端,客户端定时向服务器端发送ajax请求,服务器端接收到请求后马上响应信息并关闭连接。要求两次请求间隔时间必须尽可能的小,但若时间间隔减小,客户端浏览器在相同时间内就会发出更多的请求,这些请求中大部分都不会返回有用的数据,这会白白地浪费掉带宽和处理资源。

    JSONP轮询:

    JSONP轮询和HTTP轮询类似,不同之处在于使用JSONP可以发送跨域请求(请求不属于您所在的域),JSONP请求通常可以通过它的调用参数和返回内容识别出来,其是可执行的JavaScript代码。要想在 JavaScript 中实现轮询,可以使用setInterval来定期地发出 Ajax 请求。

    这种技术实现起来非常简单,但它不具有伸缩性,需要不断地向服务器端发送消息,会对服务器造成极大的性能浪费,加重网络负载,拖累服务器。

    Piggyback polling(捎带轮询):

    捎带轮询是一种比轮询更聪明的做法,它会删除所有非必需的请求(没有返回数据的那些),且不存在时间间隔,客户端在需要的时候向服务器端发送请求。不同之处在于响应的部分,响应被分成两个部分:对请求数据的响应和对服务器时间的响应。捎带轮询通常针对服务器端的所有 Ajax 请求可能会返回一个混合的响应。

    这种方法因为客户端控制了何时发送请求,所以没有不返回数据的请求,对资源的消耗较少,可用在所有浏览器上。但这仍然算是客户端主动去请求服务器端,当服务器累积了事件想要传送端户端时,在客户端没有发送请求时也不能主动发送给客户端。

     

    此时我们学习一下XmlHttpRequest组件,这个组件提供我们手动创建一个HTTP请求,发送我们想要的数据,服务器也可以只返回我们想要的结果,最大的好处是,当我们收到服务器的响应时,原来的页面没有被摧毁。这就好比,我喊一句"我的咖啡喝完了,我要续杯",然后服务员就拿了一杯咖啡过来,而不是会把我没吃完的套餐全部倒掉。术语就是局部刷新增量式的更新

    其实就是定时的通过Ajax查询服务端客户端按规定时间定时像服务端发送ajax请求,服务器接到请求后马上返回响应信息并关闭连接

    当我们利用AJAX实现服务器推送时,其实质是客户端不停地向服务器询问"有没有给我的消息呀?",然后服务器回答"有"或"没有"来达到的实现效果。它的实现方法也很简单,利用jQuery框架封装好的AJAX调用也很方便。

    其存在一个问题:

    1. 间隔时间越快,推送的及时性越好,服务器的消费越大;
    2. 间隔时间越慢,推送的及时性越低,服务器的消费越小。

    还有一个类似的轮询是使用JSONP跨域请求的方式轮询,在实现起来有差别,但基本原理都是相同的,都是客户端不断的向服务器发起请求。
    优点:
    服务端逻辑简单,编码实现简单。
    缺点:
    这是通过模拟服务器发起的通信,不是实时通信,不顾及应用的状态改变而盲目检查更新,导致服务器资源的浪费,且会加重网络负载,拖累服务器。其中大多数请求可能是无效请求,在大量用户轮询很频繁的情况下对服务器的压力很大。

    应用:并发用户量少,而且要求消息的实时性不高,一般很少采用。

    会造成数据同步不及时无效的请求增加后端处理压力

    总结:严格地来说,这种实际方式,并不是真正意义上的服务器主动推送消息,但由于早期技术手段缺乏,所以AJAX轮循成为了一种很普遍的手段。

    2.1.2 Comet

    轮询方式的实时性是不够的,比如基于Web的聊天功能,对实时性要求就很高。于是,comet出现了。Comet是基于HTTP长连接的服务器推送技术(long lived http),主要有流(streaming)方式长轮询(long-polling)方式,都是基于AJAX。Comet工作原理:用户发起请求后就挂起,等待服务器返回数据,在此期间不会断开连接。流方式和长轮询方式的区别是:对于流方式,客户端发起连接就不会断开连接,而是由服务器端进行控制。当服务器端有更新时,刷新数据,客户端进行更新;而对于长轮询,当服务器端有更新返回,客户端先断开连接,进行处理,然后重新发起连接

    基于 HTTP 长连接的 "服务器推送" 技术,能使服务器端主动以异步的方式向客户端程序推送数据,而不需要客户端显式的发出请求,目前有两种实现方式:

    1. 基于 AJAX 的长轮询(long-polling)方式(长连接)
    Ajax 的出现使得 JavaScript 可以调用 XMLHttpRequest 对象发出 HTTP 请求,JavaScript 响应处理函数根据服务器返回的信息对 HTML 页面的显示进行更新。使用 AJAX 实现 "服务器推送" 与传统的 AJAX 应用不同之处在于:

    • 服务器端会阻塞请求直到有数据传递或超时才返回。(不是在一个长连接里客户端轮询,而是基于请求-响应模型,在一个长连接里等待服务器响应,称之为长轮询
    • 客户端 JavaScript 响应处理函数会在处理完服务器返回的信息后,再次发出请求,重新建立连接。
    • 当客户端处理接收的数据、重新建立连接时,服务器端可能有新的数据到达。这些信息会被服务器端保存直到客户端重新建立连接,客户端会一次把当前服务器端所有的信息取回。

    在Ajax轮询基础上做的一些改进,在没有更新的时候不再返回空响应,而且把连接保持到有更新的时候,客户端向服务器发送Ajax请求,服务器接到请求后hold住连接,直到有新消息才返回响应信息并关闭连接,客户端处理完响应信息后再向服务器发送新的请求。

    普通Ajax轮询与基于Ajax的长轮询原理对比: 

    Ajax

    基于长轮询的服务器推模型

    从上图可以看出,客户端发出请求后挂起,服务端在接到请求也挂起,等待有更新时返回数据并断掉连接,然后客户端再发起新的连接。
           相对于"轮询"(poll),这种长轮询方式也可以称为"拉"(pull)。因为这种方案基于 AJAX,具有以下一些优点:请求异步发出;无须安装插件;IE、Mozilla FireFox 都支持 AJAX。长轮询 (long polling) 是在打开一条连接以后保持并等待服务器推送来数据再关闭,可以采用HTTP长轮询XHR长轮询两种方式:
           (1). HTTP 和JSONP方式的长轮询
           把 script 标签附加到页面上以让脚本执行。服务器会挂起连接直到有事件发生,接着把脚本内容发送回浏览器,然后重新打开另一个 script 标签来获取下一个事件,从而实现长轮询的模型

           实现可以使用 script 标签或是单纯的XMLHttpRequest对象来实现 HTTP 长轮询。

           (2).XHR长轮询
            这种方式是使用比较多的长轮询模式。客户端打开一个到服务器端的 AJAX 请求然后等待响应;服务器端需要一些特定的功能来允许请求被挂起,只要一有事件发生,服务器端就会在挂起的请求中送回响应并关闭该请求。客户端 JavaScript 响应处理函数会在处理完服务器返回的信息后,再次发出请求,重新建立连接;如此循环。现在浏览器已经支持CROS的跨域方式请求,因此HTTP和JSONP的长轮询方式是慢慢被淘汰的一种技术,建议采用XHR长轮询
    优点:

    • 客户端很容易实现良好的错误处理系统和超时管理,实现成本与Ajax轮询的方式类似。
    • 实时性高,无消息的情况下不会进行频繁的请求。

    缺点:

    • 需要服务器端有特殊的功能来临时挂起连接。当客户端发起的连接较多时,服务器端会长期保持多个连接,具有一定的风险。
    • 服务器维持着连接期间会消耗资源。

    注>>在这里简单的说明下长轮询,长连接的概念。

    轮询:客户端定时向服务器发送Ajax请求,服务器接到请求后马上返回响应信息并关闭连接。

    优点:后端程序编写比较容易。

    缺点:请求中有大半是无用,浪费带宽和服务器资源。

    实例:适于小型应用。

    长轮询:客户端向服务器发送Ajax请求,服务器接到请求后hold住连接,直到有新消息才返回响应信息并关闭连接,客户端处理完响应信息后再向服务器发送新的请求。

    long poll的原理是:客户端与服务器将建立一条长连接,也就是说,客户端会发出一个请求,而服务器,将阻塞请求,直到有数据需要传递,才会返回。 返回之后,客户端将关闭此连接,然后再次发出一个请求,建立一个新的连接,再次等待服务器推送数据。客户端与服务器端的连接,会一直保存着,当发生改变时,服务器才会把数据推送给客户端。这其实,就是设计模式中的观察者模式

    优点:在无消息的情况下不会频繁的请求。缺点:服务器hold连接会消耗资源。

    实例:WebQQ、Hi网页版、Facebook IM。

    另外,对于长连接和socket连接也有区分:

    长连接:在页面里嵌入一个隐蔵iframe,将这个隐蔵iframe的src属性设为对一个长连接的请求,服务器端就能源源不断地往客户端输入数据。一般指利用心跳包保持一个连接,从而延长连接持续时间。(持久连接一个HTTP连接,发送多个请求和响应)。

    优点:消息即时到达,不发无用请求。缺点:服务器维护一个长连接会增加开销。

    实例:Gmail聊天 Flash

    Socket:在页面中内嵌入一个使用了Socket类的 Flash 程序,JavaScript通过调用此Flash程序提供的Socket接口与服务器端的Socket接口进行通信,JavaScript在收到服务器端传送的信息后控制页面的显示。

    优点:实现真正的即时通信,而不是伪即时。缺点:客户端必须安装Flash插件;非HTTP协议,无法自动穿越防火墙。

    实例:网络互动游戏。
    2. 基于iframe 及 htmlfile 的流(streaming)方式
            iframe 是很早就存在的一种 HTML 标记, 通过在 HTML 页面里嵌入一个隐蔵frame,然后将这个隐蔵frame的 SRC 属性设为对一个长连接的请求,服务器端就能源源不断地往客户端输入数据。iframe 流方式是在页面中插入一个隐藏的 iframe,利用其 src 属性在服务器和客户端之间创建一条长链接,服务器向 iframe 传输数据(通常是 HTML,内有负责插入信息的 javascript),来实时更新页面。从技术上来讲,两种常见的流技术包括 Forever Iframe(或者 hidden IFrame),或是被用来在 JavaScript 中创建 Ajax 请求的XMLHttpRequest对象的多部分 (multi-part) 特性
    基于流方式的服务器推模型

    从上图可以看出,每次数据传送不会关闭连接,连接只会在通信出现错误时,或是连接重建时关闭(一些防火墙常被设置为丢弃过长的连接, 服务器端可以设置一个超时时间, 超时后通知客户端重新建立连接,并关闭原来的连接)。

    iframe流方式的优点是浏览器兼容好,但没有方法来实现可靠的错误处理或跟踪连接的 状态,且有些在缓冲方面有问题。

    • 优点:消息能够实时到达。这种方式每次数据传送不会关闭连接,连接只会在通信出现错误时,或是连接重建时关闭(一些防火墙常被设置为丢弃过长的连接, 服务器端可以设置一个超时时间, 超时后通知客户端重新建立连接,并关闭原来的连接)。
    • 缺点:服务器维持着长连接期间会消耗资源。IE、Morzilla Firefox 下端的进度栏都会显示加载没有完成,而且 IE 上方的图标会不停的转动,表示加载正在进行。

    Google 的天才们使用一个称为“htmlfile”的 ActiveX 解决了在 IE 中的加载显示问题,并将这种方法用到了 gmail+gtalk 产品中。Alex Russell 在 “What else is burried down in the depth’s of Google’s amazing JavaScript?”文章中介绍了这种方法。Zeitoun 网站提供的 comet-iframe.tar.gz,封装了一个基于 iframe 和 htmlfile 的 JavaScript comet 对象,支持 IE、Mozilla Firefox 浏览器,可以作为参考。


    Comet的优缺点:
    优点: 实时性好(消息延时小);性能好(能支持大量用户)
    缺点: 长期占用连接,丧失了无状态高并发的特点。
    Comet实现框架:
    1. Dojo CometD —— http://cometdproject.dojotoolkit.org/
    2. DWR —— http://directwebremoting.org/dwr/index.html
    3. ICEfaces —— http://www.icefaces.org/main/home/
    4. GlassFish Grizzly —— https://grizzly.dev.java.net/
           CometD 目前实现 Comet 比较成熟, DWR 弱一些。 ICEfaces 更商业化,实现得很成熟。 Grizzly 是基于GlassFish ,也很成熟。CometD, DWR 开源性好。

    CometD

    CometD 框架是基于 HTTP 的事件驱动通信解决方案,使用了Bayeux通信协议,提供了一个 Java 服务器部件和一个 Java 客户端部件,还有一个基于 jQuery 和 Dojo 的 JavaScript 客户端库。

    Bayeux 通信协议主要是基于 HTTP,提供了客户端与服务器之间的响应性双向异步通信。Bayeux 协议基于通道进行通信,通过该通道从客户端到服务器、从服务器到客户端或从客户端到客户端(但是是通过服务器)路由和发送消息。Bayeux 是一种 “发布- 订阅” 协议。

    CometD 与三个传输协议绑定在一起:JSON、JSONP 和 WebSocket。他们都依赖于 Jetty Continuations 和 Jetty WebSocket API。在默认情况下,可以在 Jetty 6、Jetty 7、和 Jetty 8 中以及其他所有支持 Servlet 3.0 Specification 的服务中使用 CometD。

    服务器和内部构件

    Atmosphere框架

    Atmosphere提供了一个通用 API,以便使用许多 Web 服务器(包括 Tomcat、Jetty、GlassFish、Weblogic、Grizzly、JBossWeb、JBoss 和 Resin)的 Comet 和 WebSocket 特性。它支持任何支持 Servlet 3.0 Specification 的 Web 服务器。

    Atmosphere 提供了一个 jQuery 客户端库,该库可以使连接设置变得更容易,它能够自动检测可以使用的最佳传输协议(WebSockets 或 CometD)。Atmosphere 的 jQuery 插件的用法与 HTML5 WebSockets API 相似。

    Pushlet

    Pushlet 使用了观察者模型:客户端发送请求,订阅感兴趣的事件;服务器端为每个客户端分配一个会话 ID 作为标记,事件源会把新产生的事件以多播的方式发送到订阅者的事件队列里。

    Pushlet 最后更新于2010年2月5号,之后至今没有再更新。

    Cometd 和Atmosphere框架参见示例代码 (https://github.com/brucefengnju/cometdatoms)。

    Bayeux 是一套基于 Publish / Subscribe 模式,以 JSON 格式在浏览器与服务器之间传输事件的通信协议。该协议规定了浏览器与服务器之问的双向通信机制,克服了传统 Web 通信模式的缺点。

    Bayeux 协议主要基于 HTTP 来传输低延迟的、异步的事件消息。这些消息通过频道 (Channels) 来投递,能够实现从服务器端到客户端、从客户端到服务器端或者通过服务器从一个客户端到另一个客户端的传送。Bayeux 协议的主要目的是为使用了 Ajax 和 Comet 技术的 Web 客户端实现高响应的用户交互。Bayeux 协议旨在通过允许执行者更容易的实现互操作性,来降低开发 Comet 应用程序的复杂性。它解决了共同的消息发布和路由问题,并提供了渐进式的改进和扩展机制。

    Dojo 已经对 Cometd 做了封装,基于 Dojo 的 Cometd 包,我们不用再浪费大量的代码在搭建 Cometd 框架上。对于前端脚本代码,我们只需要加上一个 Cometd 包的简单接口代码,便可以开始加入我们自己的业务逻辑代码了。

     

    Comet实现要点:
    不要在同一客户端同时使用超过两个的 HTTP 长连接
    我们使用 IE 下载文件时会有这样的体验,从同一个 Web 服务器下载文件,最多只能有两个文件同时被下载。第三个文件的下载会被阻塞,直到前面下载的文件下载完毕。这是因为 HTTP 1.1 规范中规定,客户端不应该与服务器端建立超过两个的 HTTP 连接, 新的连接会被阻塞。而 IE 在实现中严格遵守了这种规定。
    HTTP 1.1 对两个长连接的限制,会对使用了长连接的 Web 应用带来如下现象:在客户端如果打开超过两个的 IE 窗口去访问同一个使用了长连接的 Web 服务器,第三个 IE 窗口的 HTTP 请求被前两个窗口的长连接阻塞。所以在开发长连接的应用时, 必须注意在使用了多个 frame 的页面中,不要为每个 frame 的页面都建立一个 HTTP 长连接,这样会阻塞其它的 HTTP 请求,在设计上考虑让多个 frame 的src共用一个长连接。
    服务器端的性能和可扩展性
    一般 Web 服务器会为每个连接创建一个线程,如果在大型的商业应用中使用 Comet,服务器端需要维护大量并发的长连接。在这种应用背景下,服务器端需要考虑负载均衡和集群技术;或是在服务器端为长连接作一些改进。
           应用和技术的发展总是带来新的需求,从而推动新技术的发展。HTTP 1.1 与 1.0 规范有一个很大的不同:1.0 规范下服务器在处理完每个 Get/Post 请求后会关闭套接口连接; 而 1.1 规范下服务器会保持这个连接,在处理两个请求的间隔时间里,这个连接处于空闲状态。 Java 1.4 引入了支持异步 IO 的 java.nio 包。当连接处于空闲时,为这个连接分配的线程资源会返还到线程池,可以供新的连接使用当原来处于空闲的连接的客户发出新的请求,会从线程池里分配一个线程资源处理这个请求。 这种技术在连接处于空闲的机率较高、并发连接数目很多的场景下对于降低服务器的资源负载非常有效。
           但是 AJAX 的应用使请求的出现变得频繁,而 Comet 则会长时间占用一个连接,上述的服务器模型在新的应用背景下会变得非常低效,线程池里有限的线程数甚至可能会阻塞新的连接。Jetty 6 Web 服务器针对 AJAX、Comet 应用的特点进行了很多创新的改进。
    控制信息与数据信息使用不同的 HTTP 连接
           使用长连接时,存在一个很常见的场景:客户端网页需要关闭,而服务器端还处在读取数据的堵塞状态,客户端需要及时通知服务器端关闭数据连接。服务器在收到关闭请求后首先要从读取数据的阻塞状态唤醒,然后释放为这个客户端分配的资源,再关闭连接。所以在设计上,我们需要使客户端的控制请求和数据请求使用不同的 HTTP 连接,才能使控制请求不会被阻塞。
           在实现上,如果是基于 iframe 流方式的长连接,客户端页面需要使用两个 iframe,一个是控制帧,用于往服务器端发送控制请求,控制请求能很快收到响应,不会被堵塞;一个是显示帧,用于往服务器端发送长连接请求。如果是基于 AJAX 的长轮询方式,客户端可以异步地发出一个 XMLHttpRequest 请求,通知服务器端关闭数据连接。
    在客户和服务器之间保持“心跳”信息
           在浏览器与服务器之间维持一个长连接会为通信带来一些不确定性:因为数据传输是随机的,客户端不知道何时服务器才有数据传送。服务器端需要确保当客户端不再工作时,释放为这个客户端分配的资源,防止内存泄漏。因此需要一种机制使双方知道大家都在正常运行。在实现上:服务器端在阻塞读时会设置一个时限,超时后阻塞读调用会返回,同时发给客户端没有新数据到达的心跳信息。此时如果客户端已经关闭,服务器往通道写数据会出现异常,服务器端就会及时释放为这个客户端分配的资源。如果客户端使用的是基于 AJAX 的长轮询方式,服务器端返回数据、关闭连接后,经过某个时限没有收到客户端的再次请求,会认为客户端不能正常工作,会释放为这个客户端分配、维护的资源。当服务器处理信息出现异常情况,需要发送错误信息通知客户端,同时释放资源、关闭连接。

    我们知道HTTP请求其实是基于TCP连接实现的(也有少部分基于UDP),再看看之前说的HTTP请求处理过程:

    1. 客户端与服务器建立TCP连接
    2. 服务器根据客户端提交的报文处理并生成HTML文本
    3. 将HTML封闭成为HTTP协议报文并返回给客户端
    4. 关闭链接。

    看到这个处理过程,我们不难联想到,如果把第4步——关闭连接给省掉,那不就相当于有一个长连接一直被维持住了么。通过对服务端的一些操作,我们可以直接将数据从这个TCP连接发送客户端了。

    通过这种技术,我们可以大大提高服务器推送的实时性,还可以减去服务端不停地建立、施放连接所形成的开销。

    目前市面上有不少基于AJAX实现的Comet机制,但主要有两种方式:

    1. 建立连接后依然使用"询问"+"应答"的模式。虽然工作方式没变,但是因为使用长连接,减去了每次建立与施放连接的工作,所以性能上提升了很多。而且服务器对TCP连接可以有上下文的定义,而不像以前的AJAX完全是无状态的
    2. 通过对Stream的写入实现服务器将数据主动发送到客户端。因为是TCP连接,所以通过对服务器的编程,我们可以主动的把数据从服务端发送给客户端,从模式上真正建立起了推送的概念

    总结:基本实现了的服务器主动推送消息。服务器可以随时可以推送数据给客户端,但需要保持一个长连接,浪费资源。

    2.1.3 Server-Sent

    Server-Sent是HTML5提出一个标准,它延用了Comet的思路,并对其进行了一些规范。使得Comet这项技术由原来的分支衍生技术转成了正统的官方标准。Server-Sent Events包含新的HTML元素EventSource新的MIME类型 text/event-stream,这个MIME类型定义了事件框架格式。Server-Sent Event是基于HTTP streaming的。response会一直打开,当服务器端有事件发生的时候,事件会被写入response中。

    Server-sent-events(SSE)让服务端可以向客户端流式发送文本消息,在实现上,客户端浏览器中增加EventSource对象,使其能通过事件的方式接收到服务器推送的消息,在服务端,使用长连接的事件流协议,即请求响应时增加新数据流数据格式。非常适应于后端数据更新频繁且对实时性要求较高而又不需要客户端向服务端通信的场景下。

    它的原理与Comet相同,由客户端发起与服务器之间创建TCP连接,然后并维持这个连接,至到客户端或服务器中的做任何一放断开,ServerSent使用的是"问"+"答"的机制,连接创建后浏览器会周期性地发送消息至服务器询问,是否有自己的消息。本质还是轮询,只不过维持一个长连接,在长连接里进行轮询。

    这项标准不仅要求了支持的浏览器能够原生态的创建与服务器的长连接,更要求了对JavaScript脚本的统一性,使得兼程该功能的浏览器可以使用同一套代码完成Server-Sent的编码工作。

    优点

    • 基于现有http协议,实现简单
    • 断开后自动重连,并可设置重连超时
    • 派发任意事件
    • 跨域并有相应的安全过滤

    缺点

    • 只能单向通信,服务器端向客户端推送事件
    • 事件流协议只能传输UTF-8数据,不支持二进制流。
    • IE下目前不支持EventSource
    • 如果代理服务器或中间设备不支持SSE,会导致连接失效,正式环境中使用通过TLS协议发送SSE事件流。

    总结:总得来说SeverSent就是HTML5规范下的Comet,具有更好的统一性,而且简单好用。严格来说,技术本身没有什么改进。

    过渡:

    插件提供 socket 方式:比如利用 Flash XMLSocket,Java Applet 套接口,Activex 包装的 socket。

    • 优点:原生 socket 的支持,和 PC 端和移动端的实现方式相似。
    • 缺点:浏览器端需要装相应的插件。

    2.1.4 WebSocket

    Comet和SSE仍然是单向通信(服务器向客户端),不能适应Web应用的飞速发展。于是,各种新技术不断涌现,其中WebSocket在Google的力推之下已经成为业界标准,并不断完善中。其基于TCP之上定义了帧协议,支持双向的通信。

    WebSocket是一种全新的协议,不属于http无状态协议,协议名为ws协议或wss协议。ws不是http,所以传统的web服务器不一定支持。一个websocket连接地址会是这样的写法:ws://wilsonliu.cn:8080/webSocketServer。

    WebSocket是HTML5开始提供的一种在单个 TCP 连接上进行全双工通讯的协议。WebSocket通讯协议于2011年被IETF定为标准RFC 6455,WebSocket API被W3C定为标准。在WebSocket API中,浏览器和服务器只需要做一个握手的动作,然后,浏览器和服务器之间就形成了一条快速通道。两者之间就直接可以数据互相传送。实现了浏览器与服务器的全双工通信,扩展了浏览器与服务端的通信功能,使服务端也能主动向客户端发送数据。

    看名字就知道了,这是一个可以用在浏览器上的Socket连接。这也是一个HTML5标准中的一项内容,他要求浏览器可以通过JavaScript脚本手动创建一个TCP连接与服务端进行通讯。因为Socket是一个全双工的TCP和UDP连接,所以不需要轮询,只需要保持长连接和心跳包。通信协议的header也很小,相比与之前的long poll, web socket 能够更好的节省服务器资源和宽带并达到实时通信。

    WebSocket通信协议包含两个部分,一是开放性HTTP握手连接协商连接参数二是二进制消息分帧机制(接收消息的文本和二进制数据传输)。它是一个独立完善的协议,也可以在浏览器之外实现。

    另外WebSocket使用了ws和wss协议(表示使用加密信道通信(TCP + TLS),支持接收和发送文本和二进制数据,需要服务器有与之握手的算法才能将连接打开。

    所以WebSocket相对于之前几种手段来说,其编码量是最大的(Socket的编码量就是大),但由于没有其它的约束,因此它也可以自由地实现所有可能的功能。

    即可以满足"问"+"答"的响应机制,也可以实现主动推送的功能。与ServerSent相同,HTML5也对WebSocket调用的JavaScript进行规范。WebSocket具有较为复杂的协议,需要在服务端做额外编程才能进行数据通讯。

    总结:将Socket的建立移植到了浏览器的脚本端,JS可以创建连接与服务器通讯,包括请求-响应模型主动轮询的推送模式等。技术本身没有什么改进。最大特点就是,服务器可以主动向客户端推送信息,客户端也可以主动向服务器发送信息,是真正的双向平等对话,属于服务器推送技术的一种。

    服务端实现

    • Node (Socket.IO, WebSocket-Node, ws)
    • Java (Jetty)
    • Python (Tornado, pywebsocket)
    • swoole:官方写的websocket php客户端 https://github.com/swoole/swoole-src/edit/master/examples/websocket/WebSocketClient.php
    • ...

    使用场景:

    适合于对数据的实时性要求比较强的场景,如通信、股票、Feed、直播、共享桌面,特别适合于客户端与服务频繁交互的情况下,如实时共享、多人协作等平台

    优点:

    • 真正的全双工通信
    • 支持跨域设置(Access-Control-Allow-Origin)
    • 更好的节省服务器资源和带宽并达到实时通讯

    缺点

    • 采用新的协议,后端需要单独实现
    • 客户端并不是所有浏览器都支持,目前还未普及,浏览器支持不好
    • 代理服务器会有不支持websocket的情况
    • 无超时处理
    • 更耗电及占用资源

    代理和很多现有的HTTP中间设备可能不理解新的WebSocket协议,而这可能导致各种问题,使用时需要注意,可以使借助TLS,通过建立一条端到端的加密信道,可以让WebSocket通信绕过所有中间代理。

    HTML5 WebSocket 设计出来的目的就是要取代轮询和 Comet 技术,使客户端浏览器具备像 C/S 架构下桌面系统的实时通讯能力。浏览器向服务器发出建立 WebSocket 连接的请求,连接建立以后,客户端和服务器端就可以通过 TCP 连接直接交换数据。因为 WebSocket 连接本质上就是一个 TCP 连接,所以在数据传输的稳定性和数据传输量的大小方面,和轮询以及 Comet 技术比较,具有很大的性能优势。

    使用方法:

    WebSocket 协议本质上是一个基于 TCP 协议。为了建立一个 WebSocket 连接,客户端浏览器首先要向服务器发起一个 HTTP 请求,这个请求和通常的 HTTP 请求不同,包含了一些附加头信息,其中附加头信息 ”Upgrade: WebSocket” 表明这是一个申请协议升级的 HTTP 请求(详细的 WebSocket 消息的内容这里就不详细说了,基本和 HTTP 的差不多,而且都是由 WebSocket 对象自动发送和接收的,对用户透明),服务器端解析这些附加的头信息然后产生应答信息返回给客户端,客户端和服务器端的 WebSocket 连接就建立起来了,双方就可以通过这个连接通道自由的传递信息,并且这个连接会持续存在直到客户端或者服务器端的某一方主动的关闭连接。

    WebSocket API 最伟大之处在于服务器和客户端可以在给定的时间范围内的任意时刻,相互推送信息。WebSocket 并不限于以 Ajax (或 XmlHttpRequest)方式通信,因为 Ajax 技术需要客户端发起请求,而 WebSocket 服务器和客户端可以彼此相互推送信息;XmlHttpRequest 通信受到域的限制,而 WebSocket 允许跨域通信

    需要注意的问题是,除了安全和性能以外,服务端只管往 socket 里面写数据就可以了,WebSocket 的通信数据全部是以 ”\x00″ 开头以 ”\xFF” 结尾的,无论是服务端发出的数据还是客户端发送的数据都遵从这个格式,唯一不同的是客户端的 WebSocket 对象能够自动将头尾去除,获得主体数据,这就省却了在客户端处理原始数据的必要,而且 WebSocket 通信的消息总是 UTF-8 格式的。

    应用场景:

    WebSocket广泛应用于社交聊天、直播、弹幕、多玩家游戏、协同编辑、股票基金实时报价、体育实况更新、视频会议/聊天、基于位置的应用、在线教育、智能家居等需要高实时的场景

    WebSocket特点:

    • 建立在 TCP 协议之上,服务器端的实现比较容易。
    • 与 HTTP 协议有着良好的兼容性,能通过各种 HTTP 代理服务器。
    • 数据格式比较轻量,性能开销小,通信高效。
    • 可以发送文本,也可以发送二进制数据。
    • 没有同源限制,客户端可以与任意服务器通信。
    • 协议标识符是ws(如果加密,则为wss),服务器网址就是 URL。
    • 能实现真正意义上的数据推送。

    2.1.5 WebSocket + MessageQueue

    MessageQueue,简称MQ,也就是消息队列。是一种常常用于TCP服务端的技术。通过生产和访问各种消息类型,MQ服务器会将生产者所生成的消息发给感兴趣的客户端。市面上有很多的MQ框架,比如:ActiveMQ。本质上是一种发布-订阅模式

    ActiveMQ已经支持了WebSocket协议,也就意味着,WebSocket已经可以作为一个生产者或一个消费者,与MQ服务器连接。

    开发者可以通过MQTT的JS脚本,连接上MQ服务器,同时将Web服务器也连上MQ服务器,从此可以告别了HTTP通讯协议,完完全全使用Socket通讯来完成数据的交换。

    Java消息服务(Java Message Service)是message数据传输的典型的实现方式,是一种发布-订阅模式。系统A和系统B通过一个消息服务器进行数据交换。系统A发送消息到消息服务器,如果系统B订阅系统A发送过来的消息,消息服务器会消息推送给B。双方约定消息格式即可。目前市场上有很多开源的jms消息中间件,比如  ActiveMQ, OpenJMS 。

    2.2 C/S架构

    C/S架构主要指服务器与应用之间的通信架构,比如服务器与Android App之间的通信,不包括基于H5的移动App。

    其实上述的B/S架构所用的六种方法可以移植或者借鉴到C/S架构。所以下述的几种方法都是C/S架构特有的方法。

    2.2.1 反向连接

           如果不是B/S架构,可以交换C/S身份,进行连接。我们称之为反向/反弹连接,有点类似于反弹木马的行为方式。此时(原服务器端)客户端向服务器端(原客户端端)发送通信数据包。

    此时要求服务器有公网IP,并且FW不限制连接建立。

    2.2.2 SMS(Push)方式

          SMS(Push)方式:通过拦截SMS消息并且解析消息内容来了解服务器的命令,但这种方式一般用户在经济上很难承受。

    2.2.3 现有的推送方案

    A、C2DM云端推送方案

           在Android手机平台上,Google提供了C2DM(Cloud to Device Messaging)服务。Android Cloud to Device Messaging (C2DM)是一个用来帮助开发者从服务器向Android应用程序发送数据的服务。该服务提供了一个简单的、轻量级的机制,允许服务器可以通知移动应用程序直接与服务器进行通信,以便于从服务器获取应用程序更新和用户数据。

    总结:服务器发送通知给C2DM服务器,C2DM服务器发送通知给Android系统的C2DM服务,C2DM服务通知移动应用主动连接服务器,以获取服务器发送的数据。

    该方案存在的主要问题是C2DM需要依赖于Google官方提供的C2DM服务器,由于国内的网络环境,这个服务经常不可用。

    B、MQTT协议实现Android推送

           采用MQTT协议实现Android推送功能也是一种解决方案(https://legacy.gitbook.com/book/mcxiaoke/mqtt-cn/details)。MQTT是一个轻量级的消息发布/订阅协议,它是实现基于手机客户端的消息推送服务器的理想解决方案。 wmqtt.jar 是IBM提供的MQTT协议的实现。我们可以从这里(https://github.com/tokudu/AndroidPushNotificationsDemo)下载该项目的实例代码,并且可以找到一个采用PHP书写的服务器端实现(https://github.com/tokudu/PhpMQTTClient)。

    C、RSMB实现推送功能

           Really Small Message Broker (RSMB) ,是一个简单的MQTT代理,同样由IBM提供,其查看地址是:http://www.alphaworks.ibm.com/tech/rsmb。缺省打开1883端口,应用程序当中,它负责接收来自服务器的消息并将其转发给指定的移动设备。SAM是一个针对MQTT写的PHP库。我们可以从这个http://pecl.php.net/package/sam/download/0.2.0地址下载它。

    D、XMPP协议实现Android推送

           Google官方的C2DM服务器底层也是采用XMPP协议进行的封装XMPP(可扩展通讯和表示协议)是基于可扩展标记语言(XML)的协议,它用于即时消息(IM)以及在线探测。这个协议可能最终允许因特网用户向因特网上的其他任何人发送即时消息。 
           androidpn是一个基于XMPP协议的java开源Android push notification实现。它包含了完整的客户端和服务器端。但也存在一些不足之处: 
    1) 比如时间过长时,就再也收不到推送的信息了。 
    2)性能上也不够稳定。 
    3)将消息从服务器上推送出去,服务器就不再管理了,不管消息是否成功到达客户端手机上。 
    如果我们要使用androidpn,则还需要做大量的工作,需要理解XMPP协议、理解Androidpn的实现机制,需要调试内部存在的BUG。

    E、使用第三方平台

           目前国内、国外有一些推送平台可供使用,但是涉及到收费问题、保密问题、服务质量问题、扩展问题等等。百度云,个推等。

    实际上,主流的移动平台都已经有系统级的推送产品,Android上有GCM(Google Cloud Messaging),iOS上有APNS(Apple Push Notification service),WinPhone有MPNS(Microsoft Push Notification service)。但GCM在国内处于不可用状态(一、国内大部分Android手机都不带Google服务,也就用不了GCM,这是主要的问题。 二、在国内Google的服务一般都不太稳定),所以国内的移动应用采用另外一种做法---在后台运行一个Service,维持应用于服务端的TCP长连接,以达到实时消息送达的效果。

    但是在移动端如何稳定的维持长连接是一件非常复杂的事情,前面说了,客户端通过定时发送心跳信号(Heartbeat)以维持与服务端的长连接,但是,如果心跳的频率太频繁,移动设备耗电增加,心跳间隔太久又可能使得连接被断开。并且普遍认为移动设备处于一个多变的网络环境中,WIFI,2G,4G切换,基站切换都会引起网络变动,在不同网络环境下的心跳频率,与网络变动的重连动作,都需要大量的数据统计分析总结出来。

    这仅仅是客户端的难题,在如今移动应用动辄成百上千的用户量的情况下,如何维护如此多的长连接,如果应对大规模的消息下发以及后续针对下发消息的各种统计动作都是技术难点。

    再者,现在应用一般都是全平台的,发送一条消息,应该同时发送给Android,iOS, WinPhone,Android端走自建的TCP长连接通道,iOS与WinPhone走自家的系统推送通道。那么意味着你服务端要维护这三套推送系统。

    显然对于小团队,要独自建立一套消息推送系统的难度非常大,所以市场上涌现出很多优秀的推送产品,帮开发者聚合这些推送方式,并提供统一的推送接口。国外如 Urban Airship, Parse, 国内有JPush百度云推送信鸽LeanCloud等。(比较遗憾的是,非常优秀的Parse已经被Facebook宣布停止开发,并将于1年后关闭)

    现在除了体量非常大的公司自建推送系统外,一般普通公司都是使用第三方推送服务,以上所有的第三方推送服务,基础功能都是免费的。

    移动推送技术

    IOS

    首先是IOS平台,IOS的推送是通过苹果自己的APNs服务进行的,用户需要将device_token(设备号+包名)以及消息内容等推送信息交给APNs服务器,剩下的均由苹果自己来完成。但是如果提供的device_token是失效的(app被卸载、系统版本升级导致device_token变化等情况)那么推送过程就会被中断,频繁的断线重连甚至会被APNs认为是一直DoS攻击。

    对于iOS App来说,一般情况下(有例外情况),App不允许后台长期运行。所以当App被系统挂起或杀掉后,推送只能通过APNS送达。APNS通知到达手机后,由用户唤起App,App运行起来后,就能够启动自己的长连接(如XMPP),完成进一步的推送。当然,APNS的推送速度肯定比不上自己的长连接,所以一般iOS上还一般采用伪推送的方式。App在退到后台后能短暂执行几分钟,长连接也会持续一段时间。如果在这段时间内推送可以通过自己的长连接到达,那么App在客户端产生一个本地推送(类似于系统产生的远程推送)。

    图中,Provider是指某个iPhone软件的Push服务器,这篇文章我将使用.net作为Provider。

    APNS 是Apple Push Notification Service(Apple Push服务器)的缩写,是苹果的服务器。

    上图可以分为三个阶段。

    第一阶段:.net应用程序把要发送的消息、目的iPhone的标识打包,发给APNS。

    第二阶段:APNS在自身的已注册Push服务的iPhone列表中,查找有相应标识的iPhone,并把消息发到iPhone。

    第三阶段:iPhone把发来的消息传递给相应的应用程序, 并且按照设定弹出Push通知。

    iOS 系统的推送(APNS,即 Apple Push Notification Service)依托一个或几个系统常驻进程运作,是全局的(接管所有应用的消息推送),所以可看作是独立于应用之外,而且是设备和苹果服务器之间的通讯,而非应用的提供商服务器。你的例子里面,腾讯 QQ 的服务器(Provider)会给苹果公司对应的服务器(APNs)发出通知,然后再中转传送到你的设备(Devices)之上。当你接收到通知,打开应用,才开始从腾讯服务器接收数据,跟你之前看到通知里内容一样,但却是经由两个不同的通道而来。

    Android

    而在Android上,跟APNS对应的服务GCM,在国内无法使用。而各个手机厂商(如小米)自己的推送服务又无法覆盖所有手机终端。所以,在Android上实现推送只能自己启动Service维持一个长连接。正是因为安卓上很多应用都有后台长连接挂着,所以安卓手机更费流量,也更费电。Android平台在不使用GCM的情况下就需要将自己的服务器或是第三方推送服务提供商的服务器与设备建立一条长连接,通过长连接进行推送。但是不建议自己设置服务器实现推送功能,一是因为成本太高(开发成本、维护成本),自己搭建的服务器无论是稳定性还是速度上都比不了第三方推送服务提供商的效果。另一个是因为自己的数据量较小,使用第三方推送服务提供商可以用他们的维度进行推送,实现精准推送。友盟推送就是做的比较好的,可以根据用户分群、地区、语言等多维度进行推送,最大程度减少对于用户的干扰,仅把消息推送给相关用户。

    开发者通过第三方推送服务提供商将信息直接下发给需要的设备,第三方推送服务提供商与设备建立一条长连接通道,并且将消息路由到APP中(图中的设备1与设备2),对于像设备3这种无网络连接或是没有成功建立长连接通道的设备,会在设备3连网且推送消息没有过期的情况下自动收到由第三方推送服务提供商推送过来的消息,保证消息不会丢失。

     

    3 详细介绍

    分析来看,以上介绍的服务器主动推送技术主要分为:

    3.1 短连接轮询

    前端用定时器,每间隔一段时间发送请求来获取数据是否更新,这种方式可兼容ie和支持高级浏览器。通常采取setInterval或者setTimeout实现

    通过递归的方法,在获取到数据后每隔一定时间再次发送请求,这样虽然无法保证两次请求间隔为指定时间(数据的生成时间),但是获取的数据顺序得到保证。 

    缺点:

    1、页面会出现‘假死’

    setTimeout在等到每次EventLoop时,都要判断是否到指定时间,直到时间到再执行函数,一旦遇到页面有大量任务或者返回时间特别耗时,页面就会出现‘假死’,无法响应用户行为。

    2、无谓的网络传输

    当客户端按固定频率向服务器发起请求,数据可能并没有更新,浪费服务器资源。特别是每次轮询时候的握手,挥手建立连接。

    3.2  长轮询

    客户端像传统轮询一样从服务端请求数据,服务端会阻塞请求不会立刻返回,直到有数据或超时才返回给客户端,然后关闭连接,客户端处理完响应信息后再向服务器发送新的请求。

    长轮询解决了频繁的网络请求,建立多次连接,浪费服务器资源,并且可以及时返回给浏览器。

    缺点:

    1、保持连接也会消耗资源,占用带宽。

    2、若果服务器没有返回有效数据,程序超时。

    3.3  iframe流

    iframe流方式是在页面中插入一个隐藏的iframe,利用其src属性在服务器和客户端之间创建一条长连接,服务器向iframe传输数据(通常是HTML,内有负责插入信息的javascript),来实时更新页面

    • 前端实现步骤:

    1、Iframe设置为不显示。

    2、src设为请求的数据地址。

    3、定义个父级函数,让用户iframe子页面调用传数据给父页面。

    4、定义onload事件,服务器timeout后再次重新加载iframe。

    • 后端输出内容:

    当有新消息时服务端会向iframe中输入一段js代码.:println("<script>父级函数('" + 数据 +"<br>')</script>”);用于调用父级函数传数据。

    • 优点:

    iframe流方式的优点是浏览器兼容好,Google公司在一些产品中使用了iframe流,如Google Talk

    • 缺点:

    1、IE、Mozilla Firefox会显示加载没有完成,图标会不停旋转。

    2、服务器维护一个长连接会增加开销。

    3.4  WebSocket

    WebSocket是一种全新的协议,随着HTML5草案的不断完善,越来越多的现代浏览器开始全面支持WebSocket技术了,它将TCP的Socket(套接字)应用在了webpage上,从而使通信双方建立起一个保持在活动状态连接通道

          运行流程:浏览器通过JavaScript向服务端发出建立WebSocket连接的请求,在WebSocket连接建立成功后,客户端和服务端就可以通过 TCP连接传输数据。因为WebSocket连接本质上是TCP连接,不需要每次传输都带上重复的头部数据,所以它的数据传输量比轮询和Comet技术小 了很多。

    JavaEE 7中出了JSR-356:Java API for WebSocket规范。不少Web容器,如Tomcat,Nginx,Jetty等都支持WebSocket。Tomcat从7.0.27开始支持 WebSocket,从7.0.47开始支持JSR-356。

    • 原理:

    WebSocket协议是借用HTTP协议的101 switch protocol(服务器根据客户端的指定,将协议转换成为 Upgrade首部所列的协议)来达到协议转换的,从HTTP协议切换成WebSocket通信协议。

    • 具体连接方式:

    通过在请求头中增加 upgrade:websocket 及通信密钥(Sec-WebSocket-Key),使双方握手成功,建立全双工通信。

    WebSocket客户端连接报文: 

    WebSocket服务端响应报文:

    • 通信过程:

    websocket是纯事件驱动的,一旦 WebSocket 连接建立后,通过监听事件可以处理到来的数据和改变的连接状态。数据都以帧序列的形式传输。服务端发送数据后,消息和事件会异步到达。WebSocket编程遵循一个异步编程模型,只需要对WebSocket对象增加回调函数就可以监听事件。

    (websocket示意图)

     3.5 Server-sent Events(SSE)

    sse与长轮询机制类似,区别是每个连接不只发送一个消息。客户端发送一个请求,服务端保持这个连接直到有新消息发送回客户端,仍然保持着连接,这样连接就可以再次发送消息的,由服务器单向发送给客户端

    原理:

    SSE本质是发送的不是一次性的数据包,而是一个数据流。可以使用 HTTP 301 和 307 重定向与正常的 HTTP 请求一样。服务端连续不断的发送,客户端不会关闭连接,如果连接断开,浏览器会尝试重新连接。如果连接被关闭,客户端可以被告知使用 HTTP 204 无内容响应代码停止重新连接。

    SSE只适用于高级浏览器,ie不支持。因为ie上的XMLHttpRequest对象不支持获取部分的响应内容,只有在响应完成之后才能获取其内容。

     

    短轮询

    长轮询

    Websocket

    sse

    通讯方式

    http

    http

    基于TCP长连接通讯

    http

    触发方式

    轮询

    轮询

    事件

    事件

    优点

    兼容性好容错性强,实现简单

     

    全双工通讯协议,性能开销小、安全性高,有一定可扩展性

    实现简便,开发成本低

    缺点

    安全性差,占较多的内存资源与请求数

    安全性差,占较多的内存资源与请求数

    传输数据需要进行二次解析,增加开发成本及难度

    只适用高级浏览器

    适用范围

    b/s服务

    b/s服务

    网络游戏、银行交互和支付

    服务端到客户端单向推送

    TCP是全双工通讯,也就是只要知道双方socket,双方都可以发送数据。 

     

    sse

    websocket

    轮询

    服务器部署

    ×

    ×

    浏览器兼容性

    ×

    ×

    后端推送

    ×

     

    4 总结

    4.1 技术总结

    B/S架构

    1、AJAX的(短)轮询。(PULL拉取)

    2、Comet和SSE利用长轮询和长连接,基本实现了服务器主动推送。但其本质上依旧是基于请求-响应模式,但对实现和服务器端要求不高,技术变化不大。(PUSH推送)

    以上俩种方法都不是真正的主动推送技术。

    3、WebSocket通过建立全双工通信的Socket,实现了真正的服务器推送功能。对服务器和编码要求较高。(PUSH推)

    4、发布-订阅模式,需要专门的服务器。

    C/S架构

    1、如果不是B/S架构,可以交换C/S身份,进行连接。我们称之为反向/反弹连接。此时客户端发送轮询数据包,进行周期性询问和请求。类似于反弹木马的行为方式。

    2、SMS(Push)方式:通过拦截SMS消息并且解析消息内容来了解服务器的命令,但这种方式一般用户在经济上很难承受。

    4.2 经验总结

    (1)总得来说,在HTML5规范下,最推荐使用ServerSent和WebSocket的方式进行服务器消息的推送。

    对比这两种方式:

    ServerSent的方式,可以使服务端的开发依然依用以前的方式,但是其工作方式与Comet类似。特别是对于老项目的翻新,还是用SeverSent比较好。

    而WebSocket的方式,则对服务端的开发有着较高的要求,但其工作方式是完全的推送。真正实现了Web的实时通信,使B/S模式具备了C/S模式的实时通信能力。

    (2)

    对于简单的推送需求又不考虑兼容低版本浏览器,推荐使用server-sent Events

    如果需要多条双向数据实时交互或需要二进制传输,推荐websocket。

    对于还要考虑低版本浏览器,那么还是用轮询(基本AJAX)来实现功能。

    (3)

    IM、实时对战游戏之类的应用场景,就一定需要长连接推送。像天气类,新闻类 app,对推送实时性要求并不太高,采取定期拉取策略也是可以的(实际上实现难度要降低几个数量级)。

    (4)

    考虑问题:

    (1)采用什么协议?XMPP还是MQTT还是自定义二进制协议?是否像微信一样,需要推送二进制数据(比如短语音和缩略图数据)?
    (2)如何保证后台长连接不死?像大公司的各个App,普通会采用互拉的手段保持后台运行。而最牛的还是微信,在各个手机系统上基本都算是“开挂”了。
    (3)如何做才能真正不丢数据?涉及到系统的方方面面,比如消息的确认,客户端和服务器的数据同步,客户端的数据存储的事务保证,后台消息队列如何设计保证不丢数据。如果是IM,离线数据如何处理?
    (4)长连接的Keep Alive和连接状态的检测。比如XMPP相当于一个永远解析不完的XML流,使用一个空格作为Keep Alive消息。
    (5)在iOS上进行伪推送。前面已经提到过。还有如何利用iOS特性,用特殊APNS推送唤起App预先下载消息。
    (6)长连接的安全性。验证以及加密。
    (7)是用各种云推送还是自己实现?用哪家云推送更好?

    实现方法:

    1. html5 websocket

    2. WebSocket 通过 Flash

    3. XHR长时间连接

    4. XHR Multipart Streaming

    5. 不可见的Iframe

    6. <script>标签的长时间连接(可跨域)

    (4)基础实现;提供的推送系统API;第三方推送服务平台。

    (5)nodejs的http://socket.io,它是websocket的一个开源实现,对不支持websocket的浏览器降级成comet / ajax 轮询,http://socket.io的良好封装使代码编写非常容易。

    4.3 实例

    同步会议和即时通信是推送服务的典型事例。聊天信息和临时文件一旦被发送,用户就会通过推送服务接收到。分离的 peer-to-peer 程序(例如 WASTE )和集中的程序(例如 IRC 或 XMPP )都允许推送文件,这就意味着发件人开始进行数据的传送,而不是收件人。

    Email 可能也是一个推送系统:SMTP 协议是一个推送协议(见 Push e-mail)。然而,从邮件服务器到桌面计算机的最后一步通常使用的是像 POP3 或 IMAP 这样的 pull 协议现代电子邮件客户端通过反复轮询邮件服务器,使这一步看起来是瞬间的,它经常检查是否有新邮件。IMAP 协议包括 IDLE command,它允许服务器当新邮件到达时向客户端发消息。初代的黑莓是第一个在无线环境下推送电子邮件的设备,使得它成为当今的佳话。

    其他例子是 PointCast Network (点播式网络),它在二十世纪九十年代具有非常广泛的应用。它通过屏保推送新闻和股票行情。在浏览器战争的巅峰时期,Netscape和 Microsoft 通过在他们的软件里集成频道定义格式(CDF)推送技术,但是那时并没有多少人关注。 CDF 消失了并移出了浏览器的时代,取而代之的是2000年的 RSS(一种下拉式系统)。

    4.3.1 网页推送

    这个网络工程师极力推崇的网页推送方案是一个简单的协议,通过使用 HTTPv2.0 版本的去即时的事件。例如来电或留言,能够被即时的传达(或者说推送)出去。这个协议将所有即时的事件都合并到一个简单的会话中,其中这个会话可以确保不管在网络还是音频资源上都有很好的使用效率。这一个简单的服务包含了所有的事件,并在当它到达分发给对应的应用所有的事件。这个请求只需要一个会话,避免了重复发送这样高昂的成本。

    4.3.2 HTTP服务器推送

    HTTP 服务器推送(也称为 HTTP 流)是一种将未经请求的(异步)数据从Web服务器发送到Web浏览器的机制。任何一种机制都可以实现 HTTP 服务的推送。

    一些 HTML5 的 WebSocket API 允许 Web 服务器和客户端,通过 TCP 全双工通信进行交流。

    一般情况下,当响应完毕一个来自客户端的请求之后,Web 服务器不会去终止一个连接。 Web 服务器使连接打开,以便当发生事件(例如,需要向一个或多个客户端报告的内部数据的变化)时,可以立即发送它;否则,该事件必须排队,直到接收到客户端的下一个请求为止。大多数 Web 服务器都通过 CGI 提供这种功能(例如,Apache HTTP 服务器上未解析的报头脚本)。这是一种靠分块传输编码方法的基本机制。

    另一种机制关系到一个特殊的 MIME 类型,称为 multipart/x-mixed-replace,这是由 Netscape 在1995年引入的。当服务器想向客户端推出新版本时,Web 浏览器将此理解为一个文件改变。它如今仍然被 Firefox 、 Opera 和 Safari 支持,但它被 Internet Explorer 忽略了。它可以应用于 HTML 文档,以及用于流式传输图像的相机应用。

    网页超文本应用技术工作小组(WHATWG)Web Applications 1.0 proposal 包括一个机制来推送内容到客户端。2006年9月1日, Opera Web 浏览器在一个名为“服务器发送事件”的功能中实现了这个新的实验系统。现在它作为 HTML5 的一部分被标准化了。

    4.3.3 推送技术

    在这个技术,服务器充分利用持久的 HTTP 协议,使得响应永久打开,(即服务器永不关闭响应),有效地去欺骗浏览器保持加载,直到初始页面被认为完全加载完毕之后。然后服务器周期的发送 JavaScript 代码去更新这网页的内容。从而实现推送能力,通过使用这个技术,客户不需要JAVA程序或者其他的插件程序去保持与服务器的连接。客户端也会自动地收到新事件,推送给服务器。这个方法有一个严重的不足,就是缺乏一种控制,服务器已经结束进程使浏览器连接超时;一个页面如果发生连接超时必然会导致其刷新。

    4.3.4 长轮询

    长轮询本身并不是一个真正的推送;长轮询是传统轮询技术的一种变体,但是它允许在一个真正的推送不可能的情况下模拟推送机制,例如安全策略的网站需要阻止的HTTP/S请求。

    长轮询, 客户端从服务器请求信息与正常轮询完全相同,但正如你预料到的,服务器可能不会立即回应。当收到轮询时如果服务器的客户端没有新的信息,不是发送一个空的响应,服务器端公开请求并等待成为有用的响应信息。一旦它获得新的信息,服务器会立即向客户端发送 HTTP/S 的响应,完成开放 HTTP 的请求。收到服务器响应后,客户端通常会立即发出另一个服务器的请求,这样,通常的反应延迟(信息第一次可用到下一个客户端请求的中间时间)与客户端的消除相关。

    例如,当这样的连接是困难的或不可能直接采用的(例如,在web浏览器),作为对连续 TCP 连接的长轮询的替代,BOSH 是一个流行的、长寿的 HTTP 技术。在 XMPP是一个潜在的技术,苹果用于 iCloud 的推送支持。

    4.3.5 Flash XMLSocket 传达

    这项技术被 cbox(Cbox is a chat application for online communities and groups. Get a Cbox, and your visitors and users can engage with one another in real-time conversation.) 和其他聊天应用程序所使用,并在一个单像素 Adobe Flash 电影中使用 XMLSocket 对象。在 JavaScript 控制下,客户端创建一个 TCP 连接到服务器上的单向中继。中继服务器不会从这个套接字读取任何内容,相反它会向客户端发送一个唯一标识符。接下来客户机向 web 服务器发送 HTTP 请求,包括这个标识符。Web 应用程序可以将向客户机发送的消息发送到中继服务器的本地接口,然后中继服务器通过 Flash 套接字传递给客户机。这种方法的优点是,它能鉴别许多 web 应用程序的典型的读写不对称,包括聊天,它还提供了更高的效率。由于它不接受传出套接字的数据,所以中继服务器根本不需要轮着传出 TCP 连接,使其保持数万个并发连接成为可能。在这个模型中扩展的限制是底层服务器操作系统的 TCP 堆栈。

    4.3.6 可靠的组数据传送 (RGDD)

    例如云计算的服务,增加数据的可靠性和可用性,这通常被推送(被复制)到多台机器上。例如, Hadoop 分布式文件系统(数据库)对所有的储存对象复制两个副本。RGDD 重点研究有效的铸造一个对象从一个位置到多个位置与此同时通过发送极小的数目的副本来节省宽带(在最好的情况下只有一个)在任何跨越网络的对象。例如,数据广播是一个在数据中心传递多个节点,依靠规则和结构化拓扑和 DCCast 是一个类似接近跨过数据中心交付方法的方案

    4.3.7 电子邮件

    传统的移动邮件客户端可以通过频繁向邮件服务器查询新邮件来模拟邮件推送的用户体验。IMAP协议实际上允许在任意时间发送多个(不包括邮件数据的)通知。IDLE(闲置)就是这样一种功能,它告知服务器可以在任何时候发送通知,并通过客户端运行发送通知以外的命令,从而有效地提供了一个等同于推送的用户体验。

    IMAP POP3 都没有推送,只能定时查询。foxmail 这种也是定时去刷新登录的。

    除非是一些专有协议:Exchange ActiveSync,Mapi 。

     

    总结:

     优点缺点
    推/PUSH实时性高,管理方便,相对节省流量随时维护一个长连接,浪费电池电量,浪费系统资源
    拉/PULL灵活性高,实现简单,相对节省电量不断地访问服务器,可能带来网络带宽的浪费

    “推”主要用到的是推送机制,当后台服务器有消息更新时立即发送给应用程序;“拉”主要用到的是轮询机制,应用程序不定时的频繁访问后台服务器以获取最新的消息。

    轮询机制是客户端每隔一段时间向服务器发送 请求以确定是否有数据更新,如果有,返回更新数据;否则,返回空。轮询方式
    分为两类,普通轮询和长轮询。普通轮询指的是当服务器响应客户端的请求后立即关闭连接,这样可以保证开发的简易性和操作的便捷性,但会带来巨大的资源和带宽浪费。长轮询指的是当服务器响应客户端的请求后并不立即关闭连接,而是保持一段时间,当客户端回复终止包或者连接超时才关闭,这样可以避免无数据更新时的频繁请求,但是会带来服务器资源的浪费。减少了轮询的次数
     
    推送机制是服务器开启服务后建立持久连接,当服务器有数据更新时直接将其发送到客户端,避免了多次建立连接。
    目前,实现 Android 平台信息推送的解决方案主要有四种,基于 GCM 服务基于 XMPP 协议基于 MQTT 协议,使用第三方平台(如百度推送,极光推送)。

    展开全文
  • 方法一: 查看代码,查找关键的变量, 客户端数据传送给Web 服务端一般通过三种方式 Querystring, Form表单,以及cookie. 例如在ASP的程序中,通过Request对象获取客户端的变量 <%strUserCode = Request....
  • 使用TCP协议编写一个网络程序,设置服务器端的监听端口是8002,当与客户端建立连接后,服务器端向客户端发送数据“Hello,world”,客户端收到数据后打印输出。 服务器端 import java.io.IOException; import java.io...
  • Request(对象)

    2015-08-25 09:54:31
    Request对象功能从客户端得到数据,常用的三种取得数据的方法是: Request Form、Request.QueryString 、Request,其中第三种是 前两种的一个缩写,可以取代前两种情况。前两种主要对应的FORM提 交的两种不同的...
  • 对象存储概述

    2020-07-30 20:55:59
    相对于存储局域网(SAN)和网络附加存储(NAS)网络存储架构,对象存储(Object-based Storage)是一种新的网络存储架构,基于对象存储技术的设备就是对象存储设备(Object-based Storage Device)简称 OSD。...
  • 客户端表单提交数据方式与服务器获取数据

    万次阅读 热门讨论 2013-06-16 20:29:37
    表单提交数据的两种方式  表单form的提交有两种方式,一种是get的方法,通过超级链接后面的参数提交过来,一种是post ,通过Form表单提交过来。 post方式: method="post" action="login.aspx"> 用户名...
  • 实现客户端与服务器文件互传软件系统,包括客户端不仅可以浏览自己本地的文件列表和服务器的文件列表,而且客户端可以将本地的文件上传到服务器及将服务器上下载自己需要的文件。 二、要求 1.系统要求必须支持将...
  • session即会话对象,它保存了本次客户端与服务端的通信信息。且session数据是存放在服务端的。
  • Unity游戏客户端面试(2019)

    万次阅读 多人点赞 2019-08-02 13:55:24
    可以使用反射动态创建类型的实例,将类型绑定到现有对象,或现有对象获取类型并调用其方法或访问其字段和属性。如果代码中使用了属性,可以利用反射对它们进行访问。 5.C#中的委托是什么?事件是不是一种委托...
  • HTTP协议理解及服务端与客户端的设计实现

    万次阅读 多人点赞 2019-06-20 19:05:05
    本文主要帮助读者理解 HTTP 的协作原理、HTTP 相关的各层协议,在服务端和客户端的架构设计和一些优化的技巧,本文中主要讲述逻辑思想和协议远离,会使用部分 Java 代码,但会有详细的讲解,非开发应该也读的明白。...
  • 设计程序,分别构建通信的两端:服务器端和客户端应用程序,套接字类型为面向连接的Socket,自己构建双方的应答模式,实现双方的数据的发送和接收(S发给C,C发给S)。 服务端程序能响应单个或任意多个客户端连接...
  • 浅谈面向客户端的性能优化

    千次阅读 2020-02-01 10:43:57
    有朋友通过《智能音箱场景下的性能优化》一文找到了我,既然智能音箱的性能优化相当于一个超集,那么对其的一个子集——客户端系统如何进行性能优化呢? 反正隔离在家,不妨对客户端的性能优化梳理一下。 我思...
  • 4、利用getEntity去得到从服务器获取到的数据 //HttpEntity实体即可以是流也可以是字符串形式。 HttpEntity entity = httpResponse .getEntity(); InputStream inputstream = entity .getcontent(); //然后进行...
  • java网络编程(一)使用TCP协议完成客户端与服务端的数据传递
  • 在JS中将JSON的字符串解析成JSON数据格式,一般有两种方式:  1.一种为使用eval()函数。  2. 使用Function对象来进行返回解析。  一、使用eval函数来解析,并且使用jquery的each方法来遍历  1)首先我们来...
  • JSP有哪些内置对象功能是什么

    万次阅读 2016-12-13 10:27:03
     pageContext对象的作用是取得任何范围的参数,通过pageContext对象可以获取JSP页面的out、request、response、session、application等对象,或者可以重新定向客户的请求等,较少使用 二、request服务器端取得...
  • 客户端架构

    万次阅读 2016-05-29 10:56:36
    Blog2-客户端架构 一.客户端架构简介  客户端(Client)或称为用户端,是指与服务器相对应,为客户提供本地服务的程序。除了一些只在本地运行的应用程序之外,一般安装在普通的客户机上,需要与服务端互相配合...
  • java客户端数据发送到服务器(POST请求)总结

    万次阅读 多人点赞 2015-01-08 16:33:21
    java客户端数据发送到服务器(POST请求)总结 1.如果不设置Content-type,默认是:application/x-www-form-urlencoded。 2.GET请求的参数与对应的值位于请求行中,并附加在URL后面,通过“?”分隔开来。 POST请求的...
  • 对象存储(Object-based Storage)概述

    千次阅读 2016-12-12 22:21:37
    什么是对象存储?多次在不同场合被问起这个问题,于是就想写篇小综述文章。网上查找资料时,找到几篇不错的资料,简单整理一下,供自己和大家参考。 什么是对象存储(OSD)? 存储局域网(SAN)和网络附加存储(NAS...
  • 原文  ... C#菜鸟做这个东东竟然花了快三天的时间了,真是菜,菜,菜~~~ ...(1) 多个客户端与服务器间的数据交流 (2)可以实现群发的功能 (3)客户端与服务端可以进行文件的传输 主要用到的知识: TCP里的
  • 本来 连 Tomcat 容器 和 Servlet 的生命周期也准备在这里一起写的,但怕过去庞大,于是就简单的 引用了一些 Servlet 对象。这样的一个整个流程看下来,相信至少在理解 HTTP协议 和 request 和 resp
  • 什么是对象存储

    千次阅读 2021-01-28 09:59:59
    对象存储概述 什么是对象存储(OSD)? 存储局域网(SAN)和网络附加存储(NAS)是目前两种主流网络存储...总体上来讲,对象存储综合了NAS和SAN的优点,同时具有SAN的高速直接访问和NAS的分布式数据共享等优势,提供
  • JSP-隐式对象

    千次阅读 2018-01-03 16:53:37
    什么是隐式对象?我们可以在_jspService方法中找到对应的局部变量,这些变量我们称之为隐式对象(又叫内置对象)。 提供内部隐式对象的目的是为了简化JSP开发 JSP 隐式对象是 Web 容器加载的一组类 不需要由JSP的...
  • 简介 Netty是一个异步事件驱动的网络应用框架,可快速开发可...主EventLoopGroup负责处理客户端的连接请求事件,客户端连接成功后交由EventLoopGroup,具体的数据IO由EventLoopGroup的EventLoop处理。 EventLoop
  • Kafka客户端使用

    万次阅读 2018-03-04 00:50:12
    Consumer客户端 1 消费者模型 在开始编码之前, 我们先回顾一下一些基本概念。 在Kafka中, 每个topic被分成一组称为Partitions的logs。 Producer向这些logs的末尾写入消息, Consumer则自己按自己的节奏读取log...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 349,704
精华内容 139,881
关键字:

对象功能是从客户端得到数据