WebSocket初探

WebSocket初探

大家好,又见面了,我是全栈君,祝每个程序员都可以多学几门语言。

       众所周知,socket是编写网络通信应用的基本技术,网络数据交换大多直接或间接通过socket进行。对于直接使用socket的client与服务端,一旦连接被建立则均可主动向对方传送数据,而对于使用更上层的HTTP/HTTPS协议的应用,因为它们是非连接协议,所以通常仅仅能由client主动向服务端发送请求才干获得服务端的响应并取得相关的数据。而当前越来越多的应用希望可以及时获取服务端提供的数据,甚至希望可以达到接近实时的数据交换(比如非常多站点提供的在线客户系统)。为达到此目的,通常採用的技术主要有轮询、长轮询、流等,而伴随着HTML5的出现,相对更优异的WebSocket方案也应运而生。

一、            非WebSocket方案简单介绍

1.      轮询

       轮询是由client定时向服务端发起查询数据的请求的一种实现方式。早期的轮询是通过不断自己主动刷新页面而实现的(在那个基本是IE统治浏览器的时代,那不断刷新页面产生的噪声就难以让人忍受),后来随着技术的发展,特别是Ajax技术的出现,实现了无刷新更新数据。但本质上这些方式均是client定时轮询服务端,这样的方式的最显著的缺点是假设client数量庞大而且定时轮询间隔较短服务端将承受响应这些client海量请求的巨大的压力。

2.      长轮询

       在数据更新不够频繁的情况下,使用轮询方法获取数据时client常常会得到没有数据的响应,显然这样的轮询是一个浪费网络资源的无效的轮询。长轮询则是针对普通轮询的这样的缺陷的一种改进方案,其详细实现方式是假设当前请求没有数据能够返回,则继续保持当前请求的网络连接状态,直到服务端有数据能够返回或者连接超时。长轮询通过这样的方式降低了client与服务端交互的次数,避免了一些无谓的网络连接。可是假设数据变更较为频繁,则长轮询方式与普通轮询在性能上并无显著差异。同一时候,添加�连接的等待时间,往往意味着并发性能的下降。

3.      流

      所谓流是指client在页面之下向服务端发起一个长连接请求,服务端收到这个请求后响应它并不断更新连接状态,以确保这个连接在client与服务端之间一直有效。服务端能够通过这个连接将数据主动推送到client。显然,这样的方案实现起来相对照较麻烦,并且可能被防火墙阻断。

二、            WebSocket简单介绍

1.      WebSocket协议简单介绍

       WebSocket是为解决client与服务端实时通信而产生的技术。其本质是先通过HTTP/HTTPS协议进行握手后创建一个用于交换数据的TCP连接,此后服务端与client通过此TCP连接进行实时通信。

WebSocket规范当前还没有正式版本号,草案变化也较为迅速。Tomcat7(本文中的例程来自7.0.42)当前支持RFC 6455(http://tools.ietf.org/html/rfc6455)定义的WebSocket,而RFC 6455眼下还未冻结,将来可能会修复一些Bug,甚至协议本身也可能会产生一些变化。

        RFC6455定义的WebSocket协议由握手和传输数据两个部分组成。

    来自client的握手信息类似例如以下:

GET /chat HTTP/1.1
Host: server.example.com
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
Origin: http://example.com
Sec-WebSocket-Protocol: chat, superchat
Sec-WebSocket-Version: 13

        服务端的握手信息类似例如以下:

HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=
Sec-WebSocket-Protocol: chat


 

        一旦client和服务端都发送了握手信息而且成功握手,则传输数据部分将開始。传输数据对client和服务端而言都是一个双工通信通道,client和服务端来回传递的数据称之为“消息”。

client通过WebSocket URI发起WebSocket连接,WebSocket URIs模式定义例如以下:

 

ws-URI = "ws:" "//" host [ ":" port ] path [ "?" query ]
wss-URI = "wss:" "//" host [ ":" port ] path [ "?" query ]


 

        ws是普通的WebSocket通信协议,而wss是安全的WebSocket通信协议(就像HTTPHTTPS之间的差异一样)。在缺省情况下,ws的port是80wss的port是443

        关于WebSocke协议规范的完整详尽说明,请參考RFC 6455

2.      Tomcat7提供的WebSocket包简单介绍

        Tomcat7提供的与WebSocket相关的类均位于包org.apache.catalina.websocket之中(包org.apache.catalina.websocket的实现包括于文件catalina.jar之中),它包括有类Constants、MessageInbound、StreamInbound、WebSocketServlet、WsFrame、WsHttpServletRequestWrapper、WsInputStream、WsOutbound。这些类的关系如图 1所看到的。

 

WebSocket初探

                                                                 图1

        包org.apache.catalina.websocket中的这些类为WebSocket开发服务端提供了支持,这些类的主要功能简述例如以下:

        Constants:包org.apache.catalina.websocket中用到的常数定义在这个类中,它仅仅包括静态常数定义,无不论什么逻辑实现。

        MessageInbound:基于消息的WebSocket实现类(带内消息),应用程序应当扩展这个类并实现其抽象方法onBinaryMessageonTextMessage

       StreamInbound:基于流的WebSocket实现类(带内流),应用程序应当扩展这个类并实现其抽象方法onBinaryDataonTextData

       WebSocketServlet:提供遵循RFC6455WebSocket连接的Servlet基本实现。client使用WebSocket连接服务端时,须要将WebSocketServlet的子类作为连接入口。同一时候,该子类应当实现WebSocketServlet的抽象方法createWebSocketInbound,以便创建一个inbound实例(MessageInboundStreamInbound)

       WsFrame:代表完整的WebSocket框架。

       WsHttpServletRequestWrapper:包装过的HttpServletRequest对象。

       WsInputStream:基于WebSocket框架底层的socket的输入流。

       WsOutbound:提供发送消息到client的功能。它提供的全部向client的写方法都是同步的,能够防止多线程同一时候向client写入数据。

三、            基于Tomcat7WebSocket例程

        利用当前HTML5Tomcat7WebSocket提供的支持,基本仅仅须要编写简单的代码对不同的事件做对应的逻辑处理就能够实现利用WebSocket进行实时通信了。

        Tomcat7WebSocket提供了3个例程(echochatsnake),下面就当中的echochat分别做一简要解析。

1.      echo例程

        echo例程主要演示下面功能:client连接服务端、client向服务端发送消息、服务端收到client发送的消息后将其原样返回给client、client收到消息后将其显示在网页之上。

       在client页面选择streamsmessages作为“Connectusing”,然后点击“Connect”button,能够在右側窗体看到WebSocket连接打开的消息。随后点击“Echo message”button,client将向服务端发送一条消息,在右側窗体,能够看到,消息发出的后的瞬间,client已经收到了服务端原样返回的消息。

        client页面及执行效果截图如图 2所看到的。

WebSocket初探

                                                                                                    图2

       client实现上述功能的核心脚本例如以下,其关键点通过凝视的形式加以说明:

<script type="text/javascript">
        var ws = null;
        // 界面元素可用性控制
        function setConnected(connected) {
            document.getElementById('connect').disabled = connected;
            document.getElementById('disconnect').disabled = !connected;
            document.getElementById('echo').disabled = !connected;
        }

        function connect() {
            // 取得WebSocket连接入口(WebSocket URI)
            var target = document.getElementById('target').value;
            if (target == '') {
                alert('Please select server side connection implementation.');
                return;
            }
            // 创建WebSocket
            if ('WebSocket' in window) {
                ws = new WebSocket(target);
            } else if ('MozWebSocket' in window) {
                ws = new MozWebSocket(target);
            } else {
                alert('WebSocket is not supported by this browser.');
                return;
            }
            // 定义Open事件处理函数
            ws.onopen = function () {
                setConnected(true);
                log('Info: WebSocket connection opened.');
            };
            // 定义Message事件处理函数(收取服务端消息并处理)
            ws.onmessage = function (event) {
                log('Received: ' + event.data);
            };
            // 定义Close事件处理函数
            ws.onclose = function () {
                setConnected(false);
                log('Info: WebSocket connection closed.');
            };
        }
        // 关闭WebSocket连接
        function disconnect() {
            if (ws != null) {
                ws.close();
                ws = null;
            }
            setConnected(false);
        }

        function echo() {
            if (ws != null) {
                var message = document.getElementById('message').value;
                log('Sent: ' + message);
                // 向服务端发送消息
                ws.send(message);
            } else {
                alert('WebSocket connection not established, please connect.');
            }
        }
	    // 生成WebSocket URI 
        function updateTarget(target) {
            if (window.location.protocol == 'http:') {
                document.getElementById('target').value = 
'ws://' + window.location.host + target;
            } else {
                document.getElementById('target').value = 
'wss://' + window.location.host + target;
            }
        }
        // 在界面显示log及消息
        function log(message) {
            var console = document.getElementById('console');
            var p = document.createElement('p');
            p.style.wordWrap = 'break-word';
            p.appendChild(document.createTextNode(message));
            console.appendChild(p);
            while (console.childNodes.length > 25) {
                console.removeChild(console.firstChild);
            }
            console.scrollTop = console.scrollHeight;
        }
    </script>

       注:完整代码參见apache-tomcat-7.0.42\webapps\examples\websocket\echo.html

       以上client能够依据“Connectusing”的不同选择连接不同的服务端WebSocket。比如messages选项相应的服务端代码例如以下,其核心逻辑是在收到client发来的消息后马上将其发回client。

public class EchoMessage extends WebSocketServlet {
    private static final long serialVersionUID = 1L;
    private volatile int byteBufSize;
    private volatile int charBufSize;

    @Override
    public void init() throws ServletException {
        super.init();
        byteBufSize = getInitParameterIntValue("byteBufferMaxSize", 2097152);
        charBufSize = getInitParameterIntValue("charBufferMaxSize", 2097152);
    }

    public int getInitParameterIntValue(String name, int defaultValue) {
        String val = this.getInitParameter(name);
        int result;
        if(null != val) {
            try {
                result = Integer.parseInt(val);
            }catch (Exception x) {
                result = defaultValue;
            }
        } else {
            result = defaultValue;
        }

        return result;
    }

    // 创建Inbound实例,WebSocketServlet子类必须实现的方法
    @Override
    protected StreamInbound createWebSocketInbound(String subProtocol,
            HttpServletRequest request) {
        return new EchoMessageInbound(byteBufSize,charBufSize);
    }
    // MessageInbound子类,完毕收到WebSocket消息后的逻辑处理
    private static final class EchoMessageInbound extends MessageInbound {
        public EchoMessageInbound(int byteBufferMaxSize, int charBufferMaxSize) {
            super();
            setByteBufferMaxSize(byteBufferMaxSize);
            setCharBufferMaxSize(charBufferMaxSize);
        }
        //  二进制消息响应
        @Override
        protected void onBinaryMessage(ByteBuffer message) throws IOException {
            getWsOutbound().writeBinaryMessage(message);
        }
        // 文本消息响应
        @Override
        protected void onTextMessage(CharBuffer message) throws IOException {
            // 将收到的消息发回client
            getWsOutbound().writeTextMessage(message);
        }
    }
}

        注:完整代码參见apache-tomcat-7.0.42\webapps\examples\WEB-INF\classes\websocket\echo\EchoMessage.java。

2.      chat例程

        chat例程实现了通过网页进行群聊的功能。每一个打开的聊天网页都能够收到全部在线者发出的消息,同一时候,每一个在线者也都能够(也仅仅能够)向其他全部人发送消息。也就是说,chat实例演示了怎样通过WebSocket实现对全部在线client的广播。

WebSocket初探WebSocket初探

                                                                  图3

        chat例程client核心代码例如以下,能够看到事实上现方式与echo例程形式上稍有变化,本质依然是对WebSocket事件进行响应与处理。

<script type="text/javascript">
        var Chat = {};

        Chat.socket = null;

        Chat.connect = (function(host) {
            // 创建WebSocket
            if ('WebSocket' in window) {
                Chat.socket = new WebSocket(host);
            } else if ('MozWebSocket' in window) {
                Chat.socket = new MozWebSocket(host);
            } else {
                Console.log('Error: WebSocket is not supported by this browser.');
                return;
            }
            // 定义Open事件处理函数
            Chat.socket.onopen = function () {
                Console.log('Info: WebSocket connection opened.');
                document.getElementById('chat').onkeydown = function(event) {
                    if (event.keyCode == 13) {
                        Chat.sendMessage();
                    }
                };
            };
            // 定义Close事件处理函数
            Chat.socket.onclose = function () {
                document.getElementById('chat').onkeydown = null;
                Console.log('Info: WebSocket closed.');
            };
            // 定义Message事件处理函数
            Chat.socket.onmessage = function (message) {
                Console.log(message.data);
            };
        });

        Chat.initialize = function() {
            if (window.location.protocol == 'http:') {
                Chat.connect('ws://' + 
window.location.host + '/examples/websocket/chat');
            } else {
                Chat.connect('wss://' + 
window.location.host + '/examples/websocket/chat');
            }
        };
		// 发送消息至服务端
        Chat.sendMessage = (function() {
            var message = document.getElementById('chat').value;
            if (message != '') {
                Chat.socket.send(message);
                document.getElementById('chat').value = '';
            }
        });

        var Console = {};

        Console.log = (function(message) {
            var console = document.getElementById('console');
            var p = document.createElement('p');
            p.style.wordWrap = 'break-word';
            p.innerHTML = message;
            console.appendChild(p);
            while (console.childNodes.length > 25) {
                console.removeChild(console.firstChild);
            }
            console.scrollTop = console.scrollHeight;
        });

        Chat.initialize();

    </script>


        注:完整代码參见apache-tomcat-7.0.42\webapps\examples\websocket\chat.html

 
        chat例程服务端代码例如以下:
public class ChatWebSocketServlet extends WebSocketServlet {    private static final long serialVersionUID = 1L;    private static final String GUEST_PREFIX = "Guest";    private final AtomicInteger connectionIds = new AtomicInteger(0);    private final Set<ChatMessageInbound> connections =            new CopyOnWriteArraySet<ChatMessageInbound>();    // 创建Inbound实例,WebSocketServlet子类必须实现的方法    @Override    protected StreamInbound createWebSocketInbound(String subProtocol,            HttpServletRequest request) {        return new ChatMessageInbound(connectionIds.incrementAndGet());    }    // MessageInbound子类,完毕收到WebSocket消息后的逻辑处理    private final class ChatMessageInbound extends MessageInbound {        private final String nickname;        private ChatMessageInbound(int id) {            this.nickname = GUEST_PREFIX + id;        }        // Open事件        @Override        protected void onOpen(WsOutbound outbound) {            connections.add(this);            String message = String.format("* %s %s",                    nickname, "has joined.");            broadcast(message);        }        // Close事件        @Override        protected void onClose(int status) {            connections.remove(this);            String message = String.format("* %s %s",                    nickname, "has disconnected.");            broadcast(message);        }		// 二进制消息事件        @Override        protected void onBinaryMessage(ByteBuffer message) throws IOException {            throw new UnsupportedOperationException(                    "Binary message not supported.");        }		// 文本消息事件        @Override        protected void onTextMessage(CharBuffer message) throws IOException {            // Never trust the client            String filteredMessage = String.format("%s: %s",                    nickname, HTMLFilter.filter(message.toString()));            broadcast(filteredMessage);        }		// 向全部已连接的客户端发送文本消息(广播)        private void broadcast(String message) {            for (ChatMessageInbound connection : connections) {                try {                    CharBuffer buffer = CharBuffer.wrap(message);                    connection.getWsOutbound().writeTextMessage(buffer);                } catch (IOException ignore) {                    // Ignore                }            }        }    }


        注:完整代码參见apache-tomcat-7.0.42\webapps\examples\WEB-INF\classes\websocket\echo\ChatWebSocketServlet.java。

通过上述例程能够看到WebSocket广播实际上是通过遍历全部连接并通过每一个连接向对应的client发送消息实现的。

四、            WebSocket实战

        实时向在线用户推送通知是一个WebSocket应用的简单场景,后台提交通知信息以后,所在在线用户均应非常快收到这个通知。通过上述例程了解WebSocket后,能够尝试编写一个实现这个需求的WebSocket应用。

首先编写一个用户的Sample页面,该页面没有实质的内容,可是在收到后台发出的通知时要在右下角通过弹窗显示通知的内容。其代码例如以下:

<!DOCTYPE html><html><head>    <title>Receive Message</title>    <style type="text/css">        #winpop { width:200px; height:0px;             position:absolute;             right:0; bottom:0;             border:1px solid #999999;             margin:0;             padding:1px;             overflow:hidden;             display:none;             background:#FFFFFF}        #winpop .con { width:100%; height:80px;             line-height:80px;             font-weight:bold;             font-size:12px;             color:#FF0000;             text-align:center}    </style>    <script type="text/javascript">        // 弹窗相关        function tips_pop(){            var MsgPop=document.getElementById("winpop");            var popH=parseInt(MsgPop.style.height);                        if(isNaN(popH)) {                popH = 0;            }                        if (popH==0){                MsgPop.style.display="block";                show=setInterval("changeH('up')",100);            }            else {                hide=setInterval("changeH('down')",100);            }        }        function changeH(str) {            var MsgPop=document.getElementById("winpop");            var popH=parseInt(MsgPop.style.height);                        if(isNaN(popH)) {                popH = 0;            }                        if(str=="up"){                   if (popH<=100){                        MsgPop.style.height=(popH+4).toString()+"px";                }                else{                      clearInterval(show);                    setTimeout("tips_pop()", 5000);                }            }            if(str=="down"){                 if (popH>=4){                          MsgPop.style.height=(popH-4).toString()+"px";                }                else{                            clearInterval(hide);                        MsgPop.style.display="none";                  }            }        }                // WebSocket相关        var ws = null;                function connect() {            var target = 'ws://' + window.location.host                 + "/test/NotifyWebSocketServlet";                        if ('WebSocket' in window) {                ws = new WebSocket(target);            } else if ('MozWebSocket' in window) {                ws = new MozWebSocket(target);            } else {                alert('WebSocket is not supported by this browser.');                return;            }                        ws.onopen = function () {                 document.getElementById('msg').innerHTML =                     "WebSocket has opened, Waiting message.......";            };                        ws.onmessage = function (event) {                document.getElementById('infomsg').innerHTML = event.data;                tips_pop();            };                        ws.onclose = function () {                document.getElementById('msg').innerHTML = "WebSocket has closed";            };        }        function disconnect() {            if (ws != null) {                ws.close();                ws = null;            }        }                       connect();            </script></head><body>    <h1 align="center" id="msg">Try to connect websocket.</h1>    <div id="winpop">        <div class="con" id="infomsg"></div>    </div></body></html>


        当用户界面打开时,它会尝试通过/test/NotifyWebSocketServlet建立与server的WebSocket连接,而NotifyWebSocketServlet的实现代码则例如以下:

package net.yanzhijun.example;import javax.servlet.ServletContext;import javax.servlet.http.HttpServletRequest;import org.apache.catalina.websocket.StreamInbound;import org.apache.catalina.websocket.WebSocketServlet;public class NotifyWebSocketServlet extends WebSocketServlet {    private static final long serialVersionUID = 1L;        @Override    protected StreamInbound createWebSocketInbound(String subProtocol,            HttpServletRequest request) {        ServletContext application = this.getServletContext();        return new NofityMessageInbound(application);    }    }

        与Tomcat给出的演示样例代码不同的是,在NotifyWebSocketServlet中并未将继承于MessageInboundNofityMessageInbound作为一个内嵌类。前述演示样例代码中发送消息和接收消息都是在同一组client页面和服务端响应Servlet间进行的,而当前须要实现是在一个页面中提交通知,而在其他用户的页面上显示通知信息,因此须要将全部client与服务端的连接存储一个全局域中,故而NofityMessageInbound将不仅仅在当前Servlet中被使用,所以有必要将其独立出来。

        NofityMessageInbound的完整代码例如以下:

package net.yanzhijun.example;import java.nio.CharBuffer;import java.nio.ByteBuffer;import java.io.IOException;import java.util.Set;import java.util.concurrent.CopyOnWriteArraySet;import javax.servlet.ServletContext;import org.apache.catalina.websocket.WsOutbound;import org.apache.catalina.websocket.MessageInbound;public class NofityMessageInbound extends MessageInbound {    private ServletContext application;    private Set<NofityMessageInbound> connections = null;        public NofityMessageInbound(ServletContext application) {        this.application = application;        connections =             (Set<NofityMessageInbound>)application.getAttribute("connections");        if(connections == null) {            connections =                new CopyOnWriteArraySet<NofityMessageInbound>();        }    }        @Override    protected void onOpen(WsOutbound outbound) {        connections.add(this);            application.setAttribute("connections", connections);    }    @Override    protected void onClose(int status) {        connections.remove(this);        application.setAttribute("connections", connections);    }    @Override    protected void onBinaryMessage(ByteBuffer message) throws IOException {        throw new UnsupportedOperationException(                "message not supported.");    }    @Override    protected void onTextMessage(CharBuffer message) throws IOException {        throw new UnsupportedOperationException(                "message not supported.");    }}


        后台发送通知的页面实现的相当简单,仅仅是一个表单提交一条通知信息。

<%@ page contentType="text/html;charset=UTF-8" language="java" %><html>    <head>        <title>PushMessage</title>    </head>    <body>        <h1 align="Center">Online Broadcast</h1>        <form method="post" action="PushMessageServlet">            <p>Message:<br/>                <textarea name="message" rows="5" cols="30"></textarea>            </p>            <p><input type="submit" value="Send">                  <input type="reset" value="Reset">            </p>        </form>    </body></html>


       接收提交通知的Servlet是PushMessageServlet,它在收到后台提交的通知后,就通过全部用户的WebSocket连接将通知发送出去。

package net.yanzhijun.example;import java.io.PrintWriter;import java.nio.CharBuffer;import java.util.Set;import java.util.concurrent.CopyOnWriteArraySet;import java.io.IOException;import javax.servlet.ServletContext;import javax.servlet.ServletException;import javax.servlet.http.HttpServlet;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;public class PushMessageServlet extends HttpServlet {     private static final long serialVersionUID = 1L;         @Override    public void doGet(HttpServletRequest request,                      HttpServletResponse response)        throws IOException, ServletException {            doPost(request, response);        }            @Override    public void doPost(HttpServletRequest request,                      HttpServletResponse response)        throws IOException, ServletException {                    request.setCharacterEncoding("UTF-8");        response.setContentType("text/html;charset=UTF-8");                PrintWriter out = response.getWriter();                String message = request.getParameter("message");                if(message == null || message.length() == 0) {                        out.println("The message is empty!");            return;        }                // 广播消息        broadcast(message);                out.println("Send success!");            }        // 将參数中的消息发送至全部在线client    private void broadcast(String message) {        ServletContext application=this.getServletContext();            Set<NofityMessageInbound> connections = (Set<NofityMessageInbound>)application.getAttribute("connections");        if(connections == null){            return;        }                for (NofityMessageInbound connection : connections) {            try {                CharBuffer buffer = CharBuffer.wrap(message);                connection.getWsOutbound().writeTextMessage(buffer);            } catch (IOException ignore) {                // Ignore            }        }    } }


        编译相关文件并完毕部署,尝试在后台发送消息,能够看到用户界面右下角出现的弹窗中显示了后台所提交的内容。

WebSocket初探WebSocket初探
                                                  图4

五、            WebSocket总结

    通过以上例程和实例能够看出,从开发角度使用WebSocket相当easy,基本仅仅须要创建WebSocket实例并对关心的事件进行处理就能够了;从应用角度WebSocket提供了优异的性能,图 5是来自websocket.org的性能測试图表(
http://www.websocket.org/quantum.html
),能够看到当并发和负载添加�时轮询与WebSocket的差异。

WebSocket初探
                                                                   图5
         (以上例程client在IE10.0和Chrom28.0下測试通过。)
 
         欢迎訪问梦断酒醒的博客http://blog.csdn.net/ishallwn
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

发布者:全栈程序员-用户IM,转载请注明出处:https://javaforall.cn/118409.html原文链接:https://javaforall.cn

【正版授权,激活自己账号】: Jetbrains全家桶Ide使用,1年售后保障,每天仅需1毛

【官方授权 正版激活】: 官方授权 正版激活 支持Jetbrains家族下所有IDE 使用个人JB账号...

(0)
blank

相关推荐

  • 根据经纬度计算距离的公式是什么_经纬度距离计算公式

    根据经纬度计算距离的公式是什么_经纬度距离计算公式privateconstdoubleEARTH_RADIUS=6378.137;privatestaticdoublerad(doubled){returnd*Math.PI/180.0;}publicstaticdoubleGetDistance(doublelat1,doublelng1,doublelat2,doublelng2){do…

  • Next主题_next3d桌面主题

    Next主题_next3d桌面主题概述最近next6折腾了一段时间,最后还是回到了next5,但是添加阅读全文按钮以后,默认的摘要生成不太方便,于是就把注意打到了js上。这里整理一下next5生成摘要的方法。一、y

  • ASP.NET画直方图

    ASP.NET画直方图最近做直方图,找到了这篇文章,以此为例画一般的直方图可以实现了。usingSystem;usingSystem.Collections;usingSystem.ComponentModel;usingSystem.Data;usingSystem.Drawing;usingSystem.Web;usingSystem.Web.SessionState;

  • BCG界面库

    BCG界面库之前用过BCG界面库中的表格控件,深感其强大,现在再来用一下其它的。一.关于BCGControlBar。BCGControlBar是一个基于MFC的扩展库,您可以通过完全的用户化操作构成一些类似于M

  • J1939协议之通俗易懂—-简介

    J1939协议之通俗易懂—-简介J1939简介 J1939协议简介J1939协议是由美国汽车工程师协会(SAE)(SAE协会简介)定义的一组标准。J1939标准用于卡车、公共汽车和移动液压等重型车辆。在许多方面,J1939标准类似于旧版J1708和J1587标准,但J1939标准协议建立在CAN(控制器区域网络,ISO11898)上。物理层(J1939/11)描述了针对客车的电气接口。数据链路层描述…

  • 摩托罗拉me525刷机包_ipad刷机怎么刷机教程

    摩托罗拉me525刷机包_ipad刷机怎么刷机教程您好,仔细看完以下内容哦,很简单的,一定要一步一步来,不要偷懒,谢谢。小白也会刷机的,刷机是另一种乐趣。请珍惜我的劳动成果哦,写教程不容易的呢,弄好了后请小声的说一下哦。(*^__^*)谢谢备份好您的资料,准备内存卡,读卡器,数据线,保证电量50以上请下载以下内容,电脑里安装豌豆荚http://www.wandoujia.com/或者91助手http://soft.s

    2022年10月22日

发表回复

您的电子邮箱地址不会被公开。

关注全栈程序员社区公众号