- Java锁的逻辑(结合对象头和ObjectMonitor)
- 还在用饼状图?来瞧瞧这些炫酷的百分比可视化新图形(附代码实现)⛵
- 自动注册实体类到EntityFrameworkCore上下文,并适配ABP及ABPVNext
- 基于Sklearn机器学习代码实战
modbus_tcp 协议是工业项目中常见的一种基于 TCP/IP 协议的设备数据交互协议.
作为 TCP/IP 协议的上层协议,modbus_tcp 协议涉及到两个概念:client 和 server。但更标准的叫法应该是 master 和 slave.
而 modbus_tk 库作为 Python 中著名的 modbus 协议封装模块,其源码值得深入研究.
特别是在对并发量等方面有一定要求的情况下,如果需要在 modbus_tk 模块的基础上进行更进一步的开发,就更应该仔细研究其源代码和实现逻辑.
因此,我写下了这篇文章,希望对你有所帮助.
导入 TcpMaster 类:
from modbus_tk.modbus_tcp import TcpMaster
TcpMaster 继承于 Master,在其实例化的时候什么也没做.
class TcpMaster(Master):
def __init__(self, host="127.0.0.1", port=502, timeout_in_sec=5.0):
super(TcpMaster, self).__init__(timeout_in_sec)
self._host = host
self._port = port
self._sock = None
Master 的 __init__() 方法中也没有做什么:
class Master(object):
def __init__(self, timeout_in_sec, hooks=None):
self._timeout = timeout_in_sec
self._verbose = False
self._is_opened = False # 记住 _is_opened 现在为 False
TcpMaster 的父类 Master 提供了 execute 方法,该方法提供以下参数
self,
slave,
function_code,
starting_address,
quantity_of_x=0,
output_value=0,
data_format="",
expected_length=-1,
write_starting_address_fc23=0,
number_file=None,
pdu="",
returns_raw=False
此方法基本上算该模块的核心,无论是读写线圈、还是读写寄存器等都是调用该方法.
接下来其代码体的具体实现,我们将开始进行逐行分析:
is_read_function = False
nb_of_digits = 0
if number_file is None:
number_file = tuple()
self.open()
is_read_function 这里赋值为 False、代表后续在 Master.execute() 方法真正执行前,作者会先认为使用者调用的是 write 方法而非 read 方法.
接下来代码中又调用了 self.open() 方法。 由于实例化 TcpMaster 类时什么也没做, 所以 TCP 链接在此时是还没有建立的,而 self.open() 方法就是创建一个 TCP 的 client 端.
def open(self):
if not self._is_opened: # 在初始化方法中,它默认是 False
self._do_open()
self._is_opened = True
这里执行的 self._do_open() 方法由 TcpMaster 实现:
def _do_open(self):
if self._sock: # 如果 self._sock 不是 None、就将 socket 对象关闭
self._sock.close()
# 创建一个 socket 对象,AF_INET 为 IPV4 地址家族
# SOCK_STREAM 即为基于流的协议,也就是 TCP 协议
self._sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 设置超时时间,即实例化 TcpMaster 传入的值,默认参数为 5
self.set_timeout(self.get_timeout())
# 允许重用地址(解决端口占用问题)
self._sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
call_hooks("modbus_tcp.TcpMaster.before_connect", (self, ))
# 进行链接
self._sock.connect((self._host, self._port))
call_hooks("modbus_tcp.TcpMaster.after_connect", (self, ))
这里的 self.set_timeout 由 TcpMaster 实现
def set_timeout(self, timeout_in_sec):
super(TcpMaster, self).set_timeout(timeout_in_sec)
if self._sock:
# 注意! 这里如果 timeout_in_sec 等于 0
# 那么该 sock 对象就是一个链接时非阻塞的
# 可用于 I/O 多路复用
self._sock.setblocking(timeout_in_sec > 0)
# 如果 timeout_in_sec 为 0,则设置为阻塞的 socket 对象
# timeout 不应该传递负数
if timeout_in_sec:
self._sock.settimeout(timeout_in_sec)
看到这里,我们其实不难猜出 modbus_tk 模块中 TcpMaster 的 Master.execute() 方法其实是能支持 self._sock 异常后的无感重联的.
只需要在 slave 方失联后重新调用一次 TcpMaster._do_open() 方法即可,即可实现无感知的重新链接.
接下来 Master.execute() 方法基本是对 TCP 协议的解包、组包代码,我将具体的组包等过程代码都先给注释掉了
@threadsafe_function
def execute(
self, slave, function_code, starting_address, quantity_of_x=0, output_value=0, data_format="",
expected_length=-1, write_starting_address_fc23=0, number_file=None, pdu="", returns_raw=False
):
is_read_function = False
nb_of_digits = 0
if number_file is None:
number_file = tuple()
self.open()
if function_code == defines.READ_COILS or function_code == defines.READ_DISCRETE_INPUTS:
pass
elif function_code == defines.READ_INPUT_REGISTERS or function_code == defines.READ_HOLDING_REGISTERS:
pass
elif function_code == defines.READ_FILE_RECORD:
pass
elif (function_code == defines.WRITE_SINGLE_COIL) or (function_code == defines.WRITE_SINGLE_REGISTER):
pass
elif function_code == defines.WRITE_MULTIPLE_COILS:
pass
elif function_code == defines.WRITE_MULTIPLE_REGISTERS:
pass
elif function_code == defines.READ_EXCEPTION_STATUS:
pass
elif function_code == defines.DIAGNOSTIC:
pass
elif function_code == defines.READ_WRITE_MULTIPLE_REGISTERS:
pass
elif function_code == defines.RAW:
pass
elif function_code == defines.DEVICE_INFO:
pass
else:
raise ModbusFunctionNotSupportedError("The {0} function code is not supported. ".format(function_code))
query = self._make_query()
request = query.build_request(pdu, slave)
retval = call_hooks("modbus.Master.before_send", (self, request))
if retval is not None:
request = retval
if self._verbose:
LOGGER.debug(get_log_buffer("-> ", request))
self._send(request)
call_hooks("modbus.Master.after_send", (self, ))
if slave != 0:
pass
为了能够继续向下分析,我们这里先以写入多个寄存器的逻辑入手接着向下看:
READ_WRITE_MULTIPLE_REGISTERS
其代码为
elif function_code == defines.WRITE_MULTIPLE_REGISTERS:
# 输出值和 format,如果指定了 format 和输出值,将运行下面的逻辑
if output_value and data_format:
byte_count = struct.calcsize(data_format)
# 否则先计算整个 byte 的长度
else:
byte_count = 2 * len(output_value)
# 使用 struct 对数据进行转换, 采用大端排列
pdu = struct.pack(">BHHB", function_code, starting_address, byte_count // 2, byte_count)
# 输出值和 format,如果指定了 format 和输出值,将运行下面的逻辑
if output_value and data_format:
pdu += struct.pack(data_format, *output_value)
# 一般我们不会指定 data_format,所以直接往下看
else:
for j in output_value:
# 若 j 大于 0 fmt 就是 H 否则是 h
fmt = "H" if j >= 0 else "h"
# 继续向 pdu 里加数据
pdu += struct.pack(">" + fmt, j)
data_format = ">HH"
if expected_length < 0:
expected_length = 8
无论是读取、写入线圈或者寄存器,每一个请求都会包含一个 pdu 数据单元.
在 Master.execute() 方法下面,每一种读写操作都会运行 TcpMaster._make_query() 方法
query = self._make_query()
request = query.build_request(pdu, slave)
下面是 TcpMaster._make_query 的代码
def _make_query(self):
return TcpQuery()
TcpQuery 属于 Query 的派生类,但 Query 实际上是一个 interface 类,故没有实际代码
class TcpQuery(Query):
_last_transaction_id = 0 # 记住这个类属性
def __init__(self):
super(TcpQuery, self).__init__()
self._request_mbap = TcpMbap()
self._response_mbap = TcpMbap()
TcpMbap 类的实例化过程也非常简单,TcpQuery 中实际上就是封装了一个 request 和 response 而已:
class TcpMbap(object):
def __init__(self):
self.transaction_id = 0
self.protocol_id = 0
self.length = 0
self.unit_id = 0
TcpQuery.build_request() 的实现
def build_request(self, pdu, slave):
if (slave < 0) or (slave > 255):
raise InvalidArgumentError("{0} Invalid value for slave id".format(slave))
self._request_mbap.length = len(pdu) + 1 # pdu 数据单元的长度 + 1
self._request_mbap.transaction_id = self._get_transaction_id() # 获取一个事务 id
self._request_mbap.unit_id = slave # 站号
mbap = self._request_mbap.pack() # 打包
# mbap 和 pdu 数据单元拼接并返回
# mbap 可以认为是 head 而 pdu 则是 body
return mbap + pdu
TcpQuery._get_transaction_id() 会在每次收发包时,都让事务号自增 1,当事务号增加到 65535 后,置 0:
@threadsafe_function
def _get_transaction_id(self):
if TcpQuery._last_transaction_id < 0xffff: # 65535
TcpQuery._last_transaction_id += 1
else:
TcpQuery._last_transaction_id = 0
return TcpQuery._last_transaction_id
TcpMbap.pack() 方法会将所有 TcpMbap.__init__() 中的实例属性通过 struct 进行封包
def pack(self):
# transaction_id 事务号
# protocol_id 0
# length pdu 数据单元的长度 + 1
# unit_id 设备站号 slave
return struct.pack(">HHHB", self.transaction_id, self.protocol_id, self.length, self.unit_id)
至此,request 请求已经构建完毕了.
让我们接着回到 Master.execute() 方法中
# call_hooks 实际上是运行钩子函数,在后面会有详细介绍
retval = call_hooks("modbus.Master.before_send", (self, request))
if retval is not None:
request = retval
# 是否需要打印更多的日志?这个可以通过 Master.set_verbose() 方法进行设置
# 其默认值为 False
if self._verbose:
LOGGER.debug(get_log_buffer("-> ", request))
# 发送请求
self._send(request)
call_hooks("modbus.Master.after_send", (self, ))
在 TcpMaster._send() 方法中:
def _send(self, request):
retval = call_hooks("modbus_tcp.TcpMaster.before_send", (self, request))
if retval is not None:
request = retval
try:
# 刷新 socket 确保链接可用
flush_socket(self._sock, 3)
except Exception as msg:
LOGGER.error('Error while flushing the socket: {0}'.format(msg))
# 异常后、将再次运行 TcpMaster._do_open() 尝试重联
self._do_open()
# 若 flush_socket() 函数运行没有抛出异常,则代表链接是可用的。
# 这时候才会发送数据
self._sock.send(request)
flush_socket() 函数非常有趣,它通过 select 模块来不断的轮询监听 sock 对象的可读状态,当可读时会自动读取每一次的 1024 个字节数据并将他们抛弃,这里是为了保持发送数据前的连接状态检测没有异常而做的一步操作:
def flush_socket(socks, lim=0):
# lim 传入的是 3, 代表最多读 3 次
input_socks = [socks] # 做成一个监听列表
cnt = 0 # 当前读取到的次数
while True:
# 放入 可读事件列表、可写事件列表、错误事件列表 及监听对象
# 它会返回一个列表:
# [[r_fd, r_fd], [w_fd, w_fd], [e_fd, e_fd]]
# 而 [0] 则是指只拿到可读的文件描述符列表
# 循环事件时间设置的是 0.0 这代表它将一直阻塞在这里,直到 fd 事件被触发
# 若不为 0,则等待 n 秒,进行下一次的循环
i_socks = select.select(input_socks, input_socks, input_socks, 0.0)[0]
# 没有可读的文件描述符,则跳出 while 循环
if len(i_socks) == 0:
break
# 若拿到了,就循环得到 socks 进行 recv
# 其实这里应该也可以写成 i_socks[0].recv(1024)
# 因为可读事件文件描述符
for sock in i_socks:
sock.recv(1024)
# 超出了最大读取限制, 这里应该代表的是连接断开了
if lim > 0:
cnt += 1
if cnt >= lim:
raise Exception("flush_socket: maximum number of iterations reached")
至此、我们一次完整的组包及发送数据的源码分析就走完了.
我们接着来看 Master.execute() 方法中关于解析响应信息的代码:
if slave != 0:
response = self._recv(expected_length)
pass
首先,如果站号不等于 0 就会执行 TcpMaster._recv() 方法:
def _recv(self, expected_length=-1):
# to_data 函数会根据 Python 版本来返回不同的内容
# 若是 Python2 则直接返回 string ''
# 若是 Python3 则会返回一个 bytearray('', 'ascii')
response = to_data('')
length = 255
# 如果 response 小于 255, 则不断的读取
while len(response) < length:
rcv_byte = self._sock.recv(1)
if rcv_byte:
response += rcv_byte
# 在第 6 个字节处、通过 struct.unpack() 进行拆包
if len(response) == 6:
to_be_recv_length = struct.unpack(">HHH", response)[2]
length = to_be_recv_length + 6
else:
break
retval = call_hooks("modbus_tcp.TcpMaster.after_recv", (self, response))
if retval is not None:
return retval
return response
得到 response 后,Master.execute() 方法会开始解析响应信息
retval = call_hooks("modbus.Master.after_recv", (self, response))
if retval is not None:
response = retval
if self._verbose:
LOGGER.debug(get_log_buffer("<- ", response))
response_pdu = query.parse_response(response)
TcpQuery.parse_response() 方法的代码主要将 mbap 和 pdu 进行分离,并且通过 TcpMbap.unpack() 方法将 mbap 解包并通过 TcpMbap.check_response() 进行数据校验
def parse_response(self, response):
if len(response) > 6:
# 分别拿到 mbap 和 pdu
mbap, pdu = response[:7], response[7:]
# 解包
self._response_mbap.unpack(mbap)
# 校验数据,传入请求的 mbap 以及 pdu 的长度
self._response_mbap.check_response(self._request_mbap, len(pdu))
# 返回 pdu
return pdu
else:
raise ModbusInvalidResponseError("Response length is only {0} bytes. ".format(len(response)))
TcpMbap.unpack() 方法代码如下,将 _response_mbap 的事务号协议 id 等信息进行更新
def unpack(self, value):
(self.transaction_id, self.protocol_id, self.length, self.unit_id) = struct.unpack(">HHHB", value)
TcpMbap.check_response() 方法代码如下
def check_response(self, request_mbap, response_pdu_length):
error_str = self._check_ids(request_mbap)
error_str += self.check_length(response_pdu_length)
if len(error_str) > 0:
raise ModbusInvalidMbapError(error_str)
TcpMbap._check_ids() 方法代码如下
def _check_ids(self, request_mbap):
# self 是响应体, request_mbap 是请求体
# 对比他们的事务号等信息是否一致,若不一致则会在返回一个 error_str, 该 error_str 会在 TcpMbap.check_response()
# 中被 raise
error_str = ""
if request_mbap.transaction_id != self.transaction_id:
error_str += "Invalid transaction id: request={0} - response={1}. ".format(
request_mbap.transaction_id, self.transaction_id)
if request_mbap.protocol_id != self.protocol_id:
error_str += "Invalid protocol id: request={0} - response={1}. ".format(
request_mbap.protocol_id, self.protocol_id
)
if request_mbap.unit_id != self.unit_id:
error_str += "Invalid unit id: request={0} - response={1}. ".format(request_mbap.unit_id, self.unit_id)
return error_str
TcpMbap.check_length() 方法代码如下
def check_length(self, pdu_length):
# 这里思考 pdu 长度为什么 + 1?
# 因为 response 在 TcpMbap.unpack() 方法中,self.length 是 mbap + pdu 的长度
# 所以这里 pdu_length 长度 + 1 实际上就是指整个 head + body 的长度
following_bytes_length = pdu_length+1
# 判断长度是否相等、若不等可能造成的原因是数据拆包不正确 mbap 长了,或者 pdu 短了
# 这种时候就直接返回一个字符串
# TcpMbap.check_response() 中如果 error_str 的长度大于 0, 就会抛出异常了
if self.length != following_bytes_length:
return "Response length is {0} while receiving {1} bytes. ".format(self.length, following_bytes_length)
return ""
至此,TcpQuery().parse_response() 方法就全部执行完毕了.
Master.execute() 方法中就得到了数据单元 pdu。也就是整个数据体.
我们接着往下看 Master.execute() 方法,其实后面已经没有再深层次调用某些内部代码了,也没有新的 I/O 操作了:
response_pdu = query.parse_response(response)
(return_code, byte_2) = struct.unpack(">BB", response_pdu[0:2])
# 如果返回的 code 大于 128,直接报错
if return_code > 0x80:
# the slave has returned an error
exception_code = byte_2
raise ModbusError(exception_code)
else:
# 下面都是解析出一个 body 和一个 data_format
# 分别是 读操作、设备信息、写操作
# 他们所得到的 body 都不一样
if is_read_function:
byte_count = byte_2
data = response_pdu[2:]
if byte_count != len(data):
# the byte count in the pdu is invalid
raise ModbusInvalidResponseError(
"Byte count is {0} while actual number of bytes is {1}. ".format(byte_count, len(data))
)
elif function_code == defines.DEVICE_INFO:
data = response_pdu[1:]
data_format = ">" + (len(data) * "B")
else:
# returns what is returned by the slave after a writing function
data = response_pdu[1:]
# 默认为 False
if returns_raw:
return data
# 解包,通过 读、写、设备信息所得到的 data_format 和 data
# 对数据进行操作
result = struct.unpack(data_format, data)
# 只有 function_code 是 READ_COILS 时,nb_of_digits 才不为 0
if nb_of_digits > 0:
digits = []
for byte_val in result:
for i in range(8):
if len(digits) >= nb_of_digits:
break
digits.append(byte_val % 2)
byte_val = byte_val >> 1
result = tuple(digits)
# 如果 function_code 是 READ_FILE_RECORD 读取文件记录,则也需要对 result 进行
# 再次的修改
if function_code == defines.READ_FILE_RECORD:
sub_seq = list()
ptr = 0
while ptr < len(result):
sub_seq += ((ptr + 2, ptr + 2 + result[ptr] // 2), )
ptr += result[ptr] // 2 + 2
result = tuple(map(lambda sub_seq_x: result[sub_seq_x[0]:sub_seq_x[1]], sub_seq))
# 返回 result
return result
threadsafe 是一个装饰器函数,在 Master.execute() 方法头上和 TcpQuery._get_transaction_id() 方法头上都加了这个装饰器.
见名知意,该装饰器的主要目的就是为了保障线程安全(有的设备可能不支持同时对其进行读写操作),但是该装饰器也可能会带来另一些问题.
我们先看它的源码:
def threadsafe_function(fcn):
# 实例化出了一把递归锁
lock = threading.RLock()
def new(*args, **kwargs):
# 当 Master.execute() 和 TcpQuery._get_transaction_id() 方法没有通过
# 关键字传参传入 threadsafe=False 时,将默认开启线程安全模式来执行
# 这 2 个方法
threadsafe = kwargs.pop('threadsafe', True)
if threadsafe:
lock.acquire()
try:
ret = fcn(*args, **kwargs)
except Exception as excpt:
raise excpt
finally:
if threadsafe:
lock.release()
return ret
return new
这个 threading lock 会导致什么问题呢?当 Python 解释器运行到 Master.execute() 方法头上时,就会自动执行该装饰器.
而 lock 变量也就生成了,最后会返回内部闭函数 new().
可以理解为这个 lock 已经被当成了一个全局变量,后续无论是创建多少个 TcpMaster 的实例对象,lock 变量所指向的锁都是同一个.
通过源码分析我们得知,Master.execute() 方法中会去建立 socket 链接,一旦有 1 个 device 链接时间过长,也将会导致其他的 device 通信或链接阻塞.
因为它们都是用的同一个 lock 锁。所以,一般来说在使用时我们会在 Master.execute() 方法中显式的传递 threadsafe=False 的关键字参数,自己实现 lock 来解决同一 device 不能同时读写的问题.
在上面分析源码时,我们会看到很多 call_hooks 的运行,他们其实是 modbus_tk 模块所提供的钩子函数。只要实现相应的钩子函数就会在整个 modbus_tcp 的数据传递生命周期中自动运行.
以下是常见的钩子函数:
def install_hook(name, fct):
"""
Install one of the following hook
modbus_rtu.RtuMaster.before_open((master,))
modbus_rtu.RtuMaster.after_close((master,)
modbus_rtu.RtuMaster.before_send((master, request)) returns modified request or None
modbus_rtu.RtuMaster.after_recv((master, response)) returns modified response or None
modbus_rtu.RtuServer.before_close((server, ))
modbus_rtu.RtuServer.after_close((server, ))
modbus_rtu.RtuServer.before_open((server, ))
modbus_rtu.RtuServer.after_open(((server, ))
modbus_rtu.RtuServer.after_read((server, request)) returns modified request or None
modbus_rtu.RtuServer.before_write((server, response)) returns modified response or None
modbus_rtu.RtuServer.after_write((server, response))
modbus_rtu.RtuServer.on_error((server, excpt))
modbus_tcp.TcpMaster.before_connect((master, ))
modbus_tcp.TcpMaster.after_connect((master, ))
modbus_tcp.TcpMaster.before_close((master, ))
modbus_tcp.TcpMaster.after_close((master, ))
modbus_tcp.TcpMaster.before_send((master, request))
modbus_tcp.TcpServer.after_send((master, request))
modbus_tcp.TcpMaster.after_recv((master, response))
modbus_tcp.TcpServer.on_connect((server, client, address))
modbus_tcp.TcpServer.on_disconnect((server, sock))
modbus_tcp.TcpServer.after_recv((server, sock, request)) returns modified request or None
modbus_tcp.TcpServer.before_send((server, sock, response)) returns modified response or None
modbus_tcp.TcpServer.on_error((server, sock, excpt))
modbus_rtu_over_tcp.RtuOverTcpMaster.after_recv((master, response))
modbus.Master.before_send((master, request)) returns modified request or None
modbus.Master.after_send((master))
modbus.Master.after_recv((master, response)) returns modified response or None
modbus.Slave.handle_request((slave, request_pdu)) returns modified response or None
modbus.Slave.handle_write_multiple_coils_request((slave, request_pdu))
modbus.Slave.handle_write_multiple_registers_request((slave, request_pdu)) returns modified response or None
modbus.Slave.handle_write_single_register_request((slave, request_pdu)) returns modified response or None
modbus.Slave.handle_write_single_coil_request((slave, request_pdu)) returns modified response or None
modbus.Slave.handle_read_input_registers_request((slave, request_pdu)) returns modified response or None
modbus.Slave.handle_read_holding_registers_request((slave, request_pdu)) returns modified response or None
modbus.Slave.handle_read_discrete_inputs_request((slave, request_pdu)) returns modified response or None
modbus.Slave.handle_read_coils_request((slave, request_pdu)) returns modified response or None
modbus.Slave.handle_read_write_multiple_registers_request((slave, request_pdu)) returns modified response or None
modbus.Slave.handle_read_exception_status_request((slave, request_pdu)) returns modified response or None
modbus.Slave.on_handle_broadcast((slave, response_pdu)) returns modified response or None
modbus.Slave.on_exception((slave, function_code, excpt))
modbus.Databank.on_error((db, excpt, request_pdu))
modbus.ModbusBlock.setitem((self, slice, value))
modbus.Server.before_handle_request((server, request)) returns modified request or None
modbus.Server.after_handle_request((server, response)) returns modified response or None
modbus.Server.on_exception((server, excpt))
"""
with _LOCK:
try:
_HOOKS[name].append(fct)
except KeyError:
_HOOKS[name] = [fct]
最后此篇关于Pythonmodbus_tk库源码分析的文章就讲到这里了,如果你想了解更多关于Pythonmodbus_tk库源码分析的内容请搜索CFSDN的文章或继续浏览相关文章,希望大家以后支持我的博客! 。
ACO.Visualization项目 本项目演示蚁群算法求解旅行商问题的可视化过程,包括路径上的信息素浓度、蚁群的运动过程等。项目相关的代码:https://github.com/anycad/A
关闭。这个问题不符合Stack Overflow guidelines .它目前不接受答案。 我们不允许提问寻求书籍、工具、软件库等的推荐。您可以编辑问题,以便用事实和引用来回答。 关闭 7 年前。
我需要用Sql数据库制作并包含的PHP票务系统源码用户客户端和管理员。我需要个人 CMS 的这个来源。谢谢你帮助我。 最佳答案 我在不同的情况下使用了 osticket。 这里: http://ost
我的场景:我想在日志文件中写入发生异常的部分代码(例如,发生异常的行前 5 行和行后 5 行 - 或者至少是该方法的所有代码)。 我的想法是用 C# 代码反编译 pdb 文件,并从该反编译文件中找到一
RocketMQ设定了延迟级别可以让消息延迟消费,延迟消息会使用 SCHEDULE_TOPIC_XXXX 这个主题,每个延迟等级对应一个消息队列,并且与普通消息一样,会保存每个消息队列的消费进度
先附上Hystrix源码图 在微服务架构中,根据业务来拆分成一个个的服务,服务与服务之间可以相互调用(RPC),在Spring Cloud可以用RestTemplate+Ribbon和
此篇博客学习的api如标题,分别是: current_url 获取当前页面的url; page_source 获取当前页面的源码; title 获取当前页面的titl
? 1 2
1、前言 作为一个数据库爱好者,自己动手写过简单的sql解析器以及存储引擎,但感觉还是不够过瘾。<<事务处理-概念与技术>>诚然讲的非常透彻,但只能提纲挈领,不能让你
gory"> 目录 运行时信号量机制 semaphore 前言 作用是什么 几个主要的方法 如何实现
自己写的一个评论系统源码分享给大家,包括有表情,还有评论机制。用户名是随机的 针对某一篇文章进行评论 function subcomment() {
一、概述 StringBuilder是一个可变的字符串序列,这个类被设计去兼容StringBuffer类的API,但不保证线程安全性,是StringBuffer单线程情况下的一个替代实现。在可能的情
一、概述 System是用的非常多的一个final类。它不能被实例化。System类提供了标准的输入输出和错误输出流;访问外部定义的属性和环境变量;加载文件和库的方法;以及高效的拷贝数组中一部分元素
在JDK中,String的使用频率和被研究的程度都非常高,所以接下来我只说一些比较重要的内容。 一、String类的概述 String类的声明如下: public final class Str
一、概述 Class的实例代表着正在运行的Java应用程序的类和接口。枚举是一种类,而直接是一种接口。每一个数组也属于一个类,这个类b被反射为具有相同元素类型和维数的所有数组共享的类对象。八大基本树
一、概述 Compiler这个类被用于支持Java到本地代码编译器和相关服务。在设计上,这个类啥也不做,他充当JIT编译器实现的占位符。 放JVM虚拟机首次启动时,他确定系统属性java.comp
一、概述 StringBuffer是一个线程安全的、可变的字符序列,跟String类似,但它能被修改。StringBuffer在多线程环境下可以很安全地被使用,因为它的方法都是通过synchroni
一、概述 Enum是所有Jav中枚举类的基类。详细的介绍在Java语言规范中有说明。 值得注意的是,java.util.EnumSet和java.util.EnumMap是Enum的两个高效实现,
一、概述 此线程指的是执行程序中的线程。 Java虚拟机允许应用程序同时执行多个执行线程。 每个线程都有优先权。 具有较高优先级的线程优先于优先级较低的线程执行。 每个线程可能也可能不会被标记为守
一、抽象类Number 类继承关系 这里面的原子类、BigDecimal后面都会详细介绍。 属性和抽象方法 二、概述 所有的属性,最小-128,最大127,SIZE和BYTES代码比
我是一名优秀的程序员,十分优秀!