【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所看到的。

 

                                                                 图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所看到的。

                                                                                                    图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;}// 创建WebSocketif ('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;@Overridepublic 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子类必须实现的方法@Overrideprotected 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);}//  二进制消息响应@Overrideprotected void onBinaryMessage(ByteBuffer message) throws IOException {getWsOutbound().writeBinaryMessage(message);}// 文本消息响应@Overrideprotected void onTextMessage(CharBuffer message) throws IOException {// 将收到的消息发回clientgetWsOutbound().writeTextMessage(message);}}
}


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

2.      chat例程

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

                                                                  图3

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

<script type="text/javascript">var Chat = {};Chat.socket = null;Chat.connect = (function(host) {// 创建WebSocketif ('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子类必须实现的方法@Overrideprotected 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事件@Overrideprotected void onOpen(WsOutbound outbound) {connections.add(this);String message = String.format("* %s %s",nickname, "has joined.");broadcast(message);}// Close事件@Overrideprotected void onClose(int status) {connections.remove(this);String message = String.format("* %s %s",nickname, "has disconnected.");broadcast(message);}// 二进制消息事件@Overrideprotected void onBinaryMessage(ByteBuffer message) throws IOException {throw new UnsupportedOperationException("Binary message not supported.");}// 文本消息事件@Overrideprotected void onTextMessage(CharBuffer message) throws IOException {// Never trust the clientString 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;    @Overrideprotected 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>();}}@Overrideprotected void onOpen(WsOutbound outbound) {connections.add(this);    application.setAttribute("connections", connections);}@Overrideprotected void onClose(int status) {connections.remove(this);application.setAttribute("connections", connections);}@Overrideprotected void onBinaryMessage(ByteBuffer message) throws IOException {throw new UnsupportedOperationException("message not supported.");}@Overrideprotected 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;@Overridepublic void doGet(HttpServletRequest request,HttpServletResponse response)throws IOException, ServletException {doPost(request, response);}@Overridepublic 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!");        }// 将參数中的消息发送至全部在线clientprivate 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}}} 
}

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

五、            WebSocket总结

    通过以上例程和实例能够看出,从开发角度使用WebSocket相当easy,基本仅仅须要创建WebSocket实例并对关心的事件进行处理就能够了;从应用角度WebSocket提供了优异的性能,图 5是来自websocket.org的性能測试图表(http://www.websocket.org/quantum.html),能够看到当并发和负载添加时轮询与WebSocket的差异。
                                                                   图5
         (以上例程client在IE10.0和Chrom28.0下測试通过。)
 
         欢迎訪问梦断酒醒的博客http://blog.csdn.net/ishallwn

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/544703.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Spring Cache 实战:兼容所有缓存中间件!

作者 | 悟空聊架构来源 | 悟空聊架构&#xff08;ID&#xff1a;PassJava666&#xff09;本篇给大家介绍一种兼容所有缓存中间件的方案&#xff0c;不论我们是使用 Redis 还是 Ehcache&#xff0c;都不需要关心如何操作 Redis 或者 Ehcache&#xff0c;这套方案统统帮你搞定。这…

C# Winform 窗体美化(三、不规则窗体)

三、不规则窗体 概况 之前学习的 LayeredSkin 看到里面有个异形窗口&#xff0c;比较感兴趣&#xff0c;所以就找一下资料研究一下。不规则窗体学习有一个比较好的例子&#xff0c;叫 GoldFishProject&#xff0c;是一条鱼金鱼在屏幕上游。 不规则窗口示例代码 GoldFishProj…

Mybatis中SQL注入攻击的3种方式,真是防不胜防!

作者 | sunnyf来源 | https://www.freebuf.com/vuls/240578.html前言SQL注入漏洞作为WEB安全的最常见的漏洞之一&#xff0c;在java中随着预编译与各种ORM框架的使用&#xff0c;注入问题也越来越少。新手代码审计者往往对Java Web应用的多个框架组合而心生畏惧&#xff0c;不知…

Hadoop源代码分析(MapReduce概论)

大家都熟悉文件系统&#xff0c;在对HDFS进行分析前&#xff0c;我们并没有花很多的时间去介绍HDFS的背景&#xff0c;毕竟大家对文件系统的还是有一定的理解的&#xff0c;而且也有很好的文档。在分析Hadoop的MapReduce部分前&#xff0c;我们还是先了解系统是如何工作的&…

再有人问你MySql的隔离级别,直接把这篇文章发给他!

作者 l zyz1992来源 l Hollis&#xff08;ID&#xff1a;hollischuang&#xff09;首先要明白什么是事务&#xff1f;事务是程序中一系列严密的操作&#xff0c;所有的操作必须完成&#xff0c;否则在所有的操作中所做的所有的更改都会被撤销。也就是事务的原子性&#xff0c;一…

生产环境一次诡异的NPE问题,反转了4次

前言公司为了保证系统的稳定性&#xff0c;加了很多监控&#xff0c;比如&#xff1a;接口响应时间、cpu使用率、内存使用率、错误日志等等。如果系统出现异常情况&#xff0c;会邮件通知相关人员&#xff0c;以便于大家能在第一时间解决隐藏的系统问题。此外&#xff0c;我们这…

ZooKeeper学习笔记—配置管理

为什么80%的码农都做不了架构师&#xff1f;>>> 最近在工作中&#xff0c;为了完善公司集群服务的架构&#xff0c;提高可用性&#xff0c;降低运维成本&#xff0c;因此开始学习ZooKeeper。 至于什么是ZooKeeper&#xff1f;它能做什么&#xff1f;如何安装ZooKee…

C# Winform 窗体美化(六、双层窗体)

六、双层窗体 大概情况 双层床体是为了平滑的创建异形窗体的一个解决方案&#xff0c;找了很多资料&#xff0c;整理了一下。 双层窗体的逻辑是建立在 UpdateLayeredWindow 不能绘制控件的基础上&#xff0c;上层再添加一个专门放置控件的层&#xff1b;这样就可以在上层“控…

批处理框架 Spring Batch 这么强,你会用吗?

来源&#xff1a;blog.csdn.net/topdeveloperr/article/details/84337956spring batch简介Spring Batch架构介绍Spring Batch核心概念介绍chunk 处理流程批处理操作指南spring batch简介 spring batch是spring提供的一个数据处理框架。企业域中的许多应用程序需要批量处理才能在…

Android Bundle类别

即使在今天发现自己Bundle类不明确&#xff0c;因此&#xff0c;花时间去研究了一下。 依据google官方文件&#xff08;http://developer.android.com/reference/android/os/Bundle.html&#xff09; Bundle类是一个key-value对&#xff0c;“A mapping from String values to …

C# Winform 窗体美化(七、Win7 Aero 毛玻璃效果)

七、Win7 Aero 毛玻璃效果 在 Win7 上有一种 Aero 效果&#xff0c;毛玻璃透明效果&#xff0c;搭配不同风格的颜色&#xff0c;效果很好。在学习 Winform 美化的时候顺便看到的这种效果&#xff0c;也整理进来了。 注意&#xff1a;Win7 上想看到这种效果需要开启并使用 Aer…

非导向传输媒体| 计算机网络

Transmission media can be categorized in two ways, 传输媒体可以通过两种方式进行分类&#xff1a; Guided Transmission Media 引导传输媒体 Unguided Transmission Media 非引导传输媒体 1)非引导传输媒体 (1) Unguided Transmission Media) It is also called wireless …

面霸篇:MQ 的 5 大关键问题详解

最近mq越来越火&#xff0c;很多公司在用&#xff0c;很多人在用&#xff0c;其重要性不言而喻。但是如果我让你回答下面的这些问题&#xff1a;我们为什么要用mq&#xff1f;引入mq会多哪些问题&#xff1f;如何解决这些问题&#xff1f;你心中是否有答案了呢&#xff1f;本文…

C# Winform 窗体美化(八、Icon)

八、Icon 之前 Winform 项目也有在 Icon 上遇到些问题&#xff08;这里的 Icon 指的是 .ico 类型的文件&#xff09;&#xff0c;比如刚开始不知道怎么让自己的程序 Icon 和其他软件一样可以放大&#xff0c;还有放大之后在音量合成器中会出现比较奇葩的效果之类的问题&#x…

MyBatis 的执行流程,学废了!

作者&#xff1a;双子孤狼来源&#xff1a;blog.csdn.net/zwx900102/article/details/108455514MyBatis可能很多人都一直在用&#xff0c;但是MyBatis的SQL执行流程可能并不是所有人都清楚了&#xff0c;那么既然进来了&#xff0c;通读本文你将收获如下&#xff1a;1、Mapper接…

C# Winform 窗体美化(九、嵌入窗体)

九、嵌入窗体 还是关于 Winform 窗体的一些操作问题&#xff0c;这次是研究了一个嵌入窗体&#xff0c;这次学习纯属偶然&#xff0c;项目中确实没遇到过这种需求。就是把别人的程序嵌入到自己的程序中&#xff0c;就像这样&#xff1a; 这里我嵌入了测试显示器的程序 [外链图…

SpringBoot 优雅的参数效验!

引言 不知道大家平时的业务开发过程中 controller 层的参数校验都是怎么写的&#xff1f;是否也存在下面这样的直接判断&#xff1f;public String add(UserVO userVO) {if(userVO.getAge() null){return "年龄不能为空";}if(userVO.getAge() > 120){return &quo…

NTFS Change Journal(USN Journal)详解

写在前面 最近又用了一下usn日志来获取所有文件列表&#xff0c;在分多次加载文件列表的时候发现有文件丢失的情况&#xff0c;后来发现一篇文章比较详细的讲了usn。 用cmd来读取usn日志 如图&#xff1a; 以下是转载内容&#xff1a; 还是那个文件监控的应用&#xff0c;…

绝,Java 中创建对象的 5 种方法!

我们日常生活中会创建很多对象&#xff0c;但是这个对象和你理解的那么对象不一样&#xff0c;因为作者不是女娲&#xff0c;不能造人。作者只是程序员&#xff0c;他只能在 Java 中创建对象。那么我问你一个问题&#xff0c;你知道 Java 中如何创建对象吗&#xff1f;这个问题…

C# Winform 窗体美化(十、自定义窗体)

十、自定义窗体 写在前面 最近在做 winform 应用程序&#xff0c;需要自定义一种窗口的样式&#xff0c;所以就随便搞了一个简单的窗口。 效果图 有两种样式&#xff0c;界面如下&#xff1a; 无标题&#xff1a; 有标题&#xff1a; 关键词 1、黑色描边边框 对于…